将(嵌套的)参数说明符写入辅助文件

将(嵌套的)参数说明符写入辅助文件

这是对该问题的后续回答:

我在其中解释说我想将一些定义写入辅助文件并使用钩子来实现这一点。请参阅此问题以了解我为什么要执行以下操作。


我想将宏的定义写入涉及参数的辅助文件#1,例如

\documentclass{article}

\begin{document}


\ExplSyntaxOn

\iow_now:cn { @auxout }
  {
    \def\auxfoo#1{auxfoo: #1.}
  }
\ExplSyntaxOff

  
\end{document}

当 TeX 再次读取文档末尾的辅助文件时,会导致错误,因为写入辅助文件的字符实际上\def \auxfoo ##1{auxfoo:##1.}当然不是有效的 TeX 代码。

我怎样才能将裸#1令牌写入辅助文件或者,更一般地,如果我有一些可能涉及的令牌列表#1,,#2我怎样才能将其写入辅助文件正是如此稍后再恢复它?


在我的用例中,这稍微复杂一些,因为我有一个定义DocumentCommand嵌套的宏,其中它的主体已被捕获为参数(由最终用户提供)。我想定义这个嵌套DocumentCommand并将外部宏的调用(用户在某处输入)写入辅助文件中。但可能可以轻松调整上述 mwe 问题的答案。


更多细节

编辑:由于 Ulrich Diez 要求进一步详细说明确切用法,因此我将其提供,但可能会写得相当冗长。这就是为什么我认为这本身不会提出一个好问题,因此我简要介绍了确切的预期用法。

我正在编写一个(个人)软件包,groupthm我用它来记录我在大学的一些讲义。源代码可以在我的 GitLab 存储库

简而言之,目标是快速定义行为非常相似的定理家族,例如,Theorem名称后有一个视觉星号或其他符号的定理,或者Theorem编号比平常低一层的定理。

这个概念以定理群的形式实现,指定群的行为(例如具有这个和那个后缀),并声明定理是某个群的成员。这样,我可以通过简单地改变某个群的定义来轻松改变一大堆环境的行为。

这涉及在某个时候让用户能够使用theorem自定义主体声明环境并xparse使用方便的界面。在此主体中,用户可以使用常用语法解析环境的参数xparse,然后选择一些有关如何处理此环境的组。

例如,在声明时\begin{theorem}*,这可以是组的成员star,并且\begin{theorem}表现正常。在学期结束时,我通常想摆脱视觉星星,所以我只需更改一个组的定义,所有环境都会正确调整。

作为后端,我只是用来thmtools声明这些环境,theorem它本身是一个包装环境,然后根据给定的参数调用相应的变体。

欲了解更多信息,您可以查看文档我正在编写的软件包。(但请注意,质量可能不太好)。这只是处理过的.dtx文件。

然而,这样做的缺点是,我必须进行大量的处理,才能得到thmtools声明的底层环境(这里我不会过多地介绍细节)。在我有大量此类环境的文档中,这占了 2 秒以上的时间。

由于我很懒,我有一些包装包,默认情况下只声明了我所有的常用环境(如果你想,可以查看花哨的),即使在较小的文档上,这也会导致速度大幅下降。

是的,我知道我可以通过不默认定义所有定理来避免这个问题,但同样,我很懒,因为我认为这实现自动化,我绝对喜欢尝试这样做,并且拥有一个易于使用且速度合理的软件包。

因此,我有一个理想,即在第一次运行 LaTeX 时将底层环境的定义保存thmtools到辅助文件中,然后在以后的运行中加载它们,这样我就不必再次计算它们了。当定义发生变化时,我可以删除辅助文件,让 TeX 以较慢的速度运行以更新这些定义。

但是由于这种保存还涉及环境的声明xparse,所以我基本上必须将环境的定义保存xparse到辅助文件中 - 这产生了上面讨论的字符扩展问题#

回到辅助文件中的问题/定义

现在考虑到问题的具体用途,我写了一些概念验证包mkesler-缓存\DeclareCachedDocumentCommand通过提供如上所述的行为宏来演示所需的行为。

有了这个,请考虑以下示例文件(取自测试在我的 GitLab 存储库中并将包添加为文件内容,以便这个问题保持完整):

\begin{filecontents}{mkessler-cache.sty}
\ProvidesExplPackage{mkessler-cache}{2022/02/02}{2.8.1-14-g01254fc}{Cache definitions of previous LaTeX runs to avoid computations}

%%%% This is just a proof of concept package,
%%%% there is possibly more room for clean-up
%%%% and / or speed-up etc.

%%% Also, the provided 
%%% \DeclareCachedDocumentCommand
%%% is of course not really
%%% useful and just for demonstration
%%% purposes

\bool_new:N \g__cache_cache_bool
\int_new:N \g__cache_cache_version_document_int
\int_new:N \g__cache_cache_version_aux_int
\int_gset:Nn \g__cache_cache_version_aux_int { -1 }

\tl_new:N \g__cache_dump_auxfile_tl
\tl_new:N \g__cache_lazy_document_tl
\tl_new:N \g__cache_lazy_auxfile_tl

\keys_define:nn { cache }
  {
    cache              .bool_set:N    = \g__cache_cache_bool,
    cache              .default:n     = { true },
    __cache version__  .int_set:N     = \g__cache_cache_version_document_int,
    cache version      .meta:nn       = { cache } { cache = true, __cache version__ = #1 },
    cache version      .default:n     = { 0 },
  }
\RequirePackage{l3keys2e}
\ProcessKeysOptions{cache}


% Setting up lazy execution and
% selecting of executing after reading of aux file
% if cache option has been used
\bool_if:NTF \g__cache_cache_bool
  {
    %% Writing things (at one go) to aux file at end of document:
    \cs_new:Npn \__cache_put_aux:n #1 
      {
        \tl_gput_right:Nn \g__cache_dump_auxfile_tl { #1 }
      }
    \cs_generate_variant:Nn \__cache_put_aux:n { x, V }

    \cs_generate_variant:Nn \iow_now:Nn { c V }

    %%% Handles dumping data to aux file at end of document
    \hook_gput_code:nnn { enddocument } { cache }
      {
        \iow_now:cV { @auxout } \g__cache_dump_auxfile_tl
      }

    %%% Writes the current cache version into aux file
    \__cache_put_aux:x
      {
        \ExplSyntaxOn
        \int_gset:Nn \exp_not:N \g__cache_cache_version_aux_int
          {
            \int_use:N \g__cache_cache_version_document_int
          }
        \ExplSyntaxOff
      }

    %%% Executing something lazily at beginning of document
    %%% Lazy code only gets executed if auxfile version is older than document
    \cs_new:Npn \__cache_lazy:n
      {
        \tl_gput_right:Nn \g__cache_lazy_document_tl 
      }

    %%% Caching things. Handles writing and reading to aux file
    %%% and makes code available in the next run of LaTeX
    \cs_new:Npn \__cache_cache:n #1
      {
        \tl_set:Nn \l_tmpa_tl
          {
            \csname tl_gput_right:cn \endcsname { g__cache_lazy_auxfile_tl } { #1 }
          }
        \regex_replace_all:nnN { \cP\# } { \cO\# } \l_tmpa_tl
        \__cache_put_aux:V \l_tmpa_tl
      }

    %%% This handles loading either the cached definitions
    %%% from last run or executing the lazy definitions from the current run
    %%% after loading the aux file
    \hook_gput_code:nnn { begindocument } { cache }
      {
        \int_compare:nNnTF
          \g__cache_cache_version_aux_int < \g__cache_cache_version_document_int
            {
              \tl_use:N \g__cache_lazy_document_tl
            }
            {
              \tl_use:N \g__cache_lazy_auxfile_tl
            }
      }
  }
  {
    \cs_set_eq:NN \__cache_lazy:n \use:n
    \cs_set_eq:NN \__cache_cache:n \use_none:n
  }

\cs_new:Npn \__cached_new_document_command:nnn #1 #2 #3
  {
    \NewDocumentCommand{#1}{#2}{#3}
    \__cache_cache:n
      {
        \csname __cached_new_document_command:nnn \endcsname { #1 } { #2 } { #3 }
      }
  }

\NewDocumentCommand{\DeclareCachedDocumentCommand}{mmm}
  {
    \__cache_lazy:n
      {
        \__cached_new_document_command:nnn{#1}{#2}{#3}
      }
  }
\end{filecontents}




\documentclass{article}


%%% Setting a higher cache version will cause a re-evaluation of the
% \DeclareCachedDocumentCommand macros present in the preamble
% Otherwise, the definitions can be altered or even removed without breaking
% the document, as long as the aux file is present.



%% \DeclareCachedDocumentCommand is now just a plain wrapper around \DeclareDocumentCommand
%% that implements this caching, so it is not useful on itself
%% 
%% If \DeclareCachedDocumentCommand is, however, some macro that does lengthy computations
%% and then issues one ore more of underlying \DeclareDocumentCommands
%% we can save this computation on the second run, since we do not evaluate
%% \DeclareCachedDocumentCommand further (we just copied its definition)
%% and load the plain definitions that the  invocation produced in the last run
%% and that we saved to the aux file for restoration.

\usepackage[cache version = 1]{mkessler-cache}

\DeclareCachedDocumentCommand\foo{m}{foo: Called with argument '#1'.}
\DeclareCachedDocumentCommand\baz{m}{baz: Called with argument '#1'.}

\DeclareCachedDocumentCommand\parser{!s !t+}
  {
    \IfBooleanT {#1} {*}
    \IfBooleanT {#2} {$\dagger$}
  }

\begin{document}

\foo {  one  }

\baz{ arg }

\parser*+

Some text.
  
\end{document}

更改定义宏的行没有任何效果(前提是您不删除辅助文件),并且增加选项cache version会导致重新“计算”(请记住在预期的情况下实际上涉及计算)。

我希望这足以阐明我的用例。

答案1

不仅阅读预期用途的模糊描述,而且确切地了解具体场景是什么样子,以及所请求的知识将在其中使用,这将会很有趣。我怀疑这是关于不同 LaTeX 运行之间最广泛意义上的数据管理。也许这可以通过与将一些宏定义写入辅助文件不同的方式来解决。


如果按照您的定义,哈希(#)是唯一可以属于类别 6(参数)的字符标记,那么在将内容写入辅助文件之前,使用正则表达式将类别 6(参数)的哈希替换为类别 12(其他)的哈希可能会达到同样的效果:

\ExplSyntaxOn

\documentclass{article}

\tl_new:N \l__scratchy_tl

\begin{document}

% Set the scratch-token-register:
\tl_set:Nn  \l__scratchy_tl { \gdef\auxfoo#1{auxfoo:~#1.} }
% In the scratch register replace hashes of category 6(parameter)
% by hashes of category 12(other):
\regex_replace_all:nnN { \cP\# } { \cO\# }  \l__scratchy_tl
% Write the content of the scratch register:
\exp_args:NnV \iow_now:cn { @auxout } \l__scratchy_tl
  
\end{document}

第一次运行 LaTeX 后 .aux 文件的内容:

\relax 
\def \auxfoo #1{auxfoo: #1.}

你说评论

无论如何我都会使用钩子来执行这些在文档的开头取决于辅助文件中存在的一些元信息

请注意,.aux 文件是在 LaTe 运行开始和 LaTeX 运行结束时读取的。

在 LaTeX 运行开始时,会读取源自上一次 LaTeX 运行的 .aux 文件。例如,定义扩展到交叉引用数据的宏,这些数据已写入上一次 LaTeX 运行中的 .aux 文件。例如,从写入上一次 LaTeX 运行中的 .aux 文件的条目创建 .toc 文件/.lof 文件/.lot 文件\@writefile

然后 .aux 文件被销毁并重新创建。

在 LaTeX 运行结束时,将读取新的 .aux 文件。例如,检查交叉引用数据是否发生变化,因此是否需要进行另一次 LaTeX 运行。

当在 LaTeX 运行结束时读取新的 .aux 文件时,宏\@newl@bel被设置为等于\@testdef
因此,您可以检测是否在 LaTeX 运行的开始/结束时读取了 aux 文件,并且仅在 LaTeX 运行开始时执行钩子中存储的定义:

\ExplSyntaxOn

\documentclass{article}

\tl_new:N \l__mymodule_scratchy_tl
\cs_new_protected:Npn \OnlyAtBeginOfLaTeXRun {
  \cs_if_eq:ccF {@newl@bel} {@testdef} 
}

\begin{document}

% Set the scratch-token-register:
\tl_set:Nn  \l__mymodule_scratchy_tl {
  \OnlyAtBeginOfLaTeXRun { \gdef\auxfoo#1{auxfoo:~#1.} }
}
% In the scratch register replace hashes of category 6(parameter)
% by hashes of category 12(other):
\regex_replace_all:nnN { \cP\# } { \cO\# }  \l__mymodule_scratchy_tl
% Write the content of the scratch register:
\exp_args:NnV  \iow_now:cn { @auxout } \l__mymodule_scratchy_tl
  
\end{document}

第一次运行 LaTeX 后 .aux 文件的内容:

\relax 
\OnlyAtBeginOfLaTeXRun {\gdef \auxfoo #1{auxfoo: #1.}}

相关内容