標籤:

morewrites 宏包的主要原理是什麼?

我最近在用 LaTeX 編寫一本書,某一天突然出現了下面的錯誤

! No room for a new write .
ch@ck ...else errmessage {No room for a new #3}

網上查了一下,發現原因是 TeX 只能同時打開 16 個文件。也有資料說儘早引入 morewrites 宏包 [1] 可以解決這個問題。我試了一下,確實可以,都不用作其他修改。

我對 morewrites 的主要原理很感興趣,就去看了一下源代碼。發現它的代碼風格比較奇怪,看不大懂啊。

[1]: CTAN: Package morewrites


原理就是及時回收,重用。

代碼風格是 LaTeX3 的。

要看這個,直接看文檔啊,文檔裡面有介紹宏包內容、原理,也有源代碼的解釋。別直接打開源代碼生看。


@劉海洋 已經粗略提到了主要原理, @李阿玲 還談到了底層限制的問題,我們可以更加具體的來看一看。

早期 TeX 的寄存器分配器寫得不好,只有 new,沒有 delete,很容易造成資源的浪費。TeX 只有 16 個輸入輸出流,問題更加突出。有鑒於此,LaTeX3 就重新設計了文件流的分配方式,及時地回收和復用。但是,礙於 TeX 的底層限制,還是只能同時讀或寫 16 個文件。

morewrites 的方法就稍微不同,它重新定義了 TeX 的相關 primitive:immediate、openout、write 和 closeout,和分配器
ewwrite,可以同時寫多於 16 個文件。

其實 morewrites 的原理是比較簡單的:使用 write 的時候,不是馬上就執行輸出,而是把內容保存到宏裡面,直到遇到對應的 closeout 時,才實際輸出到外部文件中。我們可以來簡單模擬一下比較簡單的帶 immediate 的情況。

RequirePackage{expl3}

ExplSyntaxOn

% morewrites 只需要申請一個實際的輸出流。
iow_new:N g_mw_iow

% morewrites 的分配器,記錄文件編號,對應
ewwrite。
cs_new_protected:Npn mw_newwrite:N #1
{
int_gincr:N g_mw_alloc_int
int_const:Nn #1 { g_mw_alloc_int }
}
int_new:N g_mw_alloc_int

% 對應 immediateopenout,在哈希表 g_mw_iow_prop 中保存文件名稱,重置輸出宏變數。
cs_new_protected:Npn mw_immediate_openout:Nn #1#2
{
prop_gput:NVx g_mw_iow_prop #1 {#2}
l_gclear_new:c { g_mw_iow_ int_use:N #1 _tl }
}
prop_new:N g_mw_iow_prop
cs_generate_variant:Nn prop_gput:Nnn { NVx }

% 對應 immediatewrite,把內容保存到對應的宏變數中。
cs_new_protected:Npn mw_immediate_write:Nn #1#2
{
l_gput_right:cx { g_mw_iow_ int_use:N #1 _tl }
{ iow_now:Nn g_mw_iow {#2} }
}

% 對應 immediatecloseout,關閉文件流時,才把內容實際寫入到外部文件中。
cs_new_protected:Npn mw_immediate_closeout:N #1
{
prop_gpop:NVNT g_mw_iow_prop #1 l_mw_file_tl
{
iow_open:Nn g_mw_iow { l_mw_file_tl }
l_use:c { g_mw_iow_ int_use:N #1 _tl }
l_gclear:c { g_mw_iow_ int_use:N #1 _tl }
iow_close:N g_mw_iow
}
}
cs_generate_variant:Nn prop_gpop:NnNT { NV }
l_new:N l_mw_file_tl

% 測試同時「寫」100 個文件的情況。
cs_new_protected:Npn mw_test_write:Nnn #1#2#3
{
mw_newwrite:N #1
mw_immediate_openout:Nn #1 {#2}
mw_immediate_write:Nn #1 {#3}
}
cs_generate_variant:Nn mw_test_write:Nnn { c }
int_step_inline:nnnn { 1 } { 1 } { 100 }
{ mw_test_write:cnn { l_mw_test_#1_mw } { c_job_name_tl-mw-#1 } { test~#1} }
int_step_inline:nnnn { 1 } { 1 } { 100 }
{ exp_args:Nc mw_immediate_closeout:N { l_mw_test_#1_mw } }

ex_end:D

當然了,以上只是一個基本原理的演示,沒有充分考慮到各種細節。morewrites 的實現要複製得多。比如它為了不改變原來的使用方式,專門準備了 primargs 宏包用於讀取那些奇怪 primitive 參數。還有就是不帶 immediate 修飾的話,輸出是在 shipout 階段才執行的,情況要複雜一些。


推薦閱讀:

LaTeX和CTeX,TeX Live的作用分別是什麼?有什麼關係?
有沒有簡單的LaTeX多語言支持方案?
用 TeX 編輯論文時,如何選擇合適的 Packages ?
解決類似問題,應該學VBA還是Latex?

TAG:LaTeX |