為什麼要使用 \expandafter\@firstoftwo 這個慣用語?

為什麼要使用 \expandafter\@firstoftwo 這個慣用語?

我看到很多宏都是條件測試,然後是一{true}{false}對定義的宏,因此它們會導致:

\expandafter\@firstoftwo

或者

\expandafter\@secondoftwo

為什麼\expandafter會有這些?我本來希望他們會抓住下一{true}{false}對的第一個括號?

答案1

s\expandafter用於處理以下\else\fi。正如 Ryan 連結到的問題一樣,完整的程式碼類似於:

\def\ifeq#1#2{%
 \ifx#1#2\relax
   \expandafter\@firstoftwo
 \else
   \expandafter\@secondoftwo
 \fi
}

讓我們追蹤一下發生了什麼事。我們將其放入\ifeq\stuff\nonsense{true}{false}我們的文檔中。吸收\ifeq\stuff,所以\nonsense我們在第一次擴展後得到:

\ifx\stuff\nonsense\relax
  \expandafter\@firstofone
\else
  \expandafter\@secondoftwo
\fi
{true}{false}

讓我們假設\stuff\nonsense(即條件為真)。然後\ifx開始擴展其「真實」路徑中的所有內容,該路徑被定義為直到下一個\else\fi(模嵌套)的所有內容。關鍵是它開始擴張第一的並且不會向前尋找\elseor \fi。 TeX 認為當它到達時它就會知道它。所以我們有:

  \expandafter\@firstoftwo
\else
  \expandafter\@secondoftwo
\fi
{true}{false}

TeX 現在擴展了它\expandafter。這具有\@firstoftwo超越\else並擴展它的效果。 「擴展」意味著從流中\else刪除它以及直到匹配的所有內容。\fi所以我們剩下:

\@firstoftwo{true}{false}

然後這被擴展到簡單的true.

如果沒有\expandafters,我們會得到:

  \@firstoftwo
\else
  \@secondoftwo
\fi
{true}{false}

TeX 仍在擴展 true 分支,因此擴展\@firstoftwo。這會從流中吸收兩個令牌/支撐組。這些恰好是\else\@secondoftwo。然後它留下流中的第一個,所以我們得到

\else
\fi
{true}{false}

\else條件匹配,因此 TeX 吸收此內容以及直到流中\fi留下的所有內容。{true}{false}這不是我們想要的。

總之,要在執行\expandafters前排除條件處理。結果條件語句的內容被擴展,從而確保條件語句的結果看到流中的下一個位,而不是未完成的條件語句中留下的位。

答案2

我會從不同的角度來攻擊這個問題。 TeX 中的原始條件測試條件,它可以從輸入流中吸收標記,也可以不吸收標記,直到可以確定條件的真假。因此,讓我們用 <IF>原始條件和必須吸收的標記清單(可能為空)來表示。例如\ifhmode不需要令牌,\ifx需要兩個。在某些情況下 ( \if, \ifcat, \ifnum, \ifdim) TeX 執行擴展以便找到測試所需的標記類型;在其他 ( \ifx, \ifmmode, \ifhmode, \ifvmode \ifinner, \iftrue, \iffalse) 中,不執行擴展。因此<IF>將表示條件和所需的標記膨脹已經發生並且可以測試條件。

您所指的典型結構是

<IF>
  \expandafter\@firstoftwo
\else
  \expandafter\@secondoftwo
\fi

我們已經有的地方

\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}

更一般地說,我們有

<IF><true>\else<false>\fi

或者

\IF<true>\fi

其中 和<true>都可<false>以為空。

條件為真

TeX 將簡單地<IF>從輸入流中刪除,留下

<true>\else<false>\fi

或者

<true>\fi

條件為假

TeX 尋找下一個\else標記,同時考慮可能出現的巢狀條件<true>而不展開任何內容。因此,\else屬於嵌套\if...\else\fi內部的一個<true>將被跳過。在這種情況下,擴展也是空的,並且所有標記\else都會消失。如果沒有找到匹配,TeX 將停止尋找最好位於某處的\else匹配。\fi所以,在這兩種情況下,我們會得到

<false>\fi

或者如果沒有\else分支就什麼都沒有。

以下 TeX 輸入證明了這一點:

\def\showx{\show\x}
\def\showif{\afterassignment\showx
  \expandafter\def\expandafter\x\expandafter}

\showif{\ifvmode<true>\else<false>\fi}
\showif{\ifvmode<true>\fi}
\showif{\ifhmode<true>\else<false>\fi}
\showif{\ifhmode<true>\fi}
\bye

在其上運行 TeX 將產生以下腳本:

This is TeX, Version 3.1415926 (TeX Live 2012)
(./plkfi.tex
> \x=macro:
-><true>\else <false>\fi .
\showx ->\show \x

l.5 \showif{\ifvmode<true>\else<false>\fi}

?
> \x=macro:
-><true>\fi .
\showx ->\show \x

l.6 \showif{\ifvmode<true>\fi}

?
> \x=macro:
-><false>\fi .
\showx ->\show \x

l.7 \showif{\ifhmode<true>\else<false>\fi}

?
> \x=macro:
->.
\showx ->\show \x

l.8 \showif{\ifhmode<true>\fi}

?

接下來發生什麼事

  • 的擴充\else在於刪除匹配之前的所有內容\fi,並且在輸入流中不留下任何內容。將像以前一樣考慮嵌套條件。

  • 的展開\fi是空的。

的作用\expandafter

現在我們有了可以降落的基地\expandafter

讓我們來看一個典型的用法:

\def\@ifundefined#1{%
  \expandafter\ifx\csname#1\endcsname\relax
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi}

我們想要的是能夠說

\@ifundefined{foo}{T}{F}

因此,將使用參數作為名稱建立的巨集進行比較\relax(這實際上是無趣的部分),然後遵循 true 或 false 分支。

移除後<IF>我們仍然保留

\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi{T}{F}

現在 TeX 適當地擴展了第一個令牌。這會觸發 的擴展\else,這就是樂趣的開始。

的擴展\expandafter在於擴展(如果可能的話)下一個令牌之後的令牌並消失。因此\else根據上面的規則展開,我們剩下

\@firstoftwo{T}{F}

這會導致留T在輸入流中。

現在假設條件為假。然後將<IF>與 之前的所有內容一起刪除\else,留下

\expandafter\@secondoftwo\fi{T}{F}

現在\expandafter它的工作是擴大\fi和消失。這樣我們就得到

\@secondoftwo{T}{F}

那終於離開了F

重要的提示

\@ifundefined{foo}{T}{F}在我們能夠達到TF不達到的情況下執行a 指令:剛剛使用了巨集擴充。這使得\@ifundefined類似定義的巨集可以在內部使用\edef

\edef\test{\@ifundefined{foo}{T}{F}}

將相當於

\def\test{T}

in case\foo被定義(並不等同於\relaxLaTeX 中通常的 )或

\def\test{F}

if\foo未定義(或相當於\relax)。


如果沒有的話會發生什麼事\expandafter?對於真正的條件 TeX 將面臨

\@firstoftwo\else\@secondoftwo\fi{T}{F}

的兩個參數將\@firstoftwo\else\@secondoftwo,這不會做任何有用的事情,不是嗎?

類似地,對於錯誤的條件,我們會得到

\@secondoftwo\fi{T}{F}

事情又會出錯。

答案3

此類程式碼的完整上下文位於條件宏中,例如

\def\IfZero#1{%
  \ifnum0=#1\relax
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}

\expandafter如果您以較少格式的方式查看程式碼,則 s 是必需的:

\def\IfZero#1{\ifnum0=#1\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}

(對長行表示歉意)其中沒有給出有關 TeX 條件塊結構的線索。正如您所看到的,標記“after”\@firstoftwo\else,而 after\@secondoftwo\fi,它們分別由 展開\expandafter。這種愚蠢的目的是 TeX 在擴展\ifnum;時不會讀取整個條件。它只是向前掃描到正確的 true 或 false 區塊並從那裡繼續。或留在輸入流中\else\fi一旦它們被擴展,TeX 就會掃描到條件的末尾,輸入流中的下一個內容就是 後面的內容\IfZero

如果沒有任一,和\expandafter消耗的「兩個」將分別是和以及後面的宏參數,而不是實際意圖,即下面的兩個宏參數被視為「第二個」和「第三個」參數」的。\@firstoftwo\@secondoftwo\else\@secondoftwo\fi\IfZero

相關內容