我想保存环境的内容以便稍后在多个地方使用宏使用。
这是真实的用例。
- 我有一个绘制“概率”树的环境。
- 我想保存绘制树的代码(它使用了的语法
forest
)。这需要使用ID来标识内容。 - 然后稍后我想通过给出 ID 将原始内容与宏一起使用:这个宏将分析用于绘制树的代码,例如自动进行几个演算(这部分将使用 Lua 完成,我知道如何做到这一点)。
输入的内容就是Lua代码所要分析的内容。
环境中使用的代码如下所示。
\begin{probatree}
[
[A, pweight = a
[B, apweight* = b]
[C, bpweight* = c]
]
[D, pweight* = d]
]
\end{probatree}
这是一个起始 MWE。
\documentclass{article}
\usepackage{luacode}
\usepackage{forest}
\begin{luacode}
function calctree(a)
tex.print("-+-+")
tex.print("")
tex.print("$" .. a .. "$")
tex.print("")
tex.print("-+-+")
end
\end{luacode}
% This fully expands the argument
\newcommand\calctree[1]{\directlua{calctree("#1")}}%
\newenvironment{multilines}{}{}
\begin{document}
\calctree{OOO}
\calctree{a (b + 3) - c^2}
% Save the content of the environment to use it later: this needs to give
% a single ID to the environment, this ID will be used to store internally
% the content.
%
% ????
\begin{multilines}
A
B
C
\end{multilines}
Bla, bla, ...
% Use the content with \calctree.
%
% ????
Blo, blo, ...
% Use the content another time with \calctree.
%
% ????
\end{document}
答案1
由于答案的字符数限制为 30,000 个,我需要将答案分成两部分。
这是我的回答的第二部分。
我的回答第一部分包含 TeX 工作原理以及第 2 部分中提供的代码/工作示例的一般解释。
我的回答第二部分保存工作示例。
\makeatletter
%%///////////// Code for interface \UD@GatherCodeSnippetLoop and ///////////////
%%///////////// environment DefineCodeSnippet and all suplementary things //////
%%===============================================================================
%% End \romannumeral-driven expansion safely:
%%===============================================================================
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%===============================================================================
%% 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]{%
\romannumeral\expandafter\@secondoftwo\string{\expandafter
\@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
\@secondoftwo\string}\expandafter\UD@stopromannumeral\@secondoftwo}{%
\expandafter\UD@stopromannumeral\@firstoftwo}%
}%
%%===============================================================================
%% Obtain control sequence token from name of control sequence token:
%%===============================================================================
%% \CsNameToCsToken<stuff not in braces>{NameOfCs}
%% -> <stuff not in braces>\NameOfCs
%% (<stuff not in braces> may be empty.)
\@ifdefinable\CsNameToCsToken{%
\long\def\CsNameToCsToken#1#{\romannumeral\InnerCsNameToCsToken{#1}}%
}%
\newcommand\InnerCsNameToCsToken[2]{%
\expandafter\UD@exchange\expandafter{\csname#2\endcsname}{\UD@stopromannumeral#1}%
}%
\newcommand\UD@exchange[2]{#2#1}%
%%===============================================================================
%% Check if string of non-special character-tokens is substring
%% of another string of non-special character-tokens:
%%===============================================================================
%% \UD@checkstringsubsetof{<possible substring>}{<string>}%
%% {<tokens if <possible substring> and <string> are equal>}%
%% {<tokens if <possible substring> is strict subset of <string>>}%
%% {<<tokens if <possible substring> is not subset of <string>>}%
%% The length of <possible substring> must not exceed the length of <string>!!!
\newcommand\UD@checkstringsubsetof[5]{%
\UD@checkstringsubsetofloop#1\relax#2\relax{{#3}{#4}}{#5}%
}%
\@ifdefinable\UD@checkstringsubsetofloop{%
\def\UD@checkstringsubsetofloop#1#2\relax#3#4\relax{%
\if\string#1\string#3\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{%
\ifx\relax#2\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{%
\ifx\relax#4\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{\expandafter\@firstoftwo\@firstoftwo}{\expandafter\@secondoftwo\@firstoftwo}%
}%
{\UD@checkstringsubsetofloop#2\relax#4\relax}%
}{\expandafter\@secondoftwo}%
}%
}%
%%===============================================================================
%% Gather non-special character-tokens until encountering
%% \end{<name of environment>} :
%%===============================================================================
%% \UD@GatherCodeSnippetLoop{<non-special characters gathered so far>}%
%% {<subset of name of environment gathered so far>}%
%% {<tokens to apply to stuff gathered>}%
%% <non-special characters>\end{<name of environment>}
%%
%% Character-wise gathers <non-special characters>, replaces any non-space by its
%% \catcode-12-pendant. (Space is handled by giving it catcode 12 before applying
%% the routine.)
%% Then does
%% <tokens to apply to stuff gathered>{<non-special characters gathered so far>}
\newcommand\UD@GatherCodeSnippetLoop{%
\expandafter\UD@@GatherCodeSnippetLoop\expandafter{\@currenvir}%
}%
\newcommand\UD@@GatherCodeSnippetLoop[1]{%
\end{#1}%
\begingroup
\let\do\@makeother % <- this and the next line switch to
\dospecials % verbatim-category-code-régime.
\do\^^I%
\do\^^M%
\begingroup
\escapechar=-1\relax
\edef\@tempa{\string\\end\string{#1\string}}%
\expandafter\endgroup
\expandafter\UD@@@GatherCodeSnippetLoop
\expandafter{\@tempa}%
}%
\newcommand\UD@@@GatherCodeSnippetLoop[5]{%
\UD@checkstringsubsetof{#3#5}{#1}{%
\expandafter\expandafter\expandafter\UD@exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter{%
\UD@RemoveLeadingNTrailingCarriageReturn{#2}%
}}{#4}%
}{%
\expandafter\UD@exchange\expandafter{\expandafter{%
\romannumeral
\expandafter\UD@exchange\expandafter{%
\romannumeral\UD@StringifyIfNotSpace{#5}%
}{\UD@stopromannumeral#3}%
}}{\UD@@@GatherCodeSnippetLoop{#1}{#2}}{#4}%
}{%
\UD@checkstringsubsetof{#5}{#1}{%
\expandafter\expandafter\expandafter\UD@exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter{%
\UD@RemoveLeadingNTrailingCarriageReturn{#2#3}%
}}{#4}%
}{%
\expandafter\UD@exchange\expandafter{\expandafter{%
\romannumeral\UD@StringifyIfNotSpace{#5}%
}}{\UD@@@GatherCodeSnippetLoop{#1}{#2#3}}{#4}%
}{%
\expandafter\UD@exchange\expandafter{\expandafter{%
\romannumeral
\expandafter\UD@exchange\expandafter{%
\romannumeral\UD@StringifyIfNotSpace{#5}%
}{\UD@stopromannumeral#2#3}%
}}{\UD@@@GatherCodeSnippetLoop{#1}}{}{#4}%
}%
}%
}%
\newcommand\UD@StringifyIfNotSpace[1]{%
\if\string#1 \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{\UD@stopromannumeral}{\expandafter\UD@stopromannumeral\string}#1%
}%
%%===============================================================================
%% Catcode-régime for defining components used by \UD@GatherCodeSnippetLoop
%% and for defining user-level-macros/environments using \UD@GatherCodeSnippetLoop
%% via \begingroup<catcode-settings>\@firstofone{\endgroup<Definitions>}-trick:
%%===============================================================================
\begingroup
\@makeother\^^M%
\catcode`\Z=14\relax%
\@makeother\%Z
\@firstofone{Z
\endgroupZ
ZZ=============================================================================
ZZ Begin of code for removing one leading and one trailing explicit
ZZ <carriage-return>-character-token of catcode 12(other) from _verbatimized_
ZZ argument
ZZ=============================================================================
ZZ Check whether_verbatimized_ argument has a leading explicit
ZZ <carriage-return>-character-token of catcode 12(other):
ZZ-----------------------------------------------------------------------------
ZZ \UD@CheckWhetherLeadingCarriageReturn{<Argument which is to be checked>}%
ZZ {<Tokens to be delivered in case
ZZ <argument which is to be checked>'s
ZZ 1st token is an explicit <carriage-
ZZ return>-character-token of
ZZ catcode 12(other)>}%
ZZ {<Tokens to be delivered in case
ZZ <argument which is to be checked>'s
ZZ 1st token is not an explicit
ZZ <carriage-return>-character-token of
ZZ catcode 12(other)>}%
\newcommand\UD@CheckWhetherLeadingCarriageReturn[1]{Z
\UD@@CheckWhetherLeadingCarriageReturn\UD@SelDom#1\UD@SelDom^^M\UD@@SelDomZ
}Z
\@ifdefinable\UD@@CheckWhetherLeadingCarriageReturn{Z
\long\def\UD@@CheckWhetherLeadingCarriageReturn#1\UD@SelDom^^M#2\UD@@SelDom{Z
\UD@CheckWhetherNull{#2}{\@secondoftwo}{\@firstoftwo}Z
}Z
}Z
ZZ-----------------------------------------------------------------------------
ZZ Check whether_verbatimized_ argument having a leading explicit <carriage-
ZZ return>-character-token of catcode 12(other) consists only of such tokens:
ZZ-----------------------------------------------------------------------------
ZZ \UD@CheckWhetherOnlyCarriageReturn{<Argument which is to be checked and
ZZ which is known to have a leading explicit
ZZ <carriage-return>-character-token of
ZZ catcode 12(other)>}%
ZZ {<Tokens to be delivered in case <argument
ZZ which is to be checked> consists only
ZZ of explicit <carriage-return>-character-
ZZ tokens of catcode 12(other)>}%
ZZ {<Tokens to be delivered in case <argument
ZZ which is to be checked> does not consist
ZZ only of explicit <carriage-return>-
ZZ character-tokens of catcode 12(other)>}%
\newcommand\UD@CheckWhetherOnlyCarriageReturn[1]{Z
\UD@CheckWhetherLeadingCarriageReturn{#1}{Z
\expandafter\UD@CheckWhetherOnlyCarriageReturnZ
\expandafter{\UD@@TrimLeadingCarriageReturn#1}Z
}{Z
\UD@CheckWhetherNull{#1}{\@firstoftwo}{\@secondoftwo}Z
}Z
}Z
ZZ-----------------------------------------------------------------------------
ZZ Remove one leading explicit <carriage-return>-character-token of
ZZ catcode 12(other) from _verbatimized_ argument:
ZZ-----------------------------------------------------------------------------
\@ifdefinable\UD@@TrimLeadingCarriageReturn{Z
\long\def\UD@@TrimLeadingCarriageReturn^^M{}Z
}Z
ZZ-----------------------------------------------------------------------------
ZZ Check whether_verbatimized_ argument has a trailing explicit
ZZ <carriage-return>-character-token of catcode 12(other):
ZZ-----------------------------------------------------------------------------
ZZ \UD@CheckWhetherTrailingCarriageReturn{<Argument which is to be checked>}%
ZZ {<Tokens to be delivered in case
ZZ <argument which is to be checked>'s
ZZ last token is an explicit <carriage-
ZZ return>-character-token of
ZZ catcode 12(other)>}%
ZZ {<Tokens to be delivered in case
ZZ <argument which is to be checked>'s
ZZ last token is not an explicit
ZZ <carriage-return>-character-token of
ZZ catcode 12(other)>}%
\newcommand\UD@CheckWhetherTrailingCarriageReturn[1]{Z
\UD@@CheckWhetherTrailingCarriageReturn#1\UD@SelDom^^M\UD@SelDom\UD@@SelDomZ
}Z
\@ifdefinable\UD@@CheckWhetherTrailingCarriageReturn{Z
\long\def\UD@@CheckWhetherTrailingCarriageReturn#1^^M\UD@SelDom#2\UD@@SelDom{Z
\UD@CheckWhetherNull{#2}{\@secondoftwo}{\@firstoftwo}Z
}Z
}Z
ZZ-----------------------------------------------------------------------------
ZZ Remove one trailing explicit <carriage-return>-character-token of
ZZ catcode 12(other) from _verbatimized_ argument:
ZZ-----------------------------------------------------------------------------
\newcommand\UD@TrimTrailingCarriageReturn[1]{Z
\UD@@TrimTrailingCarriageReturn#1\UD@SelDomZ
}Z
\@ifdefinable\UD@@TrimTrailingCarriageReturn{Z
\long\def\UD@@TrimTrailingCarriageReturn#1^^M\UD@SelDom{#1}Z
}Z
ZZ-----------------------------------------------------------------------------
ZZ Remove one leading and one trailing explicit <carriage-return>-character-
ZZ token of catcode 12(other) from _verbatimized_ argument if present.
ZZ In the edge case of the _verbatimized_ argument consisting only of explicit
ZZ <carriage-return>-character-tokens of catcode 12(other) remove only one of
ZZ them.
ZZ Due to \romannumeral-expansion the result is delivered in 2 expansion-steps:
ZZ-----------------------------------------------------------------------------
\newcommand\UD@RemoveLeadingNTrailingCarriageReturn[1]{Z
\romannumeralZ
\UD@CheckWhetherLeadingCarriageReturn{#1}{Z
\UD@CheckWhetherTrailingCarriageReturn{#1}{Z
\UD@CheckWhetherOnlyCarriageReturn{#1}{Z
\expandafter\UD@stopromannumeral\UD@@TrimLeadingCarriageReturn#1Z
}{Z
\expandafter\expandafter\expandafter\expandafter\expandafterZ
\expandafter\expandafter\UD@stopromannumeral\expandafterZ
\UD@TrimTrailingCarriageReturn\expandafter{\UD@@TrimLeadingCarriageReturn#1}Z
}Z
}{Z
\expandafter\UD@stopromannumeral\UD@@TrimLeadingCarriageReturn#1Z
}Z
}{Z
\UD@CheckWhetherTrailingCarriageReturn{#1}{Z
\expandafter\expandafter\expandafter\UD@stopromannumeralZ
\UD@TrimTrailingCarriageReturn{#1}Z
}{\UD@stopromannumeral#1}Z
}Z
}Z
ZZ=============================================================================
ZZ End of code for removing one leading and one trailing explicit
ZZ <carriage-return>-character-token of catcode 12(other) from _verbatimized_
ZZ argument
ZZ=============================================================================
ZZ Begin of code for replacing explicit <carriage-return>-character-token of
ZZ catcode 12(other)(^^M) by explicit <line-feed>-character-token of catcode
ZZ 12(other) (^^J).
ZZ In TeX ^^M usually is the endline-character appended at the ends of lines
ZZ of .tex-input when pre-processing them (by TeX's eyes) right before
ZZ tokenization.
ZZ In TeX ^^J usually is the newline-character which is not written but yields
ZZ a transition from one line to the next line when writing to file/screen,
ZZ e.g., while \scantokens does its fake-writing.
ZZ=============================================================================
\newcommand\UD@ReplaceCarriageReturnByLineFeed[1]{Z
\romannumeral\@UDReplaceCarriageReturnByLineFeed{^^J}#1^^M\relax{}Z
}Z
\@ifdefinable\@UDReplaceCarriageReturnByLineFeed{Z
\long\def\@UDReplaceCarriageReturnByLineFeed#1#2^^M#3\relax#4#5{Z
\UD@CheckWhetherNull{#3}Z
{\UD@stopromannumeral#5{#4#2}}Z
{\@UDReplaceCarriageReturnByLineFeed{#1}#3\relax{#4#2#1}{#5}}Z
}Z
}Z
ZZ=============================================================================
ZZ End of code for replacing explicit <carriage-return>-character-token of
ZZ catcode 12(other)(^^M) by explicit <line-feed>-character-token of catcode
ZZ 12(other) (^^J).
ZZ=============================================================================
ZZ Begin of code for environment DefineCodeSnippet
ZZ=============================================================================
\newenvironment{DefineCodeSnippet}[1]{Z
\UD@GatherCodeSnippetLoop{}{}Z
{\CsNameToCsToken\endgroup\newcommand*{CodeSnippet#1}}Z
}{}Z
ZZ=============================================================================
ZZ End of code for environment DefineCodeSnippet
ZZ=============================================================================
ZZ=============================================================================
ZZ Begin of code for environment RedefineCodeSnippet
ZZ=============================================================================
\newenvironment{RedefineCodeSnippet}[1]{Z
\UD@GatherCodeSnippetLoop{}{}Z
{\CsNameToCsToken\endgroup\renewcommand*{CodeSnippet#1}}Z
}{}Z
ZZ=============================================================================
ZZ End of code for environment RedefineCodeSnippet
ZZ=============================================================================
ZZ=============================================================================
ZZ Begin of code for environment AppendToCodeSnippet
ZZ=============================================================================
\newenvironment{AppendToCodeSnippet}[1]{Z
\UD@GatherCodeSnippetLoop{}{}{\endgroup\UD@AppendToCodeSnippet{#1}}Z
}{}Z
\newcommand\UD@AppendToCodeSnippet[2]{Z
\@ifundefined{CodeSnippet#1}{Z
\@latex@error{CodeSnippet \string#1 undefined}\@ehcZ
}{Z
\CsNameToCsToken\expandafter\renewcommand\expandafter*\expandafter{CodeSnippet#1}Z
\expandafter{\romannumeral\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#1}^^M#2}Z
}Z
}Z
ZZ=============================================================================
ZZ End of code for environment AppendToCodeSnippet
ZZ=============================================================================
ZZ=============================================================================
ZZ Begin of code for environment PrependToCodeSnippet
ZZ=============================================================================
\newenvironment{PrependToCodeSnippet}[1]{Z
\UD@GatherCodeSnippetLoop{}{}Z
{\endgroup\UD@PrependToCodeSnippet{#1}}Z
}{}Z
\newcommand\UD@PrependToCodeSnippet[2]{Z
\@ifundefined{CodeSnippet#1}{Z
\@latex@error{CodeSnippet \string#1 undefined}\@ehcZ
}{Z
\CsNameToCsTokenZ
\expandafter\renewcommand\expandafter*\expandafter{CodeSnippet#1}Z
\expandafter{Z
\romannumeral\expandafter\UD@exchange\expandafter{Z
\romannumeral\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#1}Z
}{\UD@stopromannumeral#2^^M}Z
}Z
}Z
}Z
ZZ=============================================================================
ZZ End of code for environment PrependToCodeSnippet
ZZ=============================================================================
\newcommand\ExecuteCodeSnippet[1]{Z
\expandafter\UD@ReplaceCarriageReturnByLineFeed\expandafter{Z
\romannumeral\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#1}%Z
}{\scantokens}Z
}Z
\newcommand\DeliverCodeSnippetToMacro[2]{Z
\expandafter\UD@exchange\expandafter{\expandafter{\romannumeralZ
\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#1}Z
}}{#2}Z
}Z
\newcommand\DeliverCodeSnippetToTokens[2]{Z
\DeliverCodeSnippetBetweenTokens{#1}{#2}{}Z
}Z
\newcommand\DeliverCodeSnippetBetweenTokens[3]{Z
\expandafter\UD@exchange\expandafter{\romannumeralZ
\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#1}Z
}{#2}#3Z
}Z
\newcommand\ShowCodeSnippet[1]{Z
\CsNameToCsToken\show{CodeSnippet#1}Z
}Z
\newcommand\MeaningCodeSnippet[1]{Z
\CsNameToCsToken\meaning{CodeSnippet#1}Z
}Z
\newcommand\ConcatCodeSnippets{Z
\@ifstar{\ConcatCodeSnippetsAtStarNoStar{\def}}Z
{\ConcatCodeSnippetsAtStarNoStar{\newcommand*}}Z
}Z
\newcommand\ConcatCodeSnippetsAtStarNoStar[4]{Z
\@ifundefined{CodeSnippet#2}{Z
\@latex@error{CodeSnippet \string#2 undefined}\@ehcZ
\@ifundefined{CodeSnippet#3}{Z
\@latex@error{CodeSnippet \string#3 undefined}\@ehcZ
}{Z
\expandafter\UD@exchange\expandafter{\expandafter{Z
\romannumeral\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#3}Z
}}{\CsNameToCsToken#1{CodeSnippet#4}}Z
}Z
}{Z
\@ifundefined{CodeSnippet#3}{Z
\@latex@error{CodeSnippet \string#3 undefined}\@ehcZ
\expandafter\UD@exchange\expandafter{\expandafter{Z
\romannumeral\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#2}Z
}}{\CsNameToCsToken#1{CodeSnippet#4}}Z
}{Z
\expandafter\UD@exchange\expandafter{\expandafter{Z
\romannumeralZ
\expandafter\UD@exchange\expandafter{Z
\romannumeral\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#3}Z
}{Z
\CsNameToCsToken\expandafter\UD@stopromannumeral{CodeSnippet#2}^^MZ
}Z
}}Z
{\CsNameToCsToken#1{CodeSnippet#4}}Z
}Z
}Z
}Z
}%
%%///////////// End of code for interface \UD@GatherCodeSnippetLoop and ////////
%%///////////// environment DefineCodeSnippet and all suplementary things //////
\makeatother
\documentclass[a4paper]{article}
\pagestyle{empty}
\addtolength\textwidth{7cm}
\addtolength\evensidemargin{-3.5cm}
\addtolength\oddsidemargin{-3.5cm}
\enlargethispage{4cm}
\partopsep=0ex
\topsep=0ex
\begin{document}
\vspace*{-4.75cm}%
\begingroup
\catcode`\^^M=12\relax%
\catcode`\^^J=12\relax%
\leftmargini=\labelsep%
\settowidth\labelwidth{\texttt{\string^^M}}%
\advance\leftmargini\labelwidth%
\begin{itemize}%
\item[\texttt{\string^^M}]in cmtt-font denotes the \textit{\textlangle carriage-return\textrangle}-character %
(code-point-number 13 both in ASCII and in Unicode, \verb|^^M| in TeX's \verb|^^|\hbox{-}notation, ``M'' being %
the \(13^{\hbox{\tiny th}}\) letter of the uppercase-alphabet).\par%
\item[\texttt{\string^^J}] in cmtt-font denotes the \textit{\textlangle line-feed\textrangle}-character %
(code-point-number 10 both in ASCII and in Unicode, \verb|^^J| in TeX's \verb|^^|\hbox{-}notation, ``J'' being %
the \(10^{\hbox{\tiny th}}\) letter of the uppercase-alphabet).%
\end{itemize}%
\endgroup
\hrule\vfill
\noindent Define a code-snippet \verb|foobar1|:\vfill
\begin{verbatim}
\begin{DefineCodeSnippet}{foobar1}
Text Text Text
Text
\end{verbatim*}
\end{DefineCodeSnippet}
\end{verbatim}
\begin{DefineCodeSnippet}{foobar1}
Text Text Text
Text
\end{verbatim*}
\end{DefineCodeSnippet}
\vfill\hrule\vfill
\noindent\verb|\texttt{\MeaningCodeSnippet{foobar1}| yields:\vfill
\noindent\texttt{\MeaningCodeSnippet{foobar1}}%
\vfill\hrule\vfill
\noindent Define a code-snippet \verb|foobar2| with two empty lines:\vfill
\begin{verbatim}
\begin{DefineCodeSnippet}{foobar2}
\end{DefineCodeSnippet}
\end{verbatim}
\begin{DefineCodeSnippet}{foobar2}
\end{DefineCodeSnippet}
\vfill\hrule\vfill
\noindent\verb|\texttt{\MeaningCodeSnippet{foobar2}| yields:\vfill
\noindent\texttt{\MeaningCodeSnippet{foobar2}}%
\vfill\hrule\vfill
\noindent Prepend to code-snippet \verb|foobar1|:\vfill
\begin{verbatim}
\begin{PrependToCodeSnippet}{foobar1}
\begin{verbatim*}
\end{PrependToCodeSnippet}
\end{verbatim}
\begin{PrependToCodeSnippet}{foobar1}
\begin{verbatim*}
\end{PrependToCodeSnippet}
\vfill\hrule\vfill
\noindent Append to code-snippet \verb|foobar1|:\vfill
\begin{verbatim}
\begin{AppendToCodeSnippet}{foobar1}
\noindent\TeX\space is {\huge funny}%
\end{AppendToCodeSnippet}
\end{verbatim}
\begin{AppendToCodeSnippet}{foobar1}
\noindent\TeX\space is {\huge funny}%
\end{AppendToCodeSnippet}
\vfill\hrule\vfill
\noindent\verb|\texttt{\MeaningCodeSnippet{foobar1}| yields:\vfill
\noindent\texttt{\MeaningCodeSnippet{foobar1}}%
\vfill\hrule\vfill
\noindent This yields console-output:\vfill
\noindent\verb|\ShowCodeSnippet{foobar1}|%
\ShowCodeSnippet{foobar1}%
\vfill\hrule\vfill
\noindent\verb|\ExecuteCodeSnippet{foobar1}| yields:\vfill
\ExecuteCodeSnippet{foobar1}%
\vfill\hrule\vfill
\noindent\verb|\DeliverCodeSnippetToTokens{foobar1}{\begin{verbatim}}\end{verbatim}| yields:\vfill
\noindent\DeliverCodeSnippetToTokens{foobar1}{\begin{verbatim}}\end{verbatim}%
\vfill\hrule\vfill
\noindent\verb|\DeliverCodeSnippetToMacro{foobar1}{\texttt}| yields:\vfill
\noindent\DeliverCodeSnippetToMacro{foobar1}{\texttt}%
\vfill\hrule\vfill
\noindent Concatenate code-snippets \verb|foobar1| and \verb|foobar1| as code-snippet \verb|foobarDouble|:\vfill
\noindent\verb|\ConcatCodeSnippets{foobar1}{foobar1}{foobarDouble}|%
\ConcatCodeSnippets{foobar1}{foobar1}{foobarDouble}%
\vfill\hrule\vfill
\noindent This yields console-output:\vfill
\noindent \verb|\ShowCodeSnippet{foobarDouble}|%
\ShowCodeSnippet{foobarDouble}%
\vfill\hrule\vfill
\noindent\verb|\ExecuteCodeSnippet{foobarDouble}| yields:\vfill
\ExecuteCodeSnippet{foobarDouble}%
\vfill
\end{document}
pdf 输出:
控制台输出:
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex)
restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2020-10-01> patch level 4
L3 programming layer <2021-02-18>
(/usr/local/texlive/2020/texmf-dist/tex/latex/base/article.cls
Document Class: article 2020/04/10 v1.4m Standard LaTeX document class
(/usr/local/texlive/2020/texmf-dist/tex/latex/base/size10.clo))
(/usr/local/texlive/2020/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def)
(./test.aux)
Overfull \hbox (3.08784pt too wide) in paragraph at lines 437--440
[]\OT1/cmr/m/n/10 in cmtt-font de-notes the \TS1/cmr/m/it/10 <\OT1/cmr/m/it/10
line-feed\TS1/cmr/m/it/10 >\OT1/cmr/m/n/10 -character (code-point-number 10 bot
h in ASCII and in Uni-code, [][]\OT1/cmtt/m/n/10 ^^J []\OT1/cmr/m/n/10 in TeX's
[][]\OT1/cmtt/m/n/10 ^^[][]\OT1/cmr/m/n/10 notation,
> \CodeSnippetfoobar1=macro:
->\begin{verbatim*}^^MText Text Text^^MText^^M\end{verbatim*}^^M^^M\noindent
\TeX\space is {\huge funny}%.
<argument> \CodeSnippetfoobar1
l.530 \ShowCodeSnippet{foobar1}
%
?
> \CodeSnippetfoobarDouble=macro:
->\begin{verbatim*}^^MText Text Text^^MText^^M\end{verbatim*}^^M^^M\noindent
\TeX\space is {\huge funny}%^^M\begin{verbatim*}^^MText Text Text^^MText^^M\
end{verbatim*}^^M^^M\noindent\TeX\space is {\huge funny}%.
<argument> \CodeSnippetfoobarDouble
l.562 \ShowCodeSnippet{foobarDouble}
%
?
[1{/usr/local/texlive/2020/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
(./test.aux) ){/usr/local/texlive/2020/texmf-dist/fonts/enc/dvips/cm-super/cm-s
uper-ts1.enc}</usr/local/texlive/2020/texmf-dist/fonts/type1/public/amsfonts/cm
/cmr10.pfb></usr/local/texlive/2020/texmf-dist/fonts/type1/public/amsfonts/cm/c
mr17.pfb></usr/local/texlive/2020/texmf-dist/fonts/type1/public/amsfonts/cm/cmr
5.pfb></usr/local/texlive/2020/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10
.pfb></usr/local/texlive/2020/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt10.
pfb></usr/local/texlive/2020/texmf-dist/fonts/type1/public/cm-super/sfti1000.pf
b>
Output written on test.pdf (1 page, 71882 bytes).
Transcript written on test.log.
答案2
由于答案的字符数限制为 30,000 个,我需要将答案分成两部分。
这是我的回答的第一部分。
我的回答第一部分包含 TeX 工作原理以及第 2 部分中提供的代码/工作示例的一般解释。
我的回答第二部分保存工作示例。
您使用短语“原始内容”,但没有明确说明其含义/表示什么。
我可以提供一个通用接口,该\UD@GatherCodeSnippetLoop
接口切换到 verbatim-catcode-régime(特殊字符⟨space-character⟩
、\
、{
、}
、$
、&
、#
、^
、_
、的类别代码切换到 12(其他))并逐个字符地从 .tex 输入文件中读取并标记和累积事物,直到遇到字符序列,应用于每个累积的非空格字符以将所有内容(例如,由于包 inputenc 通常处于活动状态的字符)转换为 catcode 12(其他)。 (仅适用于非空格字符标记的原因如下:由于切换到 verbatim-catcode-régime,空格字符已经具有类别代码 12。应用会将它们转换为类别代码 10(空格)的显式空格标记,因为空格/空格标记是唯一的例外:将除空格之外的所有内容转换为类别代码 12(其他)并将每个空格转换为类别代码 10(空格)。)%
~
\end{⟨expansion of \@currenvir⟩}
\string
\string
\string
\string
\string
\UD@GatherCodeSnippetLoop
可用于定义具有不同行为的不同环境,其共同点是环境主体从 .tex 输入文件中收集,作为类别代码 12(其他)的显式字符标记序列。
这样的环境可用于将类别代码 12(其他)的显式字符标记序列存储为宏。
扩展这些宏之后,通过处理类别代码 12(其他)的显式字符标记序列,其\scantokens
结果与直接通过 TeX 的眼睛/嘴巴/食道/胃等处理构成环境主体的字符相同,而无需切换 catcode-régime 和保存事物的操作 - 除非在执行此类环境时有效的 catcode-régime 与执行宏时/在\scantokens
' 重新标记构成环境主体的单个字符时有效的 catcode-régime 不同。
在第 2 部分的示例中,\UD@GatherCodeSnippetLoop
定义了通用接口,并定义了基于的环境、、、。DefineCodeSnippet
此外,还定义了用户级宏 、、、RedefineCodeSnippet
。所有环境和用户级宏还处理一些表示代码片段 ID 的参数。PrependToCodeSnippet
AppendToCodeSnippet
\UD@GatherCodeSnippetLoop
\ConcatCodeSnippets
\ExecuteCodeSnippet
\DeliverCodeSnippetToMacro
\DeliverCodeSnippetToTokens
\begin{DefineCodeSnippet}{⟨ID⟩}
Text Text Text
Text
\end{DefineCodeSnippet}
从 .tex 输入文件中读取环境主体作为显式 catcode-12(other)-character-tokens 序列,并定义宏以通过顶层扩展生成该 catcode-12(other)-character-tokens 序列。\CodeSnippet⟨ID⟩
\begin{RedefineCodeSnippet}{⟨ID⟩}
Text Text Text
Text
\end{RedefineCodeSnippet}
相应地重新定义。\CodeSnippet⟨ID⟩
\begin{PrependToCodeSnippet}{⟨ID⟩}
Text Text Text
Text
\end{PrependToCodeSnippet}
在 前面添加几行。\CodeSnippet⟨ID⟩
\begin{AppendToCodeSnippet}{⟨ID⟩}
Text Text Text
Text
\end{AppendToCodeSnippet}
将行附加到。\CodeSnippet⟨ID⟩
宏
\ConcatCodeSnippets{⟨ID of source snippet 1⟩}{⟨ID of source snippet 2⟩}{⟨target ID⟩}
定义为:\CodeSnippet⟨target ID⟩
⟨sequence of explicit catcode-12(other)-character-tokens denoted by ID of source snippet 1⟩
⟨explicit ⟨carriage-return⟩-character-token of category code 12 (other)⟩
⟨sequence of explicit catcode-12(other)-character-tokens denoted by ID of source snippet 2⟩
带星号的变体\ConcatCodeSnippets*
允许覆盖/重新定义。\CodeSnippet⟨target ID⟩
宏
\ExecuteCodeSnippet{⟨ID⟩}
做
\scantokens\expandafter{\CodeSnippet⟨ID⟩%}
,尾随%
的 catcode 也是 12(其他),以便它可以通过\scantokens
' 假书写 毫无问题地写入,并可以通过\scantokens
' 在其通常的 catcode 14(注释)下读回识别,这样它就可以防止由于 TeX 插入结束符而产生虚假标记。
在将东西传递给\scantokens
⟨回车⟩-字符被替换为⟨换行⟩-人物。
为了理解后者的原因,您需要了解 TeX 工作原理的一些细节:
当 TeX 读取 .tex 输入文件中的一行并对其进行预处理时,在标记化之前,会附加一个字符,该字符在 TeX 的内部字符编码方案中的代码点编号(在传统 TeX 中为 ASCII,在 LuaTeX/XeTeX 中为 unicode)等于整数参数 的值\endlinechar
。
通常 的值为\endlinechar
13,而代码点编号 13 表示⟨回车⟩-字符在 ASCII 和 Unicode 中都是如此。
因此通常⟨回车⟩-character 在读取/预处理一行 .tex 输入时附加。
在 TeX 的^^
-notation中⟨回车⟩-字符也可以表示为^^M
M 是大写字母中的第 13 个字母。
当 TeX 将未展开的显式字符标记写入外部文本文件或屏幕时,TeX 会对每个显式字符标记检查其字符代码(= TeX 内部字符编码方案中相关字符的代码点编号)是否等于整数参数的值\newlinechar
。
如果是,则不写入相应的字符,但将相关字符标记视为信号,以在外部文本文件或屏幕上创建从当前行到下一行的转换。
结果取决于平台。 在文本文件中,根据平台的不同,⟨回车⟩-字符或⟨换行⟩-字符或两者的组合用于表示从文本的一行到文本的下一行的过渡。
通常的值为\newlinechar
10,而代码点号 10 表示⟨换行⟩-字符在 ASCII 和 Unicode 中都是如此。
因此在编写代码时通常⟨换行⟩-character-tokens 是用于创建从文本文件的一行到该文本文件的下一行的转换的信号。
在 TeX 的^^
-notation中⟨换行⟩-字符也可以表示为^^J
J 是大写字母中的第 10 个字母。
\scantokens
处理一组已标记化的标记如下:
首先 TeX 假装将这些标记未展开写入外部文本文件。
然后 TeX 使用该伪造的外部文件作为 .tex 输入源(就像您已应用 -primitive\input
切换到另一个 .tex 输入源一样)并从执行时当前的 catcode 机制中读取/预处理/标记化/摘要它\scantokens
。
在我们的案例中,已经标记化的标记集在逐字标记码制度下进行标记,因此只会产生(非特殊)显式字符标记,并且每个字符标记都会转换为类别代码 12(其他)。这意味着⟨回车⟩-字符,由于上述\endlinechar
机制,在预处理阶段被插入到行尾,被标记为类别代码 12(其他)和字符代码 13 的(非特殊)显式字符标记。
通常 TeX 会将此类标记写入文件中^^
文件,即,而不是写入以下某种组合 ⟨回车⟩-字符和/或⟨换行⟩-字符或其他,字符序列^
, ^
,M
会被写入,随后写入的字符也会出现在同一行中。
如果你想让\scantokens
' fake-writing 创建一个从一行到下一行的过渡,你可以\newlinechar
在应用 , 之前分配值 13 \scantokens
,或者记住\newlinechar
-parameter 的值通常表示⟨换行⟩-字符(10/^^J
),你可以让 TeX 替换每个显式的 catcode-12(other)-⟨回车⟩-character-token 由明确的 catcode-12(其他)-⟨换行⟩-character-token 在应用 之前\scantokens
。
我更喜欢后者,因为它不需要在\scantokens
执行 时更改 TeX 的任何参数,在极端情况下可能会导致行为偏离被认为是“通常的事情”。
(如果你让 的假写\scantokens
只写序列^
,^
,M
而不是让假写程序从一行到下一行创建过渡,那么在正常的 catcode 机制下输入/读取/标记/消化/处理假文件将是一个问题:^
,^
,M
将被当作^^M
,即^^
的 -notation ⟨回车⟩-character。在正常的 catcode-régime 下⟨回车⟩-character 的类别代码为 5(行尾),这意味着它会导致 TeX
(!!!)停止处理该行,这意味着在同一行中通过' 假书写在字符序列
^
,后面^
的每个字符都将丢失(!!!),并且M
\santokens
根据读取装置的当前状态,将没有任何标记附加到流经 TeX 通道的标记流中(状态 S),或者附加显式空格标记(状态 M),或者附加控制字标记
\par
(状态 N)。
)
宏
\DeliverCodeSnippetToMacro{⟨ID⟩}{⟨tokens⟩}
交付
⟨tokens⟩{⟨expansion of \CodeSnippet⟨ID⟩⟩}
宏
\DeliverCodeSnippetToTokens{⟨ID⟩}{⟨tokens⟩}
交付
⟨tokens⟩⟨expansion of \CodeSnippet⟨ID⟩⟩
\DeliverCodeSnippetToMacro
(和之间的区别\DeliverCodeSnippetToTokens
在于, further 传递⟨expansion of \CodeSnippet⟨ID⟩⟩
嵌套在 catcode 1(开始组)/catcode 2(结束组)一对花括号之间的内容,而后者传递的内容没有这样的花括号对。)
随着环境的领先⟨回车⟩-字符(如果存在)和一个尾随⟨回车⟩-字符(如果存在)将从中删除⟨环境主体形成的 catcode-12 字符序列⟩在(重新)定义之前。\CodeSnippet⟨ID⟩
这边走
\begin{DefineCodeSnippet}{foobar}
Text Text Text
Text
\end{DefineCodeSnippet}
结果是相同的
\begin{DefineCodeSnippet}{foobar}Text Text Text
Text\end{DefineCodeSnippet}
,即:
\newcommand*\CodeSnippetfoobar{Text Text Text⟨explicit ⟨carriage-return⟩-character-token of catcode 12(other)⟩Text}
\CodeSnippetfoobar
;的每个标记⟨定义文本⟩是类别代码 12(其他)的明确字符标记。
在环境主体的边缘情况下,其由以下部分组成⟨回车⟩-字符,只有其中一个被删除,因为在这种情况下,假设有n⟨回车⟩-字符表示(n-1)个空行。(第一个⟨回车⟩-字符表示从第一行“空”到第一行“空”的过渡。)\begin{⟨environment⟩}
请注意,TeX 对 .tex-input 行的预处理在任何情况下都会删除⟨空间⟩- 出现在 .tex-input 行尾的字符。
因此你不能保留⟨空间⟩- 出现在 .tex-input 行尾的字符,可供 TeX 重新处理(例如通过\scantokens
)。
环境DefineCodeSnippet
等依赖于临时改变类别代码制度,并在改变的类别代码制度生效时通过读取/标记 .tex 输入文件中的内容来获取构成其主体的标记。
因此,环境DefineCodeSnippet
等不能在宏定义/宏参数等中使用,因为在收集形成宏定义的标记时/在收集形成宏参数的标记时,内容已经被标记化。
答案3
您可以将内容存储在 中savebox
。因此,我向您的 添加了另一个参数\calctree
:ID(请注意,它需要反斜杠)。然后您调用\usebox\ID
以使用该框。
\documentclass{article}
\usepackage{luacode}
\begin{luacode}
function calctree(a)
tex.print("-+-+")
tex.print("")
tex.print("$" .. a .. "$")
tex.print("")
tex.print("-+-+")
end
\end{luacode}
% This fully expands the argument
\newcommand\calctree[2]{% second arg is a macroname to store the content
\newsavebox{#2}
\savebox{#2}{\directlua{calctree("#1")}}}%
\newenvironment{multilines}{}{}
\begin{document}
\calctree{OOO}{\boxOOO}
\calctree{a (b + 3) - c^2}{\boxabc}
\begin{multilines}
A
B
C
\end{multilines}
Bla, bla, ...
% Use the content with \calctree.
\usebox\boxOOO
Blo, blo, ...
% Use the content another time with \calctree.
\usebox\boxabc
\usebox\boxOOO
\end{document}