3

Taken from this post, I have the following code to define a macro that takes an arbitrary number of arguments and concatenates them with an interleaving symbol (\simeq_{k}) in math mode:

\makeatletter
\def\kleene{%
  %
  \futurelet\@let@token\@kleene
}
\def\@kleene{%
  \ifx\@let@token\bgroup
    \expandafter\@@kleene
  \else
    \expandafter%
  \fi
}
\def\@@kleene#1{%
  #1%
  \futurelet\@let@token\@@@kleene
}
\def\@@@kleene{%
  \ifx\@let@token\bgroup
    \simeq_{k}
    \expandafter\@@kleene
  \else
    \expandafter%
  \fi
}
\makeatother

Just as a remark, I use latex with Ott and the arguments of \kleene might be of the form [[some_ott_syntax]]. Unfortunately, the double delimiters [[]] are reserved for including Ott code in my latex code. So, most of the times, the arguments of \kleene contain those delimiters and I cannot avoid this.

The macro is based on checking whether the next character is a {. This forces me to provide each argument of \kleene right next to the previous, without any white space, like I show below. Note that the arguments might be of the form [[arg]] (surrounded with [[]] because they are Ott code).

\kleene{arg1}{arg2}{arg3}{arg4}

However, sometimes I want to pass a long list of arguments and every argument is perhaps quite long itself. For readability of the tex code it would be useful to be able to write the following and mean the above.

\kleene{[[some_long_ott_code1]]}
       {[[some_long_ott_code2]]}
       {[[some_long_ott_code3]]}
       {[[some_long_ott_code4]]}.

Currently this is impossible because the next character after the first argument is a newline "character", so the next arguments are not parsed as arguments of \kleene but as just some latex (math) code.

How can I possibly modify the \kleene macro so that when it checks for the next argument, it doesn't take into account the white space (newline, spaces and tabs)?

Such a formulation will perhaps need a special character (for example, it could be a full-stop) to indicate that the list of arguments is over.

  • 1
    the standard latex \@ifnextchar is a wrapper around futurelet that skips spaces – David Carlisle Jun 6 at 11:12
  • 2
    optional arguments really should take [] not {} no standard latex command has a variable number of {} arguments – David Carlisle Jun 6 at 11:13
  • @DavidCarlisle Is there a case where using {} instead of [] for the optional arguments might introduce problems? I use {} for uniformity of the syntax of \kleene. But suppose I want to define the command as you say. What extra changes should I make? – frabala Jun 6 at 11:15
  • well it has a difference if you ever need a ] in the argument (you would have to hide the ] behind {..]..} – David Carlisle Jun 6 at 11:19
  • @DavidCarlisle Currently I use latex with ott and every argument I provide in \kleene is of the form [[some_ott_code]]. It works fine. Is this what you mean? If not, please provide me a full answer, as I'm not sure what changes I need to make to the macro. – frabala Jun 6 at 11:22
2

You seem to wish \kleene to trigger interspersing the single elements of a list of brace-nested arguments with \simeq_{k}.

I can offer a slightly different syntax, where the entire list of brace-nested/undelimited arguments is to be nested into a pair of braces for iterating on that list and hereby taking advantage of the fact that (La)TeX at the time of gathering an undelimited argument silently discards space tokens that precede that argument.

This variant of \kleeneworks only by means of macro expansion—unlike things based on \futurelet and/or \@ifnextchar and/or the like.
The internal mechanism doesn't perform whatsoever temporary assignments.
The result will be delivered after two expansion steps/after having \kleene "hit" by two \expandafter.
The circumstance that things work only by means of expansion implies that you can, e.g., easily get the result of \kleene into the replacement text of another macro definition.
No additional packages and no e-TeX-extensions needed.

\documentclass[a4paper]{article}
\makeatletter
%%----------------------------------------------------------------------
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@exchange[2]{#2#1}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%%   \UD@ExtractFirstArg{ABCDE} yields  {A}
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  {AB}
%%
%% !!! The argument of \UD@ExtractFirstArg must not be empty. !!!
%% You can check for emptiness via \UD@CheckWhetherNull before applying
%% \UD@ExtractFirstArg.
%% The result is delivered after two expansion steps/after having 
%% \UD@ExtractFirstArg "hit" by two \expandafter.
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
  \romannumeral0%
  \UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  { #1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%----------------------------------------------------------------------
%% Check whether argument is empty:
%%......................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%   The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%%   <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%------------------------------------------------------------------------------
%% Check whether argument is blank (empty or only spaces):
%%..............................................................................
%% -- Take advantage of the fact that TeX discards space tokens when
%%    "fetching" _un_delimited arguments: --
%% \UD@CheckWhetherBlank{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that
%%                        argument which is to be checked is blank>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked is not blank}%
\newcommand\UD@CheckWhetherBlank[1]{%
  \romannumeral\expandafter\expandafter\expandafter\UD@secondoftwo
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#1{}.}%
}%
%%------------------------------------------------------------------------------
%% \kleene
%%..............................................................................
\newcommand\kleene[1]{\romannumeral0\kleeneloop{#1}{}{}}%
\newcommand\kleeneloop[3]{%
  % #1 - remaining list of undelimited/brace-nested arguments.
  % #2 - tokens to insert for interspersing.
  % #3 - "interspersed" list constructed so far.
  \UD@CheckWhetherBlank{#1}{ #3}{%
    \expandafter\UD@exchange\expandafter{%
       \expandafter{%
         \romannumeral0\UD@exchange{ }{\expandafter\expandafter\expandafter}%
         \expandafter\UD@exchange\expandafter{%
           \romannumeral0\UD@exchange{ }{\expandafter\expandafter\expandafter}%
           \UD@ExtractFirstArg{#1}%
         }{%
           #3#2%
         }%
       }%
    }{%
      \expandafter\kleeneloop\expandafter{\UD@firstoftwo{}#1}{\simeq_{k}}%
    }%
  }%
}%
\makeatother
\begin{document}

$\kleene{ {a} {b}{c} {d}}$

$\kleene{ a bc d}$

$\kleene{
  {aaaaaaaaa}
  {bbbbbbbbb}
  {ccccccccc}
  {ddddddddd}
  {eeeeeeeee}
}$

% This variant of \kleene works only by means of macro expansion. 
% The internal mechanism doesn't perform whatsoever temporary assignments.
% The result will be delivered after two expansion steps/after having
% \kleene "hit" by two \expandafter. The circumstance that things work 
% only by means of expansion implies that you can easily get the result of
% \kleene into the replacement text of another macro definition:

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
  \kleene{
    {111}
    {222}
    {333}
    {444}
    {555}
    {666}
    {777}
  }%
}%

\texttt{\string\test: \meaning\test}%

$\test$

\end{document}

enter image description here

6

Since you're using it in math mode, spaces are ignored, so you can simplify the definition, exploiting the fact that \@ifnextchar ignores spaces.

\documentclass{article}

\makeatletter
\newcommand\kleene{\@ifnextchar\bgroup{\@kleene}{}}
\newcommand\@kleene[1]{#1\@@kleene}
\newcommand\@@kleene{\@ifnextchar\bgroup{\@@@kleene}{}}
\newcommand\@@@kleene[1]{\simeq_{k}#1\@@kleene}
\makeatother

\begin{document}

$\kleene{a}{b}{c}{d}$

$\kleene{aaaaaaaaa}
        {bbbbbbbbb}
        {ccccccccc}
        {ddddddddd}
        {eeeeeeeee}$

\end{document}

enter image description here

On the other hand, variable number of arguments is not something I'd use.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\kleene}{m}
 {
  \seq_set_split:Nnn \l_tmpa_seq { \\ } { #1 }
  \seq_use:Nn \l_tmpa_seq { \simeq\sb{k} }
 }
\ExplSyntaxOff

\begin{document}

$\kleene{a \\ b \\ c \\ d}$

$\kleene{
  aaaaaaaaa \\
  bbbbbbbbb \\
  ccccccccc \\
  ddddddddd \\
  eeeeeeeee
}$

\end{document}

Separating the items with \\ (or | or whatever you prefer) is better syntax. Spaces (or newlines) around \\ are optional.

Your Answer

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

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