4

I have the following code

\makeatletter
\def\ip#1{\@ip#1\relax}
\def\@ip#1|#2\relax{\langle#1|#2\rangle}
\makeatother

When in the document one finds $\ip{2|3}$ he gets something like <2|3>. Since I sometimes write $\ip{2,3} I want that this produces the same result as above. If possible I would like to avoid xparse.

4 Answers 4

7

xparse wouldn't be much help here (and it's built into LaTeX, so there is really no reason to avoid it either). This code checks for a ,, and if it's not there, it assumes a |. This has the nice consequence that if neither is there, the command doesn't error with Runaway argument:

enter image description here

\documentclass{article}
\usepackage[T1]{fontenc}

\makeatletter
\def\ip#1{\@ipc#1,\@ipc,\relax}
\def\@ipc#1,#2,#3\relax{%
  \langle#1\ifx\@ipc#2\else|#2\fi\rangle}
\makeatother

\begin{document}
When in the document one finds $\ip{2|3}$ he gets something like <2|3>.
Since I sometimes write $\ip{2,3}$ or even just $\ip{2}$ I want that this
produces the same result as above. If possible I would like to avoid xparse.
\end{document}

Here's a stricter version that requires at least one delimiter, but also errors with more than one delimiter. Note that mixing delimiters like $\ip{1|2,3}$ doesn't error, because the macros check one delimiter at a time:

enter image description here

\documentclass{article}
\usepackage[T1]{fontenc}

\makeatletter
\def\@ipnil{\@ipnil}
\def\ip#1{\@ipv#1|\@ipnil|\relax}
\def\@ipcheck#1#2#3#4{%
  \ifx\@ipnil#2%
    #4\expandafter\@gobbletwo
  \else \@@ipcheck{#1}{#2}{#3}%
  \fi}
\def\@@ipcheck#1#2#3\fi#4#5{\fi
  \expandafter\ifx\@car#3\@nil\@ipnil \@ip{#1}{#2}%
  \else \@ipinvalid{#1#4#2#4#5#3}%
  \fi}
\def\@ipv#1|#2|#3\relax{%
  \@ipcheck{#1}{#2}{#3}{\@ipc#1,\@ipnil,\relax}|\@ipvclean}
\def\@ipvclean#1|\@ipnil|{#1}
\def\@ipc#1,#2,#3\relax{%
  \@ipcheck{#1}{#2}{#3}{\@ipinvalid{#1}},\@ipcclean}
\def\@ipcclean#1,\@ipnil,{#1}
\def\@ip#1#2{\langle#1|#2\rangle}
\def\@ipinvalid#1{%
  \GenericError{}{Argument '#1' invalid}\@gobble{}%
  !!#1!!}
\makeatother

\begin{document}
When in the document one finds $\ip{2|3}$ he gets something like <2|3>.
Since I sometimes write $\ip{2,3}$ I want that this produces the same
result as above. But $\ip{2}$ and $\ip{}$ should throw errors, as
should $\ip{1|2|3}$ or $\ip{1,2,3}$ or $\ip{1,2,3,4,5}$, but mixing
input syntax like $\ip{1|2,3}$ is fine. If possible I would like to
avoid xparse.
\end{document}

Compare the complexity of that code with the expl3 code that does the same, but also errors with mixed input like $\ip{1|2,3}$:

enter image description here

\documentclass{article}
\usepackage[T1]{fontenc}

\ExplSyntaxOn
\seq_new:N \l__benatti_tmp_seq
\cs_new_protected:Npn \ip #1
  {
    \regex_split:nnN { , | \| } {#1} \l__benatti_tmp_seq
    \int_compare:nNnTF { \seq_count:N \l__benatti_tmp_seq } = { 2 }
      { \langle \seq_use:Nn \l__benatti_tmp_seq { | } \rangle }
      {
        \msg_error:nnn { benatti } { invalid-arg } {#1}
        !!#1!!
      }
  }
\msg_new:nnn { benatti } { invalid-arg }
  { Argument~'#1'~invalid~for~\iow_char:N\\ip. }
\ExplSyntaxOff

\begin{document}
When in the document one finds $\ip{2|3}$ he gets something like <2|3>.
Since I sometimes write $\ip{2,3}$ I want that this produces the same
result as above. But $\ip{2}$ and $\ip{}$ should throw errors, as
should $\ip{1|2|3}$ or $\ip{1,2,3}$ or $\ip{1,2,3,4,5}$ or $\ip{1|2,3}$.
If possible I would like to avoid xparse.
\end{document}
4
  • Very nice solution, but I want only those two definitions. I prefer an error if there is no second argument. Moreover, I prefer to have the two arguments separated in the last command so that I can produce \left\langle #1 \right\vert \left. #2\right\rangle. Nov 9, 2021 at 15:22
  • @LucaBenatti What do you want if there is more than one comma or more than one | or a micture of both? Nov 9, 2021 at 15:27
  • Errors. Command has to produce a result only if \ip{possible empty something | possible empty something} or \ip{possible empty something , possible empty something} is typed. Nov 9, 2021 at 15:39
  • @LucaBenatti Added two new versions (I'd expect you to prefer the third over the second) Nov 9, 2021 at 16:35
4

You can first check for | and then for ,:

\documentclass{article}
\usepackage{amsmath}

\makeatletter
\newcommand{\ip}[1]{%
  \ip@checkbar{#1}#1|\@nil%
}
\def\ip@checkbar#1#2|#3\@nil{%
  \if\relax\detokenize{#3}\relax
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {\ip@checkcomma{#1}#1,\@nil}%
  {\ip@bar#1\@nil}%
}
\def\ip@checkcomma#1#2,#3\@nil{%
  \if\relax\detokenize{#3}\relax
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {\ERROR\ip@{#1}{??}}%
  {\ip@comma#1\@nil}
}
\def\ip@bar#1|#2\@nil{\ip@{#1}{#2}}
\def\ip@comma#1,#2\@nil{\ip@{#1}{#2}}
\def\ip@#1#2{\langle #1 \mid\nobreak #2 \rangle}
\makeatother

\begin{document}

$\ip{2|3}$

$\ip{2,3}$

$\ip{2}$

\end{document}

enter image description here

My expl3 version:

\documentclass{article}
\usepackage{amsmath}

\ExplSyntaxOn

\NewDocumentCommand{\ip}{m}
 {
  \benatti_ip_test:n { #1 }
 }

\tl_new:N \l__benatti_ip_tl
\int_new:N \l__benatti_ip_del_int

\cs_new_protected:Nn \benatti_ip:nn { \langle #1 \mid\nobreak #2 \rangle }

\cs_new_protected:Nn \benatti_ip_test:n
 {
  \regex_count:nnN { [\|\,] } { #1 } \l__benatti_ip_del_int
  \int_compare:nTF { \l__benatti_ip_del_int != 1 }
   {
    \ERROR
    \benatti_ip:nn { #1 } { ??? }
   }
   {
    \tl_set:Nn \l__benatti_ip_tl { #1 }
    \regex_replace_once:nnN
      { (.*) [\|\,] (.*) } % \1 is what's before the delimiter, \2 what's behind
      { \c{benatti_ip:nn}\{\1\}\{\2\} } % replace with \benatti_ip:nn{\1}{\2}
      \l__benatti_ip_tl
    \tl_use:N \l__benatti_ip_tl
   }
 }

\ExplSyntaxOff

\begin{document}

$\ip{2|3}$

$\ip{2,3}$

$\ip{2}$

$\ip{2|3|4}$

$\ip{2,3|4}$

\end{document}

A missing delimiter or too many of them would trigger an error (the error message might be made clearer, of course).

enter image description here

However, I would avoid using regular expressions, to allow for nested calls of \ip.

\documentclass{article}
\usepackage{amsmath}

\ExplSyntaxOn

\NewDocumentCommand{\ip}{m}
 {
  \benatti_ip_test:n { #1 }
 }

\cs_new_protected:Nn \benatti_ip_test:n
 {
  \tl_if_in:nnTF { #1 } { | }
   {
    \benatti_ip_bar:w #1 \q_stop
   }
   {
    \tl_if_in:nnTF { #1 } { , }
     {
      \benatti_ip_comma:w #1 \q_stop
     }
     {
      \ERROR \benatti_ip:nn { #1 } { ??? }
     }
   }
 }

\cs_new_protected:Nn \benatti_ip:nn { \langle #1 \mid\nobreak #2 \rangle }
\cs_new_protected:Npn \benatti_ip_bar:w #1 | #2 \q_stop { \benatti_ip:nn { #1 } { #2 } }
\cs_new_protected:Npn \benatti_ip_comma:w #1 , #2 \q_stop { \benatti_ip:nn { #1 } { #2 } }

\ExplSyntaxOff

\begin{document}

$\ip{2|3}$

$\ip{2,3}$

$\ip{2}$

$\ip{\ip{u,v}w|z}=\ip{u|v}\ip{w|z}$

\end{document}

enter image description here

This last version is essentially the same as the first one, but in my opinion it's much clearer.

3

This is a fairly simple request where you want to change , into | for ease-of-use. Below I use etoolbox to patch \ip's argument, replacing , with | before printing the result. Here is the syntax for \patchcmd:

\patchcmd{<cmd>}{<search>}{<replace>}{<success>}{<failure>}

enter image description here

\documentclass{article}

\usepackage{etoolbox}

\newcommand{\ip}[1]{%
  \def\iparg{#1}% Store
  \patchcmd{\iparg}{,}{|}{}{}% Replace , with |
  \langle \iparg \rangle% Print content
}

\begin{document}

When in the document one finds $\ip{2|3}$ he gets something like 
$\langle 2 | 3 \rangle$. Since I sometimes write $\ip{2,3}$ I want 
that this produces the same result as above.

\end{document}

This option allows for explicit nesting. For example, $\ip{\ip{2,\ip{3,4}}|\ip{5,\ip{6|7}}}$ yields:

enter image description here

3

Not the shortest solution, but triggers errors and avoids removal of curly braces:

\documentclass{article}
\usepackage[T1]{fontenc}

\makeatletter
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\expandafter\UD@stopromannumeral\@secondoftwo}{%
  \expandafter\UD@stopromannumeral\@firstoftwo}%
}%
\@ifdefinable\UD@gobbletocomma{\long\def\UD@gobbletocomma#1,{}}%
\@ifdefinable\UD@gobbletobar{\long\def\UD@gobbletobar#1|{}}%
\newcommand\UD@CheckWhetherNoComma[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletocomma#1,}%
}%
\newcommand\UD@CheckWhetherNoBar[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletobar#1|}%
}%
\@ifdefinable\UD@splitcomma{%
  \long\def\UD@splitcomma#1#2#3,{\expandafter\UD@Exchange\expandafter{\@firstoftwo{}#3}{#1}#2}%
}%
\@ifdefinable\UD@splitbar{%
  \long\def\UD@splitbar#1#2#3|{\expandafter\UD@Exchange\expandafter{\@firstoftwo{}#3}{#1}#2}%
}%
\newcommand\ip@syntaxerror{%
  %\PackageError{Package-Name}%
  %             {Invalid argument for \string\ip}%
  %             {Only syntax {<parameter 1>|<parameter 2>} \MessageBreak or
  %              {<parameter 1>,<parameter 2>} is allowed}%
  \GenericError{(macro \string\ip)\@spaces \@spaces \@spaces \@spaces }%
               {Macro \string\ip\space error: Invalid argument for \string\ip}%
               {See the comments for explanation.}%
               {Only syntax {<parameter 1>|<parameter 2>} \MessageBreak or
                 {<parameter 1>,<parameter 2>} is allowed}%
}%
\@ifdefinable\ip{%
  \DeclareRobustCommand\ip[1]{%
    \UD@CheckWhetherNoComma{#1}{%
      \UD@CheckWhetherNoBar{#1}{\ip@syntaxerror}{%
        \expandafter\UD@CheckWhetherNoBar
        \expandafter{\UD@gobbletobar#1}%
                    {\UD@splitbar{\left\langle}{\middle\vert}.#1\right\rangle}%
                    {\ip@syntaxerror}%
      }%
    }{%
      \UD@CheckWhetherNoBar{#1}{%
        \expandafter\UD@CheckWhetherNoComma
        \expandafter{\UD@gobbletocomma#1}%
                    {\UD@splitcomma{\left\langle}{\middle\vert}.#1\right\rangle}%
                    {\ip@syntaxerror}%
      }{\ip@syntaxerror}%
    }%
  }%
}%
\makeatother

\begin{document}

$\ip{2|3}$ 

$\ip{2,3}$ 

\message{^^J^^JPress enter to start those things that trigger errors}
\immediate\read-1 to \Scratchmacro

$\ip{}$ 

$\ip{A}$ 

$\ip{2|3|4}$ 

$\ip{2|3,4}$ 

$\ip{2,3,4}$ 

$\ip{2|3,4}$ 

\end{document}

Resulting pdf-file

enter image description here

Output on terminal/console:

This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2020-10-01> patch level 4
L3 programming layer <2021-02-18>
(/usr/local/texlive/2020/texmf-dist/tex/latex/base/article.cls
Document Class: article 2020/04/10 v1.4m Standard LaTeX document class
(/usr/local/texlive/2020/texmf-dist/tex/latex/base/size10.clo))
(/usr/local/texlive/2020/texmf-dist/tex/latex/base/fontenc.sty)
(/usr/local/texlive/2020/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def)
(./test.aux) 

Press enter to start those things that trigger errors

! Macro \ip error: Invalid argument for \ip.

See the comments for explanation.
Type  H <return>  for immediate help.
 ...                                              
                                                  
l.69 $\ip{}
           $
? 

! Macro \ip error: Invalid argument for \ip.

See the comments for explanation.
Type  H <return>  for immediate help.
 ...                                              
                                                  
l.71 $\ip{A}
            $
? 

! Macro \ip error: Invalid argument for \ip.

See the comments for explanation.
Type  H <return>  for immediate help.
 ...                                              
                                                  
l.73 $\ip{2|3|4}
                $
? 

! Macro \ip error: Invalid argument for \ip.

See the comments for explanation.
Type  H <return>  for immediate help.
 ...                                              
                                                  
l.75 $\ip{2|3,4}
                $
? 

! Macro \ip error: Invalid argument for \ip.

See the comments for explanation.
Type  H <return>  for immediate help.
 ...                                              
                                                  
l.77 $\ip{2,3,4}
                $
? 

! Macro \ip error: Invalid argument for \ip.

See the comments for explanation.
Type  H <return>  for immediate help.
 ...                                              
                                                  
l.79 $\ip{2|3,4}
                $
? 
[1{/usr/local/texlive/2020/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
(./test.aux) ){/usr/local/texlive/2020/texmf-dist/fonts/enc/dvips/cm-super/cm-s
uper-t1.enc}</usr/local/texlive/2020/texmf-dist/fonts/type1/public/amsfonts/cm/
cmr10.pfb></usr/local/texlive/2020/texmf-dist/fonts/type1/public/amsfonts/cm/cm
sy10.pfb></usr/local/texlive/2020/texmf-dist/fonts/type1/public/cm-super/sfrm10
00.pfb>
Output written on test.pdf (1 page, 18322 bytes).
Transcript written on test.log.


If you work with a very old TeX-engine/LaTeX 2e-kernel, where \middle is not available, you can do s. th. like this:

\documentclass{article}
\usepackage[T1]{fontenc}

\makeatletter
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\expandafter\UD@stopromannumeral\@secondoftwo}{%
  \expandafter\UD@stopromannumeral\@firstoftwo}%
}%
\@ifdefinable\UD@gobbletocomma{\long\def\UD@gobbletocomma#1,{}}%
\@ifdefinable\UD@gobbletobar{\long\def\UD@gobbletobar#1|{}}%
\newcommand\UD@CheckWhetherNoComma[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletocomma#1,}%
}%
\newcommand\UD@CheckWhetherNoBar[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletobar#1|}%
}%
\@ifdefinable\UD@splitcomma{%
  \long\def\UD@splitcomma#1#2#3,{%
    \expandafter\UD@PassFirstToSecond\expandafter{\expandafter{\@firstoftwo{}#3}#2}{#1}{}%
  }%
}%
\@ifdefinable\UD@splitbar{%
  \long\def\UD@splitbar#1#2#3|{%
    \expandafter\UD@PassFirstToSecond\expandafter{\expandafter{\@firstoftwo{}#3}#2}{#1}{}%
  }%
}%
\newcommand\ip@syntaxerror{%
  %\PackageError{Package-Name}%
  %             {Invalid argument for \string\ip}%
  %             {Only syntax {<parameter 1>|<parameter 2>} \MessageBreak or
  %              {<parameter 1>,<parameter 2>} is allowed}%
  \GenericError{(macro \string\ip)\@spaces \@spaces \@spaces \@spaces }%
               {Macro \string\ip\space error: Invalid argument for \string\ip}%
               {See the comments for explanation.}%
               {Only syntax {<parameter 1>|<parameter 2>} \MessageBreak or
                 {<parameter 1>,<parameter 2>} is allowed}%
}%
\newcommand\UD@Makeformula[2]{%
  \mathopen{\left\langle\vphantom{#1}#2\right\vert}%
  \mathclose{\left.\kern-\nulldelimiterspace\vphantom{#2}#1\right\rangle}%
}%
\@ifdefinable\ip{%
  \DeclareRobustCommand\ip[1]{%
    \UD@CheckWhetherNoComma{#1}{%
      \UD@CheckWhetherNoBar{#1}{\ip@syntaxerror}{%
        \expandafter\UD@CheckWhetherNoBar
        \expandafter{\UD@gobbletobar#1}%
                    {\UD@splitbar{\UD@splitbar{\expandafter\UD@Makeformula\@firstoftwo}}{}{}#1|}%
                    {\ip@syntaxerror}%
      }%
    }{%
      \UD@CheckWhetherNoBar{#1}{%
        \expandafter\UD@CheckWhetherNoComma
        \expandafter{\UD@gobbletocomma#1}%
                    {\UD@splitcomma{\UD@splitcomma{\expandafter\UD@Makeformula\@firstoftwo}}{}{}#1,}%
                    {\ip@syntaxerror}%
      }{\ip@syntaxerror}%
    }%
  }%
}%
\makeatother

\begin{document}

$\ip{2|3}$ 

$\ip{2,3}$ 

%$\left\langle2\middle\vert3\right\rangle$

\message{^^J^^JPress enter to start those things that trigger errors}
\immediate\read-1 to \Scratchmacro

$\ip{}$

$\ip{A}$ 

$\ip{2|3|4}$ 

$\ip{2|3,4}$ 

$\ip{2,3,4}$ 

$\ip{2|3,4}$ 

\end{document}

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.