\span
Я пару раз натыкался на примитив. У меня сложилось впечатление, что он определен в программе TeX, даже не в plain.tex
. Это верно? И чтоделаетчто делает эта команда и для чего она?
решение1
Примитив \span
имеет два совершенно разных значения, независимо от того, появляется ли он в преамбуле \halign
или в основной части.
Если он находится в преамбуле, то есть перед первым, \cr
то означает «расширить» следующий токен; если он находится в теле, то он заканчивает текущую ячейку, но объединяет ее со следующей. В этом случае он обычно находится в сочетании с \omit
.
Самый простой пример:
\halign{\hfil#\tabskip1em&#\hfil\cr
a&b\cr
c\span d\cr
}
где вторая строка будет иметь только одну ячейку. Выход будет
без \tabskip
пробела между «c» и «d», поскольку вторая строка — это всего лишь одна ячейка с \hfil
содержимым до и после, но TeX учтет это \tabskip
при определении ширины ячейки.
Обычно, однако, также добавляют, \omit
чтобы удалитьтыивчасти (тыотносится к тому, что было до этого #
,вк тому, что следует за этим).
Когда TeX читает преамбулу, \halign
он не расширяет токены, пока не найдет \tabskip
(когда он расширяет токены для поиска соответствующей спецификации склеивания). Расширение токена можно принудительно выполнить, поставив перед ним \span
.
Примером может служитьamsmath.sty
\def\align@#1#2{%
\inalign@true \intertext@ \Let@ \chardef\dspbrk@context\z@
\ifingather@\else\displ@y@\fi
\let\math@cr@@@\math@cr@@@align
\ifxxat@\else \let\tag\tag@in@align \fi
\let\label\label@in@display
#1% set st@r
\ifst@rred\else \global\@eqnswtrue \fi
\measure@{#2}%
\global\row@\z@
\tabskip\eqnshift@
\halign\bgroup
\span\align@preamble\crcr
#2%
}
Это вспомогательный макрос, используемый для align
и друзей; преамбула определяется один раз и навсегда в макросе\align@preamble
\def\align@preamble{%
&\hfil
\strut@
\setboxz@h{\@lign$\m@th\displaystyle{##}$}%
\ifmeasuring@\savefieldlength@\fi
\set@field
\tabskip\z@skip
&\setboxz@h{\@lign$\m@th\displaystyle{{}##}$}%
\ifmeasuring@\savefieldlength@\fi
\set@field
\hfil
\tabskip\alignsep@
}
Это значительно упрощает определение \align@
, помещая большой кусок кода в макрос. Это также позволяет изменять поведение align
путем модификации \align@preamble
(см.Можно ли сделать так, чтобы нечетные столбцы имели неявный префикс {}?).
Ядро LaTeX использует другой метод tabular
, поскольку ему необходимо построить преамбулу в соответствии со своими правилами, то есть комбинацию lcrp
символов и т. д.
Второе использование for \span
— для объединения столбцов в \halign
. В Plain TeX мы находим\multispan
\def\multispan#1{\omit \mscount#1\relax
\loop\ifnum\mscount>\@ne \sp@n\repeat}
\def\sp@n{\span\omit\advance\mscount\m@ne}
который делает \omit
и добавляет столько \span\omit
пар, сколько указано в аргументе (минус один); поэтому \multispan{1}
эквивалентно \omit
, \multispan{2}
и \omit\span\omit
так далее. \multicolumn
Макрос LaTeX построен на той же идее
% latex.ltx, line 5053:
\long\def\multicolumn#1#2#3{\multispan{#1}\begingroup
\@mkpream{#2}%
\def\@sharp{#3}\set@typeset@protect
\let\@startpbox\@@startpbox\let\@endpbox\@@endpbox
\@arstrut \@preamble\hbox{}\endgroup\ignorespaces}
Сначала он делает \multispan{#1}
(определяется точно так же, как в Plain), а затем приступает к построению «локальной» преамбулы выравнивания, используя ту же, что \@mkpream
используется tabular
и array
для оценки их обязательного аргумента.
Почему Кнут использовал один и тот же примитив для двух совершенно разных значений? Чтобы сэкономить место; TeX был написан, когда памяти компьютера было мало, и экономия на определении была важна. Поскольку \span
может появляться только в \halign
(или \valign
, конечно), это не проблема.
Неправильное использование \span
в первом значении появляется в решении упражнения 20.16 в TeXbook
Не стоит воспринимать следующее слишком серьезно, но это работает:
{\setbox0=\vbox{\halign{#{\c\span\d}\cr \let\next=0\edef\next#1{\gdef\next{\b#1}}\next\cr}}} \let\a=\next
Упражнение заключается в определении \a
эквивалентности \b
(полностью развернутому) с последующим \c
(не развернутым) и \d
(развернутым только один раз) без использования \noexpand
и \the
. Например, если у нас есть
\def\foo{xy\baz}
\def\baz{z}
\def\b{\foo\foo}
\def\c{--}
\def\d{\baz}
мы хотим определить, \a
чтобы иметь в качестве заменяющего текста
xyzxyz\c\baz
Часть \setbox0=\vbox{
просто используется \halign
без какого-либо вывода. Теперь TeX оценивает \halign
преамбулу как #{\c\baz}\cr
, поскольку \span
вызывает одноуровневое расширение \d
. Единственная ячейка содержит
\let\next=0\edef\next#1{\gdef\next{\b#1}}\next
который, согласно правилам, используется вместо #
в преамбуле, так что входной поток становится
\let\next=0\edef\next#1{\gdef\next{\b#1}}\next{\c\baz}\cr
Просто \cr
заканчивает ячейку, поэтому она не имеет значения. Из-за \let\next=0
, \next
становится нерасширяемым, поэтому \edef
вызывает \next
определение, как если бы она была
\def\next#1{\gdef\next{<full expansion of \b>#1}}
и поэтому \next{\c\baz}
будет добросовестно исполнять
\gdef\next{<full expansion of \b>\c\baz}
и финал \let\a=\next
кладет всему конец.
Это хорошо объясняет, что \span
делает толькоодиншаг расширения.
Другое решение с e-TeX (он ведь не использует \noexpand
и ) было бы таким:\the
\edef\a{\b\unexpanded\expandafter{\expandafter\c\d}}