與保存堆疊條目相關的本地組中全域定義的語義差異

與保存堆疊條目相關的本地組中全域定義的語義差異

這是一個後續問題\csname 的 \global 變體...\endcsname,它要求提供那裡解釋的問題的解決方案,但不要求解釋。

問題的要點是

{ \gdef\foo{...} }

似乎表現不同

{ \expandafter\gdef\csname foo\endcsname{...} }

其中後者「在 save_stack 上新增一個保留條目」。

所以我想知道:

  1. 為什麼這兩個構造的行為會有差異?這只是實現方式的副作用\csname...\endcsname,還是有意的語意差異(據可能判斷)。
  2. {retaining ...}連結問題中提到的日誌檔案中的條目到底意味著什麼?為什麼在群組關閉後需要保留本地定義?

答案1

感謝您提出這個問題。我不太明白這個例子另一個問題當我讀到它時,但這個問題促使我再看一遍,我想我現在明白了。

基本上,從這個例子中還不清楚的是,當我們\expandafter\gdef\csname foo\endcsname{...}在內部有幾個這樣的結構時,就會出現問題。相同的群組(或嵌套在該群組內的群組),即保存堆疊僅在群組內增長。 「retaining…」訊息是在退出群組時(彈出堆疊時)列印的,但該訊息本身並不是問題;它僅作為堆疊較早增長的證據。

該段落可能令人困惑,所以讓我們從頭開始了解保存堆疊。 :-)


1.考慮這個例子:

\def\a{hello}
{
    \def\a{world}
}

在這裡,當內部群組中的 TeX 發現\a被重新定義(到world)時,它將先前的值(對包含 的標記清單的參考hello)保存到保存堆疊中。然後,當它到達群組的末尾時,它會彈出堆疊以恢復 的定義hello。這是保存堆疊的明顯原因,以下是相應的追蹤輸出,假設您有\tracingrestores=2\tracinggroups=2以及 並且\tracingassigns=2內部群組從第 10 行開始(比如說) - 我還修改了輸出以刪除換行符前{into…}

{changing \a=undefined}{into \a=macro:->hello}
{entering simple group (level 1) at line 10}
{changing \a=macro:->hello}{into \a=macro:->world}
{restoring \a=macro:->hello}
{leaving simple group (level 1) entered at line 10}

2.現在考慮同一個例子,沒有頂部\def\a{hello}的:

{
    \def\a{world}
}

— 再一次,當 TeX 看到\def\a{world}組內部時,它必須保存 之前的含義\a。它恰好是未定義的,但我們仍然需要在離開群組後再次將其重新定義為未定義,因此TeX需要將「未定義」的含義放入保存堆疊中。追蹤輸出為:

{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=macro:->world}
{restoring \a=undefined}
{leaving simple group (level 1) entered at line 10}

3.現在考慮一個類似的例子:

{
  \let\a=\relax
  \gdef\a{world}
}

在這裡,當 TeX 看到第一個 時\let\a=\relax,它必須將先前的含義(「未定義」)保存到保存堆疊中,就像前面的範例一樣。然後,當它看到 時,\gdef它不必將任何內容放入保存堆疊中。最後,當它到達群組的末尾並開始彈出其堆疊(當前包含 的“未定義”含義\a)時,它注意到\a現在具有全局定義,因此它忽略“未定義”含義並保留全局定義。這解釋了追蹤輸出:

{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=\relax}
{globally changing \a=\relax}{into \a=macro:->world}
{retaining \a=macro:->world}
{leaving simple group (level 1) entered at line 10}

4.最後,考慮這個例子:

{
  \expandafter\gdef\csname a\endcsname{world}
}

事實證明,這與之前的情況完全相同。這是因為,當 TeX 看到 時,它\expandafter會暫時跳過\gdef並開始作用於下一個標記 ( \csname) — 這意味著作用於\csname a\endcsname創建宏\a,並定義\relax它是否尚未定義(這就是\csname工作原理),並且只有在那之後它才會作用於(之前暫時忽略的)\gdef並重新定義為( )\a後面的新定義。world因此追蹤輸出與上次相同:

{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=\relax}
{globally changing \a=\relax}{into \a=macro:->world}
{retaining \a=macro:->world}
{leaving simple group (level 1) entered at line 10}

這裡的所有都是它的。回答您的具體問題:

  • \csname … \endcsname(1) 這只是實現方式的副作用;即它\let是第一個的令牌\relax。然而,這是有詳細記錄的,因此可以說也是語義的一部分(正如每個人所期望的那樣)。

  • (2a){retaining ...}日誌檔案中的條目表示(請參閱第 301 頁)教材)那早些時候\relax放在保存堆疊上的定義,即巨集在設定為by之前的值\csname … \endcsname,已被忽略\gdef(並且全域定義已保留)。

  • (2b)“為什麼在小組關閉後有必要保留本地定義?” — 事實並非如此,而且它們也沒有被保留。相反,你看到的是什麼時候該群組正在關閉,所有先前因同一組內所做的本地定義而保存的定義現在都將被檢查,並且如果在任何時候存在全域定義,則現在才被丟棄。在群組的末尾,保存堆疊將為空(或更確切地說,具有與進入群組時相同的大小)。


更具體地說,這是問題和解決方案的範例那個問題。提問者在一個群組內定義了很多宏(本質上),其方式大致相當於:

{
    \expandafter\gdef\csname A\endcsname{I'm A}
    \expandafter\gdef\csname B\endcsname{I'm B}
    \expandafter\gdef\csname C\endcsname{I'm C}
}

等等。正如我們在上面的範例 3 和 4 中看到的,這相當於:

{
    \let\A=\relax \gdef\A{I'm A}
    \let\B=\relax \gdef\B{I'm B}
    \let\C=\relax \gdef\C{I'm C}
}

等等。因此,上面的每個定義都會在保存堆疊上放置一個條目(用於控制序列名稱之前的含義\letto\relax​​ by \csname ... \endcsname),並且僅在群組的末尾才彈出所有這些條目。所以如果這樣的定義太多;您將用完“保存大小”。

第一個發布的答案(作者:Steven B. Segletes)建議執行相當於\csname … \endcsname在最頂層執行每個操作的操作(其中不會將任何內容放入保存堆疊中)。

第二個發布的答案(Marcel Krüger)建議做相當於:

{
  \begingroup\expandafter\endgroup\expandafter\gdef\csname A\endcsname{I'm A}
  \begingroup\expandafter\endgroup\expandafter\gdef\csname B\endcsname{I'm B}
  \begingroup\expandafter\endgroup\expandafter\gdef\csname C\endcsname{I'm C}
}

其中定義發生在立即退出的群組內,因此每個堆疊都會立即彈出:追蹤輸出為(模換行符):

{entering simple group (level 1) at line 10}

{entering semi simple group (level 2) at line 11}
{changing \A=undefined}{into \A=\relax}
{restoring \A=undefined}
{leaving semi simple group (level 2) entered at line 11}
{globally changing \A=undefined}{into \A=macro:->I'm A}

{entering semi simple group (level 2) at line 12}
{changing \B=undefined}{into \B=\relax}
{restoring \B=undefined}
{leaving semi simple group (level 2) entered at line 12}
{globally changing \B=undefined}{into \B=macro:->I'm B}

{entering semi simple group (level 2) at line 13}
{changing \C=undefined}{into \C=\relax}
{restoring \C=undefined}
{leaving semi simple group (level 2) entered at line 13}
{globally changing \C=undefined}{into \C=macro:->I'm C}

{leaving simple group (level 1) entered at line 10}

如果您不關心可擴展等,而只想了解保存堆疊,那麼建議的解決方案如下:

{
    {\let\A=\relax} \gdef\A{I'm A}
    {\let\B=\relax} \gdef\B{I'm B}
    {\let\C=\relax} \gdef\C{I'm C}
}

這樣您就可以明白為什麼保存堆疊不會繼續增長。

相關內容