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.

frabala
  • 373
  • 1
    the standard latex \@ifnextchar is a wrapper around futurelet that skips spaces – David Carlisle Jun 06 '19 at 11:12
  • 2
    optional arguments really should take [] not {} no standard latex command has a variable number of {} arguments – David Carlisle Jun 06 '19 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 06 '19 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 06 '19 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 06 '19 at 11:22
  • looking at that output you have lots of [[]] in the character content so you probably don't want to use [] delimited arguments. – David Carlisle Jun 06 '19 at 11:28
  • @frabala People can't read your mind. You mention arguments in the form \kleene{a}{b}{c} and then, out of the blue, it turns out they're in the form \kleene[[a]][[b]][[c]]. What will happen with the next update? – egreg Jun 06 '19 at 11:29
  • @DavidCarlisle @egreg My apologies and thanks for pointing this out. I didn't expect there to be issues with {} and []. I have updated my question accordingly. – frabala Jun 06 '19 at 11:34
  • @frabala Now the question is very difficult to understand: do you have braces around [[...]]? Is that automatically generated? Do you want to keep the double brackets or remove them? – egreg Jun 06 '19 at 11:52
  • What about ambiguous things like \kleene{arg1}{arg2}{arg3}{arg4}{\large This should not be treated as a kleene argument but how can kleene know this?} ? Would you mind nesting everything that shall be processed by \kleene into one additional brace group: \kleene{{arg1}{arg2}{arg3}{arg4}}? That way one could easily implement taking the argument of \kleene for a list of undelimited arguments and have LaTeX iterate on that expandably... – Ulrich Diez Jun 06 '19 at 20:08
  • @UlrichDiez, please read the last line of my question. For now, no such case happens: a brace that is not meant to start an argument, placed after the kleene command. But, as I build up the latex document, nothing guarantees that this won't happen later. I hadn't thought of surrounding the whole kleene command in brackets. Thank you. I don't understand your last sentence, though – frabala Jun 06 '19 at 20:26
  • @frabala An "undelimited argument" in TeX-terminology is a macro argument that either consists of a single token not nested in curly braces or of a set of tokens nested into a pair of matching curly braces ({ and }). "Iterating expandably" means that everything works only by means of macro expansion, no temporary assignments/"definitions" by means of \let or \futurelet or the like needed. This might save some memory. This makes getting the result of \kleene into the replacement text of another macro definition more easy. I just wrote a full anwer... – Ulrich Diez Jun 06 '19 at 21:57

2 Answers2

5

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.

egreg
  • 1,121,712
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

Ulrich Diez
  • 28,770