Diferenças semânticas de definições globais em grupo local relacionadas a entradas de save-stack

Diferenças semânticas de definições globais em grupo local relacionadas a entradas de save-stack

Esta é uma pergunta de acompanhamento\variante global de \csname…\endcsname, que pede soluções para o problema ali explicado, mas não pede explicação.

A essência do problema é que

{ \gdef\foo{...} }

parece se comportar de maneira diferente

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

onde o último "adiciona uma entrada de retenção no save_stack."

Então eu gostaria de saber:

  1. Por que há uma diferença de comportamento para essas duas construções? Isso é apenas um efeito colateral da maneira como \csname...\endcsnameé implementado ou é uma diferença semântica intencional (até onde é possível dizer).
  2. O que significa exatamente a {retaining ...}entrada no arquivo de log mencionada na pergunta vinculada? Por que seria necessário manter as definições locais após o encerramento do grupo?

Responder1

Obrigado por fazer esta pergunta. Não entendi muito bem o exemplo ema outra perguntaquando li, mas essa pergunta me motivou a olhar novamente e acho que agora entendo.

Basicamente, o que não ficou claro no exemplo é que o problema se aplica quando temos várias dessas \expandafter\gdef\csname foo\endcsname{...}construções dentro domesmogrupo (ou grupos aninhados dentro desse grupo), ou seja, a pilha salvada cresce apenas enquanto dentro do grupo. A mensagem “retaining…” é impressa no momento da saída do grupo (quando a pilha está sendo estourada), mas esta mensagem em si não é um problema; serve apenas como evidência de que a pilha cresceu antes.

Esse parágrafo provavelmente foi confuso, então vamos entender a pilha de salvamentos do zero. :-)


1.Considere este exemplo:

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

Aqui, quando o TeX no grupo interno \aé redefinido (para world), ele salva o valor anterior (uma referência a uma lista de tokens contendo hello) na pilha de salvamento. Então, quando chega ao final do grupo, ele abre a pilha para restaurar a definição de hello. Esta é a razão óbvia para salvar a pilha, e a seguir está a saída de rastreamento correspondente, assumindo que você tem \tracingrestores=2e \tracinggroups=2e também \tracingassigns=2e que o grupo interno começa na linha 10 (digamos) - também modifiquei a saída para remover a quebra de linha antes {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.Agora considere o mesmo exemplo,semo que \def\a{hello}está no topo:

{
    \def\a{world}
}

— mais uma vez, quando o TeX vê \def\a{world}dentro do grupo, ele tem que salvar o significado anterior de \a. Acontece que era indefinido, mas ainda precisamos que seja indefinido novamente após sair do grupo, então o TeX precisa colocar o significado “indefinido” na pilha de salvamento. A saída do rastreamento é:

{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.Agora considere um exemplo semelhante:

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

Aqui, quando o TeX vê o primeiro \let\a=\relax, ele deve salvar o significado anterior (“indefinido”) na pilha de salvamento, assim como no exemplo anterior. Então, quando ele vir, \gdefnão será necessário colocar nada na pilha de salvamento. Finalmente, quando chega ao final do grupo e começa a estourar sua pilha (que atualmente contém o significado “indefinido” para \a), ele observa que \aagora possui uma definição global, portanto ignora o significado “indefinido” e retém a definição global. Isso explica a saída do rastreamento:

{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.Finalmente, considere este exemplo:

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

Acontece que é exatamente igual ao caso anterior. Isso ocorre porque quando o TeX vê o \expandafterele passa temporariamente por cima do \gdefe começa a agir no próximo token ( \csname) — isso significa agir \csname a\endcsnamepara criar a macro \a, com uma definição de \relaxse ela ainda não estava definida (é assim que \csnamefunciona), e só depois disso atua sobre o (anteriormente preterido temporariamente) \gdefe redefine \apara a nova definição que segue ( world). Assim, a saída do rastreamento é a mesma da vez anterior:

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

Isso é tudo que há para fazer. Para responder às suas perguntas específicas:

  • (1) É apenas um efeito colateral da forma como \csname … \endcsnameé implementado; ou seja, \leté o token para \relaxprimeiro. No entanto, isso está bem documentado e, portanto, também faz parte da semântica (como todos esperam).

  • (2a) A {retaining ...}entrada no arquivo de log significa (ver página 301 doO TeXbook) que omais cedoA definição colocada na pilha de salvamento, ou seja, qualquer que fosse o valor da macro antes de ser definida como \relaxby \csname … \endcsname, foi ignorada por causa de \gdef(e a definição global foi mantida).

  • (2b) “Por que seria necessário manter as definições locais após o encerramento do grupo?” — Não é, e eles não são guardados. Em vez disso, o que você vê é quequandoo grupo está sendo fechado, todas as definições que foram salvas anteriormente por causa de definições locais feitas dentro do mesmo grupo são agora examinadas, e se houve uma definição global em algum ponto, só agora descartadas. Ao final do grupo, a pilha de salvamento estará vazia (ou melhor, terá o mesmo tamanho de quando o grupo foi inserido).


Mais especificamente, aqui está um exemplo do problema e das soluções emaquela questão. O solicitante estava definindo muitas macros (essencialmente) dentro de um grupo, de maneira aproximadamente equivalente a:

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

e assim por diante. Como vimos nos Exemplos 3 e 4 acima, isso é equivalente a:

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

e assim por diante. Assim , cada definição acima coloca uma entrada na pilha de salvamento (para o significado anterior ao nome da sequência de controle ser \letby ), e somente no final do grupo todas essas entradas são exibidas. Portanto, se houver muitas dessas definições; você ficará sem “tamanho salvo”.\relax\csname ... \endcsname

Oprimeira resposta postada(por Steven B. Segletes) sugeriu fazer o equivalente a \csname … \endcsnameexecutar cada um no nível mais alto (onde nada será colocado na pilha de salvamento).

Osegunda resposta postada(por Marcel Krüger) sugeriu fazer o equivalente a:

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

onde as definições acontecem dentro de grupos que são encerrados imediatamente, então cada pilha é imediatamente removida: a saída do rastreamento é (módulo de quebra de linha):

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

Se você não se importa com expansão, etc. e está apenas tentando entender a pilha de salvamento, a solução proposta é como:

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

para que você possa ver por que a pilha de salvamentos não continua crescendo.

informação relacionada