%% This is part of the OpTeX project, see http://petr.olsak.net/optex \_codedecl \newif {Special if-macros, is-macros and loops <2024-11-24>} % preloaded in format \_doc ---------------------------- \secc Classical \code{\\newif} The \`\newif` macro implements boolean value. It works as in plain \TeX. It means that after `\newif\ifxxx` you can use `\xxxtrue` or `\xxxfalse` to set the boolean value and use `\ifxxx true\else false\fi` to test this value. The default value is false. The macro \`\_newifi` enables to declare `\_ifxxx` and to use `\_xxxtrue` and `\_xxxfalse`. This means that it is usable for the internal namespace (`_`prefixed macros). \_cod ---------------------------- \_def\_newif #1{\_ea\_newifA \_string #1\_relax#1} \_ea\_def \_ea\_newifA \_string\if #1\_relax#2{% \_sdef{#1true}{\_let#2=\_iftrue}% \_sdef{#1false}{\_let#2=\_iffalse}% \_let#2=\_iffalse } \_def\_newifi #1{\_ea\_newifiA \string#1\_relax#1} \_ea\_def \_ea\_newifiA \_string\_if #1\_relax#2{% \_sdef{_#1true}{\_let#2=\_iftrue}% \_sdef{_#1false}{\_let#2=\_iffalse}% \_let#2=\_iffalse } \_public \newif ; \_doc ---------------------------- \`\afterfi` `{}\fi` closes condition by `\fi` and processes . Usage: \begtt \catcode`<=13 \if \afterfi{} \else \afterfi{} \fi \endtt \`\afterxfi` `{}\xfipoint` closes all opened `\if..\fi` conditionals until \`\xfipoint` is found and then proceses . The text must expand to nothing (or spaces only); arbitrary number of opened conditionals can be closed here. Example: \begtt \ifodd#1 \ifodd#2 \afterxfi{odd-odd} \else \afterxfi{odd-even} \fi \else \ifodd#2 \afterxfi{even-odd} \else \afterxfi{even-even} \fi \fi \xfipoint \endtt Nested `\if..\afterfi{\if..\afterfi{...}\fi}\fi` are possible. Another approach is mentioned in \ulink[http://petr.olsak.net/optex/optex-tricks.html\#fiinif]{OpTeX trick 0098} which also solves the `\fi` in `\if` problem. \_cod ---------------------------- \_long\_def \_afterfi#1#2\_fi{\_fi#1} \_long\_def \afterfi#1#2\fi{\_fi#1} \_let\_xfipoint=\_empty \_let\xfipoint=\_empty % \xfipoint alone does nothing \_long\_def \_afterxfi#1#2\_xfipoint{\_romannumeral#20 #1} \_long\_def \afterxfi#1#2\xfipoint{\_romannumeral#20 #1} \_doc ---------------------------- \secc Loops The \`\loop` ` \ifsomething ` \`\repeat` loops `` until `\ifsomething` is false. Then `` is not executed and loop is finished. This works like in plain \TeX, but implementation is somewhat better (you can use `\else` clause after the `\ifsomething`). There are public version `\loop...\repeat` and private version \`\_loop` ...\`\_repeat`. You cannot mix both versions in one loop. The `\loop` macro keeps its original plain \TeX/ meaning. It is not expandable and nested `\loop`s are possible only in a \TeX/ group. \_cod ---------------------------- \_long\_def \_loop #1\_repeat{\_def\_body{#1}\_iterate} \_long\_def \loop #1\repeat{\_def\_body{#1}\_iterate} \_let \_repeat=\_fi % this makes \loop...\if...\repeat skippable \_let \repeat=\_fi \_def \_iterate {\_body \_ea \_iterate \_fi} \_doc ----------------------------- \`\xloop` `\do \ifsomething \repeat` or\nl \`\_xloop` `\_do \_ifsomething \_repeat` behave analogically like \^`\loop`, but moreover: you can specify a rule of parameters scanning (like in `\def` primitive) and you can use these parameters (i.e. `#1`, `#2`, etc.) in the or . Each iteration of this loop reads next parameters from the input queue (just after `\repeat`) using the rule. Example: \begtt \def\countparams{\tmpnum=0 \xloop ##1,\do \ifx\end##1\empty \else \incr\tmpnum \repeat} \def\list{A, B, C} \ea \countparams \list,\end, % \tmpnum = the number of comma separated parameters \endtt Second new feature: the \^`\xloop` macro is expandable, if and include only expandable macros. This is not the case of previous example but you can prefix `\tmpnum=0` and `\incr` by the `\immediateassignment` \LuaTeX/ primitive and you get expandable macro `\countparams`. \_cod ----------------------------- \_long\_def\xloop#1\do#2\repeat{\_immediateassignment\_long\_def\_body#1{#2\_ea\_body\_fi}\_body} \_long\_def\_xloop#1\_do#2\_repeat{\_immediateassignment\_long\_def\_body#1{#2\_ea\_body\_fi}\_body} \_doc ----------------------------- {\_let\_moremainpoints=\_relax \`\foreach` ``\`\do` `{}` repeats `` for each element of the ``. The `` can include `#1` which is substituted by each element of the ``. The macro is expandable. \nl \`\foreach` ``\`\do` `{}` reads parameters from repeatedly and does for each such reading. The parameters are declared by . Examples: \begtt \foreach (a,1)(b,2)(c,3)\do (#1,#2){#1=#2 } \foreach word1,word2,word3,\do #1,{Word is #1.} \foreach A=word1 B=word2 \do #1=#2 {"#1 is set as #2".} \endtt Note that `\foreach \do {}` is equivalent to `\foreach \do #1{}`. Recommendation: it is better to use private variants of \`\_foreach`. When the user writes `\load[tikz]` then `\foreach` macro is redefined in each TikZ environment. The private variants use \`\_do` separator instead `\do` separator and it isn't redefined.} \_cod ----------------------------- \_newcount\_frnum % the numeric variable used in \fornum \_def\_do{\_doundefined} % we need to ask \_ifx#1\_do ... \_def\_foreach{\_foreachAx\_empty} \_def\foreach{\_foreachAy\_empty} \_long\_def\_foreachAx #1\_do #2#{\_ea\_foreachAz\_ea{#1}{#2}} \_long\_def\_foreachAy #1\do #2#{\_ea\_foreachAz\_ea{#1}{#2}} \_long\_def\_foreachAz #1#2{\_isempty{#2}\_iftrue \_afterfi{\_foreachA{#1}{##1}}\_else\_afterfi{\_foreachA{#1}{#2}}\_fi} \_long\_def\_foreachA #1#2#3{\_putforstack \_immediateassignment \_long\_gdef\_fbody#2{\_testparam##1..\_iftrue #3\_ea\_fbody\_fi}% \_fbody #1#2\_finbody\_getforstack } \_long\_def\_testparam#1#2#3\_iftrue{\_ifx###1\_empty\_ea\_finbody\_else} \_long\_def\_finbody#1\_finbody{} \_doc ----------------------------- {\_let\_moremainpoints=\_relax \`\fornum` `..` \`\do` `{}` or \`\fornumstep` `: ..` \`\do` `{}` repeats `` for each number from `` to `` (with step `` or with step one). The `` can include `#1` which is substituted by current number. The , , parameters can be numeric expressions. The macro is expandable.\nl The test in the \`\_fornumB` says: if ( $\string<$ AND is positive) or if ( $>$ AND is negative) then close loop by `\_getforstack`. Sorry, the condition is written by somewhat cryptoid \TeX/ language.} \_cod ----------------------------- \_def\_fornum#1..#2\_do{\_fornumstep 1:#1..#2\_do} \_long\_def\_fornumstep#1:#2..#3\_do#4{\_putforstack \_immediateassigned{% \_gdef\_fbody##1{#4}% \_global\_frnum=\_numexpr#2\_relax }% \_ea\_fornumB\_ea{\_the\_numexpr#3\_ea}\_ea{\_the\_numexpr#1}% } \_def\_fornumB #1#2{\_ifnum#1\_ifnum#2>0<\_else>\_fi \_frnum \_getforstack \_else \_afterfi{\_ea\_fbody\_ea{\_the\_frnum}% \_immediateassignment\_global\_advance\_frnum by#2 \_fornumB{#1}{#2}}\_fi } \_def\fornum#1..#2\do{\_fornumstep 1:#1..#2\_do} \_def\fornumstep#1:#2..#3\do{\_fornumstep #1:#2..#3\_do} \_doc ---------------------------- The `\foreach` and `\fornum` macros can be nested and arbitrary combined. When they are nested then use `##1` for the variable of nested level, `####1` for the variable of second nested level etc. Example: \begtt \foreach ABC \do {\fornum 1..5 \do {letter:#1, number: ##1. }} \endtt Implementation note: we cannot use \TeX/-groups for nesting levels because we want to do the macros expandable. We must implement a special for-stack which saves the data needed by `\foreach` and `\fornum`. The \`\_putforstack` is used when `\for*` is initialized and \`\_getforstack` is used when the `\for*` macro ends. The \`\_forlevel` variable keeps the current nesting level. If it is zero, then we need not save nor restore any data. \_cod ---------------------------- \_newcount\_forlevel \_def\_putforstack{\_immediateassigned{% \_ifnum\_forlevel>0 \_sxdef{_frnum:\_the\_forlevel\_ea}{\_the\_frnum}% \_global\_slet{_fbody:\_the\_forlevel}{_fbody}% \_fi \_incr\_forlevel }} \_def\_getforstack{\_immediateassigned{% \_decr\_forlevel \_ifnum\_forlevel>0 \_global\_slet{_fbody}{_fbody:\_the\_forlevel}% \_global\_frnum=\_cs{_frnum:\_the\_forlevel}\_space \_fi }} \_ifx\_immediateassignment\_undefined % for compatibility with older LuaTeX \_let\_immediateassigned=\_useit \_let\_immediateassignment=\_empty \_fi \_doc ---------------------------- User can define own expandable \"foreach" macro by \`\foreachdef` `\macro {}` which can be used by `\macro {}`. The macro reads repeatedly parameters from using and does for each such reading. For example \begtt \foreachdef\mymacro #1,{[#1]} \mymacro{a,b,cd,efg,} \endtt expands to [a][b][cd][efg]. Such user defined macros are more effective during processing than `\foreach` itself because they need not to operate with the for-stack. \_cod ---------------------------- \_def\_foreachdef#1#2#{\_toks0{#2}% \_long\_edef#1##1{\_ea\_noexpand\_csname _body:\_csstring#1\_endcsname ##1\_the\_toks0 \_noexpand\_finbody}% \_foreachdefA#1{#2}} \_long\_def\_foreachdefA#1#2#3{% \_long\_sdef{_body:\_csstring#1}#2{\_testparam##1..\_iftrue #3\_cs{_body:\_csstring#1\_ea}\_fi}} \_public \foreachdef ; \_doc ---------------------------- \secc Is-macros and selection of cases There are a collection of macros \^`\isempty`, \^`\istoksempty`, \^`\isequal`, \^`\ismacro`, \^`\isdefined`, \^`\isinlist`, \^`\isfile` and \^`\isfont` with common syntax: \begtt \catcode`\<=13 \issomething \iftrue \else \fi or \issomething \iffalse \else \fi \endtt The `\else` part is optional. The `` is processed if `\issomething` generates true condition. The `` is processed if `\issomething` generates false condition. The `\iftrue` or `\iffalse` is an integral part of this syntax because we need to keep skippable nested `\if` conditions. Implementation note: we read this `\iftrue` or `\iffalse` into unseparated parameter and repeat it because we need to remove an optional space before this command. \medskip\noindent \`\isempty` `{}\iftrue` is true if the `` is empty. This macro is expandable.\nl \`\istoksempty` `\iftrue` is true if the `` is empty. It is expandable. \_cod ---------------------------- \_long\_def \_isempty #1#2{\_if\_relax\_detokenize{#1}\_relax \_else \_ea\_unless \_fi#2} \_def \_istoksempty #1#2{\_ea\_isempty\_ea{\_the#1}#2} \_public \isempty \istoksempty ; \_doc ---------------------------- \`\isequal` `{}{}\iftrue` is true if the and are equal, only from strings point of view, category codes are ignored. The macro is expandable. \_cod ---------------------------- \_long\_def\_isequal#1#2#3{\_directlua{% if "\_luaescapestring{\_detokenize{#1}}"=="\_luaescapestring{\_detokenize{#2}}" then else tex.print("\_nbb unless") end}#3} \_public \isequal ; \_doc ---------------------------- \`\ismacro` `\macro{text}\iftrue` is true if macro is defined as {}. Category codes are ignored in this testing. The macro is expandable. \_cod ---------------------------- \_long\_def\_ismacro#1{\_ea\_isequal\_ea{#1}} \_public \ismacro ; \_doc ---------------------------- \`\isdefined` `{}\iftrue` is true if `\` is defined. The macro is expandable. \_cod ---------------------------- \_def\_isdefined #1#2{\_ifcsname #1\_endcsname \_else \_ea\_unless \_fi #2} \_public \isdefined ; \_doc ---------------------------- \`\isinlist` `\list{}\iftrue` is true if the `` is included the macro body of the `\list`. The category codes are relevant here. The macro is expandable. \_cod ---------------------------- \_long\_def\_isinlist#1#2#3{% \_immediateassignment\_long\_def\_isinlistA##1#2##2\_end/_% {\_if\_relax\_detokenize{##2}\_relax \_ea\_unless\_fi#3}% \_ea\_isinlistA#1\_endlistsep#2\_end/_% } \_public \isinlist ; \_doc ----------------------------- \`\isfile` `{}\iftrue` is true if the file exists and are readable by \TeX. \_cod ----------------------------- \_newread \_testin \_def\_isfile #1#2{% \_openin\_testin ={#1}\_relax \_ifeof\_testin \_ea\_unless \_else \_closein\_testin \_fi #2% } \_public \isfile ; \_doc ----------------------------- \`\isfont` `{}\iftrue` is true if a given font exists. The result of this testing is saved to the \`\_ifexistfam`. \_cod ----------------------------- \_newifi \_ifexistfam \_def\_isfont#1#2{% \_begingroup \_suppressfontnotfounderror=1 \_font\_testfont={#1}\_relax \_ifx\_testfont\_nullfont \_def\_tmp{\_existfamfalse \_unless} \_else \_def\_tmp{\_existfamtrue}\_fi \_ea \_endgroup \_tmp #2% } \_public \isfont ; \_doc ---------------------------- The macro \`\isnextchar` `{}{}` has a different syntax than all other is-macros. It executes `` if next character is equal to . Else the `` is executed. The macro is expandable. \_cod ---------------------------- \_long\_def\_isnextchar#1#2#3{\_immediateassignment \_def\_isnextcharA{\_isnextcharB{#1}{#2}{#3}}% \_immediateassignment\_futurelet \_next \_isnextcharA } \_long\_def\_isnextcharB#1{\_ifx\_next#1\_ea\_ignoresecond\_else\_ea\_usesecond\_fi} \_public \isnextchar ; \_doc ---------------------------- \`\casesof` ` ` implements something similar to the `switch` command known from C language. It is expandable macro. The is a list of arbitrary number of pairs in the format ``\,`{}` which must be finalized by the pair `\_finc {}`. The optional spaces after s and between listed cases are ignored. The usage of \^`\casesof` looks like: \begtt \catcode`<=13 \casesof {} {} ... {} \_finc {} \endtt The meaning of tokens are compared by `\ifx` primitive. The parts `` can be finalized by a macro which can read more data from the input stream as its parameters. \_cod ---------------------------- \_long\_def \_casesof #1#2#3{\_ifx #2\_finc \_ea\_ignoresecond \_else \_ea\_usesecond \_fi {#3}{\_ifx#1#2\_ea\_ignoresecond \_else \_ea\_usesecond \_fi {\_finc{#3}}{\_casesof#1}}% } \_long\_def \_finc #1#2\_finc#3{#1} \_public \casesof ; \_doc ----------------------------- \`\qcasesof` `{} ` behaves like \^`\casesof` but it compares phrases with the given using \^`\isequal`. The includes pairs `{} {}` finalized by a pair `\_finc {}`. The `` is a single phrase or phrases separated by `|` which means \"or". For example the pair `{ab|cde|f} {}` runs if the given is `ab` or `cde` or~`f`. The usage of `\qcasesof` can be found in \ulink[http://petr.olsak.net/optex/optex-tricks.html\#thedimen]{OpTeX trick 0132}. \_cod ----------------------------- \_long\_def \_qcasesof #1#2#3{\_ifx\_finc#2\_ea\_ignoresecond \_else \_ea\_usesecond \_fi {#3}{\_qcasesofA{#1}#2|\_qcasesofA|{\_finc{#3}}{\_qcasesof{#1}}}% } \_long\_def\_qcasesofA#1#2|{\_ifx\_qcasesofA#2\_ea\_usesecond \_else \_isequal{#1}{#2}\_iftrue \_qcasesofB \_fi \_afterfi{\_qcasesofA{#1}}\_fi } \_long\_def\_qcasesofB #1\_qcasesofA|#2#3{\_fi\_fi#2} \_public \qcasesof ; \_doc ----------------------------- \`\xcasesof` `` extends the features of the macro \^`\casesof`. Each pair from the `` is in the format `{}{}`, only the last pair must have the different format: `\_finc {}`. The `` can be arbitrary primitive `\if*` condition (optionally prefixed by `\unless`) and it must be closed in its expansion. It means that `{\ifnum\mycount>0}` is bad, `{\ifnum\mycount>0 }` is correct. Optional spaces between parameters are ignored. Example: \begtt \message {The \tmpnum has \xcasesof {\ifnum\tmpnum>0 } {positive} {\ifnum\tmpnum=0 } {zero} \_finc {negative} value} \endtt The \^`\xcasesof` macro works with principle: first true condition wins, next conditions are not evaluated. \_cod ----------------------------- \_def \_xcasesof {\_nospacefuturelet\_next\_xcasesofA} \_def \_xcasesofA {\_ifx\_next\_finc \_ea\_usesecond \_else \_ea \_xcasesofB \_fi} \_long\_def \_xcasesofB #1#2{% #1\_ea\_ignoresecond\_else \_ea\_usesecond\_fi {\_finc{#2}}{\_xcasesof}% } \_public \xcasesof ; \_endcode 2024-11-24 \foreach bug fixed: \foreach {xx}\do {...} 2024-11-12 \isinlist, \isfile allow space before \iftrue/\iffalse 2024-11-10 \xloop implemented. 2024-09-10 \afterxfi implemented. 2024-02-19 \qcasesof reimplemented, it supports | for more phrases. 2023-12-07 \_testparam define \long (big fixed). 2023-10-17 \qcasesof introduced, \xcasesof reimplemented. 2023-01-16 \isnextchar created expandable. 2022-12-02 \xcasesof: its first parameter is \long too. 2022-11-29 renamed to \casesof, \xcasesof. 2022-11-26 \casesby, \casesbyif introduced. 2022-05-04 \isinlist created expandable. 2022-03-30 \afterfi defined as \long. 2021-08-02 more robust \fornum: \fi moved by \afterfi 2021-02-03 public version of \loop and \foreach are \long 2020-05-22 \foreach, \fornum: all settings are global, independent on TeX group 2020-05-06 \isnextchar: \let\tmp=#1 -> \let\tmp= #1 (bug fix, #1 should be space) 2020-05-02 \newif bug fix 2020-04-15 \fornumstep 3: 1..12 instead \fornum 1..12\step 3 2020-04-15 \fornum, \foreach can be nested without groups 2020-04-01 implemented