我有一个查询,其中最多可以保存一个expl3
列表中的项目数。感谢@egreg 给我的答复避免使用 xparser 传递空的 [可选参数] 环境(带有逐字内容)?我能够完成一个能够在列表中保存有效LaTeX
代码(包括verbatim
)的环境的实现expl3
......类似于“ConTextbuffers”。
让我解释一下,我有许多问答文件,其中我滥用来filecontents
记录临时文件并将它们包含在基于的环境中 environ
(不支持verbatim
),然后在文档的不同部分使用它们。
使用的新版本,filecontentsdef
无需编写外部文件(包括在内verbatim
)即可将它们放在中\macro
,因此,我设法创建了一个scontents
封装的环境并收集了想法\filecontentsdefmacro
,运行并保存在一个seq
列表中,我可以使用来访问\getstored[index]{seq name}
,环境和命令Scontents
运行得非常完美。
我的问题是这样的,环境扩展的内容\macro
并按顺序存储,命令直接按顺序存储(相同或其他),在某些时候列表seq
会有一个限制。我可以保存的物品数量有多少?
我已经测试了一些文件,但是,我使用转换将所有旧文件script
传递到新scontents
环境。
我在这里留下使用的示例代码l3seq
\documentclass{article}
\usepackage{filecontentsdef}[2019/04/20]
\usepackage{xparse}
\usepackage{pgffor,fvextra} % only for example
\pagestyle{empty} % only for example
\setlength{\parindent}{0pt} % only for example
\ExplSyntaxOn
\tl_new:c { l_scontents_macro_tmp_tl }
\keys_define:nn { scontents }
{
save-env .tl_set:N = \l_scontents_name_seq_tl,
save-env .initial:n = contents,
show-env .bool_set:N = \l_scontents_show_env_bool,
show-env .initial:n = false,
save-cmd .tl_set:N = \l_scontents_name_seq_cmd_tl,
save-cmd .initial:n = contents,
show-cmd .bool_set:N = \l_scontents_show_cmd_bool,
show-cmd .initial:n = false
}
\cs_new_protected:Npn \_scontents_append_contents:nn #1#2
{
\seq_if_exist:cF { g_scontents_seq_name_#1_seq }
{
\seq_new:c { g_scontents_seq_name_#1_seq }
}
\seq_gput_right:cn { g_scontents_seq_name_#1_seq } {#2}
}
\cs_new_protected:Npn \_scontents_getfrom_seq:nn #1#2
{
\seq_item:cn { g_scontents_seq_name_#2_seq } {#1}
}
\ProvideExpandableDocumentCommand{ \countseqsc }{ m }
{
\seq_count:c { g_scontents_seq_name_#1_seq }
}
\keys_define:nn { scontents / Scontents }
{
show-cmd .code:n = { \keys_set:nn { scontents } { show-cmd = #1 } },
save-cmd .code:n = { \keys_set:nn { scontents } { save-cmd = #1 } }
}
\ProvideDocumentCommand{ \Scontents }{ O{} +m }
{
\group_begin:
\IfNoValueF {#1} { \keys_set:nn { scontents / Scontents } {#1} }
\_scontents_append_contents:nn { \l_scontents_name_seq_cmd_tl } { #2 } % add to seq
\bool_if:NT \l_scontents_show_cmd_bool
{
\_scontents_getfrom_seq:nn { -1 }{ \l_scontents_name_seq_cmd_tl }
}
\group_end:
}
\ProvideExpandableDocumentCommand{ \getstored }{ O{1} m }
{
\_scontents_getfrom_seq:nn {#1} {#2}
}
\ProvideDocumentEnvironment{ scontents }{}
{
\char_set_catcode_active:N \^^M
\scontents_start_environment:w
}
{
\scontents_stop_environment:
\scontents_atend_environment:
}
\cs_new_protected:Npn \scontents_environment_keys:w [#1]
{
\keys_set:nn { scontents } {#1}
}
% Star environment
\group_begin:
\char_set_catcode_active:N \^^M
\cs_new_protected:Npn \scontents_start_environment:w #1 ^^M
{
\tl_if_blank:nF {#1} { \scontents_environment_keys:w #1 }
\group_begin: % open group for env
\use:c { filecontentsdefmacro } { \l_scontents_macro_tmp_tl } ^^M
}
\group_end:
% Stop environment
\cs_new_protected:Nn \scontents_stop_environment:
{
\endfilecontentsdefmacro
\group_end: % close group for env
}
% A variant to replace \^^M for \^^J (need by Verb{..} from fvextra)
\cs_generate_variant:Nn \tl_replace_all:Nnn { Nxx }
% Expand \l_tmpa_tl and pass to seq
\cs_gset_protected:Nn \_scontents_macro_to_seq:
{
\regex_replace_all:nnN { \^^M } { \^^J } \l_scontents_macro_tmp_tl
\cs_log:N \l_scontents_macro_tmp_tl
\exp_args:NNx \_scontents_append_contents:nn \l_scontents_name_seq_tl
{
\exp_not:N \scantokens \exp_after:wN { \tl_use:c { l_scontents_macro_tmp_tl} } %
}
}
\cs_new_protected:Nn \scontents_atend_environment:
{
\_scontents_macro_to_seq:
\bool_if:NT \l_scontents_show_env_bool
{
\_scontents_getfrom_seq:nn { -1 }{ \l_scontents_name_seq_tl }
}
\cs_undefine:N \l_scontents_macro_tmp_tl
}
\ExplSyntaxOff
\begin{document}
\section{Test \Verb{\begin{scontents}[key=val]}}
Test \verb+\begin{scontents}+ no \verb+[key=val]+\par
\begin{scontents}
Using \Verb{scontents} env no \verb+[key=val]+, save in seq \verb+contents+ with index 1.\par
\begin{verbatim}
(A) verbatim environment
\end{verbatim}
\end{scontents}
Test \verb+\begin{scontents}[show-env=true]+\par
\begin{scontents}[show-env=true]
Using \verb+scontents+ env with \Verb{[show-env=true]}, save in seq \verb+contents+ with index 2.
We have coded this in \LaTeX: $E=mc^2$.\par
\begin{Verbatim*}
(B) verbatim environment
\end{Verbatim*}
\end{scontents}
Test \verb+\begin{scontents}[show-env=false]+\par
\begin{scontents}[show-env=false]
Using \verb+scontents+ env with \verb+[show-env=false]+, save in seq \verb+contents+ with index 3.
We have coded this in \LaTeX: $E=mc^2$.\par
\begin{verbatim}
(C) verbatim environment
\end{verbatim}
\end{scontents}
Test \verb+\begin{scontents}[show-env=true]+\par
\begin{scontents}[show-env=true]
Using \verb+scontents+ env with \verb+[show-env=true]+, save in seq \verb+contents+ with index 4.
We have coded this in \LaTeX: $E=mc^2$.\par
\begin{verbatim*}
(D) verbatim environment
\end{verbatim*}
\end{scontents}
Test \verb+\Scontents{...}+\par
\Scontents{Using \texttt{Scontents} command (no verbatim), save in seq \texttt{contents} with index 5}
\section{Test \Verb{\getstored[index]{contents}}}
The total contents stored in \verb+contents+ seq are \countseqsc{contents}.\par
\getstored[5]{contents}\par
\getstored{contents}
\section{A simple aplication using \Verb{pgffor} loop}
\newcounter{exeNr}
\newenvironment{exercise}
{\refstepcounter{exeNr}\par\noindent This is exercise~\theexeNr}
{\par}
\subsection{Exercises}
\begin{exercise}
\end{exercise}
\begin{scontents}[save-env = myansewer]
This is the answer to exercise 1, the shebang line for a Perl script
\begin{verbatim}
#!/usr/bin/env perl
\end{verbatim}
\end{scontents}
\begin{exercise}
\end{exercise}
\begin{scontents}[save-env = myansewer]
This is the answer to exercise 2
\end{scontents}
\begin{exercise}
\end{exercise}
\Scontents[save-cmd = myansewer]{This is the answer to exercise 3}
\subsection{Answers}
\newcounter{ansNr}
\newenvironment{answer}
{\refstepcounter{ansNr}\par\noindent Answer~\theansNr:}
{\par}
\foreach \i in {1,...,\countseqsc{myansewer}} {
\begin{answer}
\getstored[\i]{myansewer}
\end{answer}
}
\end{document}
附件查询,expl3
提供了两个模块l3seq
来l3prop
存储数据,请问这个具体案例有什么区别吗?
问候
答案1
好吧,首先,你的代码似乎向expl3
seq
变量中添加了内容(在编辑之前,它们现在是prop
变量,但在内存消耗方面差别不大)。seq
变量只不过是一个宏,其中包含你添加到该序列中的项目。例如,执行此操作后\seq_set_from_clist:Nn \l_tmpa_seq {a,b,c}
,seq
变量将是一个宏,其中包含:
\s__seq
\__seq_item:n {a}
\__seq_item:n {b}
\__seq_item:n {c}
任意大的序列变量将是一个包含任意多个的宏\__seq_item:n {<stuff>}
。
列表prop
类似。执行此操作后,\prop_set_from_keyval:Nn \l_tmpa_prop { a = x, b = y, c = zzz }
变量prop
将是一个宏,其中包含:
\s__prop
\__prop_pair:wn a\s__prop {x}
\__prop_pair:wn b\s__prop {y}
\__prop_pair:wn c\s__prop {zzz}
因此,一个任意大的prop
列表将包含任意多个\__prop_pair:wn <name>\s__prop {<value>}
。基本区别在于,在这里您不仅要存储一个值,还要存储它的名称,因此开销会稍微高一些。使用变量时,seq
您有<stuff>
3 个标记(\__seq_item:n
、{
和}
),而使用prop
变量时,您有<name>
、<value>
和 4 个标记(\__prop_pair:wn
、\s__prop
、{
和}
)。
当然,一旦两者的内容开始增长,开销就会变得微不足道,因此选择取决于用途。如果您想存储一系列事物,请使用变量seq
。如果您想存储事物并为每个事物命名,请使用列表prop
。与它们的内容相比,两者的内存开销都很小。
现在回到你的问题,变量可以包含的项目数量没有限制,seq
因为 TeX 中的大小没有限制。它可以存储任意大的定义,只要它们适合 TeX 可用的内存量。因此,你的问题的答案将类似于“ TeX 可以在其内存中存储多大的内容?”\def
的答案。\def
如果我只是加载expl3
一个简单的 TeX 文件,创建一个(最初)空的seq
变量,并询问内存统计信息:
\input expl3-generic.tex
\ExplSyntaxOn
\seq_new:N \l_test_seq
\ExplSyntaxOff
\tracingstats1
\bye
我得到了这个(使用 TeXLive 2019 中的 pdfTeX):
Here is how much of TeX's memory you used:
9137 strings out of 494647
176410 string characters out of 6148463
160755 words of memory out of 5000000
10799 multiletter control sequences out of 15000+600000
543492 words of font info for 60 fonts, out of 8000000 for 9000
1416 hyphenation exceptions out of 8191
27i,0n,59p,187b,317s stack positions out of 5000i,500n,10000p,200000b,80000s
XeTeX 将显示基本相同的信息,可能数字上略有不同,因为它与 pdfTeX 非常相似。另一方面,LuaTeX 将显示略有不同的摘要:
Here is how much of LuaTeX's memory you used:
8789 strings out of 496318
100000,154059 words of node,token memory allocated
244 words of node memory still in use:
3 hlist, 1 rule, 1 glyph, 27 glue_spec nodes
avail lists: 2:10,3:3,5:3,7:2
10892 multiletter control sequences out of 65536+600000
60 fonts using 6755909 bytes
61i,0n,59p,271b,446s stack positions out of 5000i,500n,10000p,200000b,100000s
因为 LuaTeX 的内存管理与其前身有很大不同(请参阅3.4.1 内存分配LuaTeX 手册)。但是,底层信息相当相似。接下来的段落将基于 pdfTeX 的输出,但相同的原则也适用于其他引擎,只是稍微改变了数字。我还将使用变量,seq
因为与变量相比,它更容易通过编程构建prop
,但原则都是一样的。
如果我向其中添加一个项目,则seq
内存使用情况不会有任何变化。事实上,如果我向该seq
变量添加一堆项目,则在某个点之前,我不会看到任何变化。我认为(但不确定)TeX 显示的内存统计数据是运行期间使用的最大量,但在某些时候它会清除一些内存,当我填充时,seq
它会使用该内存,但不会显示在统计数据中。
但是,一旦我超过该点,任何添加到seq
变量的内容都会计入内存。此代码勉强超过该阈值:
\input expl3-generic.tex
\ExplSyntaxOn
\seq_new:N \l_test_seq
% \int_step_inline:nn {751} { \seq_put_right:Nn \l_test_seq { 0123456 } }% No difference
\int_step_inline:nn {752} { \seq_put_right:Nn \l_test_seq { 0123456 } }
\ExplSyntaxOff
\tracingstats1
\bye
除了 之外,内存使用量是相同的words of memory
,它从 增加到160755
。160769
此操作不会影响其他内存类型。如果我向该变量添加另一个项目(通过将 更改为 ,则增加到752
。753
添加另一个,它将变为,始终以 为步长。你问为什么?每个添加的项目是,它们恰好是标记(,,7 个字符和),所以这告诉我们定义中的每个标记都算作两个(可能是因为 TeX 通过其字符和类别代码来表示一个标记,所以每个标记 2 个单词;但我只是在这里猜测,抱歉)。如果你要使用列表,则必须考虑每个项目名称的长度,加上这个答案开头显示的额外标记。words of memory
160789
160809
20
20
\__seq_item:n {0123456}
10
\__seq_item:n
{
}
words of memory
prop
现在我们开始有了进展。如果我取可用总数words of memory
,减去已用数量,然后除以,20
我就能估算出仍可添加到上述序列中的 7 个标记项的数量,大约为241962
,这将花费大量时间来运行 :)
为了减少构建该序列所需的时间,您可以扩大每个项目。以下代码将 997 个标记项目添加到变量中seq
,这些标记添加到变量的 3 个标记中seq
,以内存字为单位增加序列2000
:
\input expl3-generic.tex
\ExplSyntaxOn
\seq_new:N \l_test_seq
\int_step_inline:nn {2426} % Add 1 to see TeX blow up :)
{
\seq_put_right:Nn \l_test_seq
{ % Indentation is important even here :)
0123456 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
}
}
\ExplSyntaxOff
\tracingstats1
\bye
上述代码将内存带至4999713 words of memory out of 5000000
(99.994%
:)。
结论 (?)
这一切意味着,当您使用 pdfTeX 或 XeTeX 时,您可以(大致)将2400
1000 个标记项存储在一个seq
变量中。如果您将所有存储的标记视为 ASCII 字符,则每个 1000 个标记项为 1 kB,那么您大约有2.4 兆字节此变量(或任何其他 2.4 MB 定义)的可用内存seq
。当然,seq
变量的固有内存占用量为 3 个标记,因此存储的项目越小,可用空间就越少。
如果您使用的是 LuaTeX,内存使用量将与其他引擎大致相同。上面的测试使用了 LuaTeX 100000,5906145 words of node,token memory allocated
(即,100000 words of node and 5906145 words of token memory
后者是我们感兴趣的),比 pdfTeX 多出约 20%。但是 LuaTeX 具有动态内存分配,因此,引用手册:
“主内存不足”错误仍然会发生,但限制因素现在是系统中的 RAM 数量,而不是预定义的限制。
所以是的,如果你使用 LuaTeX,你可以在循环计数器上进行相当疯狂的操作:)
main_memory
对于其他引擎,可以通过改变in的值来扩大内存texmf.cnf
(默认为5000000
words,2.5 MB)。
但是,如果您在文档中达到这一点,最好将其保存到外部文件中。一种可能超出内存限制的情况是,环境使用宏抓取其内容,并且内容非常大,例如“外部化时超出 TeX 容量“。然而,即使在这种情况下,也可以通过某种方式进行改变,从而大幅减少内存使用量。