Defining 3*(2^4) complex macro at once

This should be what you want. The mandatory argument to \fa should be a four character string:

  • First byte: 1 or 2 (one way or two way)
  • Second byte: S or M (single or multi head)
  • Third byte: D or N (deterministic or non deterministic)
  • Fourth byte: + or - (with or without stack)

The optional argument is the number of heads, while the macro \fa can be followed by * for printing the abbreviation in boldface or by + for the long version.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\fa}{ s t+ m o }
 {
  \IfNoValueTF{#4}
   {% there is no trailing optional argument, set the boolean to false
    \bool_set_false:N \l_clement_show_heads_bool
   }
   {% there is a trailing optional argument, set the boolean to false
    \bool_set_true:N \l_clement_show_heads_bool
    % and store the argument for later usage
    \tl_set:Nn \l_clement_heads_tl { $#4$ }
   }
  \IfBooleanTF{#2}
   {% the `+` modifier has been given, call the long version
    \clement_fa_long:n { #3 }
   }
   {% no + modifier
    \IfBooleanTF{#1}
     {% the `*` modifier has been given, call the short version in \textbf
      \textbf{\clement_fa_short:n { #3 }}
     }
     {% no `*` modifier, just call the short version
      \clement_fa_short:n { #3 }
     }
   }
 }

% First byte:  1 or 2 (one way or two way)
% Second byte: S or M (single or multi head)
% Third byte:  D or N (deterministic or non deterministic)
% Fourth byte: + or - (with or without stack)

\bool_new:N \l_clement_show_heads_bool
\tl_new:N \l_clement_heads_tl

\cs_new_protected:Npn \clement_fa_short:n #1
 {% separate the four characters
  \clement_fa_short_aux:NNNN #1
 }

\cs_new_protected:Npn \clement_fa_short_aux:NNNN #1 #2 #3 #4
 {% just use #1, #2 and #3 and add FA
  #1#2#3FA
  \str_case:nn { #4 }
   {% check if the fourth character is - or +
    {-}{}% -: do nothing
    {+}{+S}% +: add +S
   }
  % if the boolean is true, add the number of heads in parentheses
  \bool_if:NT \l_clement_show_heads_bool { (\l_clement_heads_tl) }
 }

\cs_new_protected:Npn \clement_fa_long:n #1
 {% separate the four characters
  \clement_fa_long_aux:NNNN #1
 }

\cs_new_protected:Npn \clement_fa_long_aux:NNNN #1 #2 #3 #4
 {% add 1-way or 2-way and a space
  #1-way~%
  \bool_if:NTF \l_clement_show_heads_bool
   {% if the boolean is true, add the number of heads
    \l_clement_heads_tl
   }
   {% else add S if single-head, M if multi-head
    \str_case:nn { #2 }
     {
      {S}{single}
      {M}{multi}
     }
   }
  -head~% add -head and a space
  \str_case:nn { #3 }
   {% add `non` and a space if nondeterministic
    {N}{non~}
    {D}{}
   }
  deterministic~finite~automaton~% the words
  \str_case:nn { #4 }
   {% add `with` or `without`
    {+}{with~}
    {-}{without~}
   }
  stack% add `stack`
 }
\ExplSyntaxOff

\begin{document}

\fa{2MD+} --- \fa+{2MD+}

\fa{2MD-}[3] --- \fa+{2MD-}[3]

\fa*{2MN+}[k+1] --- \fa+{2MN-}[k+1]

\end{document}

enter image description here

Note that the word is automaton (automata is plural) and there should be no “s” in the prefixes (two way, not two ways; 3-head, not 3-heads), according to English grammar.


A variation that prints the number of heads parenthesized in the long form, when it's not an explicit number and it contains more than a symbol.

\documentclass{article}
\usepackage{xparse,l3regex}

\ExplSyntaxOn
\NewDocumentCommand{\fa}{ s t+ m o }
 {
  \IfNoValueTF{#4}
   {% there is no trailing optional argument, set the boolean to false
    \bool_set_false:N \l_clement_show_heads_bool
   }
   {% there is a trailing optional argument, set the boolean to false
    \bool_set_true:N \l_clement_show_heads_bool
    % and store the argument for later usage
    \tl_set:Nn \l_clement_heads_tl { #4 }
   }
  \IfBooleanTF{#2}
   {% the `+` modifier has been given, call the long version
    \clement_fa_long:n { #3 }
   }
   {% no + modifier
    \IfBooleanTF{#1}
     {% the `*` modifier has been given, call the short version in \textbf
      \textbf{\clement_fa_short:n { #3 }}
     }
     {% no `*` modifier, just call the short version
      \clement_fa_short:n { #3 }
     }
   }
 }

% First byte:  1 or 2 (one way or two way)
% Second byte: S or M (single or multi head)
% Third byte:  D or N (deterministic or non deterministic)
% Fourth byte: + or - (with or without stack)

\bool_new:N \l_clement_show_heads_bool
\tl_new:N \l_clement_heads_tl

\cs_new_protected:Npn \clement_fa_short:n #1
 {% separate the four characters
  \clement_fa_short_aux:NNNN #1
 }

\cs_new_protected:Npn \clement_fa_short_aux:NNNN #1 #2 #3 #4
 {% just use #1, #2 and #3 and add FA
  #1#2#3FA
  \str_case:nn { #4 }
   {% check if the fourth character is - or +
    {-}{}% -: do nothing
    {+}{+S}% +: add +S
   }
  % if the boolean is true, add the number of heads in parentheses
  \bool_if:NT \l_clement_show_heads_bool { \__clement_parens:V \l_clement_heads_tl }
 }

\cs_new_protected:Npn \clement_fa_long:n #1
 {% separate the four characters
  \clement_fa_long_aux:NNNN #1
 }

\cs_new_protected:Npn \clement_fa_long_aux:NNNN #1 #2 #3 #4
 {% add 1-way or 2-way and a space
  #1-way~%
  \bool_if:NTF \l_clement_show_heads_bool
   {% if the boolean is true, add the number of heads
    \tl_if_single:NTF \l_clement_heads_tl
     {
      \__clement_simple:V \l_clement_heads_tl
     }
     {% \D matches anything which is not a digit
      \regex_match:nVTF { \D } \l_clement_heads_tl 
       { \__clement_parens:V \l_clement_heads_tl }
       { \__clement_simple:V \l_clement_heads_tl }
     }
   }
   {% else add S if single-head, M if multi-head
    \str_case:nn { #2 }
     {
      {S}{single}
      {M}{multi}
     }
   }
  -head~% add -head and a space
  \str_case:nn { #3 }
   {% add `non` and a space if nondeterministic
    {N}{non~}
    {D}{}
   }
  deterministic~finite~automaton~% the words
  \str_case:nn { #4 }
   {% add `with` or `without`
    {+}{with~}
    {-}{without~}
   }
  stack% add `stack`
 }

\cs_new:Npn \__clement_parens:n #1 { $( #1 )$ }
\cs_new:Npn \__clement_simple:n #1 { $ #1 $ }
\cs_generate_variant:Nn \__clement_parens:n { V }
\cs_generate_variant:Nn \__clement_simple:n { V }
\cs_generate_variant:Nn \regex_match:nnTF { nV }
\ExplSyntaxOff

\begin{document}

\fa{2MD+} --- \fa+{2MD+}

\fa{2MD-}[3] --- \fa+{2MD-}[3]

\fa{1MD+}[24] --- \fa+{1MN+}[24]

\fa{2MN+}[k] --- \fa+{2MN+}[k]

\fa*{2MN+}[k+1] --- \fa+{2MN+}[k+1]

\end{document}

enter image description here


Here's an approach which is initially a little top heavy but allows you to make various formatting choices down the line:

\documentclass{article}

\makeatletter
\let\@xp\expandafter

\newcommand\set{\ae@set}

\def\ae@set(#1){%%
  \@ifnextchar;%%
    {\@ae@set(#1)}%%
    {\ae@@set(#1)}}


\def\@ae@set(#1);{\ae@@set(#1)->#1;}

\def\ae@@set(#1)->#2;{%%
  \def\ae@name{#1}%%
  \ae@parse#2;%%
  }

\def\ae@parse#1#2#3#4;{%%
  \@namedef{automata:\ae@name:way}{#1}%%
  \@namedef{automata:\ae@name:head}{#2}%%
  \@namedef{automata:\ae@name:det}{#3}%%
  \@namedef{automata:\ae@name:stack}{#4}%%
  \ae@parse@automata@name
  \ae@parse@meaning
  \@namedef{automata:\ae@name:name\@xp}\@xp{\ae@automata@name}%%
  \@namedef{automata:\ae@name:meaning\@xp}\@xp{\ae@meaning}%%
  }

\def\ae@test@for@formatting#1#2#3#4{%%
  \def\ae@test{#1}%%
  \@xp\ifx\csname automata:\ae@name:#2\endcsname\ae@test
    \def\ae@formatting{#3}%%
  \else
    \def\ae@formatting{#4}%%
  \fi}

\def\ae@parse@automata@name{%%
  \def\ae@formatting{}%%
  \ae@test@for@formatting{M}{head}{\bfseries\sffamily}{\itshape}%%
  \@xp\def\@xp\ae@automata@name\@xp{%%
    \@xp{\ae@formatting
     \csname automata:\ae@name:way\endcsname
     \csname automata:\ae@name:head\endcsname
     \csname automata:\ae@name:det\endcsname
     \csname automata:\ae@name:stack\endcsname}}}

\def\ae@test@for@meaning#1#2#3#4{%%
  \def\ae@test{#1}%%
  \@xp\ifx\csname automata:\ae@name:#2\endcsname\ae@test
    \edef\ae@meaning{\ae@meaning\space #3}%%
  \else
    \edef\ae@meaning{\ae@meaning\space #4}%%
  \fi}

\def\ae@parse@meaning{%%
  \def\ae@meaning{}%%
  \ae@test@for@meaning{1}{way}{1-way,}{2-way,}%%
  \ae@test@for@meaning{M}{head}{multi-head,}{single-head,}%%
  \ae@test@for@meaning{D}{det}{deterministic}{non-deterministic}%%
  \ae@test@for@meaning{S}{stack}{with stack}{without stack}%%
}

\newcommand\get{\ae@get}

\def\ae@get(#1){%%
  \@ifnextchar;%%
  {\@ae@get(#1)}%%
  {\ae@@get(#1)}}

\def\@ae@get(#1);{\ae@@get(#1)->meaning;}
\def\ae@@get(#1)->#2;{\csname automata:#1:#2\endcsname}

\makeatother

\begin{document}

\set(ginger)->1MDS;
\set(rodger)->2SNS;
\set(1MDN);

\begin{tabular}{llll}
  Nickname & Sample getter & Name & Meaning \\\hline
  Ginger & \get(ginger)->way; & \get(ginger)->name; & \get(ginger)->meaning;\\
  Rodger & \get(rodger)->det; & \get(rodger)->name; & \get(rodger)->meaning;\\
  1MDN   & \get(1MDN)->det;   & \get(1MDN)->name;   & \get(1MDN);
\end{tabular}

\end{document}

enter image description here

I illustrate above how you can make a decision on how to format things within \ae@parse@automata@name by testing whether it's a multiheaded automaton or not.

Of course, names don't have to be names like ginger or rodger, you could just simply say

\set(1MDS)->1MDS;

and then later call it by

\get(1MDS)->name;

UPDATE

I've rewritten the above code to take into consideration the suggestions made by @egreg. Also, I've removed the dependency upon etoolbox, which is not needed to create this sort of functionality. Finally, I've improved the setter and getter to take an optional second argument. That way you can just create the automaton as

\set(1MDS);

and then call it with

\get(1MDS);

but I've left the old syntax just in case you really want to anthropomorphize your automata (go Ginger!).


Since there are other solutions of the “front end” here I just a the solution for parsing the argument.

This is just a simple “reader”: it reads a certain element and checks if it's equal to some predefined cases, and, in case it is not, it does what it's inside the F argument (…:nnnF).

  1. First element, 1 or 2, otherwise does nothing (no error).
  2. Second element, M or S, otherwise puts it in math mode before -head (e.g., k = $k$-head, {k+1} = $k+1$-head).
  3. Third element, D or N, otherwise does nothing.
  4. Fourth element, + or nothing (or something else, it doesn't matter). I'm not sure if you ever want an explicit “without stack” so I leave it to you.

The second element, if more than one token, must be enclosed in braces, e.g., \automata{1{(k+1)}N+}.

UPDATE: I forgot some part of the “sentence”.

\documentclass{scrartcl}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand \automata { m }
  {
    \clement_automata_process:n {#1}
  }
\cs_new:Npn \clement_check_item:nnnF #1 #2
  {
    \str_case_e:nnF { \tl_item:nn {#1} {#2} }
  }
\cs_new:Npn \clement_check_item:nnn #1 #2
  {
    \str_case_e:nn { \tl_item:nn {#1} {#2} }
  }
\cs_new_nopar:Npn \clement_automata_process:n #1
  {
    \clement_check_item:nnn {#1} { 1 }
      {
        { 1 } { $1$-way }
        { 2 } { $2$-way }
      } ~
    \clement_check_item:nnnF {#1} { 2 }
      {
        { S } { single-head }
        { M } { multiple-head }
      }
      { $\tl_item:nn {#1} { 2 }$-head } ~
    \clement_check_item:nnn {#1} { 3 }
      {
        { D } { deterministic }
        { N } { non-deterministic }
      } ~ finite ~ automaton ~ 
    \clement_check_item:nnn {#1} { 4 }
      {
        { + } { with ~ stack }
%       { - } { without ~ stack} % if it's needed you can uncomment it
      } % Or you can add it here if you want it (nnnF) always “without” unless `+` is given
  }
\ExplSyntaxOff

\begin{document}
\obeylines
\automata{1MD+}
\automata{2kN}
\automata{2{(k+1)}N+}
\end{document}

enter image description here