%D \module %D [ file=syst-gen, %D version=1996.03.20, %D title=\CONTEXT\ System Macros, %D subtitle=General, %D author=Hans Hagen, %D date=\currentdate, %D copyright={PRAGMA / Hans Hagen \& Ton Otten}] %C %C This module is part of the \CONTEXT\ macro||package and is %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. % nagaan : \ifinstringelse in syst-ext.tex % do => p! dodo pp! dododo ppp! %D The following macros are responsible for the interaction %D with \CONTEXT. These macros have proven their use. These %D macros are optimized as far as possible within of course, %D the know how of the author. %D %D In this module we also show some of the optimizations, %D mainly because we don't want to forget them and start doing %D things over and over again. If showing them has a learing %D effect for others too, we've surved another purpose too. %D \macros %D {abortinputifdefined} %D %D Because this module can be used in a different context, we %D want to prevent it being loaded more than once. This can be %D done using: %D %D \starttyping %D \abortinputifdefined\command %D \stoptyping %D %D where \type{\command} is a command defined in the module %D to be loaded only once. %D %D \starttyping %D \def\abortinputifdefined#1% %D {\ifx#1\undefined %D \let\next=\relax %D \else %D \let\next=\endinput %D \fi %D \next} %D \stoptyping %D %D This macro can be speed up in terms of speed as well as %D memory. Because this is a nice example of a bit strange %D command (\type{\endinput}), we spend some more lines on this. %D %D If we perform such actions directly, we can say: %D %D \starttyping %D \ifx\somecommand\undefined %D \let\next=\relax %D \else %D \let\next=\endinput %D \fi %D \next %D \stoptyping %D %D We need the \type{\next} because we need to end the %D \type{\fi}. The efficient one is: %D %D \starttyping %D \ifx\somecommand\undefined %D \else %D \expandafter\endinput %D \fi %D \stoptyping %D %D Because \type{\endinput} comes into action after the current %D line, we can also say: %D %D \starttyping %D \ifx\somecommand\undefined \else \endinput \fi %D \stoptyping %D %D When we define a macro, we tend to use a format which %D shows as besat as can how things are done. \TEX\ however %D stores the definitions as a sequence of tokens, so in fact %D we can use a formatted definition: \def\abortinputifdefined#1% {\ifx#1\undefined \else \endinput \fi} %D which also works. Keep in mind that this is entirely due to %D the fact that \type{\endinput} after the line, i.e. at the %D end of the macro. We therefore can burry this primitive quite %D deep in code. %D And because this module implements \type{\writestatus}, we %D just say: \abortinputifdefined\writestatus %D Normally we tell the users what module is being loaded. %D However, the command that is needed for this is not yet %D defined. %D %D \starttyping %D \writestatus{laden}{Context Systeem Macro's (a)} %D \stoptyping %D The next few macros are needed in case this module is %D used outside \CONTEXT. \ifx\beginTEX\undefined \let\beginTEX\relax\let\endTEX\relax \long\def\beginETEX #1\endETEX {} \long\def\beginOMEGA #1\endOMEGA{} % \long\def\onlyTEX #1{#1} % \long\def\onlyETEX #1{} % \long\def\onlyOMEGA#1{} \fi %D \macros %D [beschermen] %D {protect,unprotect} %D %D We can shield macros from users by using some special %D characters in their names. Some characters that are normally %D no letters and therefore often used are: \type{@}, \type{!} %D and \type{?}. Before and after the definition of protected %D macros, we have to change the \CATCODE\ of these characters. %D This is done by \type{\unprotect} and \type{\protect}, for %D instance: %D %D \starttyping %D \unprotect %D \def\!test{test} %D \protect %D \stoptyping %D %D The defined command \type{\!test} can of course only be %D called upon when we are in the \type{\unprotect}'ed state, %D otherwise \TEX\ reads \type{\!} and probably complains %D loudly about not being in math mode. %D %D Both commands can be used nested, but only the \CATCODE\ %D of the outermost level is saved. We make use of %D an auxilary macro \type{\doprotect} to prevent us from %D conflicts with existing macro's \type{\protect}. When %D nesting deeper than one level, the system shows the %D protection level. \newcount\protectionlevel \ifx\protect\undefined \def\protect{\message{}} \else % a simple version is already defined \fi \let\normalprotect=\protect % only for latex %D Although we don't need the \type{%} after commands that %D don't take arguments, unless lines are obeyed, I decided %D to put it there as a reminder. I only mention this once. \chardef\protectionthreshold=2 %chardef\protectionthreshold=0 % for testing % \def\unprotect % {\ifcase\protectionlevel % \edef\doprotectcharacters% % {\catcode`\noexpand @=\the\catcode`@ % \catcode`\noexpand !=\the\catcode`! % \catcode`\noexpand ?=\the\catcode`? }% % \let\protect\doprotect % \fi % \catcode`@=11 % \catcode`!=11 % \catcode`?=11 % \advance\protectionlevel 1 % \ifnum\protectionlevel>\protectionthreshold % \message{}% % \fi} % % \def\doprotect% % {\ifnum\protectionlevel=1 % \doprotectcharacters % \let\doprotectcharacters\relax % \let\protect\normalprotect % \fi % \ifnum\protectionlevel>\protectionthreshold % \message{}% % \fi % \advance\protectionlevel -1 } \def\saveprotectedcharacters {\edef\doprotectcharacters {\catcode`\noexpand @\the\catcode`@ \catcode`\noexpand !\the\catcode`! \catcode`\noexpand ?\the\catcode`? }} \def\setprotectedcharacters {\catcode`@=11 \catcode`!=11 \catcode`?=11 } \def\unprotect {\ifcase\protectionlevel \saveprotectedcharacters \let\protect\doprotect \fi \setprotectedcharacters \advance\protectionlevel 1 \ifnum\protectionlevel>\protectionthreshold \reportunprotection \fi} \def\doprotect {\ifcase\protectionlevel\or \doprotectcharacters \let\doprotectcharacters\relax \let\protect\normalprotect \fi \ifnum\protectionlevel>\protectionthreshold \reportprotection \fi \advance\protectionlevel -1 } \def\reportunprotection {\message{}} \def\reportprotection {\message{}} \def\reportprotectionstate{\message{}} \def\protectionstate {\the\protectionlevel \ifcase\protectionthreshold :\space @=\the\catcode`@\space\space !=\the\catcode`!\space\space ?=\the\catcode`?% \fi} \ifx\everyeof\undefined \let\checkprotection\relax \else \def\checkprotection {\everyeof{\writestatus{protection}{state: \protectionstate}}} \fi %D Now it is defined, we can make use of this very useful %D macro. \unprotect %D So, now we can redefine a previously defined macro as %D follows: \def\setprotectedcharacters {\catcode`@\@@letter \catcode`!\@@letter \catcode`?\@@letter} %D \macros %D {@@escape,@@begingroup,@@endgroup,@@mathshift,@@alignment, %D @@endofline,@@parameter,@@superscript,@@subscript, %D @@ignore,@@space,@@letter,@@other,@@active,@@comment} %D %D In \CONTEXT\ we sometimes manipulate the \CATCODES\ of %D certain characters. Because we are not that good at numbers, %D we introduce some symbolic names. \chardef\@@escape = 0 \chardef\@@begingroup = 1 \chardef\@@endgroup = 2 \chardef\@@mathshift = 3 \chardef\@@alignment = 4 \chardef\@@endofline = 5 \chardef\@@parameter = 6 \chardef\@@superscript = 7 \chardef\@@subscript = 8 \chardef\@@ignore = 9 \chardef\@@space = 10 \chardef\@@letter = 11 \chardef\@@other = 12 \chardef\other = 12 \chardef\@@active = 13 \chardef\active = 13 \chardef\@@comment = 14 %D \macros %D {normalspace} %D %D We often need a space as defined in \PLAIN\ \TEX. Because %D we cannot be sure of \type{\space} is redefined, we define: \def\normalspace{ } %D \macros %D {scratchcounter, %D scratchdimen,scratchskip,scratchmuskip, %D scratchbox,globalscratchbox, %D scratchtoks, %D ifdone} %D %D Because we often need counters on a temporary basis, we %D define the \COUNTER\ \type{\scratchcounter}. This is a %D real \COUNTER, and not a pseudo one, as we will meet %D further on. We also define some other scratch registers. \newcount \scratchcounter \newdimen \scratchdimen \newskip \scratchskip \newmuskip \scratchmuskip \newbox \scratchbox \newtoks \scratchtoks \newif \ifdone %D \macros %D {ifCONTEXT} %D %D In the system and support modules we sometimes show examples %D that make use of core commands. We can skip those parts of %D the documentation when we use another macropackage. Of %D course we default to false. \newif \ifCONTEXT %D \macros %D {!!count, !!toks, !!dimen, !!box, %D !!width, !!height, !!depth, !!string, !!done} %D %D We define some more \COUNTERS\ and \DIMENSIONS. We also %D define some shortcuts to the local scatchregisters~0, 2, 4, %D 6 and~8. \newcount\!!counta \toksdef\!!toksa=0 \dimendef\!!dimena= 0 \chardef\!!boxa =0 \newcount\!!countb \toksdef\!!toksb=2 \dimendef\!!dimenb= 2 \chardef\!!boxb =2 \newcount\!!countc \toksdef\!!toksc=4 \dimendef\!!dimenc= 4 \chardef\!!boxc =4 \newcount\!!countd \toksdef\!!toksd=6 \dimendef\!!dimend= 6 \chardef\!!boxd =6 \newcount\!!counte \toksdef\!!tokse=8 \dimendef\!!dimene= 8 \chardef\!!boxe =8 \newcount\!!countf \dimendef\!!dimenf=10 %skipdef\!!skipa=0 \dimendef\!!dimeng=12 %skipdef\!!skipb=2 \dimendef\!!dimenh=14 %skipdef\!!skipc=4 \dimendef\!!dimeni=16 %skipdef\!!skipd=6 \dimendef\!!dimenj=18 %skipdef\!!skipe=8 \let\!!stringa=\empty \let\!!stringb=\empty \let\!!stringc=\empty \let\!!stringd=\empty \let\!!stringe=\empty \let\!!stringf=\empty \newdimen\!!widtha \newdimen\!!heighta \newdimen\!!deptha \newdimen\!!widthb \newdimen\!!heightb \newdimen\!!depthb \newdimen\!!widthc \newdimen\!!heightc \newdimen\!!depthc \newif\if!!donea \newif\if!!doneb \newif\if!!donec \newif\if!!doned \newif\if!!donee \newif\if!!donef \ifx \z@\undefined \else \let\zeropoint\z@ \fi \ifx \@ne\undefined \else \let\plusone \@ne \fi \ifx\m@ne\undefined \else \let\minusone \m@ne \fi \ifx\zeropoint\undefined \csname newdimen\endcsname\zeropoint \fi % plain \ifx\plusone \undefined \chardef\plusone =1 \fi \ifx\plustwo \undefined \chardef\plustwo =2 \fi \ifx\plusthree\undefined \chardef\plusthree=3 \fi \ifx\plusfour \undefined \chardef\plusfour =4 \fi \ifx\plusfive \undefined \chardef\plusfive =5 \fi \ifx\minusone \undefined \count\minusone \minusone=-1 \fi \ifx\@m\undefined \else \let\plusthousand \@m \fi \ifx\@M\undefined \else \let\plusttenhousand\@M \fi \ifx\plusten \undefined \mathchardef\plusten = 10 \fi \ifx\plushundred \undefined \mathchardef\plushundred = 100 \fi \ifx\plusthousand \undefined \mathchardef\plusthousand = 1000 \fi \ifx\plustenthousand\undefined \mathchardef\plustenthousand=10000 \fi \newcount\zerocount %D \macros %D {s!,c!,e!,p!,v!,@@,??} %D %D To save memory, we use constants (sometimes called %D variables). Redefining these constants can have desastrous %D results. \def\v!prefix! {v!} \def\c!prefix! {c!} \def\s!prefix! {s!} \def\p!prefix! {p!} \def\s!next {next} \def\s!default {default} \def\s!dummy {dummy} \def\s!unknown {unknown} \def\s!do {do} \def\s!dodo {dodo} \def\s!complex {complex} \def\s!start {start} \def\s!simple {simple} \def\s!stop {stop} %D \macros %D {@EA,@EAEA,@EAEAEA,@EAEAEAEAEAEA,expanded} %D %D When in unprotected mode, to be entered with %D \type{\unprotect}, one can use \type{\@EA} as equivalent %D of \type{\expandafter}. \let\@EA =\expandafter \def\@EAEA {\expandafter\expandafter} \def\@EAEAEA{\expandafter\expandafter\expandafter} \def\@EAEAEAEAEAEA{\expandafter\@EAEAEA\expandafter} %D Sometimes we pass macros as arguments to commands that %D don't expand them before interpretation. Such commands can %D be enclosed with \type{\expanded}, like: %D %D \starttyping %D \expanded{\setupsomething[\alfa]} %D \stoptyping %D %D Such situations occur for instance when \type{\alfa} is a %D commalist or when data stored in macros is fed to index of %D list commands. If needed, one should use \type{\noexpand} %D inside the argument. Later on we will meet some more clever %D alternatives to this command. \long\def\@@expanded{} % always long; global (less restores) \long\def\expanded#1% {\long\xdef\@@expanded{\noexpand#1}\@@expanded} %D \macros %D {safeexpanded,everysafeexpanded} %D %D In addition we provide: \newtoks\everysafeexpanded \long\def\safeexpanded#1% {\begingroup \the\everysafeexpanded\long\xdef\@@expanded{\noexpand#1}% \endgroup \@@expanded} \def\safeedef#1#2% {\begingroup \the\everysafeexpanded\long\xdef\@@expanded{\noexpand#2}% \endgroup \let#1\@@expanded} \def\safexdef#1#2% {\begingroup \the\everysafeexpanded\long\xdef\@@expanded{\noexpand#2}% \endgroup \global\let#1\@@expanded} %D You can append protective measures to the token register if %D needed, as we will do later. %D \macros %D {expandoneargafter,expandtwoargsafter} %D %D These two commands make macros more readable by hiding a %D lot of \type {\expandafter}'s. They expand the arguments %D after the first command. %D %D \starttyping %D \expandoneargafter \command{\abc} %D \expandtwoargsafter\command{\abc}{\def} %D \stoptyping %D %D These commands expect the arguments to be macros. \def\expandoneargafter #1{\@EA#1\@EA} \def\expandtwoargsafter#1#2{\@EA\@EA\@EA#1\@EA\@EA\@EA{\@EA#2\@EA}\@EA} %D These two do a full expansion: \def\fullexpandoneargafter #1#2{\long\xdef\@@expanded{\noexpand#1{#2}}\@@expanded} \def\fullexpandtwoargsafter#1#2#3{\long\xdef\@@expanded{\noexpand#1{#2}{#3}}\@@expanded} %D \macros %D {gobbleoneargument,gobble...arguments} %D %D The next set of macros just do nothing, except that they %D get rid of a number of arguments. \long\def\gobbleoneargument #1{} \long\def\gobbletwoarguments #1#2{} \long\def\gobblethreearguments #1#2#3{} \long\def\gobblefourarguments #1#2#3#4{} \long\def\gobblefivearguments #1#2#3#4#5{} \long\def\gobblesixarguments #1#2#3#4#5#6{} \long\def\gobblesevenarguments #1#2#3#4#5#6#7{} \long\def\gobbleeightarguments #1#2#3#4#5#6#7#8{} \long\def\gobbleninearguments #1#2#3#4#5#6#7#8#9{} \long\def\gobbletenarguments #1{\gobbleninearguments} %D \macros %D {doifnextcharelse} %D %D When we started using \TEX\ in the late eighties, our %D first experiences with programming concerned a simple shell %D around \LATEX. The commands probably use most at \PRAGMA, %D are the itemizing ones. One of those few shell commands took %D care of an optional argument, that enabled us to specify %D what kind of item symbol we wanted. Without understanding %D anything we were able to locate a \LATEX\ macro that could %D be used to inspect the next character. %D %D It's this macro that the ancester of the next one presented %D here. It executes one of two actions, dependant of the next %D character. Disturbing spaces and line endings, which are %D normally interpreted as spaces too, are skipped. %D %D \starttyping %D \doifnextcharelse {karakter} {then ...} {else ...} %D \stoptyping %D %D This macro differs from the original in the use of \type %D {\localnext} because we don't want clashes with \type %D {\next}. \long\def\doifnextcharelse#1#2#3% #1 should not be {} ! {\let\charactertoken=#1% = needed here \def\!!stringa{#2}% \def\!!stringb{#3}% \futurelet\nexttoken\inspectnextcharacter} \def\inspectnextcharacter {\ifx\nexttoken\blankspace \@EA\reinspectnextcharacter \else\ifx\nexttoken\charactertoken \@EAEAEA\!!stringa \else \@EAEAEA\!!stringb \fi\fi} %\let\endoflinetoken=^^M % %\long\def\reinspectaftercharacter#1% % {\futurelet\nexttoken\inspectnextcharacter} %D This macro uses some auxiliary macros. Although we were able %D to program quite complicated things, I only understood these %D after rereading the \TEX book. The trick is in using a %D command with a one character name. Such commands differ from %D the longer ones in the fact that trailing spaces are {\em %D not} skipped. This enables us to indirectly define a long %D named macro that gobbles a space. %D %D In the first line we define \type{\blankspace}. Next we %D make \type{\:} equivalent to \type{\reinspect...}. This %D one||character command is expanded before the next %D \type{\def} comes into action. This way the space after %D \type{\:} becomes a delimiter of the longer named %D \type{\reinspectnextcharacter}. The chain reaction is %D visually compatible with the next sequence: %D %D \starttyping %D \expandafter\def\reinspectnextcharacter % %D {\futurelet\nexttoken\inspectnextcharacter} %D \stoptyping %D %D However complicated it may look, I'm still glad I stumbled %D into this construction. Saving and restoring \type {\:} is %D needed when we use \PPCHTEX\ in \LATEX. \let\next\: \def\:{\let\blankspace= } \: \def\:{\reinspectnextcharacter} \expandafter\def\: {\futurelet\nexttoken\inspectnextcharacter} \let\:\next %D \macros %D {setvalue,setgvalue,setevalue,setxvalue, %D letvalue, %D getvalue, %D resetvalue} %D %D \TEX's primitive \type{\csname} can be used to construct %D all kind of commands that cannot be defined with %D \type{\def} and \type{\let}. Every macro programmer sooner %D or later wants macros like these. %D %D \starttyping %D \setvalue {name}{...} = \def\name{...} %D \setgvalue {name}{...} = \gdef\name{...} %D \setevalue {name}{...} = \edef\name{...} %D \setxvalue {name}{...} = \xdef\name{...} %D \letvalue {name}=\... = \let\name=\... %D \letgvalue {name}=\... = \global\let\name=\... %D \getvalue {name} = \name %D \resetvalue {name} = \def\name{} %D \stoptyping %D %D As we will see, \CONTEXT\ uses these commands many times, %D which is mainly due to its object oriented and parameter %D driven character. \def\setvalue #1{\expandafter \def\csname#1\endcsname} \def\setgvalue #1{\expandafter\gdef\csname#1\endcsname} \def\setevalue #1{\expandafter\edef\csname#1\endcsname} \def\setxvalue #1{\expandafter\xdef\csname#1\endcsname} \def\getvalue #1{\csname#1\endcsname} \def\letvalue #1{\expandafter\let\csname#1\endcsname} \def\letgvalue #1{\global\expandafter\let\csname#1\endcsname} \def\resetvalue #1{\expandafter\let\csname#1\endcsname\empty} \def\ignorevalue#1#2{\expandafter\let\csname#1\endcsname\empty} %D \macros %D {globallet,glet} %D %D In \CONTEXT\ of May 2000 using \type {\globallet} %D instead of the two tokens will save us some %D $300\times4=1200$ bytes of format file on a 32~bit %D system. So: \def\globallet{\global\let} \let\glet\globallet %D \macros %D {donottest,unexpanded} %D %D When expansion of a macro gives problems, we can precede it %D by \type{\donottest}. It seems that protection is one of the %D burdens of developers of packages, so maybe that's why in %D \ETEX\ protection is solved in a more robust way. %D %D Sometimes prefixing the macro with \type{\donottest} leads %D to defining an auxiliary macro, like %D %D \starttyping %D \def\dosomecommand {... ... ...} %D \def\somecommand {\donottest\dosomecommand} %D \stoptyping %D %D This double definition can be made transparant by using %D \type{\unexpanded}, as in: %D %D \starttyping %D \unexpanded\def\somecommand{... ... ...} %D \stoptyping %D %D The protection mechanism uses: \beginTEX \def\dontprocesstest#1{==} \def\doprocesstest #1{#1} \let\donottest=\doprocesstest \endTEX \beginETEX \detokenize \def\donottest#1{#1} % {\detokenize{#1}} \endETEX %D By the way, we use a placeholder because we don't want %D interference when testing on empty strings. Using a %D placeholder of 8~characters increases the processing time %D of simple \type{\doifelse} tests by about 10 \%. When we %D process the test, we have to remove the braces and %D therefore explictly gobble \type{#1}. %D \macros %D {honorunexpanded,forceunexpanded} %D %D The fact that many macros have the same prefix, could have %D a negative impact on searching in the hash table. Because %D some simple testing does not show differences, we just use: %D %D \starttyping %D \def\unexpanded#1#2% %D {\@EA#1\@EA#2\@EA{\@EA\donottest\csname\s!do\string#2\endcsname}% %D \@EA#1\csname\s!do\string#2\endcsname} %D \stoptyping %D %D Well, in fact we use the bit more versatile alternative. The %D \type {\honorunexpanded} can be used to \type {\string} %D the protected command, which by the way is seldom needed %D in \CONTEXT. \beginTEX \def\dosetunexpanded#1#2% {\@EA#1\@EA{\@EA#2\@EA}% \@EA{\@EA\donottest\csname\s!do\@EA\string\csname#2\endcsname\endcsname}% \@EA#1\@EA{\@EA\s!do\@EA\string\csname#2\endcsname}} \def\docomunexpanded#1#2% {\@EA#1\@EA#2\@EA{\@EA\donottest\csname\s!do\string#2\endcsname}% \@EA#1\csname\s!do\string#2\endcsname} \def\unexpanded#1% {\def\dounexpanded {\ifx\next\bgroup \@EA\dosetunexpanded \else \@EA\docomunexpanded \fi#1}% \futurelet\next\dounexpanded} \def\honorunexpanded% for writing to a file or message {\def\donottest##1{\expandafter\gobblethreearguments\string##1}} \def\forceunexpanded% for preventing expansion in \xdef {\def\donottest##1% {\expandafter\noexpand\csname\expandafter\gobblefourarguments\string##1\endcsname}} \def\resetunexpanded% {\let\donottest\doprocesstest} \endTEX \beginETEX \protected \let \unexpanded \normalprotected \let \honorunexpanded \empty % \relax \let \forceunexpanded \empty % \relax \let \resetunexpanded \empty % \relax \endETEX %D This one accepts the more direct \type{\def} and cousins %D as well as the \CONTEXT\ specific \type{\setvalue} ones. %D %D And so the definition in our example turns out to be: %D %D \starttyping %D \def\csname do\somecommand\endcsname{... ... ...} %D \def\somecommand{\donottest\csname do\somecommand\endcsname} %D \stoptyping %D %D In which \type{do\somecommand} is hidden from the user and %D cannot lead to confusion. It's still permitted to define %D auxiliary macros like \type{\dosomecommand}. %D %D When we are going to use e-\TEX, we'll probably end up %D redefining some commands, but we can probably keep the %D \type{\unexpanded} ones unchanged. %D \macros %D {doifundefined,doifdefined, %D doifundefinedelse,doifdefinedelse, %D doifalldefinedelse} %D %D The standard way of testing if a macro is defined is %D comparing its meaning with another undefined one, usually %D \type{\undefined}. To garantee correct working of the next %D set of macros, \type{\undefined} may never be defined! %D %D \starttyping %D \doifundefined {string} {...} %D \doifdefined {string} {...} %D \doifundefinedelse {string} {then ...} {else ...} %D \doifdefinedelse {string} {then ...} {else ...} %D \doifalldefinedelse {commalist} {then ...} {else ...} %D \stoptyping %D %D Every macroname that \TEX\ builds gets an entry in the hash %D table, which is of limited size. It is expected that e-\TeX\ %D will offer a less memory||consuming alternative. %D Although it will probably never be a big problem, it is good %D to be aware of the difference between testing on a macro %D name to be build by using \type{\csname} and %D \type{\endcsname} and testing the \type{\name} directly. %D %D \starttyping %D \expandafter\ifx\csname NameA\endcsname\relax ... \else ... \fi %D %D \ifx\NameB\undefined ... \else ... \fi %D \stoptyping %D %D I became aware of this when I mistakenly testen the first %D one against \type{\undefined}. When \TEX\ build a name using %D \type{\csname} it automatically sets it to \type{\relax}, %D which is definitely not the same as \type{\undefined}. The %D quickest way to check these things is asking \TEX\ to show %D the meaning of the names: %D %D \starttyping %D \expandafter\show\csname NameA\endcsname %D %D \show\NameB %D \stoptyping %D %D The main reason why this never will be a big problem is that %D when one uses the \type{\csname} way, one probably has to do %D with some macroname that always is dealt with that way. %D Confusion can however arise when one applies both testing %D methods to the same macroname. By the way, the assignment %D of \type{\relax} obeys grouping. %D The first one gets rid of \type{#1}, but still expands to %D something and the second one expands to \type{#1}. Because %D we accept arguments between \type{{}}, we have to get rid %D of one level of braces. %D %D Our first implementation of \type{\ifundefined} was %D straightforward and readable: %D %D \starttyping %D \def\ifundefined#1% %D {\expandafter\ifx\csname#1\endcsname\relax}% %D %D \def\doifundefinedelse#1#2#3% %D {\let\donottest=\dontprocesstest %D \ifundefined{#1}% %D \let\donottest=\doprocesstest#2% %D \else %D \let\donottest=\doprocesstest#3% %D \fi} %D %D \def\doifdefinedelse#1#2#3% %D {\doifundefinedelse{#1}{#3}{#2}} %D %D \def\doifundefined#1#2% %D {\doifundefinedelse{#1}{#2}{}} %D %D \def\doifdefined#1#2% %D {\doifundefinedelse{#1}{}{#2}} %D %D \def\doifalldefinedelse#1#2#3% %D {\begingroup %D \donetrue %D \def\checkcommand##1% %D {\doifundefined{##1}{\donefalse}}% %D \processcommalist[#1]\checkcommand %D \ifdone %D \endgroup#2% %D \else %D \endgroup#3% %D \fi} %D \stoptyping %D %D When this module was optimized, timing showed that the %D next alternative can be upto twice as fast, especially when %D longer arguments are used. Watch how we reach over the %D \type {\else} and \type {\fi}: this way they cannot get %D into the way (and we can avoid those \type {\next} %D hacks); I started using this method after I did some %D speed optimization tests in the xtag modules; the %D efficiency of such hacks depends on the length of the %D argument etc. etc.) \beginTEX \def\ifundefined#1% {\expandafter\ifx\csname#1\endcsname\relax} \def\p!doifundefined#1% {\let\donottest\dontprocesstest \expandafter\ifx\csname#1\endcsname\relax} \def\doifundefinedelse#1% {\p!doifundefined{#1}% \let\donottest\doprocesstest\@EA\firstoftwoarguments \else \let\donottest\doprocesstest\@EA\secondoftwoarguments \fi} \def\doifdefinedelse#1% {\p!doifundefined{#1}% \let\donottest\doprocesstest\@EA\secondoftwoarguments \else \let\donottest\doprocesstest\@EA\firstoftwoarguments \fi} \def\doifundefined#1% {\p!doifundefined{#1}% \let\donottest\doprocesstest\@EA\firstofoneargument \else \let\donottest\doprocesstest\@EA\gobbleoneargument \fi} \def\doifdefined#1% {\p!doifundefined{#1}% \let\donottest\doprocesstest\@EA\gobbleoneargument \else \let\donottest\doprocesstest\@EA\firstofoneargument \fi} \endTEX \beginETEX \ifcsname \def\ifundefined#1% ongelukkige naam {\unless\ifcsname#1\endcsname} \def\p!doifundefined#1% {\edef\p!defined{#1}% \unless\ifcsname\detokenize\@EA{\p!defined}\endcsname} \def\doifundefinedelse#1% {\edef\p!defined{#1}% \ifcsname\detokenize\@EA{\p!defined}\endcsname \expandafter\secondoftwoarguments \else \expandafter\firstoftwoarguments \fi} \def\doifdefinedelse#1% {\edef\p!defined{#1}% \ifcsname\detokenize\@EA{\p!defined}\endcsname \expandafter\firstoftwoarguments \else \expandafter\secondoftwoarguments \fi} \def\doifundefined#1% {\edef\p!defined{#1}% \ifcsname\detokenize\@EA{\p!defined}\endcsname \expandafter\gobbleoneargument \else \expandafter\firstofoneargument \fi} \def\doifdefined#1% {\edef\p!defined{#1}% \ifcsname\detokenize\@EA{\p!defined}\endcsname \expandafter\firstofoneargument \else \expandafter\gobbleoneargument \fi} \endETEX %D \macros %D {letbeundefined} %D %D Testing for being undefined comes down to testing on \type %D {\relax} when we use \type {\csname}, but when using \type %D {\ifx}, we test on being \type {\undefined}! In \ETEX\ we %D have \type {\ifcsname} and that way of testing on existance %D is not the same as the one described here. Therefore we %D introduce: \beginTEX \def\letbeundefined#1% {\expandafter\let\csname#1\endcsname\relax} \endTEX \beginETEX \undefined \def\letbeundefined#1% potential stack buildup when used \global {\expandafter\let\csname#1\endcsname\undefined} \endETEX %D Beware, being \type {\undefined} in \ETEX\ means that the macro %D {\em is} defined! %D Before we start using this variant, we used another one, %D which is even a bit faster. This one looked like: %D %D \starttyping %D \def\p!doifundefined% %D {\begingroup %D \let\donottest=\dontprocesstest %D \ifundefined} %D %D \def\doifundefinedelse#1#2#3% %D {\p!doifundefined{#1}% %D \endgroup#2% %D \else %D \endgroup#3% %D \fi} %D \stoptyping %D %D A even more previous version used \type{\bgroup} and %D \type {\egroup}. In math mode however, \type{$1{x}2$} differs %D from \type{$1x2$}. This can been seen when one compares the %D output of: %D %D \starttyping %D $\kern10pt\showthe\lastkern$ %D $\kern10pt{\showthe\lastkern}$ %D $\kern10pt\begingroup\showthe\lastkern\endgroup$ %D \stoptyping %D %D Also in math mode, one can better use \type {\begingroup} %D and companion instead of \type {\bgroup}. %D %D When we were developing the scientific units module, we %D encountered different behavior in text and math mode, which %D was due to this grouping subtilities. We therefore decided %D to use \type{\begingroup} instead of \type{\bgroup}. %D Later, when we had optimized some macro's the grouped %D solution turned out to be unsafe when typesetting this %D documentation, especially when using \type{\globaldefs}. %D %D We still have to define \type{\doifalldefinedelse}. Watch %D the use of grouping, which garantees local use of the %D boolean \type{\ifdone}. \beginTEX \def\docheckonedefined#1% {\ifundefined{#1}% \donefalse \fi} \def\doifalldefinedelse#1% {\begingroup \let\donottest\dontprocesstest \donetrue \processcommalist[#1]\docheckonedefined \ifdone \endgroup\let\donottest\doprocesstest \expandafter\firstoftwoarguments \else \endgroup\let\donottest\doprocesstest \expandafter\secondoftwoarguments \fi} \endTEX \beginETEX \ifcsname \def\docheckonedefined#1% {\unless\ifcsname#1\endcsname \donefalse \fi} \def\doifalldefinedelse#1% {\begingroup \donetrue \processcommalist[#1]\docheckonedefined \ifdone \endgroup\expandafter\firstoftwoarguments \else \endgroup\expandafter\secondoftwoarguments \fi} \endETEX %D \macros %D {doif,doifelse,doifnot, %D donottest} %D %D Programming in \TEX\ differs from programming in procedural %D languages like \MODULA. This means that one --- well, let me %D speek for myself --- tries to do the things in the well %D known way. Therefore the next set of \type{\ifthenelse} %D commands were between the first ones we needed. A few years %D later, the opposite became true: when programming in %D \MODULA, I sometimes miss handy things like grouping, %D runtime redefinition, expansion etc. While \MODULA\ taught %D me to structure, \TEX\ taught me to think recursive. %D %D \starttyping %D \doif {string1} {string2} {...} %D \doifnot {string1} {string2} {...} %D \doifelse {string1} {string2} {then ...}{else ...} %D \stoptyping %D %D When expansion gives problems, we can precede the %D troublemaker with \type{\donottest}. %D %D This implementatie does not use the construction which is %D more robust for nested conditionals. %D %D \starttyping %D \ifx\!!stringa\!!stringb %D \def\next{#3}% %D \else %D \def\next{#4}% %D \fi %D \next %D \stoptyping %D %D In practice, this alternative is at least 20\% slower than %D the alternative used here. The few cases in which we %D really need the \type{\next} construction, often need some %D other precautions and or adaptions too. \beginTEX % \long\def\doif#1#2#3% % {\let\donottest\dontprocesstest % \edef\!!stringa{#1}% % \edef\!!stringb{#2}% % \let\donottest\doprocesstest % \ifx\!!stringa\!!stringb % #3% % \fi} % % \long\def\doifnot#1#2#3% % {\let\donottest\dontprocesstest % \edef\!!stringa{#1}% % \edef\!!stringb{#2}% % \let\donottest\doprocesstest % \ifx\!!stringa\!!stringb % \else % #3% % \fi} % % \long\def\doifelse#1#2#3#4% % {\let\donottest\dontprocesstest % \edef\!!stringa{#1}% % \edef\!!stringb{#2}% % \let\donottest\doprocesstest % \ifx\!!stringa\!!stringb % #3% % \else % #4% % \fi} %D Slightly faster on big arguments, as well as \type %D {\next} avoiding: \long\def\doif#1#2% {\let\donottest\dontprocesstest \edef\!!stringa{#1}% \edef\!!stringb{#2}% \let\donottest\doprocesstest \ifx\!!stringa\!!stringb \expandafter\firstofoneargument \else \expandafter\gobbleoneargument \fi} \long\def\doifnot#1#2% {\let\donottest\dontprocesstest \edef\!!stringa{#1}% \edef\!!stringb{#2}% \let\donottest\doprocesstest \ifx\!!stringa\!!stringb \expandafter\gobbleoneargument \else \expandafter\firstofoneargument \fi} \long\def\doifelse#1#2% {\let\donottest\dontprocesstest \edef\!!stringa{#1}% \edef\!!stringb{#2}% \let\donottest\doprocesstest \ifx\!!stringa\!!stringb \expandafter\firstoftwoarguments \else \expandafter\secondoftwoarguments \fi} \endTEX \beginETEX \protected % \long\def\doif#1#2#3% % {\edef\!!stringa{#1}\edef\!!stringb{#2}% % \ifx\!!stringa\!!stringb#3\fi} % % \long\def\doifnot#1#2#3% % {\edef\!!stringa{#1}\edef\!!stringb{#2}% % \unless\ifx\!!stringa\!!stringb#3\fi} % % \long\def\doifelse#1#2#3#4% % {\edef\!!stringa{#1}\edef\!!stringb{#2}% % \ifx\!!stringa\!!stringb#3\else#4\fi} %D Slightly faster on big arguments, as well as \type %D {\next} avoiding: \long\def\doif#1#2% {\edef\!!stringa{#1}\edef\!!stringb{#2}% \ifx\!!stringa\!!stringb \expandafter\firstofoneargument \else \expandafter\gobbleoneargument \fi} \long\def\doifnot#1#2% {\edef\!!stringa{#1}\edef\!!stringb{#2}% \ifx\!!stringa\!!stringb \expandafter\gobbleoneargument \else \expandafter\firstofoneargument \fi} \long\def\doifelse#1#2% {\edef\!!stringa{#1}\edef\!!stringb{#2}% \ifx\!!stringa\!!stringb \expandafter\firstoftwoarguments \else \expandafter\secondoftwoarguments \fi} \endETEX %D One could wonder why we don't follow the the same approach %D as in \type{\doifdefined} c.s.\ and use \type{\begingroup} %D and \type{\endgroup}. In this case, this alternative is %D slower, which is probably due to the fact that more meanings %D need to be restored. %D %D The in terms of memory more efficient alternative using a %D auxiliary macro also proved to be slower, so we definitely %D did not choose for: %D %D \starttyping %D \def\p!doifelse#1#2% %D {\let\donottest=\dontprocesstest %D \edef\!!stringa{#1}% %D \edef\!!stringb{#2}% %D \let\donottest=\doprocesstest %D \ifx\!!stringa\!!stringb} %D %D \long\def\doif#1#2#3% %D {\p!doifelse{#1}{#2}#3\fi} %D %D \long\def\doifnot#1#2#3% %D {\p!doifelse{#1}{#2}\else#3\fi} %D %D \long\def\doifelse#1#2#3#4% %D {\p!doifelse{#1}{#2}#3\else#4\fi} %D \stoptyping %D %D Optimizations like this are related of course to the %D bottlenecks in \TEX. It seems that restoring saved meanings %D and passing arguments takes some time. %D \macros %D {doifempty,doifemptyelse,doifnotempty} %D %D We complete our set of conditionals with: %D %D \starttyping %D \doifempty {string} {...} %D \doifnotempty {string} {...} %D \doifemptyelse {string} {then ...} {else ...} %D \stoptyping %D %D This time, the string is not expanded. \long\def\doifemptyelse#1% {\def\!!stringa{#1}% \ifx\!!stringa\empty \expandafter\firstoftwoarguments \else \expandafter\secondoftwoarguments \fi} \long\def\doifempty#1% {\def\!!stringa{#1}% \ifx\!!stringa\empty \expandafter\firstofoneargument \else \expandafter\gobbleoneargument \fi} \long\def\doifnotempty#1% {\def\!!stringa{#1}% \ifx\!!stringa\empty \expandafter\gobbleoneargument \else \expandafter\firstofoneargument \fi} %D \macros %D {doifinset,doifnotinset,doifinsetelse} %D %D We can check if a string is present in a comma separated %D set of strings. Depending on the result, some action is %D taken. %D %D \starttyping %D \doifinset {string} {string,...} {...} %D \doifnotinset {string} {string,...} {...} %D \doifinsetelse {string} {string,...} {then ...} {else ...} %D \stoptyping %D %D The second argument is the comma separated set of strings. %D %D \starttyping %D \long\def\doifinsetelse#1#2#3#4% %D {\doifelse{#1}{} %D {#4} %D {\donefalse %D \def\p!checkiteminset##1% %D {\doif{#1}{##1} %D {\donetrue %D \let\p!checkiteminset=\gobbleoneargument}}% %D \processcommalist[#2]\p!checkiteminset %D \ifdone %D #3% %D \else %D #4% %D \fi}} %D %D \long\def\doifinset#1#2#3% %D {\doifinsetelse{#1}{#2}{#3}{}} %D %D \long\def\doifnotinset#1#2#3% %D {\doifinsetelse{#1}{#2}{}{#3}} %D \stoptyping %D %D Because this macro is called quite often we've spent some %D time optimizing it. This time, the gain in speed is due to %D (1)~defining an external auxiliary macro, (2)~not calling %D any other macros and (3)~minimizing the passing of %D arguments. The gain in speed is impressive. % \def\p!dodocheckiteminset#1% % {\edef\!!stringb{#1}% % \ifx\!!stringa\!!stringb % \donetrue % \let\p!docheckiteminset\gobbleoneargument % \fi} % % \beginTEX % % \def\p!doifinsetelse#1#2% % {\let\donottest\dontprocesstest % \donefalse % \edef\!!stringa{#1}% % \ifx\!!stringa\empty % \else % \let\p!docheckiteminset\p!dodocheckiteminset % \processcommalist[#2]\p!docheckiteminset % \fi % \let\donottest\doprocesstest % \ifdone} % % \endTEX % % \beginETEX \protected % % \def\p!doifinsetelse#1#2% % {\donefalse % \edef\!!stringa{#1}% % \ifx\!!stringa\empty % \else % \let\p!docheckiteminset\p!dodocheckiteminset % \processcommalist[#2]\p!docheckiteminset % \fi % \ifdone} % % \endETEX % then we had: % % \def\p!docheckiteminset#1% % {\edef\!!stringb{#1}% % \ifx\!!stringa\!!stringb % \donetrue % \expandafter\quitcommalist % \fi} % % \beginTEX % % \def\p!doifinsetelse#1#2% % {\let\donottest\dontprocesstest % \donefalse % \edef\!!stringa{#1}% % \ifx\!!stringa\empty % \else % \processcommalist[#2]\p!docheckiteminset % \fi % \let\donottest\doprocesstest % \ifdone} % % \endTEX % % % can be sped up with processnext... % % \beginETEX \protected % % \def\p!doifinsetelse#1#2% % {\donefalse % \edef\!!stringa{#1}% % \ifx\!!stringa\empty % \else % \processcommalist[#2]\p!docheckiteminset % \fi % \ifdone} % % \endETEX % % \long\def\doifinsetelse#1#2% % {\p!doifinsetelse{#1}{#2}% % \expandafter\firstoftwoarguments % \else % \expandafter\secondoftwoarguments % \fi} % % \long\def\doifinset#1#2% % {\p!doifinsetelse{#1}{#2}% % \expandafter\firstofoneargument % \else % \expandafter\gobbleoneargument % \fi} % % \long\def\doifnotinset#1#2% % {\p!doifinsetelse{#1}{#2}% % \expandafter\gobbleoneargument % \else % \expandafter\firstofoneargument % \fi} % % now we have \def\p!docheckiteminset#1% {\edef\!!stringb{#1}% \ifx\!!stringa\!!stringb \donetrue \expandafter\quitcommalist \fi} \beginTEX \def\p!doifinsetelse#1#2#3#4% {\let\donottest\dontprocesstest \donefalse \edef\!!stringa{#3}% \ifx\!!stringa\empty \else \processcommalist[#4]\p!docheckiteminset \fi \let\donottest\doprocesstest \ifdone\expandafter#1\else\expandafter#2\fi} \endTEX \beginETEX \protected \def\p!doifinsetelse#1#2#3#4% {\donefalse \edef\!!stringa{#3}% \ifx\!!stringa\empty \else \processcommalist[#4]\p!docheckiteminset \fi \ifdone\expandafter#1\else\expandafter#2\fi} \endETEX \long\def\doifinsetelse {\p!doifinsetelse\firstoftwoarguments\secondoftwoarguments} \long\def\doifinset {\p!doifinsetelse\firstofoneargument\gobbleoneargument} \long\def\doifnotinset {\p!doifinsetelse\gobbleoneargument\firstofoneargument} %D \macros %D {doifcommon,doifnotcommon,doifcommonelse} %D %D Probably the most time consuming tests are those that test %D for overlap in sets of strings. %D %D \starttyping %D \doifcommon {string,...} {string,...} {...} %D \doifnotcommon {string,...} {string,...} {...} %D \doifcommonelse {string,...} {string,...} {then ...} {else ...} %D \stoptyping %D %D We show the slower alternative first, because it shows us %D how things are done. %D %D \starttyping %D \long\def\doifcommonelse#1#2#3#4% %D {\donefalse %D \def\p!docommoncheck##1% %D {\def\p!dodocommoncheck####1% %D {\doif{####1}{##1} %D {\donetrue %D \def\commalistelement{##1}% %D \let\p!docommoncheck=\gobbleoneargument %D \let\p!dodocommoncheck=\gobbleoneargument}}% %D \processcommalist[#2]\p!dodocommoncheck}% %D \processcommalist[#1]\p!docommoncheck %D \ifdone %D #3% %D \else %D #4% %D \fi} %D %D \long\def\doifcommon#1#2#3% %D {\doifcommonelse{#1}{#2}{#3}{}} %D %D \long\def\doifnotcommon#1#2#3% %D {\doifcommonelse{#1}{#2}{}{#3}} %D \stoptyping %D %D The processing time is shortened by getting the auxiliary %D macro to the outermost level and using less \type{\edef}'s. %D Sometimes it makes more sence to define local macro's not %D only because this way we can be sure that they are not %D redefined, but also because it shows the dependance. In %D compiled languages, this is no problem at all. It can even %D save us bytes and processing time. In interpreted languages %D like \TEX\ it nearly always slows down processing. % \def\p!dododocommoncheck#1% % {\edef\!!stringb{#1}% % \ifx\!!stringa\!!stringb % \donetrue % \let\p!docommoncheck\gobbleoneargument % \let\p!dodocommoncheck\gobbleoneargument % \fi} % % \beginTEX % % \def\p!doifcommonelse#1#2% % {\donefalse % \let\donottest\dontprocesstest % \let\p!dodocommoncheck\p!dododocommoncheck % \def\p!docommoncheck##1% % {\edef\!!stringa{##1}% % \def\commalistelement{##1}% % \processcommalist[#2]\p!dodocommoncheck}% % \processcommalist[#1]\p!docommoncheck % \let\donottest\doprocesstest % \ifdone} % % \endTEX % % \beginETEX \protected % % \def\p!doifcommonelse#1#2% % {\donefalse % \let\p!dodocommoncheck\p!dododocommoncheck % \def\p!docommoncheck##1% % {\edef\!!stringa{##1}% % \def\commalistelement{##1}% % \processcommalist[#2]\p!dodocommoncheck}% % \processcommalist[#1]\p!docommoncheck % \ifdone} % % \endETEX % \def\p!dodocommoncheck#1% % {\edef\!!stringb{#1}% % \ifx\!!stringa\!!stringb % \donetrue % \expandafter\quitprevcommalist % \fi} % % \beginTEX % % \def\p!doifcommonelse#1#2% % {\donefalse % \let\donottest\dontprocesstest % \def\p!docommoncheck##1% % {\edef\!!stringa{##1}% % \def\commalistelement{##1}% no let to stringa % \processcommalist[#2]\p!dodocommoncheck}% % \processcommalist[#1]\p!docommoncheck % \let\donottest\doprocesstest % \ifdone} % % \endTEX % % \beginETEX \protected % % \def\p!doifcommonelse#1#2% % {\donefalse % \def\p!docommoncheck##1% % {\edef\!!stringa{##1}% % \def\commalistelement{##1}% % \processcommalist[#2]\p!dodocommoncheck}% % \processcommalist[#1]\p!docommoncheck % \ifdone} % % \endETEX % % \long\def\doifcommonelse#1#2% % #3#4% % {\p!doifcommonelse{#1}{#2}% % #3\else#4\fi} % \expandafter\firstoftwoarguments % \else % \expandafter\secondoftwoarguments % \fi} % % \long\def\doifcommon#1#2% % {\p!doifcommonelse{#1}{#2}% % \expandafter\firstofoneargument % \else % \expandafter\gobbleoneargument % \fi} % % \long\def\doifnotcommon#1#2% % {\p!doifcommonelse{#1}{#2}% % \expandafter\gobbleoneargument % \else % \expandafter\firstofoneargument % \fi} \def\p!dodocommoncheck#1% {\edef\!!stringb{#1}% \ifx\!!stringa\!!stringb \donetrue \expandafter\quitprevcommalist \fi} \beginTEX \def\p!doifcommonelse#1#2#3#4% {\donefalse \let\donottest\dontprocesstest \def\p!docommoncheck##1% {\edef\!!stringa{##1}% \def\commalistelement{##1}% no let to stringa \processcommalist[#4]\p!dodocommoncheck}% \processcommalist[#3]\p!docommoncheck \let\donottest\doprocesstest \ifdone\expandafter#1\else\expandafter#2\fi} \endTEX \beginETEX \protected \def\p!doifcommonelse#1#2#3#4% {\donefalse \def\p!docommoncheck##1% {\edef\!!stringa{##1}% \def\commalistelement{##1}% \processcommalist[#4]\p!dodocommoncheck}% \processcommalist[#3]\p!docommoncheck \ifdone\expandafter#1\else\expandafter#2\fi} \endETEX \def\doifcommonelse {\p!doifcommonelse\firstoftwoarguments\secondoftwoarguments} \def\doifcommon {\p!doifcommonelse\firstofoneargument \gobbleoneargument} \def\doifnotcommon {\p!doifcommonelse\gobbleoneargument \firstofoneargument} %D \macros %D {processcommalist,processcommacommand,quitcommalist, %D processcommalistwithparameters} %D %D We've already seen some macros that take care of comma %D separated lists. Such list can be processed with %D %D \starttyping %D \processcommalist[string,string,...]\commando %D \stoptyping %D %D The user supplied command \type{\commando} receives one %D argument: the string. This command permits nesting and %D spaces after commas are skipped. Empty sets are no problem. %D %D \startbuffer %D \def\dosomething#1{(#1)} %D %D 1: \processcommalist [\hbox{$a,b,c,d,e,f$}] \dosomething \par %D 2: \processcommalist [{a,b,c,d,e,f}] \dosomething \par %D 3: \processcommalist [{a,b,c},d,e,f] \dosomething \par %D 4: \processcommalist [a,b,{c,d,e},f] \dosomething \par %D 5: \processcommalist [a{b,c},d,e,f] \dosomething \par %D 6: \processcommalist [{a,b}c,d,e,f] \dosomething \par %D 7: \processcommalist [] \dosomething \par %D 8: \processcommalist [{[}] \dosomething \par %D \stopbuffer %D %D \typebuffer %D %D Before we show the result, we present the macro's: \newcount\commalevel \def\dododoprocesscommaitem {\csname\s!next\the\commalevel\endcsname} %\def\dodoprocesscommaitem% % {\ifx\nexttoken\blankspace % \let\nextcommaitem\redoprocesscommaitem % \else\ifx\nexttoken]% % \let\nextcommaitem\gobbleoneargument % \else % \let\nextcommaitem\dododoprocesscommaitem % \fi\fi % \nextcommaitem} % % faster ? \def\dodoprocesscommaitem {\ifx\nexttoken\blankspace \@EA\redoprocesscommaitem \else\ifx\nexttoken]% \@EAEAEA\gobbleoneargument \else \@EAEAEA\dododoprocesscommaitem \fi\fi} \def\doprocesscommaitem {\futurelet\nexttoken\dodoprocesscommaitem} %D Empty arguments are not processed. Empty items (\type{,,}) %D however are treated. We have to check for the special case %D \type{[{a,b,c}]}. %D %D \starttyping %D \def\processcommalist[% %D {\futurelet\nexttoken\docheckcommaitem} %D %D \def\docheckcommaitem% %D {\ifx\nexttoken]% %D \let\nextcommaitem\gobbletwoarguments %D \else\ifx\nexttoken\bgroup %D \let\nextcommaitem\doprocesscommalistA %D \else %D \let\nextcommaitem\doprocesscommalistB %D \fi\fi %D \nextcommaitem} %D %D \def\doprocesscommalistA#1#2]#3% %D {\global\advance\commalevel 1 %D \long\expandafter\def\csname\s!next\the\commalevel\endcsname##1,% %D {#3{##1}\doprocesscommaitem}% %D \doprocesscommaitem{#1}#2,]\relax %D \global\advance\commalevel -1 } %D %D \def\doprocesscommalistB#1]#2% %D {\global\advance\commalevel 1 %D \long\expandafter\def\csname\s!next\the\commalevel\endcsname##1,% %D {#2{##1}\doprocesscommaitem}% %D \doprocesscommaitem#1,]\relax %D \global\advance\commalevel -1 } %D \stoptyping %D %D However, this is not a the most straightforward solution! %D We can misuse one of \TEX's hidden features, and prepend %D and remove a \type {\relax}. By the way, although it %D involves less testing, this cleaner alternative is not %D faster. \def\processcommalist[% {\futurelet\nexttoken\docheckcommaitem} \def\docheckcommaitem {\ifx\nexttoken]% \expandafter\gobblethreearguments \else \expandafter\doprocesscommalist \fi \relax} % this one preserved the next {} \def\doprocesscommalist#1]#2% {\global\advance\commalevel \plusone \long\expandafter\def\csname\s!next\the\commalevel\endcsname##1,% {#2{##1}\doprocesscommaitem}% \@EA\dodoprocesscommaitem\gobbleoneargument#1,]\relax \global\advance\commalevel \minusone } %D One way of quitting a commalist halfway is: \def\quitcommalist {\begingroup\let\doprocesscommaitem\doquitcommalist} \def\doquitcommalist#1]% {\endgroup} \def\quitprevcommalist {\begingroup\let\doprocesscommaitem\doquitprevcommalist} \def\doquitprevcommalist#1]% {\let\doprocesscommaitem\doquitcommalist} %D The hack we used for checking the next character %D \type {\doifnextcharelse} is also used here. \def\:{\redoprocesscommaitem} \expandafter\def\: {\futurelet\nexttoken\dodoprocesscommaitem} %D The previous examples lead to: %D %D \getbuffer %D When a list is saved in a macro, we can use a construction %D like: %D %D \starttyping %D \expandafter\processcommalist\expandafter[\list]\command %D \stoptyping %D %D Such solutions suit most situations, but we wanted a bit %D more. %D %D \starttyping %D \processcommacommand[string,\stringset,string]\commando %D \stoptyping %D %D where \type{\stringset} is a predefined set, like: %D %D \starttyping %D \def\first{aap,noot,mies} %D \def\second{laatste} %D %D \processcommacommand[\first]\message %D \processcommacommand[\first,second,third]\message %D \processcommacommand[\first,between,\second]\message %D \stoptyping %D %D Commands that are part of the list are expanded, so the %D use of this macro has its limits. \def\processcommacommand[#1]% {\expanded{\processcommalist[#1]}} %D The argument to \type{\command} is not delimited. Because %D we often use \type{[]} as delimiters, we also have: %D %D \starttyping %D \processcommalistwithparameters[string,string,...]\command %D \stoptyping %D %D where \type{\command} looks like: %D %D \starttyping %D \def\command[#1]{... #1 ...} %D \stoptyping \def\processcommalistwithparameters[#1]#2% {\def\docommand##1{#2[##1]}% \processcommalist[#1]\docommand} %D \macros %D {processaction, %D processfirstactioninset, %D processallactionsinset} %D %D \CONTEXT\ makes extensive use of a sort of case or switch %D command. Depending of the presence of one or more provided %D items, some actions is taken. These macros can be nested %D without problems. %D %D \starttyping %D \processaction [x] [a=>\a,b=>\b,c=>\c] %D \processfirstactioninset [x,y,z] [a=>\a,b=>\b,c=>\c] %D \processallactionsinset [x,y,z] [a=>\a,b=>\b,c=>\c] %D \stoptyping %D %D We can supply both a \type{default} action and an action %D to be undertaken when an \type{unknown} value is met: %D %D \starttyping %D \processallactionsinset %D [x,y,z] %D [ a=>\a, %D b=>\b, %D c=>\c, %D default=>\default, %D unknown=>\unknown{... \commalistelement ...}] %D \stoptyping %D %D When \type{#1} is empty, this macro scans list \type{#2} for %D the keyword \type{default} and executed the related action %D if present. When \type{#1} is non empty and not in the list, %D the action related to \type{unknown} is executed. Both %D keywords must be at the end of list \type{#2}. Afterwards, %D the actually found keyword is available in %D \type{\commalistelement}. An advanced example of the use of %D this macro can be found in \PPCHTEX, where we completely %D rely on \TEX\ for interpreting user supplied keywords like %D \type{SB}, \type{SB1..6}, \type{SB125} etc. %D %D Even a quick glance at the macros below show some overlap, %D which means that more efficient alternatives are possible. %D Because these macro's are very sensitive to subtle changes, %D we've decided to present the readable originals first %D Maybe these these macros look complicated, but this is a %D direct result of the support of nesting. Protection is only %D applied in \type{\processaction}. %D %D \starttyping %D \newcount\processlevel %D %D \def\processaction[#1]#2[#3]% %D {\doifelse{#1}{} %D {\def\p!compareprocessaction[##1=>##2]% %D {\edef\!!stringa{##1}% %D \ifx\!!stringa\s!default %D \def\commalistelement{#1}% %D ##2% %D \fi}} %D {\let\donottest=\dontprocesstest %D \edef\!!stringb{#1}% %D \let\donottest=\doprocesstest %D \def\p!compareprocessaction[##1=>##2]% %D {\edef\!!stringa{##1}% %D \ifx\!!stringa\!!stringb %D \def\commalistelement{#1}% %D ##2% %D \let\p!doprocessaction=\gobbleoneargument %D \else\ifx\!!stringa\s!unknown %D \def\commalistelement{#1}% %D ##2% %D \fi\fi}}% %D \def\p!doprocessaction##1% %D {\p!compareprocessaction[##1]}% %D \processcommalist[#3]\p!doprocessaction} %D %D \def\processfirstactioninset[#1]#2[#3]% %D {\doifelse{#1}{} %D {\processaction[][#3]} %D {\def\p!compareprocessaction[##1=>##2][##3]% %D {\edef\!!stringa{##1}% %D \edef\!!stringb{##3}% %D \ifx\!!stringa\!!stringb %D \def\commalistelement{##3}% %D ##2% %D \let\p!doprocessaction=\gobbleoneargument %D \let\p!dodoprocessaction=\gobbleoneargument %D \else\ifx\!!stringa\s!unknown %D \def\commalistelement{##3}% %D ##2% %D \fi\fi}% %D \def\p!doprocessaction##1% %D {\def\p!dodoprocessaction####1% %D {\p!compareprocessaction[####1][##1]}% %D \processcommalist[#3]\p!dodoprocessaction}% %D \processcommalist[#1]\p!doprocessaction}} %D %D \def\processallactionsinset[#1]#2[#3]% %D {\doifelse{#1}{} %D {\processaction[][#3]} %D {\advance\processlevel by 1 %D \def\p!compareprocessaction[##1=>##2][##3]% %D {\edef\!!stringa{##1}% %D \edef\!!stringb{##3}% %D \ifx\!!stringa\!!stringb %D \def\commalistelement{##3}% %D ##2% %D \let\p!dodoprocessaction=\gobbleoneargument %D \else\ifx\!!stringa\s!unknown %D \def\commalistelement{##3}% %D ##2% %D \fi\fi}% %D \setvalue{\s!do\the\processlevel}##1% %D {\def\p!dodoprocessaction####1% %D {\p!compareprocessaction[####1][##1]}% %D \processcommalist[#3]\p!dodoprocessaction}% %D \processcommalist[#1]{\getvalue{\s!do\the\processlevel}}% %D \advance\processlevel by -1 }} %D \stoptyping %D %D The gain of speed in the (again) next implementation is %D around 20\%, depending on the application. \newcount\processlevel \def\p!compareprocessactionA[#1=>#2][#3]% {\edef\!!stringb{#1}% \ifx\!!stringb\s!default \let\commalistelement\empty #2% \fi} % \def\p!compareprocessactionB[#1=>#2][#3]% % {\expandedaction\!!stringb{#1}% % \ifx\!!stringa\!!stringb % \def\commalistelement{#3}% % #2% % \let\p!doprocessaction\gobbleoneargument % \else % \edef\!!stringb{#1}% % \ifx\!!stringb\s!unknown % \def\commalistelement{#3}% beware of loops % #2% % \fi % \fi} % met \quitcommalist tot meer dan 25\% sneller \def\p!compareprocessactionB[#1=>#2][#3]% {\expandedaction\!!stringb{#1}% \ifx\!!stringa\!!stringb \def\commalistelement{#3}% #2% \expandafter\quitcommalist \else \edef\!!stringb{#1}% \ifx\!!stringb\s!unknown \def\commalistelement{#3}% beware of loops #2% \fi \fi} \beginTEX \def\processaction[#1]#2[#3]% {\let\donottest\dontprocesstest \expandedaction\!!stringa{#1}% \let\donottest\doprocesstest \ifx\!!stringa\empty \let\p!compareprocessaction\p!compareprocessactionA \else \let\p!compareprocessaction\p!compareprocessactionB \fi \def\p!doprocessaction##1% {\p!compareprocessaction[##1][#1]}% \processcommalist[#3]\p!doprocessaction \expandactions} \endTEX \beginETEX \protected \def\processaction[#1]#2[#3]% faster version follows {\expandedaction\!!stringa{#1}% \ifx\!!stringa\empty \let\p!compareprocessaction\p!compareprocessactionA \else \let\p!compareprocessaction\p!compareprocessactionB \fi \def\p!doprocessaction##1% {\p!compareprocessaction[##1][#1]}% \processcommalist[#3]\p!doprocessaction \expandactions} \endETEX % \def\p!compareprocessactionC[#1=>#2][#3]% % {\expandedaction\!!stringa{#1}% % \expandedaction\!!stringb{#3}% % \ifx\!!stringa\!!stringb % \def\commalistelement{#3}% % #2% % \let\p!doprocessaction\gobbleoneargument % \let\p!dodoprocessaction\gobbleoneargument % \else % \edef\!!stringa{#1}% % \ifx\!!stringa\s!unknown % \def\commalistelement{#3}% % #2% % \fi % \fi} \def\p!compareprocessactionC[#1=>#2][#3]% {\expandedaction\!!stringa{#1}% \expandedaction\!!stringb{#3}% \ifx\!!stringa\!!stringb \def\commalistelement{#3}% #2% \expandafter\quitprevcommalist \else \edef\!!stringa{#1}% \ifx\!!stringa\s!unknown \def\commalistelement{#3}% #2% \fi \fi} \def\processfirstactioninset[#1]#2[#3]% faster version follows {\expandedaction\!!stringa{#1}% \ifx\!!stringa\empty \processaction[][#3]% \else \def\p!doprocessaction##1% {\def\p!dodoprocessaction####1% {\p!compareprocessactionC[####1][##1]}% \processcommalist[#3]\p!dodoprocessaction}% \processcommalist[#1]\p!doprocessaction \fi \expandactions} % \def\p!compareprocessactionD[#1=>#2][#3]% % {\expandedaction\!!stringa{#1}% % \expandedaction\!!stringb{#3}% % \ifx\!!stringa\!!stringb % \def\commalistelement{#3}% % #2% % \let\p!dodoprocessaction\gobbleoneargument % \else % \edef\!!stringa{#1}% % \ifx\!!stringa\s!unknown % \def\commalistelement{#3}% % #2% % \fi % \fi} \def\p!compareprocessactionD[#1=>#2][#3]% {\expandedaction\!!stringa{#1}% \expandedaction\!!stringb{#3}% \ifx\!!stringa\!!stringb \def\commalistelement{#3}% #2% \expandafter\quitcommalist \else \edef\!!stringa{#1}% \ifx\!!stringa\s!unknown \def\commalistelement{#3}% #2% \fi \fi} \def\doprocessallactionsinset {\csname\s!do\the\processlevel\endcsname} \def\processallactionsinset[#1]#2[#3]% faster version follows {\expandedaction\!!stringa{#1}% \ifx\!!stringa\empty \processaction[][#3]% \else \advance\processlevel \plusone \expandafter\def\csname\s!do\the\processlevel\endcsname##1% {\def\p!dodoprocessaction####1% {\p!compareprocessactionD[####1][##1]}% \processcommalist[#3]\p!dodoprocessaction}% \processcommalist[#1]\doprocessallactionsinset \advance\processlevel \minusone \fi \expandactions} %D We can speed up these macros a bit when we use a dedicated %D commalist processor, one that avoids passing the (often) %D big action list. \beginTEX \def\processaction[#1]#2[% {\let\donottest\dontprocesstest \expandedaction\!!stringa{#1}% \let\donottest\doprocesstest \ifx\!!stringa\empty \let\p!compareprocessaction\p!compareprocessactionA \else \let\p!compareprocessaction\p!compareprocessactionB \fi \def\p!doprocessaction##1% {\p!compareprocessaction[##1][#1]}% \processnextcommalist\relax\expandactions\p!doprocessaction[} \endTEX \beginETEX \def\processaction[#1]#2[% {\expandedaction\!!stringa{#1}% \ifx\!!stringa\empty \let\p!compareprocessaction\p!compareprocessactionA \else \let\p!compareprocessaction\p!compareprocessactionB \fi \def\p!doprocessaction##1% {\p!compareprocessaction[##1][#1]}% \processnextcommalist\relax\expandactions\p!doprocessaction[} \endETEX \def\processfirstactionsinset[#1]% {\expandedaction\!!stringa{#1}% \ifx\!!stringa\empty \expandafter\processaction \else \expandafter\processfirstactionsinsetindeed \fi [#1]} \def\processfirstactioninsetindeed[#1]#2[#3]% {\def\p!doprocessaction##1% {\def\p!dodoprocessaction####1% {\p!compareprocessactionC[####1][##1]}% \processcommalist[#3]\p!dodoprocessaction}% \processcommalist[#1]\p!doprocessaction \expandactions} \def\processallactionsinset[#1]% {\expandedaction\!!stringa{#1}% \ifx\!!stringa\empty \expandafter\processaction \else \expandafter\processallactionsinsetindeed \fi [#1]} \def\processallactionsinsetindeed[#1]#2[#3]% {\advance\processlevel \plusone \expandafter\def\csname\s!do\the\processlevel\endcsname##1% {\def\p!dodoprocessaction####1% {\p!compareprocessactionD[####1][##1]}% \processcommalist[#3]\p!dodoprocessaction}% \processcommalist[#1]\doprocessallactionsinset \advance\processlevel \minusone \expandactions} \def\processnextcommalist#1#2#3[#4#5]% {#1% \let\nexttoken#4% \global\advance\commalevel \plusone \long\expandafter\def\csname\s!next\the\commalevel\endcsname##1,% {#3{##1}\doprocesscommaitem}% \dodoprocesscommaitem#4#5,]\relax \global\advance\commalevel \minusone #2} %D I do have an even faster version (saving 3 sec on a 13 %D sec run for 50K invocations, but normally we don't have %D that many calls and that alternative uses more macros and is %D even less readable. What we did add, was \type {\@EA}, so %D that we can pass a command. %D \macros %D {unexpandedprocessaction, %D unexpandedprocessfirstactioninset, %D unexpandedprocessallactionsinset} %D %D Now what are those expansion commands doing there. Well, %D sometimes we want to compare actions that may consist off %D commands (i.e. are no constants). In such occasions we can %D use the a bit slower alternatives: \def\unexpandedprocessfirstactioninset{\dontexpandactions\processfirstactioninset} \def\unexpandedprocessaction {\dontexpandactions\processaction} \def\unexpandedprocessallactionsinset {\dontexpandactions\processallactionsinset} %D By default we expand actions: \def\expandactions{\let\expandedaction\edef} \expandactions %D But when needed we convert the strings to meaningful %D sequences of characters. \def\unexpandedaction#1>{} \def\noexpandedaction#1#2% {\def\convertedargument{#2}% \@EA\edef\@EA#1\@EA{\@EA\unexpandedaction\meaning\convertedargument}} \def\dontexpandactions% {\let\expandedaction\noexpandedaction} %D \macros %D {getfirstcharacter,firstcharacter, doiffirstcharacter} %D %D Sometimes the action to be undertaken depends on the %D next character. This macro get this character and puts it in %D \type{\firstcharacter}. %D %D \starttyping %D \getfirstcharacter {string} %D \stoptyping %D %D A two step expansion is used to prevent problems with %D complicated arguments, for instance arguments that %D consist of two or more expandable tokens. \def\dogetfirstcharacter#1#2\\% {\def\firstcharacter{#1}} \def\getfirstcharacter#1% {\edef\!!stringa{#1}% \expandafter\dogetfirstcharacter\!!stringa\\} \def\doiffirstcharelse#1#2% char string % kort (maar onleesbaar) % {\expanded{\dogetfirstcharacter#2}\\\doifelse{#1}\firstcharacter} % korter (en begrijpelijk)) {\getfirstcharacter{#2}\doifelse{#1}\firstcharacter} % snel (maar zelden gebruikt, dus niet zo belangrijk) % {\getfirstcharacter{#2}% % \edef\!!stringa{#1}% % \ifx\!!stringa\firstcharacter % \expandafter\firstoftwoarguments % \else % \expandafter\secondoftwoarguments % \fi} %D \macros %D {doifinstringelse, doifincsnameelse} %D %D We can check for the presence of a substring in a given %D sequence of characters. %D %D \starttyping %D \doifinsetelse {substring} {string} {then ...} {else ...} %D \stoptyping %D %D An application of this command can be found further on. %D Like before, we first show some alternatives, like the one %D we started with: %D %D \starttyping %D \long\def\p!doifinstringelse#1#2#3#4% %D {\def\pp!doifinstringelse##1#1##2##3\war% %D {\if##2@% %D #4% %D \else %D #3% %D \fi}% %D \pp!doifinstringelse#2#1@@\war} %D %D \def\doifinstringelse% %D {\ExpandBothAfter\p!doifinstringelse} %D \stoptyping %D %D After this we came to: %D %D \starttyping %D \def\p!doifinstringelse#1#2% %D {\def\pp!doifinstringelse##1#1##2##3\war% %D {\if##2@}% %D \pp!doifinstringelse#2#1@@\war} %D %D \def\doifinstringelse#1#2#3#4% %D {\ExpandBothAfter\p!doifinstringelse{#1}{#2}% %D #4% %D \else %D #3% %D \fi} %D \stoptyping %D %D Sometimes the second argument is passed as a macro. By %D postponing the expansion of this macro, we gain quite some %D run time, simply because the less tokens we pass, the faster %D \TEX\ runs. So finally the definition became: \long\def\doifinstringelse#1% {\edef\@@@instring{#1}% expand #1 here \ifx\@@@instring\empty \@EA\thirdofthreearguments \else \@EA\dodoifinstringelse \fi} \long\def\dodoifinstringelse#1% {\p!doifinstringelse\@@@instring{#1}% \@EA\firstoftwoarguments \else \@EA\secondoftwoarguments \fi} \long\def\doifinstring#1%% {\edef\@@@instring{#1}% expand #1 here \ifx\@@@instring\empty \@EA\gobbletwoarguments \else \@EA\dodoifinstring \fi} \long\def\dodoifinstring#1% {\p!doifinstringelse\@@@instring{#1}% \@EA\firstofoneargument \else \@EA\gobbleoneargument \fi} %D \starttyping %D \beginTEX %D %D \long\def\p!doifinstringelse#1#2% %D {\long\def\pp!doifinstringelse##1#1##2##3\war% %D {\csname if\if##2@fals\else tru\fi e\endcsname}% %D \expanded{\pp!doifinstringelse#2#1@@\noexpand\war}} % expand #2 here %D %D \endTEX %D %D \beginETEX \unless %D %D \long\def\p!doifinstringelse#1#2% %D {\long\def\pp!doifinstringelse##1#1##2##3\war% %D {\unless\if##2@}% %D \expanded{\pp!doifinstringelse#2#1@@\noexpand\war}} % expand #2 here %D %D \endETEX %D \stoptyping %D %D And then \unknown\ after a couple of years, we ran into a %D situation where \type {##2} was something \type {{bla}}. So %D finally we need to use an auxiliary macro, otherwise we get %D funny strings in the output. % \long\def\p!doifinstringelse#1#2% ##2 can be {abc} % {\long\@EA\def\@EA\pp!doifinstringelse\@EA##\@EA1#1##2##3\war % expand #1 here % {\ppp!doifinstringelse##2\war}% % \expanded{\pp!doifinstringelse#2#1@@\noexpand\war}} % expand #2 here % % \beginTEX % % \def\ppp!doifinstringelse#1#2\war% % {\csname if\ifx#1@fals\else tru\fi e\endcsname}% % % \endTEX % % \beginETEX \unless % % \def\ppp!doifinstringelse#1#2\war% % {\unless\ifx#1@} % % \endETEX \beginETEX \long\def\p!doifinstringelse#1#2% ##2 can be {abc} {\long\@EA\def\@EA\pp!doifinstringelse\@EA##\@EA1#1##2##3\war % expand #1 here {\unless\if##2@}% % \expanded{\pp!doifinstringelse#2#1@@\noexpand\war}} % expand #2 here \expanded{\pp!doifinstringelse#2#1}@@\war} % expand #2 here \endETEX \beginTEX \long\def\p!doifinstringelse#1#2% ##2 can be {abc} {\long\@EA\def\@EA\pp!doifinstringelse\@EA##\@EA1#1##2##3\war % expand #1 here {\csname if\if##2@fals\else tru\fi e\endcsname}% %\expanded{\pp!doifinstringelse#2#1@@\noexpand\war}} % expand #2 here \expanded{\pp!doifinstringelse#2#1}@@\war} % expand #2 here \endTEX %D The next alternative proved to be upto twice as fast on %D tasks like checking reserved words in pretty verbatim %D typesetting! This is mainly due to the fact that passing %D (expanded) strings is much slower that passing a macro. %D %D \starttyping %D \doifincsnameelse {substring} {\string} {then ...} {else ...} %D \stoptyping %D %D Where \type{\doifinstringelse} does as much expansion as %D possible, the latter alternative does minimal (one level) %D expansion. \beginTEX \long\def\p!doifincsnameelse#1#2% {\long\def\pp!doifincsnameelse##1#1##2##3\war {\csname if\if##2@fals\else tru\fi e\endcsname}% \@EA\pp!doifincsnameelse#2#1@@\war} \endTEX \beginETEX \unless \long\def\p!doifincsnameelse#1#2% {\long\def\pp!doifincsnameelse##1#1##2##3\war {\unless\if##2@}% \@EA\pp!doifincsnameelse#2#1@@\war} \endETEX \long\def\doifincsnameelse#1#2% % #3#4% {\edef\@@@instring{#1}% \@EA\p!doifincsnameelse\@EA{\@@@instring}{#2}% % #3\else#4\fi} \expandafter\firstoftwoarguments \else \expandafter\secondoftwoarguments \fi} %D \macros %D {doifnumberelse} %D %D The next macro executes a command depending of the outcome %D of a test on numerals. This is probably one of the fastest %D test possible, exept from a less robust 10||step %D \type{\if}||ladder or some tricky \type{\lcode} checking. %D %D \starttyping %D \doifnumberelse {string} {then ...} {else ...} %D \stoptyping %D %D The macro accepts \type{123}, \type{abc}, \type{{}}, %D \type{\getal} and \type{\the\count...}. This macro is a %D rather dirty one. %D %D \starttyping %D \long\def\doifnumberelse#1#2#3% %D {\begingroup\donefalse %D \ifcase1#1\or\or\or\or\or\or\or\or\or\else\donetrue\fi %D \ifdone\endgroup#2\else\endgroup#3\fi} %D \stoptyping %D %D Or better: %D %D \starttyping %D \long\def\doifnumberelse#1% %D {\begingroup\donefalse %D \ifcase1#1\or\or\or\or\or\or\or\or\or\else\donetrue\fi %D \ifdone %D \endgroup\expandafter\firstoftwoarguments %D \else %D \endgroup\expandafter\secondoftwoarguments %D \fi} %D \stoptyping %D %D A previous implementation was: %D %D \starttyping %D \long\def\doifnumberelse#1#2#3% %D {\getfirstcharacter{#1}% %D \@EA\p!doifinstringelse\@EA{\firstcharacter}{1234567890}% %D #2% %D \else %D #3% %D \fi} %D \starttyping %D %D And before we had \type{\p!doifinstringelse} available, we %D used: %D %D \starttyping %D \def\doifnumberelse#1% %D {\getfirstcharacter{#1}% %D \rawdoifinsetelse{\firstcharacter}{1,2,3,4,5,6,7,8,9,0}} %D \stoptyping %D %D The implementation using \type {\ifcase} is much faster, but %D the next one is not, not even when testing milion calls. %D %D \starttyping %D \newif\ifitsanumber %D %D \long\def\isitanumber#1% %D {\itsanumberfalse %D \ifcase1#1\or\or\or\or\or\or\or\or\or\else\itsanumbertrue\fi} %D %D \long\def\doifnumberelse#1#2#3% %D {\isitanumber{#1}\ifitsanumber#2\else#3\fi} %D \stoptyping %D %D After a while the next evolved and this one is the one we %D will use. This one is some 5\% faster than the group/done %D one (partly because it does not have to pass arguments). %D Even more important is that this alternative is fully %D expandable! \long\def\doifnumberelse#1% {\ifcase0\ifcase1#1\or\or\or\or\or\or\or\or\or\else1\fi\space \expandafter\secondoftwoarguments \else \expandafter\firstoftwoarguments \fi} %D \macros %D {makerawcommalist, %D rawdoinsetelse, %D rawprocesscommalist, %D rawprocessaction} %D %D Some of the commands mentioned earlier are effective but %D slow. When one is desperately in need of faster alternatives %D and when the conditions are predictable safe, the \type{\raw} %D alternatives come into focus. A major drawback is that %D they do not take \type{\c!constants} into account, simply %D because no expansion is done. This is no problem with %D \type{\rawprocesscommalist}, because this macro does not %D compare anything. Expandable macros are permitted as search %D string. %D %D \starttyping %D \makerawcommalist[string,string,...]\stringlist %D \rawdoifinsetelse{string}{string,...}{...}{...} %D \rawprocesscommalist[string,string,...]\commando %D \rawprocessaction[x][a=>\a,b=>\b,c=>\c] %D \stoptyping %D %D Spaces embedded in the list, for instance after commas, %D spoil the search process. The gain in speed depends on the %D length of the argument (the longer the argument, the less %D we gain). %D %D The slow alternative looks like: %D %D \starttyping %D \def\makerawcommalist[#1]#2% %D {\def\appendtocommalist##1% %D {\doifelse{#2}{} %D {\edef#2{##1}} %D {\edef#2{#2,##1}}}% %D \def#2{}% %D \processcommalist[#1]\appendtocommalist} %D \stoptyping %D %D But we prefer: % \appendtocommalist is defined in syst-ext \def\makerawcommalist[#1]#2% use \processnext ... here {\def\domakerawcommalist##1% we don't expand ##1 {\ifx#2\empty \def#2{##1}% \else \@EA\def\@EA#2\@EA{#2,##1}% \fi}% \let#2\empty \processcommalist[#1]\domakerawcommalist} \def\rawprocesscommaitem#1,#2% #2 eats up preceding space {\if]#1\else \csname\s!next\the\commalevel\endcsname{#1}% \expandafter\rawprocesscommaitem \fi#2} \def\rawprocesscommalist[#1]#2% accepteert ook [\cs] {\global\advance\commalevel \plusone \expandafter\let\csname\s!next\the\commalevel\endcsname#2% \expandafter\rawprocesscommaitem#1,],% \relax \global\advance\commalevel \minusone } \def\rawprocesscommacommand[#1]% not really needed {\expanded{\rawprocesscommalist[#1]}} % \def\rawdoifinsetelse#1#2{\doifinstringelse{,#1,}{,#2,}} % \def\rawdoifinset #1#2{\doifinstring {,#1,}{,#2,}} \def\@@rawempty{,,} \long\def\rawdoifinsetelse#1% {\edef\@@@instring{,#1,}% expand #1 here \ifx\@@@instring\@@rawempty \@EA\thirdofthreearguments \else \@EA\rawdodoifinsetelse \fi} \long\def\rawdodoifinsetelse#1% {\p!doifinstringelse\@@@instring{,#1,}% \@EA\firstoftwoarguments \else \@EA\secondoftwoarguments \fi} \long\def\rawdoifinset#1% {\edef\@@@instring{,#1,}% expand #1 here \ifx\@@@instring\@@rawempty \@EA\gobbletwoarguments \else \@EA\rawdodoifinset \fi} \long\def\rawdodoifinset#1%% {\p!doifinstringelse\@@@instring{,#1,}% \@EA\firstofoneargument \else \@EA\gobbleoneargument \fi} %D Some more raw material: \def\p!rawprocessaction[#1][#2]% {\def\pp!rawprocessaction##1,#1=>##2,##3\war% {\if##3@\else \def\!!processaction{##2}% \fi}% \pp!rawprocessaction,#2,#1=>,@\war} \def\rawprocessaction[#1]#2[#3]% {\edef\!!stringa{#1}% \edef\!!stringb{undefined}% better \!!undefined \let\!!processaction\!!stringb \ifx\!!stringa\empty \@EA\p!rawprocessaction\@EA[\s!default][#3]% \else \expandafter\p!rawprocessaction\expandafter[\!!stringa][#3]% \ifx\!!processaction\!!stringb \@EA\p!rawprocessaction\@EA[\s!unknown][#3]% \fi \fi \ifx\!!processaction\!!stringb \else \!!processaction \fi} % not needed % % \def\rawprocessallactionsinset[#1]#2[#3]% % {\def\docommando##1% % {\rawprocessaction[##1][#3]}% % \processcommalist[#1]\docommando} %D When we process the list \type{a,b,c,d,e}, the raw routine %D takes over 30\% less time, when we feed $20+$ character %D strings we gain about 20\%. Alternatives which use %D \type{\futurelet} perform worse. Part of the speedup is %D due to the \type{\let} and \type{\expandafter} in the test. % %D \macros % %D {processunexpandedcommalist} % %D % %D When processing commalists, the arguments are expanded. The % %D main reason for doing so lays in the fact that these % %D macros are used for interfacing. The next alternative can be used % %D for % %D % %D \starttyping % %D \processunexpandedcommalist % %D [\alfa\beta,\gamma,\delta\epsilon] % %D \handleitem % %D \stoptyping % %D % %D This time nesting is not supported. % % %\def\processunexpandedcommaitem#1,% % % {\if]\noexpand#1% % % \let\nextcommaitem\relax % % \else % % \handleunexpandedcommaitem{#1}% % % \let\nextcommaitem\processunexpandedcommaitem % \fi % \nextcommaitem} % % faster: % % \def\processunexpandedcommaitem#1,% % {\if]\noexpand#1\else % \handleunexpandedcommaitem{#1}% % \expandafter\processunexpandedcommaitem % \fi} % % \def\processunexpandedcommalist[#1]#2% % {\def\handleunexpandedcommaitem{#2}% % \processunexpandedcommaitem#1,],}% \relax} % % %D Or faster: % % \def\processunexpandedcommaitem#1,% % {\if]\noexpand#1\else % \handleunexpandedcommaitem{#1}% % \expandafter\processunexpandedcommaitem % \fi} %D \macros %D {dosetvalue,dosetevalue,dosetgvalue,docopyvalue,doresetvalue, %D dogetvalue} %D %D When we are going to do assignments, we have to take %D multi||linguality into account. For the moment we keep %D things simple and single||lingual. %D %D \starttyping %D \dosetvalue {label} {variable} {value} %D \dosetevalue {label} {variable} {value} %D \dosetgvalue {label} {variable} {value} %D \docopyvalue {to label} {from label} {variable} %D \doresetvalue {label} {variable} %D \stoptyping %D %D These macros are in fact auxiliary ones and are not meant %D for use outside the assignment macros. \def\dosetvalue#1#2% #3 {\@EA\def\csname#1#2\endcsname} % {#3}} \def\dosetevalue#1#2% #3 {\@EA\edef\csname#1#2\endcsname} % {#3}} \def\dosetgvalue#1#2% #3 {\@EA\gdef\csname#1#2\endcsname} % {#3}} \def\doresetvalue#1#2% {\@EA\let\csname#1#2\endcsname\empty} \def\doignorevalue#1#2#3% {\@EA\let\csname#1#2\endcsname\empty} \def\docopyvalue#1#2#3% {\@EA\def\csname#1#3\endcsname{\csname#2#3\endcsname}} %D \macros %D {doassign,undoassign,doassignempty} %D %D Assignments are the backbone of \CONTEXT. Abhorred by the %D concept of style file hacking, we took a considerable effort %D in building a parameterized system. Unfortunately there is a %D price to pay in terms of speed. Compared to other packages %D and taking the functionality of \CONTEXT\ into account, the %D total size of the format file is still very acceptable. Now %D how are these assignments done. %D %D Assignments can be realized with: %D %D \starttyping %D \doassign[label][variable=value] %D \undoassign[label][variable=value] %D \stoptyping %D %D and: %D %D \starttyping %D \doassignempty[label][variable=value] %D \stoptyping %D %D Assignments like \type{\doassign} are compatible with: %D %D \starttyping %D \def\labelvariable{value} %D \stoptyping %D %D We do check for the presence of an \type{=} and loudly %D complain of it's missed. We will redefine this macro later %D on, when a more advanced message mechanism is implemented. \newif\iferrorisfatal \def\waitonfatalerror {\iferrorisfatal\wait\fi} \def\showassignerror#1#2% {\writestatus{setup}{missing or ungrouped '=' after '#1' in line #2}% \waitonfatalerror} %\def\p!doassign#1[#2][#3=#4=#5]% % {\let\donottest\dontprocesstest % \edef\!!stringa{#5}% % \let\!!stringb\relax % \let\donottest\doprocesstest % \ifx\!!stringa\!!stringb % \showassignerror{#3}% % \else % #1{#2}{#3}{#4}% % \fi} \def\p!doassign#1[#2][#3=#4=#5]% {\ifx\empty#3\else % and definitely not \ifx#3\empty \ifx\relax#5% \showassignerror{#3}{\the\inputlineno\space(#2)}% \else #1{#2}{#3}{#4}% \fi \fi} \def\doassign [#1][#2]{\p!doassign\dosetvalue [#1][#2==\relax]} \def\doeassign [#1][#2]{\p!doassign\dosetevalue [#1][#2==\relax]} \def\undoassign[#1][#2]{\p!doassign\doresetvalue[#1][#2==\relax]} \def\doassignempty[#1][#2=#3]% {\ifundefined{#1#2}\dosetvalue{#1}{#2}{#3}\fi} %D \macros %D {getparameters,geteparameters,getgparameters, %D forgetparameters} %D %D Using the assignment commands directly is not our %D ideal of user friendly interfacing, so we take some further %D steps. %D %D \starttyping %D \getparameters [label] [...=...,...=...] % %D \forgetparameters [label] [...=...,...=...] %D \stoptyping %D %D Again, the label identifies the category a variable %D belongs to. The second argument can be a comma separated %D list of assignments. %D %D \starttyping %D \getparameters %D [demo] %D [alfa=1, %D beta=2] %D \stoptyping %D %D is equivalent to %D %D \starttyping %D \def\demoalfa{1} %D \def\demobeta{2} %D \stoptyping %D %D %D In the pre||multi||lingual stadium \CONTEXT\ took the next %D approach. With %D %D \starttyping %D \def\??demo {@@demo} %D \def\!!alfa {alfa} %D \def\!!beta {beta} %D \stoptyping %D %D calling %D %D \starttyping %D \getparameters %D [\??demo] %D [\!!alfa=1, %D \!!beta=2] %D \stoptyping %D %D lead to: %D %D \starttyping %D \def\@@demoalfa{1} %D \def\@@demobeta{2} %D \stoptyping %D %D Because we want to be able to distinguish the \type{!!} %D pre||tagged user supplied variables from internal %D counterparts, we will introduce a slightly different tag in %D the multi||lingual modules. There we will use \type{c!} or %D \type{v!}, depending on the context. %D %D By calling \type{\p!doassign} directly, we save ourselves %D some argument passing and gain some speed. Whatever %D optimizations we do, this command will always be one of the %D bigger bottlenecks. %D %D The alternative \type{\geteparameters} --- it's funny to %D see that this alternative saw the light so lately --- can be %D used to do expanded assigments. \def\dogetparameters#1[#2]#3[#4]% {\def\p!dogetparameter##1% {\p!doassign#1[#2][##1==\relax]}% \processcommalist[#4]\p!dogetparameter} \def\getparameters {\dogetparameters\dosetvalue} \def\geteparameters {\dogetparameters\dosetevalue} \def\getgparameters {\dogetparameters\dosetgvalue} \def\forgetparameters{\dogetparameters\doignorevalue} \let\getexpandedparameters=\geteparameters %D This one is slightly faster: \def\dogetparameters#1[#2]#3[#4% {\if\noexpand#4]% \expandafter\gobbleoneargument \else \def\p!dogetparameter##1{\p!doassign#1[#2][##1==\relax]}% \expandafter\xdogetparameters \fi#4} \def\xdogetparameters#1]% {\processcommalist[#1]\p!dogetparameter} %D The next alternative is much faster but also uglier. Because %D in \XML\ processing we will probably set much more parameters %D than normally we need this faster one. \def\dogetparameters#1[#2]#3[#4% {\if\noexpand#4]% \expandafter\gobbleoneargument \else \def\p!dogetparameter{\p!doassign#1#2}% \expandafter\xdogetparameters \fi#4} \def\xdogetparameters#1]% {\xprocesscommaitem#1,],\@relax@} % \long\def\xprocesscommaitem#1,#2% #2 takes space before , % {\if]#1% % \expandafter\gobbleoneargument % \else % \p!dogetparameter\@relax@#1==\@relax@ % \expandafter\xprocesscommaitem % \fi#2} \long\def\xprocesscommaitem#1,#2% #2 takes space before , {\if,#1,% dirty trick for testing #1=empty \@EA\xprocesscommaitem \else\if]#1% \@EAEAEA\gobbleoneargument \else \p!dogetparameter\@relax@#1==\empty\@relax@ \@EAEAEA\xprocesscommaitem \fi\fi#2} %D Here we use a slightly different assignment macro: % \def\p!doassign#1#2\@relax@#3=#4=#5\@relax@ % {\ifx\@relax@#5\@EA\xshowassignerror\else\@EA#1\fi{#2}{#3}{#4}} % \def\p!doassign#1#2\@relax@#3=#4=#5\@relax@ % {\ifx#5\empty\@EA\xshowassignerror\else\@EA#1\fi{#2}{#3}{#4}} \def\p!doassign#1#2\@relax@#3=#4=#5#6\@relax@ {\ifx#5\empty \@EA\xshowassignerror \else\ifx#5=% \@EAEAEA#1% \else \@EAEAEA\xshowassignerror \fi\fi {#2}{#3}{#4}} \def\xshowassignerror#1#2#3% {\showassignerror{#2}{\the\inputlineno\space(#1)}} %D Now we also have to change the other macros that depend %D on this low level one. % \def\doassign [#1][#2]{\p!doassign\dosetvalue #1\@relax@#2==\@relax@} % \def\doeassign [#1][#2]{\p!doassign\dosetevalue #1\@relax@#2==\@relax@} % \def\undoassign[#1][#2]{\p!doassign\doresetvalue#1\@relax@#2==\@relax@} \def\doassign [#1][#2]{\p!doassign\dosetvalue #1\@relax@#2==\empty\@relax@} \def\doeassign [#1][#2]{\p!doassign\dosetevalue #1\@relax@#2==\empty\@relax@} \def\undoassign[#1][#2]{\p!doassign\doresetvalue#1\@relax@#2==\empty\@relax@} %D \macros %D {getemptyparameters} %D %D Sometimes we explicitly want variables to default to an %D empty string, so we welcome: %D %D \starttyping %D \getemptyparameters [label] [...=...,...=...] %D \stoptyping \def\getemptyparameters[#1]#2[#3]% {\def\p!dogetemptyparameter##1% {\doassignempty[#1][##1]}% \processcommalist[#3]\p!dogetemptyparameter} %D \macros %D {copyparameters} %D %D Some \CONTEXT\ commands take their default setups from %D others. All commands that are able to provide backgounds %D or rules around some content, for instance default to the %D standard command for ruled boxes. Is situations like this %D we can use: %D %D \starttyping %D \copyparameters [to-label] [from-label] [name1,name2,...] %D \stoptyping %D %D For instance %D %D \starttyping %D \copyparameters %D [internal][external] %D [alfa,beta] %D \stoptyping %D %D Leads to: %D %D \starttyping %D \def\internalalfa {\externalalfa} %D \def\internalbeta {\externalbeta} %D \stoptyping %D %D By using \type{\docopyvalue} we've prepared this command %D for use in a multi||lingual environment. \def\copyparameters[#1]#2[#3]#4[#5]% {\doifnot{#1}{#3} {\def\docopyparameter{\docopyvalue{#1}{#3}}% %\def\docopyparameter##1{\docopyvalue{#1}{#3}{##1}}% \processcommalist[#5]\docopyparameter}} %D \macros %D {doifassignmentelse} %D %D A lot of \CONTEXT\ commands take optional arguments, for %D instance: %D %D \starttyping %D \dothisorthat[alfa,beta] %D \dothisorthat[first=foo,second=bar] %D \dothisorthat[alfa,beta][first=foo,second=bar] %D \stoptyping %D %D Although a combined solution is possible, we prefer a %D seperation. The next command takes care of propper %D handling of such multi||faced commands. %D %D \starttyping %D \doifassignmentelse {...} {then ...} {else ...} %D \stoptyping % not robust % % \def\doifassignmentelse% % {\doifinstringelse{=}} % % readable % % \def\doifassignmentelse#1% % {\convertargument#1\to\ascii % \doifinstringelse{=}{\ascii}} \def\doifassignmentelse#1% {\convertargument#1\to\ascii \doifinstringelse=\ascii} %D \macros %D {ifparameters,checkparameters} %D %D A slightly different one is \type{\checkparameters}, which %D also checks on the presence of a~\type{=}. %D %D The boolean \type{\ifparameters} can be used afterwards. %D Combining both in one \type{\if}||macro would lead to %D problems with nested \type{\if}'s. %D %D \starttyping %D \checkparameters[argument] %D \stoptyping \newif\ifparameters \def\p!checkparameters#1=#2#3\war% {\if#2@\parametersfalse\else\parameterstrue\fi} \def\checkparameters[#1]% {\p!checkparameters#1=@@\war} %D \macros %D {getfromcommalist,getfromcommacommand, %D commalistelement, %D getcommalistsize,getcommacommandsize} %D %D It's possible to get an element from a commalist or a %D command representing a commalist. %D %D \starttyping %D \getfromcommalist [string] [n] %D \getfromcommacommand [string,\strings,string,...] [n] %D \stoptyping %D %D The difference betwee the two of them is the same as the %D difference between \type{\processcomma...}. The found string %D is stored in \type{\commalistelement}. %D %D We can calculate the size of a comma separated list by %D using: %D %D \starttyping %D \getcommalistsize [string,string,...] %D \getcommacommandsize [string,\strings,string,...] %D \stoptyping %D %D Afterwards, the length is available in the macro %D \type{\commalistsize} (not a \COUNTER). \newcount\commalistcounter \def\commalistsize{0} \def\p!dogetcommalistsize#1% {\advance\commalistcounter\plusone} \def\getcommalistsize[#1]% {\commalistcounter\zerocount \processcommalist[#1]\p!dogetcommalistsize % was [{#1}] \edef\commalistsize{\the\commalistcounter}} \def\getcommacommandsize[#1]% {\edef\commacommand{#1}% \scratchtoks\expandafter{\expandafter[\commacommand]}% \expandafter\getcommalistsize\the\scratchtoks } % to be tested first % % \def\getcommacommandsize[#1]% % {\expanded{\getcommalistsize[#1]}} % \def\p!dogetfromcommalist#1% % {\advance\commalistcounter \minusone % \ifcase\commalistcounter % \def\commalistelement{#1}% % \begingroup\def\doprocesscommaitem##1]{\endgroup}% % \fi} \def\p!dogetfromcommalist#1% {\advance\commalistcounter \minusone \ifcase\commalistcounter \def\commalistelement{#1}% \expandafter\quitcommalist \fi} \def\getfromcommalist[#1]#2[#3]% {\let\commalistelement\empty \commalistcounter#3\relax \processcommalist[#1]\p!dogetfromcommalist} % \def\getfromcommacommand[#1]% why so complicated, still needed? % {\edef\commacommand{#1}% % \toks0=\expandafter{\expandafter[\commacommand]}% % \expandafter\getfromcommalist\the\toks0 } \def\getfromcommacommand[#1]% {\expanded{\getfromcommalist[#1]}} %D Because 0, 1 and~2 are often asked for, we optimize this %D macro for those cases. The indirect call however slows %D down the other cases. %D %D \starttyping %D \def\p!dogetfirstfromcommalist [#1,#2]{\def\commalistelement{#1}} %D \def\p!dogetsecondfromcommalist[#1,#2,#3]{\def\commalistelement{#2}} %D \let\p!dogetotherfromcommalist=\getfromcommalist %D %D \def\getfromcommalist[#1]#2[#3]% optimized for 0,1,2 %D {\ifcase#3\relax %D \let\commalistelement\empty %D \or %D \p!dogetfirstfromcommalist[#1,]% %D \or %D \p!dogetsecondfromcommalist[#1,,]% %D \else %D \p!dogetotherfromcommalist[#1][#3]% %D \fi} %D \stoptyping %D %D Even worse, this alternative does not strip preceding %D spaces, which is what we want. So, we stick to the slow %D alternative. %D Watertight (and efficient) solutions are hard to find, due %D to the handling of braces during parameters passing and %D scanning. Nevertheless: %D %D \startbuffer %D \def\dosomething#1{(#1=\commalistsize) } %D %D \getcommalistsize [\hbox{$a,b,c,d,e,f$}] \dosomething 1 %D \getcommalistsize [{a,b,c,d,e,f}] \dosomething 1 %D \getcommalistsize [{a,b,c},d,e,f] \dosomething 4 %D \getcommalistsize [a,b,{c,d,e},f] \dosomething 4 %D \getcommalistsize [a{b,c},d,e,f] \dosomething 4 %D \getcommalistsize [{a,b}c,d,e,f] \dosomething 4 %D \getcommalistsize [] \dosomething 0 %D \getcommalistsize [{[}] \dosomething 1 %D \stopbuffer %D %D \typebuffer %D %D reports: %D %D \getbuffer %D \macros %D {dogetcommalistelement,dogetcommacommandelement} %D %D For low level (fast) purposes, we can also use the next %D alternative, which can handle 8~elements at most. %D %D \starttyping %D \dogetcommalistelement1\from a,b,c\to\commalistelement %D \stoptyping \def\dodogetcommalistelement#1\from#2,#3,#4,#5,#6,#7,#8\to#9% {\edef#9{\ifcase#1\relax\or#2\or#3\or#4\or#5\or#6\or#7\or#8\fi}} % maybe better: % % {\@EA\edef\@EA#9\@EA{\ifcase#1\relax\or#2\or#3\or#4\or#5\or#6\or#7\or#8\fi}} \def\dogetcommalistelement#1\from#2\to% {\dodogetcommalistelement#1\from#2,,,,,,\to} % check sources \def\dogetcommacommandelement#1\from#2\to% {\@EA\dodogetcommalistelement\@EA#1\@EA\from#2,,,,,,\to} %D \macros %D {dosingleargument,dodoubleargument,dotripleargument, %D doquadrupleargument,doquintupleargument,dosixtupleargument, %D doseventupleargument} %D %D When working with delimited arguments, spaces and %D lineendings can interfere. The next set of macros uses %D \TEX' internal scanner for grabbing everything between %D arguments. Forgive me the funny names. %D %D \starttyping %D \dosingleargument\commando = \commando[#1] %D \dodoubleargument\commando = \commando[#1][#2] %D \dotripleargument\commando = \commando[#1][#2][#3] %D \doquadrupleargument\commando = \commando[#1][#2][#3][#4] %D \doquintupleargument\commando = \commando[#1][#2][#3][#4][#5] %D \dosixtupleargument\commando = \commando[#1][#2][#3][#4][#5][#6] %D \doseventupleargument\command= \commando[#1][#2][#3][#4][#5][#6][#7] %D \stoptyping %D %D These macros are used in the following way: %D %D \starttyping %D \def\dosetupsomething[#1][#2]% %D {... #1 ... #2 ...} %D %D \def\setupsomething% %D {\dodoubleargument\dosetupsomething} %D \stoptyping %D %D The implementation can be surprisingly simple and needs no %D further explanation, like: %D %D \starttyping %D \def\dosingleargument#1[#2]% %D {#1[#2]} %D \def\dotripleargument#1[#2]#3[#4]#5[#6]% %D {#1[#2][#4][#6]} %D \def\doquintupleargument#1% %D {\def\dodoquintupleargument[##1]##2[##3]##4[##5]##6[##7]##8[##9]% %D {#1[##1][##3][##5][##7][##9]}% %D \dodoquintupleargument} %D \stoptyping %D %D Because \TEX\ accepts 9~arguments at most, we have to use %D two||step solution when getting five or more arguments. %D %D When developing more and more of the real \CONTEXT, we %D started using some alternatives that provided empty %D arguments (in fact optional ones) whenever the user failed %D to supply them. Because this more complicated macros enable %D us to do some checking, we reimplemented the non||empty %D ones. \def\dosingleargument {\chardef\expectedarguments 1 \dosingleempty } \def\dodoubleargument {\chardef\expectedarguments 2 \dodoubleempty } \def\dotripleargument {\chardef\expectedarguments 3 \dotripleempty } \def\doquadrupleargument {\chardef\expectedarguments 4 \doquadrupleempty } \def\doquintupleargument {\chardef\expectedarguments 5 \doquintupleempty } \def\dosixtupleargument {\chardef\expectedarguments 6 \dosixtupleempty } \def\doseventupleargument{\chardef\expectedarguments 7 \doseventupleempty} %D \macros %D {iffirstagument,ifsecondargument,ifthirdargument, %D iffourthargument,iffifthargument,ifsixthargument, %D ifseventhargument} %D %D We use some signals for telling the calling macros if all %D wanted arguments are indeed supplied by the user. \newif\iffirstargument \newif\ifsecondargument \newif\ifthirdargument \newif\iffourthargument \newif\iffifthargument \newif\ifsixthargument \newif\ifseventhargument %D \macros %D {dosingleempty,dodoubleempty,dotripleempty, %D doquadrupleempty,doquintupleempty,dosixtupeempty, %D doseventupleempty} %D %D The empty argument supplying macros mentioned before, look %D like: %D %D \starttyping %D \dosingleempty \command %D \dodoubleempty \command %D \dotripleempty \command %D \doquadrupleempty \command %D \doquintupleempty \command %D \dosixtupleempty \command %D \doseventupleempty\command %D \stoptyping %D %D So \type{\dodoubleempty} leades to: %D %D \starttyping %D \command[#1][#2] %D \command[#1][] %D \command[][] %D \stoptyping %D %D Depending of the generousity of the user. Afterwards one can %D use the \type{\if...argument} boolean. For novice: watch %D the stepwise doubling of \type{#}'s % idea: \ignorespaces afterwards \chardef\noexpectedarguments=0 \chardef\expectedarguments =0 \def\showargumenterror#1#2% {\writestatus{system}{#1 argument(s) expected in line #2}} % \long\def\dogetargument#1#2#3#4% redefined in mult-ini % {\doifnextcharelse{#1} % {\let\expectedarguments\noexpectedarguments % #3\dodogetargument} % {\ifnum\expectedarguments>\noexpectedarguments % \showargumenterror{\expectedarguments} % \fi % \let\expectedarguments\noexpectedarguments % #4\dodogetargument#1#2}} % % less to pass \def\doshowargumenterror {\ifnum\expectedarguments>\noexpectedarguments \showargumenterror{\number\expectedarguments}{\number\inputlineno}% \fi \noshowargumenterror} \def\noshowargumenterror {\let\expectedarguments\noexpectedarguments} % \long\def\dogetargument#1#2#3#4% % {\doifnextcharelse#1% % {\noshowargumenterror#3\dodogetargument} % {\doshowargumenterror#4\dodogetargument#1#2}} % % faster ? \long\def\dogetargument#1#2#3#4% {\let\charactertoken=#1% \def\!!stringa{\noshowargumenterror#3\dodogetargument}% \def\!!stringb{\doshowargumenterror#4\dodogetargument#1#2}% \futurelet\nexttoken\inspectnextcharacter} %\def\getsingleempty#1#2#3% % {\def\dodogetargument% % {#3}% % \dogetargument#1#2\firstargumenttrue\firstargumentfalse} % %\def\getdoubleempty#1#2#3% % {\def\dodogetargument#1##1#2% % {\def\dodogetargument% % {#3#1##1#2}% % \dogetargument#1#2\secondargumenttrue\secondargumentfalse}% % \dogetargument#1#2\firstargumenttrue\firstargumentfalse} % %\def\gettripleempty#1#2#3% % {\def\dodogetargument#1##1#2% % {\def\dodogetargument#1####1#2% % {\def\dodogetargument% % {#3#1##1#2% % #1####1#2}% % \dogetargument#1#2\thirdargumenttrue\thirdargumentfalse}% % \dogetargument#1#2\secondargumenttrue\secondargumentfalse}% % \dogetargument#1#2\firstargumenttrue\firstargumentfalse} % %\def\getquadrupleempty#1#2#3% % {\def\dodogetargument#1##1#2% % {\def\dodogetargument#1####1#2% % {\def\dodogetargument#1########1#2% % {\def\dodogetargument% % {#3#1##1#2% % #1####1#2% % #1########1#2}% % \dogetargument#1#2\fourthargumenttrue\fourthargumentfalse}% % \dogetargument#1#2\thirdargumenttrue\thirdargumentfalse}% % \dogetargument#1#2\secondargumenttrue\secondargumentfalse}% % \dogetargument#1#2\firstargumenttrue\firstargumentfalse} % %\def\getquintupleempty#1#2#3% % {\def\dodogetargument#1##1#2% % {\def\dodogetargument#1####1#2% % {\def\dodogetargument#1########1#2% % {\def\dodogetargument#1################1#2% % {\def\dodogetargument% % {#3#1##1#2% % #1####1#2% % #1########1#2% % #1################1#2}% % \dogetargument#1#2\fifthargumenttrue\fifthargumentfalse}% % \dogetargument#1#2\fourthargumenttrue\fourthargumentfalse}% % \dogetargument#1#2\thirdargumenttrue\thirdargumentfalse}% % \dogetargument#1#2\secondargumenttrue\secondargumentfalse}% % \dogetargument#1#2\firstargumenttrue\firstargumentfalse} % %\def\getsixtupleempty#1#2#3% % {\def\dodogetargument#1##1#2% % {\def\dodogetargument#1####1#2% % {\def\dodogetargument#1########1#2% % {\def\dodogetargument#1################1#2% % {\def\dodogetargument#1################################1#2% % {\def\dodogetargument% % {#3#1##1#2% % #1####1#2% % #1########1#2% % #1################1#2% % #1################################1#2}% % \dogetargument#1#2\sixthargumenttrue\sixthargumentfalse}% % \dogetargument#1#2\fifthargumenttrue\fifthargumentfalse}% % \dogetargument#1#2\fourthargumenttrue\fourthargumentfalse}% % \dogetargument#1#2\thirdargumenttrue\thirdargumentfalse}% % \dogetargument#1#2\secondargumenttrue\secondargumentfalse}% % \dogetargument#1#2\firstargumenttrue\firstargumentfalse} % %\def\getseventupleempty#1#2#3% % {\def\dodogetargument#1##1#2% % {\def\dodogetargument#1####1#2% % {\def\dodogetargument#1########1#2% % {\def\dodogetargument#1################1#2% % {\def\dodogetargument#1################################1#2% % {\def\dodogetargument#1###############################% % ################################1#2% % {\def\dodogetargument% % {#3#1##1#2% % #1####1#2% % #1########1#2% % #1################1#2% % #1################################1#2% % #1###############################% % ################################1#2}% % \dogetargument#1#2\seventhargumenttrue\seventhargumentfalse}% % \dogetargument#1#2\sixthargumenttrue\sixthargumentfalse}% % \dogetargument#1#2\fifthargumenttrue\fifthargumentfalse}% % \dogetargument#1#2\fourthargumenttrue\fourthargumentfalse}% % \dogetargument#1#2\thirdargumenttrue\thirdargumentfalse}% % \dogetargument#1#2\secondargumenttrue\secondargumentfalse}% % \dogetargument#1#2\firstargumenttrue\firstargumentfalse} \def\getsingleempty#1#2#3% {\def\dodogetargument% {#3}% \dogetargument#1#2\firstargumenttrue\firstargumentfalse} \def\getdoubleempty#1#2#3% {\def\dodogetargument#1##1#2% {\def\dodogetargument% {#3#1{##1}#2}% \dogetargument#1#2\secondargumenttrue\secondargumentfalse}% \dogetargument#1#2\firstargumenttrue\firstargumentfalse} \def\gettripleempty#1#2#3% {\def\dodogetargument#1##1#2% {\def\dodogetargument#1####1#2% {\def\dodogetargument% {#3#1{##1}#2% #1{####1}#2}% \dogetargument#1#2\thirdargumenttrue\thirdargumentfalse}% \dogetargument#1#2\secondargumenttrue\secondargumentfalse}% \dogetargument#1#2\firstargumenttrue\firstargumentfalse} \def\getquadrupleempty#1#2#3% {\def\dodogetargument#1##1#2% {\def\dodogetargument#1####1#2% {\def\dodogetargument#1########1#2% {\def\dodogetargument% {#3#1{##1}#2% #1{####1}#2% #1{########1}#2}% \dogetargument#1#2\fourthargumenttrue\fourthargumentfalse}% \dogetargument#1#2\thirdargumenttrue\thirdargumentfalse}% \dogetargument#1#2\secondargumenttrue\secondargumentfalse}% \dogetargument#1#2\firstargumenttrue\firstargumentfalse} \def\getquintupleempty#1#2#3% {\def\dodogetargument#1##1#2% {\def\dodogetargument#1####1#2% {\def\dodogetargument#1########1#2% {\def\dodogetargument#1################1#2% {\def\dodogetargument% {#3#1{##1}#2% #1{####1}#2% #1{########1}#2% #1{################1}#2}% \dogetargument#1#2\fifthargumenttrue\fifthargumentfalse}% \dogetargument#1#2\fourthargumenttrue\fourthargumentfalse}% \dogetargument#1#2\thirdargumenttrue\thirdargumentfalse}% \dogetargument#1#2\secondargumenttrue\secondargumentfalse}% \dogetargument#1#2\firstargumenttrue\firstargumentfalse} \def\getsixtupleempty#1#2#3% {\def\dodogetargument#1##1#2% {\def\dodogetargument#1####1#2% {\def\dodogetargument#1########1#2% {\def\dodogetargument#1################1#2% {\def\dodogetargument#1################################1#2% {\def\dodogetargument% {#3#1{##1}#2% #1{####1}#2% #1{########1}#2% #1{################1}#2% #1{################################1}#2}% \dogetargument#1#2\sixthargumenttrue\sixthargumentfalse}% \dogetargument#1#2\fifthargumenttrue\fifthargumentfalse}% \dogetargument#1#2\fourthargumenttrue\fourthargumentfalse}% \dogetargument#1#2\thirdargumenttrue\thirdargumentfalse}% \dogetargument#1#2\secondargumenttrue\secondargumentfalse}% \dogetargument#1#2\firstargumenttrue\firstargumentfalse} \def\getseventupleempty#1#2#3% {\def\dodogetargument#1##1#2% {\def\dodogetargument#1####1#2% {\def\dodogetargument#1########1#2% {\def\dodogetargument#1################1#2% {\def\dodogetargument#1################################1#2% {\def\dodogetargument#1################################% ################################1#2% {\def\dodogetargument% {#3#1{##1}#2% #1{####1}#2% #1{########1}#2% #1{################1}#2% #1{################################1}#2% #1{################################% ################################1}#2}% \dogetargument#1#2\seventhargumenttrue\seventhargumentfalse}% \dogetargument#1#2\sixthargumenttrue\sixthargumentfalse}% \dogetargument#1#2\fifthargumenttrue\fifthargumentfalse}% \dogetargument#1#2\fourthargumenttrue\fourthargumentfalse}% \dogetargument#1#2\thirdargumenttrue\thirdargumentfalse}% \dogetargument#1#2\secondargumenttrue\secondargumentfalse}% \dogetargument#1#2\firstargumenttrue\firstargumentfalse} \def\dosingleempty {\getsingleempty []} \def\dodoubleempty {\getdoubleempty []} \def\dotripleempty {\gettripleempty []} \def\doquadrupleempty {\getquadrupleempty []} \def\doquintupleempty {\getquintupleempty []} \def\dosixtupleempty {\getsixtupleempty []} \def\doseventupleempty{\getseventupleempty[]} %D Because some of these are called quite often, we will now %D replace the more general version by alternatives tuned for %D speed. \def\dosingleempty#1% {\noshowargumenterror % \relax % prevents lookahead, brr \doifnextcharelse[% {\firstargumenttrue#1} {\dosinglefakeempty#1}} \def\dodoubleempty#1% {\noshowargumenterror % \relax % prevents lookahead, brr \doifnextcharelse[% {\dodoubletestempty#1} {\dodoublefakeempty#1}} \def\dotripleempty#1% {\noshowargumenterror % \relax % prevents lookahead, brr \doifnextcharelse[% {\dotripletestempty#1} {\dotriplefakeempty#1}} \def\dosinglefakeempty#1% {\firstargumentfalse#1[]} \def\dodoublefakeempty#1% {\firstargumentfalse\secondargumentfalse#1[][]} \def\dotriplefakeempty#1% {\firstargumentfalse\secondargumentfalse\thirdargumentfalse#1[][][]} \long\def\dodoubletestempty#1[#2]% {\firstargumenttrue \doifnextcharelse[% {\secondargumenttrue #1[{#2}]} {\secondargumentfalse#1[{#2}][]}} \long\def\dotripletestempty#1[#2]% {\firstargumenttrue \doifnextcharelse[% {\dotripletestemptyx #1[{#2}]} {\secondargumentfalse \thirdargumentfalse #1[{#2}][][]}} \long\def\dotripletestemptyx#1[#2][#3]% {\secondargumenttrue \doifnextcharelse[% {\thirdargumenttrue #1[{#2}][{#3}]} {\thirdargumentfalse#1[{#2}][{#3}][]}} %D \macros %D {dosingleargumentwithset, %D dodoubleargumentwithset,dodoubleemptywithset, %D dotripleargumentwithset,dotripleemptywithset} %D %D These maybe too mysterious macros enable us to handle more %D than one setup at once. %D %D \starttyping %D \dosingleargumentwithset \command[#1] %D \dodoubleargumentwithset \command[#1][#2] %D \dotripleargumentwithset \command[#1][#2][#3] %D \dodoubleemptywithset \command[#1][#2] %D \dotripleemptywithset \command[#1][#2][#3] %D \stoptyping %D %D The first macro calls \type{\command[##1]} for each string %D in the set~\type{#1}. The second one calls for %D \type{\commando[##1][#2]} and the third, well one may guess. %D These commands support constructions like: %D %D \starttyping %D \def\dodefinesomething[#1][#2]% %D {\getparameters[\??xx#1][#2]} %D %D \def\definesomething% %D {\dodoubleargumentwithset\dodefinesomething} %D \stoptyping %D %D Which accepts calls like: %D %D \starttyping %D \definesomething[alfa,beta,...][variable=...,...] %D \stoptyping %D %D Now a whole bunch of variables like \type{\@@xxalfavariable} %D and \type{\@@xxbetavariable} is defined. \def\dosingleargumentwithset#1% {\def\dodosinglewithset[##1]% {\def\dododosinglewithset####1% {#1[####1]}% \processcommalist[##1]\dododosinglewithset}% \dosingleargument\dodosinglewithset}% \def\dodoublewithset#1#2% {\def\dododoublewithset[##1][##2]% {\doifnot{##1}{} {\def\dodododoublewithset####1% {#2[####1][##2]}% \processcommalist[##1]\dodododoublewithset}}% #1\dododoublewithset}% \def\dodoubleemptywithset% {\dodoublewithset\dodoubleempty} \def\dodoubleargumentwithset% {\dodoublewithset\dodoubleargument} \def\dotriplewithset#1#2% {\def\dodotriplewithset[##1][##2][##3]% {\doifnot{##1}{} {\def\dododotriplewithset####1% {#2[####1][##2][##3]}% \processcommalist[##1]\dododotriplewithset}}% #1\dodotriplewithset}% \def\dotripleemptywithset% {\dotriplewithset\dotripleempty} \def\dotripleargumentwithset% {\dotriplewithset\dotripleargument} %D \macros %D {strippedcsname} %D %D The next macro can be very useful when using \type{\csname} %D like in: %D %D \starttyping %D \csname if\strippedcsname\something\endcsname %D \stoptyping %D %D This expands to \type{\ifsomething}. \def\strippedcsname% {\expandafter\gobbleoneargument\string} %D \macros %D {complexorsimple,complexorsimpleempty} %D %D Setups can be optional. A command expecting a setup is %D prefixed by \type{\complex}, a command without one gets the %D prefix \type{\simple}. Commands like this can be defined by: %D %D \starttyping %D \complexorsimple\command %D \stoptyping %D %D When \type{\command} is followed by a \type{[setup]}, then %D %D \starttyping %D \complexcommand [setup] %D \stoptyping %D %D executes, else we get %D %D \starttyping %D \simplecommand %D \stoptyping %D %D An alternative for \type{\complexorsimple} is: %D %D \starttyping %D \complexorsimpleempty {command} %D \stoptyping %D %D Depending on the presence of \type{[setup]}, this one %D leads to one of: %D %D \starttyping %D \complexcommando [setup] %D \complexcommando [] %D \stoptyping %D %D Many \CONTEXT\ commands started as complex or simple ones, %D but changed into more versatile (more object oriented) ones %D using the \type{\get..argument} commands. % This method is needed when traditional tex is used with % the efficient definition (marked **) below. % an old one: % % \def\setnameofcommand#1% handles {abc} as well as \abc % {\begingroup % \escapechar=-1 % \globaldefs=0 % pretty important! % \xdef\nameofcommand{\string#1}% % \endgroup} % % \def\complexorsimple#1% % {\setnameofcommand{#1}% % \doifnextcharelse{[} % {\firstargumenttrue \getvalue{\s!complex\nameofcommand}} % {\firstargumentfalse\getvalue{\s!simple \nameofcommand}}} % % \def\complexorsimpleempty#1% % {\setnameofcommand{#1}% % \doifnextcharelse{[} % {\firstargumenttrue \getvalue{\s!complex\nameofcommand}} % {\firstargumentfalse\getvalue{\s!complex\nameofcommand}[]}} % % a newer one: \def\complexorsimple#1% {% \relax % prevents lookahead, brrr \doifnextcharelse[% {\firstargumenttrue \csname\s!complex\strippedcsname#1\endcsname} {\firstargumentfalse\csname\s!simple \strippedcsname#1\endcsname}} \def\complexorsimpleempty#1% {% \relax % prevents lookahead, brrr \doifnextcharelse[% {\firstargumenttrue \csname\s!complex\strippedcsname#1\endcsname} {\firstargumentfalse\csname\s!complex\strippedcsname#1\endcsname[]}} %D \macros %D {definecomplexorsimple,definecomplexorsimpleempty} %D %D The previous commands are used that often that we found it %D worthwile to offer two more alternatives. Watch the build %D in protection. % See earlier. Because we don't want \type {\simple..} and % \type {\complex..} commands to show up in expansions, we need % to pass them as \type {simple..} and \type {complex..}. % \beginTEX % % \def\definewithnameofcommand#1#2% % {\setnameofcommand{#2}% % \@EA\def\@EA#2\@EA{\@EA\donottest\@EA#1\@EA{\nameofcommand}}} % % \def\definecomplexorsimple% % {\definewithnameofcommand\complexorsimple} % % \def\definecomplexorsimpleempty% % {\definewithnameofcommand\complexorsimpleempty} % % \endTEX % % \beginETEX \protected % % \def\definecomplexorsimple#1% % {\normalprotected\def#1{\complexorsimple#1}} % % \def\definecomplexorsimpleempty#1% % {\normalprotected\def#1{\complexorsimpleempty#1}} % % \endETEX % However, since this one uses an idirect method, things go % okay (at the cost of extra macros). % \def\definecomplexorsimple#1% % {\unexpanded\def#1{\complexorsimple#1}} % % \def\definecomplexorsimpleempty#1% % {\unexpanded\def#1{\complexorsimpleempty#1}} % % faster, since no \strippedcsname needed in call, but more spacy \def\docomplexorsimple#1#2% {\doifnextcharelse[{\firstargumenttrue#1}{\firstargumentfalse#2}} \def\docomplexorsimpleempty#1% {\doifnextcharelse[{\firstargumenttrue#1}{\firstargumentfalse#1[]}} \def\definecomplexorsimple#1% {\unexpanded\edef#1% {\noexpand\docomplexorsimple \@EA\noexpand\csname\s!complex\strippedcsname#1\endcsname \@EA\noexpand\csname\s!simple \strippedcsname#1\endcsname}} \def\definecomplexorsimpleempty#1% {\unexpanded\edef#1% {\noexpand\docomplexorsimpleempty \@EA\noexpand\csname\s!complex\strippedcsname#1\endcsname}} %D These commands are called as: %D %D \starttyping %D \definecomplexorsimple\command %D \stoptyping %D %D Of course, we must have available %D %D \starttyping %D \def\complexcommand[#1]{...} %D \def\simplecommand {...} %D \stoptyping %D %D Using this construction saves a few string now and then. %D \macros %D {definestartstopcommand} %D %D Those who get the creeps of expansion may skip the next %D one. It's one of the most recent additions and concerns %D \type{\start}||\type{\stop} pairs with complicated %D arguments. %D %D We won't go into details here, but the general form of %D this using this command is: %D %D \starttyping %D \definestartstopcommand\somecommand\e!specifier{arg}{arg}% %D {do something with arg} %D \stoptyping %D %D This expands to something like: %D %D \starttyping %D \def\somecommand arg \startspecifier arg \stopspecifier% %D {do something with arg} %D \stoptyping %D %D The arguments can be anything reasonable, but double %D \type{#}'s are needed in the specification part, like: %D %D \starttyping %D \definestartstopcommand\somecommand\e!specifier{[##1][##2]}{##3}% %D {do #1 something #2 with #3 arg} %D \stoptyping %D %D which becomes: %D %D \starttyping %D \def\somecommand[#1][#2]\startspecifier#3\stopspecifier% %D {do #1 something #2 with #3 arg} %D \stoptyping %D %D We will see some real applications of this command in the %D core modules. \def\definestartstopcommand#1#2#3#4% can be done with \expanded ot better, toks {\def\!stringa{#3}% % but let's keep this unused one crappy \def\!stringb{\e!start#2}% \def\!stringc{#4}% \def\!stringd{\e!stop#2}% \@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA \def\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA\@EA #1\@EA\@EA\@EA\@EA\@EA\@EA\@EA \!stringa\@EA\@EA\@EA \csname\@EA\@EA\@EA\!stringb\@EA\@EA\@EA\endcsname\@EA \!stringc \csname\!stringd\endcsname} %D \macros %D {dosinglegroupempty,dodoublegroupempty,dotriplegroupempty, %D doquadruplegroupempty, doquintuplegroupempty} %D %D We've already seen some commands that take care of %D optional arguments between \type{[]}. The next two commands %D handle the ones with \type{{}}. They are called as: %D %D \starttyping %D \dosinglegroupempty \ineedONEargument %D \dodoublegroupempty \ineedTWOarguments %D \dotriplegroupempty \ineedTHREEarguments %D \doquadruplegroupempty \ineedFOURarguments %D \doquintuplegroupempty \ineedFIVEarguments %D \stoptyping %D %D where \type{\ineedONEargument} takes one and the others %D two and three arguments. These macro's were first needed in %D \PPCHTEX. %D %D \starttyping %D \def\dogetgroupargument#1#2% redefined in mult-ini %D {\def\nextnextargument% %D {\ifx\nextargument\bgroup %D \let\expectedarguments\noexpectedarguments %D \def\nextargument{#1\dodogetargument}% %D %\else\ifx\nextargument\lineending % this can be an option %D % \def\nextargument{\begingroup\def\\ {\endgroup\dogetgroupargument#1#2}\\}% %D %\else\ifx\nextargument\blankspace % but it may never be default %D % \def\nextargument{\begingroup\def\\ {\endgroup\dogetgroupargument#1#2}\\}% %D \else %D \ifnum\expectedarguments>\noexpectedarguments %D \writestatus %D {setup} %D {\the\expectedarguments\space argument(s) expected %D in line \the\inputlineno\space}% %D \fi %D \let\expectedarguments\noexpectedarguments %D \def\nextargument{#2\dodogetargument{}}% %D \fi%\fi\fi % so let's get rid of it %D \nextargument}% %D \futurelet\nextargument\nextnextargument} %D \stoptyping %D %D In order to catch \type {\nextargument}'s that expand to %D \type {\if} and friends, in practice we will use a %D slightly more complicated macro. \let\normalif \if \let\normalifx \ifx \let\normalifnum \ifnum \let\normalifdim \ifdim \let\normalifcat \ifcat \let\normalifcase \ifcase \let\normalifcsname \ifcsname \let\normalifhmode \ifhmode \let\normalifvmode \ifvmode \let\normalor \or \let\normalelse \else \let\normalfi \fi \def\beginrobusttest {\begingroup \let\if \relax \let\ifx \relax \let\ifnum \relax \let\ifdim \relax \let\ifcat \relax \let\ifcase \relax \let\ifcsname\relax \let\or \relax \let\else \relax \let\fi \relax} \let\endrobusttest\endgroup % \def\dogetgroupargument#1#2% % {\def\nextnextargument% % {\normalifx\nextargument\bgroup % \endrobusttest % \let\expectedarguments\noexpectedarguments % \def\nextargument{#1\dodogetargument}% % %\normalelse\normalifx\nextargument\lineending % this can be an option % % \def\nextargument{\begingroup\def\\ {\endgroup\dogetgroupargument#1#2}\\}% % %\normalelse\normalifx\nextargument\blankspace % but may never be default % % \def\nextargument{\begingroup\def\\ {\endgroup\dogetgroupargument#1#2}\\}% % \normalelse % \endrobusttest % \ifnum\expectedarguments>\noexpectedarguments % \showargumenterror{\number\expectedarguments}{\number\inputlineno}% % \fi % \let\expectedarguments\noexpectedarguments % \def\nextargument{#2\dodogetargument{}}% % \normalfi%\normalfi\normalfi % so let's get rid of it % \nextargument}% % \beginrobusttest % \futurelet\nextargument\nextnextargument} % \def\dogetgroupargument#1#2% % {\def\nextnextargument% % {\normalifx\nextargument\bgroup % \endrobusttest % \noshowargumenterror % \def\nextargument{#1\dodogetargument}% % %\normalelse\normalifx\nextargument\lineending % this can be an option % % \def\nextargument{\begingroup\def\\ {\endgroup\dogetgroupargument#1#2}\\}% % %\normalelse\normalifx\nextargument\blankspace % but may never be default % % \def\nextargument{\begingroup\def\\ {\endgroup\dogetgroupargument#1#2}\\}% % \normalelse % \endrobusttest % \doshowargumenterror % \def\nextargument{#2\dodogetargument{}}% % \normalfi%\normalfi\normalfi % so let's get rid of it % \nextargument}% % \beginrobusttest % \futurelet\nextargument\nextnextargument} % we need to use an \ifcase in order to honor the \normal... \def \permitspacesbetweengroups{\chardef\@@permitspacesbetweengroups=0 } \def\dontpermitspacesbetweengroups{\chardef\@@permitspacesbetweengroups=1 } \dontpermitspacesbetweengroups \def\dogetgroupargument#1#2% {\def\nextnextargument% {\normalifx\nextargument\bgroup \endrobusttest \noshowargumenterror \def\nextargument{#1\dodogetargument}% \normalelse \normalifcase\@@permitspacesbetweengroups \normalifx\nextargument\lineending \endrobusttest \def\nextargument{\begingroup\def\\ {\endgroup\dogetgroupargument#1#2}\\}% \normalelse\normalifx\nextargument\blankspace \endrobusttest \def\nextargument{\begingroup\def\\ {\endgroup\dogetgroupargument#1#2}\\}% \normalelse \endrobusttest \doshowargumenterror \def\nextargument{#2\dodogetargument{}}% \normalfi\normalfi \normalelse \endrobusttest \doshowargumenterror \def\nextargument{#2\dodogetargument{}}% \normalfi \normalfi \nextargument}% \beginrobusttest \futurelet\nextargument\nextnextargument} \def\dosinglegroupempty#1% {\def\dodogetargument% {\dontpermitspacesbetweengroups #1}% \dogetgroupargument\firstargumenttrue\firstargumentfalse} \def\dodoublegroupempty#1% {\def\dodogetargument##1% {\def\dodogetargument% {\dontpermitspacesbetweengroups #1{##1}}% \dogetgroupargument\secondargumenttrue\secondargumentfalse}% \dogetgroupargument\firstargumenttrue\firstargumentfalse} \def\dotriplegroupempty#1% {\def\dodogetargument##1% {\def\dodogetargument####1% {\def\dodogetargument% {\dontpermitspacesbetweengroups #1{##1}{####1}}% \dogetgroupargument\thirdargumenttrue\thirdargumentfalse}% \dogetgroupargument\secondargumenttrue\secondargumentfalse}% \dogetgroupargument\firstargumenttrue\firstargumentfalse} \def\doquadruplegroupempty#1% {\def\dodogetargument##1% {\def\dodogetargument####1% {\def\dodogetargument########1% {\def\dodogetargument% {\dontpermitspacesbetweengroups #1{##1}{####1}{########1}}% \dogetgroupargument\fourthargumenttrue\fourthargumentfalse}% \dogetgroupargument\thirdargumenttrue\thirdargumentfalse}% \dogetgroupargument\secondargumenttrue\secondargumentfalse}% \dogetgroupargument\firstargumenttrue\firstargumentfalse} \def\doquintuplegroupempty#1% {\def\dodogetargument##1% {\def\dodogetargument####1% {\def\dodogetargument########1% {\def\dodogetargument################1% {\def\dodogetargument% {\dontpermitspacesbetweengroups #1{##1}{####1}{########1}{################1}}% \dogetgroupargument\fifthargumenttrue\fifthargumentfalse}% \dogetgroupargument\fourthargumenttrue\fourthargumentfalse}% \dogetgroupargument\thirdargumenttrue\thirdargumentfalse}% \dogetgroupargument\secondargumenttrue\secondargumentfalse}% \dogetgroupargument\firstargumenttrue\firstargumentfalse} %D These macros can explictly take care of spaces, which means %D that the next definition and calls are valid: %D %D \starttyping %D \def\test#1#2#3{[#1#2#3]} %D %D \dotriplegroupempty\test {a}{b}{c} %D \dotriplegroupempty\test {a}{b} %D \dotriplegroupempty\test {a} %D \dotriplegroupempty\test %D \dotriplegroupempty\test {a} {b} {c} %D \dotriplegroupempty\test {a} {b} %D \dotriplegroupempty\test %D {a} %D {b} %D \stoptyping %D %D And alike. %D \macros %D {firstofoneargument, firstoftwoarguments, firstofthreearguments %D secondoftwoarguments, secondofthreearguments, %D thirdofthreearguments} %D %D The next six macros (dedicated to Taco) can conveniently %D used to select arguments. Their names explain their %D functionality. \long\def\firstofoneargument #1{#1} \long\def\firstoftwoarguments #1#2{#1} \long\def\firstofthreearguments #1#2#3{#1} \long\def\firstoffourarguments #1#2#3#4{#1} \long\def\firstoffivearguments #1#2#3#4#5{#1} \long\def\secondoftwoarguments #1#2{#2} \long\def\secondofthreearguments #1#2#3{#2} \long\def\secondoffourarguments #1#2#3#4{#2} \long\def\secondoffivearguments #1#2#3#4#5{#2} \long\def\thirdofthreearguments #1#2#3{#3} \long\def\thirdoffourarguments #1#2#3#4{#3} \long\def\thirdoffivearguments #1#2#3#4#5{#3} \long\def\fourthoffourarguments #1#2#3#4{#4} \long\def\fourthoffivearguments #1#2#3#4#5{#4} \long\def\fifthoffivearguments #1#2#3#4#5{#5} %D \macros %D {globalletempty,letempty,letvalueempty,letgvalueempty} %D %D Trivial: \def\letempty #1{\let#1\empty} \def\globalletempty#1{\global\let#1\empty} \def\letvalueempty #1{\expandafter\let\csname#1\endcsname\empty} \def\letgvalueempty#1{\global\expandafter\let\csname#1\endcsname\empty} %D \macros %D {wait} %D %D The next macro hardly needs explanation. Because no %D nesting is to be expected, we can reuse \type{\wait} within %D \type{\wait} itself. \def\wait {\begingroup \read16 to \wait \endgroup} %D \macros %D {writestring,writeline, %D writestatus,statuswidth} %D %D Maybe one didn't notice, but we've already introduced a %D macro for showing messages. In the multi||lingual modules, %D we will also introduce a mechanism for message passing. For %D the moment we stick to the core macros: %D %D \starttyping %D \writestring {string} %D \writeline %D \writestatus {category} {message} %D \stoptyping %D %D Messages are formatted. One can provide the maximum with %D of the identification string with the macro %D \type{\statuswidth}. % \chardef\statuswidth=15 % % \def\writestring% % {\immediate\write16} % % \def\writeline% % {\writestring{}} % % \def\dosplitstatus#1#2\end% % {\ifx#1?% % \loop % \advance\scratchcounter by 1 % \ifnum\scratchcounter<\statuswidth % \edef\messagecontentA{\messagecontentA\space}% % \repeat % \else % \advance\scratchcounter by 1 % \ifnum\scratchcounter<\statuswidth % \edef\messagecontentA{\messagecontentA#1}% % \fi % \dosplitstatus#2\end % \fi} % % \def\writestatus#1#2% % {\begingroup % \let\messagecontentA=\empty % \edef\messagecontentB{#2}% maybe it's \the\scratchcounter % \scratchcounter=0 % \expandafter\dosplitstatus#1?\end % \writestring{\messagecontentA\space:\space\messagecontentB}% % \endgroup} \chardef\statuswidth=15 \chardef\statuswrite=16 \def\writestring {\immediate\write\statuswrite} \def\writeline {\writestring{}} \newcount\statuscounter \def\dosplitstatus#1% {\advance\statuscounter \minusone \ifcase\statuscounter \expandafter\nosplitstatus \else \scratchtoks\@EA{\the\scratchtoks#1}% \expandafter\dosplitstatus \fi} \def\nosplitstatus#1\end {} \def\writestatus#1#2% {\begingroup \scratchtoks\emptytoks \statuscounter\statuswidth \expandafter\dosplitstatus#1% \space\space\space\space\space\space\space \space\space\space\space\space\space\space \space\space\space\space\space\space\end %\expanded{\writestring{\the\scratchtoks\space:\space#2}}% \@EA\writestring\@EA{\the\scratchtoks\space:\space#2}% \endgroup} %D Because we're grouped, we could have initialized at forehand: %D %D \starttyping %D \statuscounter\statuswidth %D \stoptyping %D The next implementation saves only some 10 words of format %D memory, but we hardly gain any speed. %D %D \starttyping %D \def\dosplitstatus#1#2#3#4#5#6#7#8#9% %D {#1#2#3#4#5#6#7#8#9\dodosplitstatus} %D %D \def\dodosplitstatus#1#2#3#4#5#6\end %D {#1#2#3#4#5} %D %D \def\writestatus#1#2% %D {\writestring %D {\expandafter\dosplitstatus#1% %D \space\space\space\space\space %D \space\space\space\space\space %D \space\space\space\space\space\end %D \space:\space#2}} %D \stoptyping %D Okay then. More obscure but slightly faster: no split grabs %D the do split part and skipping the else branch has to happen %D anyway, so: \def\dosplitstatus#1% {\advance\statuscounter \minusone \ifcase\statuscounter \expandafter\nosplitstatus \fi \scratchtoks\@EA{\the\scratchtoks#1}% \dosplitstatus} %D \macros %D {emptytoks} %D %D For this we need an empty token register, analogous %D to \type {\empty}. \newtoks\emptytoks %D \macros %D {debuggerinfo} %D %D For debugging purposes we can enhance macros with the %D next alternative. Here \type{debuggerinfo} stands for both %D a macro accepting two arguments and a boolean (in fact a %D few macro's too). \newif\ifdebuggerinfo \def\debuggerinfo#1#2% {\ifdebuggerinfo \writestatus{debugger}{#1:: #2}% \fi} %D Finally we do what from now on will be done at the top of %D the files: we tell the user what we are loading. \writestatus{loading}{Context System Macros / General} %D Well, the real final command is the one that resets the %D unprotected characters \type{@}, \type{?} and \type{!}. \protect \endinput