Семантические различия глобальных определений в локальной группе, касающиеся записей стека сохранения

Семантические различия глобальных определений в локальной группе, касающиеся записей стека сохранения

Это дополнительный вопрос по теме\глобальный вариант \csname…\endcsname, который просит решения описанной там проблемы, но не требует объяснений.

Суть проблемы в том, что

{ \gdef\foo{...} }

похоже, ведет себя по-другому

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

где последний «добавляет сохраняющую запись в save_stack».

Итак, я хотел бы знать:

  1. Почему существует разница в поведении этих двух конструкций? Это просто побочный эффект способа \csname...\endcsnameреализации или это намеренное семантическое различие (насколько это возможно определить).
  2. Что {retaining ...}именно означает запись в файле журнала, упомянутая в связанном вопросе? Зачем нужно сохранять локальные определения после закрытия группы?

решение1

Спасибо, что задали этот вопрос. Я не совсем понял пример надругой вопроскогда я это прочитал, но этот вопрос побудил меня перечитать его еще раз, и, кажется, теперь я понимаю.

По сути, из примера не было ясно, что проблема возникает, когда у нас есть несколько таких \expandafter\gdef\csname foo\endcsname{...}конструкций в пределахтакой жегруппа (или группы, вложенные в эту группу), т. е. стек сохранения растет только пока находится внутри группы. Сообщение «сохранение…» печатается во время выхода из группы (когда стек выталкивается), но это сообщение само по себе не является проблемой; оно лишь служит доказательством того, что стек вырос ранее.

Этот абзац, вероятно, сбил с толку, поэтому давайте разберемся со стеком сохранений с нуля. :-)


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, он должен сохранить предыдущее значение («undefined») в стеке сохранения, как и в предыдущем примере. Затем, когда он видит , ему \gdefне нужно ничего помещать в стек сохранения. Наконец, когда он достигает конца группы и начинает выталкивать свой стек (который в настоящее время содержит значение «undefined» для \a), он замечает, что \aтеперь имеет глобальное определение, поэтому он игнорирует значение «undefined» и сохраняет глобальное определение. Это объясняет вывод трассировки:

{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}

Вот и все. Чтобы ответить на ваши конкретные вопросы:

  • (1) Это просто побочный эффект способа \csname … \endcsnameреализации; а именно, это \letтокен для \relaxfirst. Однако это хорошо документировано и, следовательно, возможно, также является частью семантики (как все и ожидают).

  • (2a) {retaining ...}Запись в файле журнала означает (см. стр. 301TeXbook), чторанееопределение, помещенное в стек сохранения, а именно, какое бы значение макроса ни было до того, как ему было присвоено значение \relax, \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 \relaxby \csname ... \endcsname), и только в конце группы все эти записи выталкиваются. Так что если таких определений слишком много; вы исчерпаете «размер сохранения».

Theпервый опубликованный ответ(Стивен Б. Сеглете) предложил сделать эквивалент того, что каждое действие \csname … \endcsnameбудет выполняться на самом верхнем уровне (где в стек сохранений ничего не будет помещено).

Theвторой опубликованный ответ(Марсель Крюгер) предложил сделать что-то похожее:

{
  \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}
}

теперь вы можете понять, почему стопка сохранений не растет.

Связанный контент