3

I have a custom macro and custom environment.

\begin{myenv}
...
\mycmd
...
\mycmd
...
\mycmd % last usage
...
\end{myenv}

I want to detect the first & last usage of \mycmd and print something special with them. Currently I am using \toggletrue, \togglefalse, and \iftoggle to detect first usage. However, I cannot figure out how to detect the last usage. How do I do this?

P.S. I cannot modify the usage of the command, as I am making this as part of a .cls for auto-generated documents.

  • 2
    You need two passes. Either read the content of myenv into a macro and process that twice or write something to the aux file. – Henri Menke Sep 16 '17 at 6:59
  • The last \mycmd is considered the last in a specific environment or the last in the document? Is there only one of such environments which can contain \mycmd? – Skillmon likes topanswers.xyz Sep 16 '17 at 7:04
  • is \mycmd always at "top" level in your environment, i.e. neither inside braces ? (being in sub-environment is still ok) – user4686 Sep 16 '17 at 7:04
  • @jfbu why would braces matter? – Skillmon likes topanswers.xyz Sep 16 '17 at 7:13
  • Is \mycmd only ever used inside the myenv environment? Or could it occur anywhere between \begin{document} and \end{document}? – Mico Sep 16 '17 at 7:41
5

Edit for more robust approach

The initial answer has the defect to tie \mycmd to usage only inside environment myenv and also it will create low-level TeX error if \mycmd is used inside braces.

Following this suggestion of @BrunoLeFloch I modify the logic: in new method, command \mycmd is not tied at all to environment myenv but it is environment myenv whose definition is modified to (using \Collect@Body from package environ) gather its body (hence, as in earlier method, this is not compatible with usage of verbatim material in it), and to parse it in order to replace the last found \mycmd token by \mycmdLAST token. Hence one only needs to define \mycmdLAST to do whatever is desired.

In this approach \mycmd is not re-defined, it can also be used inside braces in the environment without errors, but it can not then be detected. In the example I define \mycmdLAST (as per Bruno's comment) to locally re-define \mycmd so if it is encountered after \mycmdLAST (necessarily hidden in braces) it will raise deliberately some error message. But this is up to document author to decide what the macro should do in those cases.

To recapitulate: the user needs only to load package environ and to put last in the "begin" part of the environment definition the two tokens \Collect@Body\mycmd@Parse. The environment name is arbitrary but the command name must be \mycmd. The command \mycmd can be used everywhere in the document and needs no special definition. It is mandatory though to define a \mycmdLAST command (a priori with the same number of arguments as original \mycmd but it could set a toggle and then insert the token \mycmd, the latter fetching the arguments and having been defined to check the toggle), and if \mycmd was the last one in the environment, then \mycmdLAST is executed.

\documentclass{article}

\usepackage{color}
\usepackage{environ}

\makeatletter

\newtoks\mytoksA
\newtoks\mytoksB

\newenvironment{myenv}{BEGIN CODE OF ENVIRONMENT\par
                       \Collect@Body\mycmd@Parse}
                      {\par END CODE OF ENVIRONMENT\par}

\long\def\mycmd@Parse #1{\mytoksA{}%
                         \mycmd@Parse@start\empty#1\mycmd\mycmd@Parse@start
                         \the\mytoksA}

\long\def\mycmd@Parse@start #1\mycmd{%
    \mytoksB\expandafter{#1}%
    \futurelet\mycmd@Token
    \mycmd@Parse@checkifnone
}%

\def\mycmd@Parse@checkifnone {%
    \ifx\mycmd@Token\mycmd@Parse@start
        \mytoksA\expandafter{\the\mytoksB}% no use of \mycmd in environment
        \expandafter\@gobbletwo % get rid of \mycmd@Parse@start end token
    \else
        \expandafter\mycmd@Parse@loop
    \fi\empty
}%

\long\def\mycmd@Parse@loop #1\mycmd{%
    \mytoksA\expandafter\expandafter\expandafter
           {\expandafter\the\expandafter\mytoksA\the\mytoksB}%
    \mytoksB\expandafter{#1}%
    \futurelet\mycmd@Token
    \mycmd@Parse@check % check if we had hit earlier the last \mycmd
}%

\def\mycmd@Parse@check {%
    \ifx\mycmd@Token\mycmd@Parse@start
        \mytoksA\expandafter\expandafter\expandafter
          {\expandafter\the\expandafter\mytoksA\expandafter
           \mycmdLAST
           \the\mytoksB}%
        \expandafter\@gobbletwo % get rid of \mycmd@Parse@start end token
    \else
        \mytoksA\expandafter{\the\mytoksA\mycmd}%
        \expandafter\mycmd@Parse@loop
    \fi\empty
}%

\makeatother

\newcommand\mycmdLAST{% whatever is wanted
    % use same number of arguments as \mycmd
    \textcolor{blue}{\bfseries I am last!}%
    % optional: (use same number of arguments as \mycmd)
    \renewcommand{\mycmd}% just an example
        {{\color{red}ERROR: USAGE INSIDE BRACES AFTER LAST ONE AT TOP LEVEL}}%
}

\newcommand\mycmd{\textcolor{red}{\bfseries I WAS HERE!}}

\begin{document}

Test 1:

\begin{myenv}\mycmd
  \begin{description}
\item[xintbinhex] \mycmd 
  is for conversions to and from binary and
  hexadecimal \mycmd bases.

\item[xintseries] provides some \emph{\mycmd basic functionality} for computing in an
  expandable manner partial sums of series and power series with fractional
  coefficients.\mycmd

\item[xintgcd] implements {\footnotesize\mycmd} the Euclidean algorithm and its typesetting.\mycmd

\item[xintcfrac] \mycmd deals with the computation of continued fractions.
\end{description}
\end{myenv}

\bigskip

Test 2:

\begin{myenv}
  No usage of \texttt{\string\mycmd} here.
\end{myenv}

\bigskip

Test 3:

\begin{myenv}
  \mycmd
  Here we have a braced one after the last usage {\mycmd}
\end{myenv}

\bigskip

Test 4:

\begin{myenv}
  Here we have only a braced one {\mycmd}
\end{myenv}

\end{document}

enter image description here


Earlier answer

Here is a method requiring only one pass and without multiple expansions of contents.

Defect: you can not nest your custom myenv (else the test for being last will have false positives for the \mycmd in the outer one).

Defect 2: a \mycmd inside braces will not be detected by the other ones, hence possibly cause a false positive. Besides a \mycmd inside braces will probably lead to a TeX error because it will not find the \end{myenv} it is looking for.

In brief, this solution is fine for \mycmd at top level inside a single myenv environment, and they may actually be inside sub-environments (but not again myenv).

\documentclass{article}

\newenvironment{myenv}{}{}
\newcommand{\myenvname}{myenv}

\newif\ifmycmdlast
\newtoks\mytoksA
\newtoks\mytoksB

\makeatletter

\newcommand{\mycmd}{\mytoksA{}\mycmd@\empty}%
% \empty token serves to avoid brace removal next

\long\def\mycmd@#1\end#2{%
    \mytoksB\expandafter{#1\end{#2}}%
    \mytoksA\expandafter\expandafter\expandafter
          {\expandafter\the\expandafter\mytoksA\the\mytoksB}%
    \long\def\mycmd@temp{#2}%
    \ifx\mycmd@temp\myenvname
        \expandafter\@firstoftwo
    \else
        \expandafter\@secondoftwo
    \fi
    \mycmd@check{\mycmd@\empty}%
}%

\def\mycmd@check
   {\expandafter\mycmd@check@i\the\mytoksA\mycmd\mycmd\mycmd@temp}

\long\def\mycmd@check@i #1\mycmd#2\mycmd#3\mycmd@temp{%
    \if\relax\detokenize{#3}\relax
        \mycmdlasttrue
    \else
        \mycmdlastfalse
    \fi
    \expandafter\myRealCmd\the\mytoksA
}
\makeatother

\newcommand\myRealCmd{% whatever is wanted
    \ifmycmdlast
       \textbf{\textcolor{blue}{I am last}}%
    \else
       \textbf{\textcolor{red}{I am not last}}%
    \fi
}
\usepackage{color}

\begin{document}

\begin{myenv}
  \begin{description}
\item[xintbinhex] \mycmd 
  is for conversions to and from binary and
  hexadecimal \mycmd bases.

\item[xintseries] provides some basic functionality for computing in an
  expandable manner partial sums of series and power series with fractional
  coefficients.\mycmd

\item[xintgcd] implements the Euclidean algorithm and its typesetting.\mycmd

\item[xintcfrac] deals with the computation of continued fractions.
\end{description}
\end{myenv}

\end{document}

enter image description here

I actually wanted this test:

\begin{document}

\begin{myenv}\mycmd
  \begin{description}
\item[xintbinhex] \mycmd 
  is for conversions to and from binary and
  hexadecimal \mycmd bases.

\item[xintseries] provides some basic functionality for computing in an
  expandable manner partial sums of series and power series with fractional
  coefficients.\mycmd

\item[xintgcd] implements the Euclidean algorithm and its typesetting.\mycmd

\item[xintcfrac] deals with the computation of continued fractions.
\end{description}
\mycmd
\end{myenv}

\end{document}

which produces

enter image description here

  • I also forgot to say that the method is not compatible with usage of verbatim or other catcode changing environments or macros (except those using \scantokens) because it fetches tokens and hence TeX assigns catcodes at that time which can't be changed (if not using \scantokens) afterwards. – user4686 Sep 16 '17 at 12:36
  • Wouldn't it be safer to use environ to define myenv to look for \mycmd and replace the last one by \mylastcmd, defined as \newcommand{\mylastcmd}{...\renewcommand{\mycmd}{\ERROR}} so that any use of \mycmd in braces after \mylastcmd would produce an error? – Bruno Le Floch Sep 16 '17 at 15:36
  • @BrunoLeFloch I was just going for the simple minded approach with no auxiliary file nor e-TeX stuff nor extra package ;-) I would say your idea isn't simpler but is certainly better, as it is more general (allowing braced \mycmd). It is another answer, you should post it! – user4686 Sep 16 '17 at 17:20
  • 1
    @BrunoLeFloch done... I will communicate Paypal account details offline for re-imbursement of my work hours! – user4686 Sep 17 '17 at 10:18
  • 2
    I suspect as the document is generated by some software that handing over to that software the detection of case of last usage in environment could be an easier method. – user4686 Sep 17 '17 at 10:26
3

The following is a simple approach, that does work, but if myenv does contain stuff like figures or anything numbered, this leads to problems, as the full content is evaluated twice (once inside a box, which isn't printed), note that \mycmd@orig should contain the correct macro-definition, as \mycmd gets defined inside the environment.

EDIT: I've added the possibility to specify counters which should be reset after the box-test. By default the contents of \countlist is reset, but one can give additional counters as an optional argument to myenv.

\documentclass[]{article}

\makeatletter
\def\csedef#1#2{\expandafter\edef\csname#1\endcsname{#2}}
\newcount\mycmdcount
\def\countlist{figure,table}
\def\b@ckupcount@myenv{%
  \@for\cs:=\countlist\do{%
    \csedef{b@ckup@myenv@\cs}{\the\value{\cs}}}}
\def\rest@recount@myenv{%
  \@for\cs:=\countlist\do{%
    \setcounter{\cs}{\csname b@ckup@myenv@\cs\endcsname}}}
\newenvironment*{myenv}[1][]{%
  \if\relax\detokenize{#1}\relax%
  \else%
    \edef\countlist{\countlist,#1}%
  \fi%
  \grab@myenv%
}{%
}
\def\mycmd@orig{%
  \global\advance\mycmdcount by 1\relax%
  \ifnum\mycmdcount=1\relax%
    first usage%
  \else%
    \expandafter\ifnum\lastmycmdcount=\mycmdcount\relax%
      last usage%
    \else%
      a usage, not the first, not the last%
    \fi%
  \fi%
}
\def\grab@myenv#1\END{%
  \mycmdcount=0\relax%
  \b@ckupcount@myenv%
  \bgroup%
  \def\mycmd{\global\advance\mycmdcount by 1\relax}%
  \setbox0\vbox{#1}%
  \xdef\lastmycmdcount{\the\mycmdcount}%
  \egroup%
  \rest@recount@myenv%
  \let\mycmd\mycmd@orig%
  \mycmdcount=0\relax%
  #1%
  \end}
\makeatother

\begin{document}
\tableofcontents
\begin{myenv}[section]
  \section{foo}
  foo bar baz\\
  \mycmd\\% first
  foo bar baz\\
  \mycmd\\% another usage
  foo bar baz\\
  \mycmd\\% last
\END{myenv}
\end{document}

enter image description here

2

This assumes \mycmd always appears explicitly (not called by other macros). The idea is to search for \mycmd that's not followed by any other call of itself up to the end; the regex is

\c{mycmd}([^\c{mycmd]*)\Z

which exactly fits the description.

\documentclass{article}

\usepackage{color}
\usepackage{xparse,environ}

\ExplSyntaxOn
\NewEnviron{myenv}
 {
  \regex_replace_once:nnN
   { \c{mycmd}([^\c{mycmd}]*)\Z }
   { \c{mycmdLAST}\1 }
   \BODY
  \BODY
 }
\ExplSyntaxOff

\newcommand\mycmdLAST{% whatever is wanted
  \textcolor{blue}{\bfseries I am last!}%
}

\newcommand\mycmd{\textcolor{red}{\bfseries I WAS HERE!}}

\begin{document}

Test 1:

\begin{myenv}\mycmd
  \begin{description}
\item[xintbinhex] \mycmd 
  is for conversions to and from binary and
  hexadecimal \mycmd bases.

\item[xintseries] provides some \emph{\mycmd{} basic functionality} for computing in an
  expandable manner partial sums of series and power series with fractional
  coefficients.\mycmd

\item[xintgcd] implements {\footnotesize\mycmd} the Euclidean algorithm and its typesetting.\mycmd

\item[xintcfrac] \mycmd deals with the computation of continued fractions.
\end{description}
\end{myenv}

\bigskip

Test 2:

\begin{myenv}
  No usage of \texttt{\string\mycmd} here.
\end{myenv}

\bigskip

Test 3:

\begin{myenv}
  \mycmd{}
  Here we have a braced one after the last usage {\mycmd} with something after
\end{myenv}

\bigskip

Test 4:

\begin{myenv}
  Here we have only a braced one {\mycmd}
\end{myenv}

Test 5:

\begin{myenv}
  Here we have only a braced one {\mycmd} and something after
\end{myenv}

\end{document}

I used essentially the same tests as in jfbu's answer.

enter image description here

You can extend it to also catch the first usage; of course you have to decide what happens in case there's a single occurrence of \mycmd.

\documentclass{article}

\usepackage{color}
\usepackage{xparse,environ}

\ExplSyntaxOn
\NewEnviron{myenv}
 {
  \regex_replace_once:nnN
   { \c{mycmd} }
   { \c{mycmdFIRST} }
   \BODY
  \regex_replace_once:nnN
   { \c{mycmd}([^\c{mycmd}]*)\Z }
   { \c{mycmdLAST}\1 }
   \BODY
  \BODY
 }
\ExplSyntaxOff

\newcommand\mycmd{\textcolor{red}{\bfseries I WAS HERE!}}
\newcommand\mycmdFIRST{\textcolor{green}{\bfseries I am first!}}
\newcommand\mycmdLAST{% whatever is wanted
  \textcolor{blue}{\bfseries I am last!}%
}

\begin{document}

Test 1:

\begin{myenv}\mycmd
  \begin{description}
\item[xintbinhex] \mycmd 
  is for conversions to and from binary and
  hexadecimal \mycmd bases.

\item[xintseries] provides some \emph{\mycmd{} basic functionality} for computing in an
  expandable manner partial sums of series and power series with fractional
  coefficients.\mycmd

\item[xintgcd] implements {\footnotesize\mycmd} the Euclidean algorithm and its typesetting.\mycmd

\item[xintcfrac] \mycmd deals with the computation of continued fractions.
\end{description}
\end{myenv}

\bigskip

Test 2:

\begin{myenv}
  No usage of \texttt{\string\mycmd} here.
\end{myenv}

\bigskip

Test 3:

\begin{myenv}
  \mycmd{}
  Here we have a braced one after the last usage {\mycmd} with something after
\end{myenv}

\bigskip

Test 4:

\begin{myenv}
  Here we have only a braced one {\mycmd}
\end{myenv}

Test 5:

\begin{myenv}
  Here we have only a braced one {\mycmd} and something after
\end{myenv}

\end{document}

enter image description here

For instance, we can decide that a lonely appearance of \mycmd executes \mycmdLONELY:

\documentclass{article}

\usepackage{xcolor}
\usepackage{xparse,environ}

\ExplSyntaxOn
\NewEnviron{myenv}
 {
  \regex_count:nVN { \c{mycmd} } \BODY \l_tmpa_int
  \int_compare:nTF { \l_tmpa_int < 2 }
   { % less than two occurrences
    \regex_replace_once:nnN { \c{mycmd} } { \c{mycmdLONELY} } \BODY
   }
   {
    \regex_replace_once:nnN
     { (.*?)\c{mycmd}(.*)\c{mycmd}([^\c{mycmd}]*) }
     { \1 \c{mycmdFIRST} \2 \c{mycmdLAST} \3 }
     \BODY
   }
  % deliver the contents
  \BODY
 }
\cs_generate_variant:Nn \regex_count:nnN { nV }
\ExplSyntaxOff

\newcommand\mycmd{\textcolor{red}{\bfseries I WAS HERE!}}
\newcommand\mycmdLONELY{\textcolor{green!50!blue}{\bfseries I feel lonely!}}
\newcommand\mycmdFIRST{\textcolor{green}{\bfseries I am first!}}
\newcommand\mycmdLAST{\textcolor{blue}{\bfseries I am last!}}

\begin{document}

Test 1:

\begin{myenv}\mycmd
  \begin{description}
\item[xintbinhex] \mycmd 
  is for conversions to and from binary and
  hexadecimal \mycmd bases.

\item[xintseries] provides some \emph{\mycmd{} basic functionality} for computing in an
  expandable manner partial sums of series and power series with fractional
  coefficients.\mycmd

\item[xintgcd] implements {\footnotesize\mycmd} the Euclidean algorithm and its typesetting.\mycmd

\item[xintcfrac] \mycmd deals with the computation of continued fractions.
\end{description}
\end{myenv}

\bigskip

Test 2:

\begin{myenv}
  No usage of \texttt{\char`\\mycmd} here.
\end{myenv}

\bigskip

Test 3:

\begin{myenv}
  \mycmd{}
  Here we have a braced one after the last usage {\mycmd} with something after
\end{myenv}

\bigskip

Test 4:

\begin{myenv}
  Here we have only a braced one {\mycmd}
\end{myenv}

Test 5:

\begin{myenv}
  Here we have only a braced one {\mycmd} and something after
\end{myenv}

\end{document}

enter image description here

  • peripherical to main: I am not familiar with \NewEnviron but assuming myenv is supposed to do other things how do you insert that in it? isn't here simpler with just adding some \Collect@Body usage to original OP's myenv environment definition? – user4686 Sep 17 '17 at 12:11
  • @jfbu \NewEnviron is just an easier interface to \Collect@Body. – egreg Sep 17 '17 at 12:24
  • but does it allow the "end" part? the doc which I skimmed mentions an optional argument for "final tokens", it was not clear on reading if this serves as equivalent to the "end" part of standard environment. (I understand the user can add the end tokens directly after \BODY usage, but possibly not quite same as end tokens from end part of standard environment) – user4686 Sep 17 '17 at 12:44
  • @jfbu \NewEnviron takes a trailing optional argument; it's explained in the doc. – egreg Sep 17 '17 at 13:17
  • ah ok. Indeed it is exactly equivalent to the end part from standard environment. On first reading I wasn't sure. – user4686 Sep 17 '17 at 13:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.