I can offer a macro \GobbleTillProblemOrContinueOrBye
which within a local scope switches catcodes of {
and }
to 12 and makes \bye
non-outer and - if the \meaning
of \everyeof
yields the string \everyeof
and thus denotes an existing primitive - sets \everyeof{\continue}
and then calls a macro \Gobbleloop
which tail-recursively gobbles tokens until the token currently processed either is the control-word-token \problem
or the control-word-token \continue
or the control-word-token \bye
in which case the local scope is closed and the token in question is delivered.
You can have the commands \solution
and \note
call this macro in case you want to print problems only.
Notice that the implementation internally does without any \if..
-conditional but the switch \ifproblemonly
requested by the questioner.
Be aware:
\input
-commands occurring in notes/solutions are not carried out if only problems are to be printed.
\endinput
-commands occurring in notes/solutions are not carried out, too, if only problems are to be printed.
In case of e-TeX-extensions/\everyeof
not being available \Gobbleloop
does not check whether the end of the file is reached.
Thus: Files processed with engines where \everyeof
of the e-TeX-extensions is not available and not ending with the control-word-token \bye
should end with the control-word-token \continue
, probably trailed by \endinput
or whatever command you use for terminating the processing of the file/the .tex-document.
Otherwise, if only problems are to be printed, the routine \Gobbleloop
for gobbling things might deliver an error-message about unexpected end of file.
%%=============================================================================
%% Toggle: problem only -- also solutions/notes
%%.............................................................................
\newif\ifproblemonly
\problemonlytrue
%\problemonlyfalse
%%=============================================================================
%% PARAPHERNALIA:
%% \UDfirstoftwo, \UDsecondoftwo, \UDExchange, \UDstopromannumeral,
%% \UDgobbletoexclam, \UDCheckWhetherNull,
%%-----------------------------------------------------------------------------
\long\def\UDfirstoftwo#1#2{#1}%
\long\def\UDsecondoftwo#1#2{#2}%
\long\def\UDExchange#1#2{#2#1}%
\chardef\UDstopromannumeral=`\^^00%
\long\def\UDgobbletoexclam#1!{}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UDCheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\long\def\UDCheckWhetherNull#1{%
\romannumeral\expandafter\UDsecondoftwo\string{\expandafter
\UDsecondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UDsecondoftwo\string}\expandafter\UDfirstoftwo\expandafter{\expandafter
\UDsecondoftwo\string}\expandafter\UDstopromannumeral\UDsecondoftwo}{%
\expandafter\UDstopromannumeral\UDfirstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% \Gobbleloop
%%.............................................................................
\let\savedbye=\bye
\edef\bye{\noexpand\bye}%
\def\neutralizebye{\edef\bye{\noexpand\bye}}%
\long\def\Gobbleloop#1{%
\expandafter\UDCheckWhetherNull\expandafter{\UDgobbletoexclam#1!}%
{%
\ByeContinueProblemFork
!#1!\continue!\problem!{\endgroup#1}%
!\bye!#1!\problem!{\endgroup#1}%
!\bye!\continue!#1!{\endgroup#1}%
!\bye!\continue!\problem!{\Gobbleloop}%
!!!!%
}{\Gobbleloop}%
}%
\long\def\ByeContinueProblemFork#1!\bye!\continue!\problem!#2#3!!!!{#2}%
%%.............................................................................
%% \everyeoffork is needed to check the meaning of \everyeof
%%.............................................................................
\expandafter\def\expandafter\everyeoffork\expandafter#%
\expandafter1\expandafter$\string\everyeof$#2#3$$${#2}%
%%.............................................................................
%% Macro to initiate \Gobbleloop depending on \if...-switch:
%%.............................................................................
\def\GobbleTillProblemOrContinueOrBye#1{%
\ifproblemonly
\begingroup\catcode`\{=12 \catcode`\}=12 \neutralizebye
%%.........................................................................
% In case \everyeof is defined to be the \everyeof-primitive, i.e., in
% case \meaning\everyeof yields the string \everyeof, do
% \everyeof{\continue} for handling the case of reaching the end of a file
\expandafter\UDExchange\expandafter{\string\everyeof${}}%
{\expandafter\everyeoffork\expandafter$\meaning\everyeof$%
{\everyeof{\continue}}$}$$$%
%%.........................................................................
\expandafter\Gobbleloop
\else#1. \fi
}%
\let\bye=\savedbye
\let\savedbye\UnDeFineD
%%=============================================================================
\def\problem{Problem. }%
\def\solution{\GobbleTillProblemOrContinueOrBye{Solution}}%
\def\note{\GobbleTillProblemOrContinueOrBye{Note}}%
\def\continue{}%
%%=============================================================================
\problem
(The first problem)
\solution (Solution to the first problem)
\note (Note to the first problem)
\problem (The second problem)
\problem (The third problem)
\solution (Solution to the third problem)
\continue (something that shall occur in any case although it is not a problem)
\note (Note to the third problem)
\problem % some comment
(The fourth problem)
\solution (Solution to the fourth problem)
\bye
This is what you get with \problemonlyfalse
:

This is what you get with \problemonlytrue
:

If you like really complicated code suitable not only for detecting single control-word-tokens but suitable for detecting phrases consisting of several tokens I can offer a routine \GobbleTillProblemOrSolutionOrNoteOrContinueOrBye
which switches to verbatim-catcode-régime and by means of accumulating characters and checking by means of delimited arguments gobbles everything until finding one of the phrases \problem⟨space⟩
/ \problem⟨carriage return⟩
/ \continue⟨space⟩
/ \continue⟨carriage return⟩
/ \bye⟨space⟩
/ \bye⟨carriage return⟩
.
!!! A trailing/separating ⟨space⟩ or ⟨carriage return⟩ must be present!!!
E.g. \continue\relax
is not taken for a marker for terminating the gobbling-loop.
E.g. \continue%
is not taken for a marker for terminating the gobbling-loop.
But with \continue⟨space⟩\relax
, i.e., with \continue \relax
, the sequence \continue⟨space⟩
is taken for a marker that terminates the gobbling-loop.
You can have the command \solution
or \note
call this routine in case you want to print problems only.
You can omit scanning for \solution⟨space⟩
/ \solution⟨carriage return⟩
and \note⟨space⟩
/ \note⟨carriage return⟩
in case of printing problems only because in this case these just initiate more gobbling.
I decided to add scanning for these commented out so you can more easily introduce more \if
-switches, e.g., for printing notes as well but not printing solutions or for printing solutions as well but not printing notes, where scanning for these would be needed.
%%=============================================================================
%% Toggle: problem only -- also solutions/notes
%%.............................................................................
\newif\ifproblemonly
\problemonlytrue
%\problemonlyfalse
%%=============================================================================
%% PARAPHERNALIA:
%% \UDfirstoftwo, \UDsecondoftwo, \UDExchange, \UDstopromannumeral,
%% \UDgobbletoexclam, \UDCheckWhetherNull,
%%-----------------------------------------------------------------------------
\long\def\UDfirstoftwo#1#2{#1}%
\long\def\UDsecondoftwo#1#2{#2}%
\long\def\UDExchange#1#2{#2#1}%
\chardef\UDstopromannumeral=`\^^00%
\long\def\UDgobbletoexclam#1!{}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UDCheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\long\def\UDCheckWhetherNull#1{%
\romannumeral\expandafter\UDsecondoftwo\string{\expandafter
\UDsecondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UDsecondoftwo\string}\expandafter\UDfirstoftwo\expandafter{\expandafter
\UDsecondoftwo\string}\expandafter\UDstopromannumeral\UDsecondoftwo}{%
\expandafter\UDstopromannumeral\UDfirstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Scratch-macros for defining final version of \Gobbleloop
%%.............................................................................
\def\Gobbleloop#1#2#3{%
% #1: definition-text/(sub-)phrase-checks of final \Gobbleloop
% accumulated so far.
% #2: <phrase> whose matching-check is to be defined and
% appended to #1 this time.
% #3: emptiness or tokens to carry out in case characters
% gathered match <phrase>.
\UDCheckWhetherNull{#2}{\def\Gobbleloop##1##2{#1}}{%
\csname\string\fork\expandafter\endcsname
\csname#2fork\expandafter\endcsname
\csname fork#2\endcsname
{#3}{#2}{#1}%
}%
}%
\expandafter\def\csname\string\fork\expandafter\endcsname#1#2#3#4#5{%
% #1: \<phrase>fork - 1st macro-token of mechanism forming
% matching-check for <phrase>
% #2: \fork<phrase> - 2nd macro-token of mechanism forming
% matching-check for <phrase>
% #3: emptiness or tokens to carry out in case characters
% gathered match <phrase>.
% #4: <phrase> whose matching-check is to be defined and
% appended to #1 this time.
% #5: definition-text/(sub-)phrase-checks of final \Gobbleloop
% accumulated so far.
\def#1##1{%
\expandafter\UDCheckWhetherNull\expandafter{\UDgobbletoexclam##1!}%
{#2!##1!{\UDfirstoftwo}!#4!{\UDsecondoftwo}!!!!}{\UDsecondoftwo}%
}\long\def#2##1!#4!##2##3!!!!{##2}%
\UDCheckWhetherNull{#3}{%
\Gobbleloop{#1{##1##2}{\Gobbleloop{#4}}{#5}}%
}{%
\Gobbleloop{#1{##1##2}{\endgroup#3}{#5}}%
}%
}%
%%.............................................................................
%% \everyeoffork is needed to check the meaning of \everyeof
%%.............................................................................
\expandafter\def\expandafter\everyeoffork\expandafter#%
\expandafter1\expandafter$\string\everyeof$#2#3$$${#2}%
%%.............................................................................
%% Change the catcode-régime for defining final version of \Gobbleloop
%% and call the scratch-\Gobbleloop for defining the final \Gobbleloop
%%.............................................................................
\catcode`\/=0\relax%
\catcode`\^^M=12\relax%
\catcode`\ =12\relax%
\catcode`\!=14\relax%
\catcode`\%=12\relax!
\catcode`\\=12/relax!
/def/continuephrase{\continue }!
!! The scratch-\Gobbleloop recursively iterates over 2-tuples of arguments
!! for defining the final \Gobbleloop.
!! The 1st component of such a 2-tuple s the character-sequence forming the
!! (sub-)phrase to be matched.
!! The 2nd component is the tokens to carry out after terminating the loop
!! in case of match.
!! The final \Gobbleloop recursively gathers characters one by one from
!! the input and compares the characters gathered in previous iterations
!! plus the character gathered in this iteration to each (sub-)phrase.
!! If a match occurs and with that match the second component of the
!! corresponding 2-tuple is not empty, the loop terminates and delivers
!! the tokens comeing from the 2nd component of the corresponding 2-tuple.
!! If a match occurs and with that match the second component of the
!! corresponding 2-tuple is empty, the loop continues with the matching
!! phrase forming the characters gathered so far.
!! If no match occurs, the loop continues with characters gathered so far
!! being empty.
/Gobbleloop{/Gobbleloop{}}!
!..............................................................................
! If you want \Gobbleloop to terminate when finding \note<return> or
! \note<space> and to call \note:
!{\note^^M}{note}{\note }{note}{\note}{}{\not}{}{\no}{}{\n}{}!
!..............................................................................
! If you want \Gobbleloop to terminate when finding \solution<return> or
! \solution<space> and to call \solution:
!{\solution^^M}{/solution}{\solution }{/solution}!
!{\solution}{}{\solutio}{}{\soluti}{}{\solut}{}{\solu}{}{\sol}{}{\so}{}{\s}{}!
!..............................................................................
! If you want \Gobbleloop to terminate when finding \bye<return> or
! \bye<space> and to call \bye:
{\bye^^M}{/csname/UDfirstoftwo{}{}bye/endcsname}!
{\bye }{/csname/UDfirstoftwo{}{}bye/endcsname}{\bye}{}{\by}{}{\b}{}!
!..............................................................................
! If you want \Gobbleloop to terminate when finding \continue<return> or
! \continue<space> and to call \continue:
{\continue^^M}{/continue}{\continue }{/continue}!
{\continue}{}{\continu}{}{\contin}{}{\conti}{}{\cont}{}{\con}{}{\co}{}{\c}{}!
!..............................................................................
! If you want \Gobbleloop to terminate when finding \problem<return> or
! \problem<space> and to call \problem
{\problem^^M}{/problem}{\problem }{/problem}{\problem}{}!
{\proble}{}{\probl}{}{\prob}{}{\pro}{}{\pr}{}{\p}{}!
!..............................................................................
! Scan for the backslash:
{\}{}!
! ( When scanning for more (sub-)phrases you need to add tuples
! only for those (sub-)phrases that are not already in the
! 2-tuple-list. )
! Scanning for the backslash must be the last item of the 2-tuple
! list before the terminating-tuple because this overrides the
! scratch-definition of the \\fork-macro.
!
! This is the tuple terminating the recursion of the scratch-\Gobbleloop -
! emptiness as sub-phrase to match would not make sense:
{}{}!
!!.............................................................................
!! Reset the catcode-régime:
!!.............................................................................
/catcode`/\=0/relax!
\catcode`\%=14\relax!
\catcode`\!=12\relax%
\catcode`\ =10\relax%
\catcode`\^^M=5\relax%
\catcode`\/=12\relax%
%%.............................................................................
%% Macro to switch catcode-régime and to call \Gobbleloop
%%.............................................................................
\def\InitiateGobbleloop{%
\begingroup
\catcode`\^^M=12 \catcode`\ =12 \catcode`\\=12 \catcode`\{=12 \catcode`\}=12 %
\catcode`\$=12 \catcode`\&=12 \catcode`\#=12 \catcode`\^=12 \catcode`\_=12 %
\catcode`\~=12 % \catcode`\%=12 %
%%.........................................................................
% In case \everyeof is defined to be the \everyeof-primitive, i.e., in
% case \meaning\everyeof yields the string \everyeof, do
% \everyeof{\continue} for handling the case of reaching the end of a file
\expandafter\UDExchange\expandafter{\string\everyeof${}}%
{\expandafter\everyeoffork\expandafter$\meaning\everyeof$%
{\everyeof\expandafter{\continuephrase}}$}$$$%
%%.........................................................................
\Gobbleloop{}%
}%
%%.............................................................................
%% Macro to initiate \Gobbleloop depending on \if...-switch:
%%.............................................................................
\def\GobbleTillProblemOrSolutionOrNoteOrContinueOrBye#1{%
\ifproblemonly
\expandafter\InitiateGobbleloop
\else
#1. \ignorespaces\expandafter\noexpand
\fi
}%
%%=============================================================================
\def\problem{Problem. \ignorespaces\noexpand}%
\def\solution{\GobbleTillProblemOrSolutionOrNoteOrContinueOrBye{Solution}}%
\def\note{\GobbleTillProblemOrSolutionOrNoteOrContinueOrBye{Note}}%
\def\continue{\ignorespaces\noexpand}%
%%=============================================================================
\problem
(The first problem)
\solution (Solution to the first problem)
\note (Note to the first problem)
\problem (The second problem)
\problem (The third problem)
\solution (Solution to the third problem)
\continue (something that shall occur in any case although it is not a problem)
\note (Note to the third problem)
\problem % some comment
(The fourth problem)
\solution (Solution to the fourth problem)
\bye
This is what you get with \problemonlyfalse
:

This is what you get with \problemonlytrue
:

Notice that the implementation internally does without any \if..
-conditional but the switch \ifproblemonly
requested by the questioner.
With both examples you can easily modify the code so that the gobbling-routine also stops when finding \input
or \endinput
.
If you do this I suggest having copies of \input
and \endinput
via \let\noteinput=\input
/\let\solutioninput=\input
and \let\noteendinput=\endinput
/\let\solutionendinput=\endinput
where gobbling will not be stopped so that these are carried out only if not only problems but solutions/notes as well are produced. These can be used for inputting files whose entire content belongs to a solution or a note only where the corresponding \noteinput
-/\solutioninput
-command occurs.
\solution
and\note
. All would be easier if you switch to a syntax such as\solution...\endsolution
.\def\problem (#1) {\ifproblems Problem. (#1)\fi}
with an appropriate switch. Beware of nested parentheses within the text of such a delimited "argument".