Eu quero redefinir frame
como frame + itemize
. Eu até agora tentei isso -
\documentclass{beamer}
\newenvironment{myframe}{\begin{frame}\begin{itemize}}{\end{itemize}\end{frame}}
\begin{document}
\begin{myframe}
\item
\end{myframe}
\end{document}
O que exatamente há de errado nisso e como redefinir isso corretamente?
Responder1
O beamer
ambiente frame
coleta seu corpo usando a seguinte técnica: ele lê e armazena tokens seguindo \begin{frame}
a busca \end{frame}
sem expandi-los exceto em um caso (veja abaixo), mas aceitará an \end{frame}
como final do corpo do ambiente sendo coletado somente se tiver visto o mesmo número que \begin{...}
durante \end{...}
este processo de coleta (não tenta combiná-los). É \beamer@begin@stack
para isso que é usado \beamer@collect@@body
from beamerbaseframe.sty
.
Caso a pilha esteja vazia (ou seja, tenha visto tantas \end{...}
quanto \begin{...}
depois de \begin{frame}
) e o seguinte \end{...}
não seja um \end{frame}
, ele expande isso \end
esperando que isso faça \end{frame}
aparecer. 1 Mas issoapenasacontece sob a condição que dei (tantas \end{...}
depois \begin{...}
da inicial \begin{frame}
devem ter sido vistas).
No seu exemplo, \begin{itemize}
empurra um b
(ou seja, abre um nível) para a \beamer@begin@stack
pilha. Isso b
aparece quando \end{myframe}
é lido. Neste ponto, \beamer@collect@@body
sabe que já viu tantos \begin{...}
como \end{...}
depois do \begin{frame}
. Isto é verdade, mas enganoso, pois se \end{myframe}
fosse expandido, produziria dois pops. Então, \beamer@begin@stack
pensa que o próximo \end{...}
deve fornecer \end{frame}
, possivelmente após expandir o \end...
token (ver nota de rodapé 1). Infelizmente, isso está incorreto; o próximo \end{...}
é \end{document}
. \enddocument
é assim expandido, \beamer@collect@@body
continua procurando, \end{frame}
mas é claro que nunca o encontra, chega ao final do arquivo, o que aciona o erro:
Runaway argument?
\let \AtEndDocument \@firstofone \@enddocumenthook \@checkend {docume\ETC.
! File ended while scanning use of \beamer@collect@@body.
<inserted text>
\par
Então, basicamente, o problema é que \beamer@collect@@body
não é possível manter uma contagem adequada de \end{...}
porque você \end{myframe}
esconde dois desses ( \end{itemize}\end{frame}
) e \beamer@collect@@body
não os descobrirá por meio da expansão de \endmyframe
apesar da presença de \end{myframe}
, porque viu mais \begin{...}
do que \end{...}
no ponto em que vê this \end{myframe}
(pilha não vazia devido ao \begin{itemize}
).
As soluções exigem que você não se esconda \end{...}
em macros que \beamer@collect@@body
não vão se expandir. Um está usando o environ
pacote como emresposta de ferahfeza, outro está usando \itemize
e \enditemize
da seguinte maneira (nenhum deles afeta a \beamer@begin@stack
pilha, portanto, quando \beamer@collect@@body
vê \end{myframe}
, a pilha está vazia, portanto \endmyframe
é expandida uma vez, o que torna o \end{frame}
processo de digitalização do corpo visível para o ambiente):
\documentclass{beamer}
\newenvironment*{myframe}[1]
{\begin{frame}{#1}%
\begingroup\itemize}
{\enditemize\endgroup
\end{frame}}
\begin{document}
\begin{myframe}{Frame title}
\item An item
\item Another item
\end{myframe}
\end{document}
Nota de rodapé
Mais precisamente, o que acontece é o seguinte (in
beamer 2018/12/02 v3.55
). Se:- a
\beamer@begin@stack
pilha está vazia (tantas quantas\end{...}
foram\begin{...}
vistas após\begin{frame}
cujo corpo está sendo coletado) e - o próximo
\end{...}
não é um\end{frame}
- digamos que é um\end{foobar}
,
em seguida
\beamer@collect@@body
, substitui a expansão de primeiro nível\endfoobar\endgroup
por isso\end{foobar}
no material coletado. Isto é semelhante ao que a expansão\end{foobar}
produziria, embora esta última fizesse mais algumas coisas (versource2e.pdf
p. 272):- verifique se o texto de substituição de
\@currenvir
isfoobar
(isso falharia nobeamer
frame
processo de coleta do corpo de que estamos falando, as\@currenvir
isframe
); - respeitar um comando anterior
\@endparenv
(usado para suprimir o recuo do parágrafo no início do texto após alguns ambientes de criação de parágrafos, a menos que o referido texto seja precedido por uma linha em branco ou por\par
); - honrar um
\ignorespacesafterend
comando anterior (usado para fazer o próximo\end{...}
ignorar os espaços que possam segui-lo).
- a