5

I created a macro to recognize between two patterns #1 - #2 or #1 - #2 - #3 and assure an output on the form # - # - # (for dates).

  \def\cal@ymd#1:#2:#3:{#1-#2-#3}
  \def\cal@md#1:#2:#3:{\cal@Year-#1-#2}
  \def\cal@datesplit#1-#2-#3-#4#5#6{#5#1:#2:#3:}

  \def\cal@Xdateexpand#1{\expandafter\cal@datesplit#1--\cal@ymd\cal@md\empty}

This version above "almost" does what I want (it works, but fails if the calling argument includes some other macros calls).

If instead, I use the following definition

\def\cal@Ydateexpand#1{\edef\cal@arg{#1}\expandafter\cal@datesplit\cal@arg--\cal@ymd\cal@md\empty}

It works even if the macro is called using other macros as arguments.

My first question is if there is another way to assure that #1 will be expanded before calling \cal@datesplit? I had expected that \expandafter\cal@datesplit#1 would be enough for this...

Now, the real question that is bugging me. when I try to use both macros in the definition of another macro, using \edef, the first definition \cal@Xdateexpand "is ok" but the second \cal@Ydateexpand fails to compile.

Below is the minimum code showing it all... and I would like to know why it doesn't even compile (cases B.7 and B.8 below)?

I'm using MiKTeX 2.9.7347.

\documentclass{article}

\makeatletter

            \def\cal@ymd#1:#2:#3:{#1-#2-#3}
            \def\cal@md#1:#2:#3:{\cal@Year-#1-#2}
            \def\cal@datesplit#1-#2-#3-#4#5#6{#5#1:#2:#3:}

            \def\cal@Xdateexpand#1{\expandafter\cal@datesplit#1--\cal@ymd\cal@md\empty}

            \def\cal@Ydateexpand#1{\edef\cal@arg{#1}\expandafter\cal@datesplit\cal@arg--\cal@ymd\cal@md\empty}

\def\cal@Year{2020}


\begin{document}
\def\dateMD{03-01} $\backslash$dateMD :: (\dateMD)

\def\dateYMD{2021-03-01} $\backslash$dateYMD :: (\dateYMD)

\def\dateY{2020} $\backslash$dateY :: (\dateY)

%% Using the \cal@X macro
A.1 (X) 03-01 :: (\cal@Xdateexpand{03-01})

A.2 (X) 2020-03-01 :: (\cal@Xdateexpand{2020-03-01})

A.3 (X) $\backslash$dateMD :: (\cal@Xdateexpand{\dateMD})

A.4 (X) 2020-$\backslash$dateMD :: (\cal@Xdateexpand{2020-\dateMD}) -- fails%% it fails in this case

A.5 (X) $\backslash$dateYMD :: (\cal@Xdateexpand{\dateYMD})

A.6 (X) $\backslash$dateY-$\backslash$dateMD :: (\cal@Xdateexpand{\dateY-\dateMD}) -- fails %% it fails in this case

%%
A.7 (X) edef 2020-03-01 :: \edef\SOME{\cal@Xdateexpand{2020-03-01}} (\SOME) 

A.8 (X) edef 2020-$\backslash$dateMD :: \edef\SOME{\cal@Xdateexpand{2020-\dateMD}} (\SOME) -- fails but compile%% it still fails, but compiles


%% Using the \cal@Y macro
B.1 (Y) 03-01 :: (\cal@Ydateexpand{03-01})

B.2 (Y) 2020-03-01 :: (\cal@Ydateexpand{2020-03-01})

B.3 (Y) $\backslash$dateMD :: (\cal@Ydateexpand{\dateMD})

B.4 (Y) 2020-$\backslash$dateMD :: (\cal@Ydateexpand{2020-\dateMD}) -- desired behaviour % This is the intended behaviour

B.5 (Y) $\backslash$dateYMD :: (\cal@Ydateexpand{\dateYMD})

B.6 (Y) $\backslash$dateY-$\backslash$dateMD :: (\cal@Ydateexpand{\dateY-\dateMD}) -- desired behaviour

%% the two edef below fails to compile, with a "missing control sequence inserted"
%B.7 (X) edef 2020-03-01 :: \edef\SOME{\cal@Ydateexpand{2020-03-01}} (\SOME) -- fails to compile

%B.8 (X) edef 2020-$\backslash$dateMD :: \edef\SOME{\cal@Ydateexpand{2020-\dateMD}} (\SOME) -- fails to compile


\end{document}
  • In \expandafter\A\B\C, the \expandafter skips over \A, expands \B (once), and does nothing to \C. You cases A.4, A.6, and A.8 don't work because not only the first token needs expanding, so \expandafter isn't enough (in case A.8, if you put \noexpand before \cal@Xdateexpand it will work). Cases B.7 and B.8 don't work because nested \(e,x,g)def don't work as you are expecting them to. – Phelype Oleinik Mar 9 at 18:32
  • I knew that the \cal@Xdateexpand definition had an 'expansion problem´ (expanding only once... and the use of \expanded (thanks steve) solved the issue)... my question was why the nested \edef wasn't working (better said, why the compiler just ended producing and error message for it... (curiosity)). – Alceu Mar 9 at 18:44
  • \edef doesn't expand inside another \edef. When you do \edef\SOME{\cal@Ydateexpand{2020-\dateMD}}, \cal@Ydateexpand expands, leaving you with \edef\SOME{\edef\cal@arg{#1}\expandafter\cal@datesplit\cal@arg--\cal@ymd\cal@md\empty}, then the (nested) \edef is skipped, and \cal@arg expands to whatever, and so on. If nothing went wrong so far, you are left with \def\SOME{\edef<expansion-of-\cal@arg>{<expansion-of-#1>}<expansion...>}. Then only when you use \SOME, the inner \edef will be executed. – Phelype Oleinik Mar 9 at 19:21
  • that´s interesting. I mean, I though the \edef would fully expand the 'argument' before defining the macro (which would include the nested \edef). ok. note taken about how \edef works. thanks – Alceu Mar 9 at 19:38
  • Think of TeX as if it processes the input in two stages: first is expansion, which only expands tokens, and then execution, which does everything else, like assignments, creating boxes, etc. \edef first triggers the expansion stage, which only expands expandable tokens, and once the expansion is over, then it does the assignment in the second stage (expandable tokens can be macros or expandable primitives (see the list here). Everything else is left untouched). – Phelype Oleinik Mar 9 at 19:47
5

Perhaps \expanded is what you are looking for. Then you don't need the \edef approach at all.

\documentclass{article}

\makeatletter

            \def\cal@ymd#1:#2:#3:{#1-#2-#3}
            \def\cal@md#1:#2:#3:{\cal@Year-#1-#2}
            \def\cal@datesplit#1-#2-#3-#4#5#6{#5#1:#2:#3:}

            \def\cal@Xdateexpand#1{\expandafter\cal@datesplit
              \expanded{#1}--\cal@ymd\cal@md\empty}

            \def\cal@Ydateexpand#1{\edef\cal@arg{#1}\expandafter\cal@datesplit
              \cal@arg--\cal@ymd\cal@md\empty}

\def\cal@Year{2020}


\begin{document}
\def\dateMD{03-01} $\backslash$dateMD :: (\dateMD)

\def\dateYMD{2021-03-01} $\backslash$dateYMD :: (\dateYMD)

\def\dateY{2020} $\backslash$dateY :: (\dateY)

%% Using the \cal@X macro
A.1 (X) 03-01 :: (\cal@Xdateexpand{03-01})

A.2 (X) 2020-03-01 :: (\cal@Xdateexpand{2020-03-01})

A.3 (X) $\backslash$dateMD :: (\cal@Xdateexpand{\dateMD})

A.4 (X) 2020-$\backslash$dateMD :: (\cal@Xdateexpand{2020-\dateMD}) -- fails%% it fails in this case

A.5 (X) $\backslash$dateYMD :: (\cal@Xdateexpand{\dateYMD})

A.6 (X) $\backslash$dateY-$\backslash$dateMD :: (\cal@Xdateexpand{\dateY-\dateMD}) -- fails %% it fails in this case

%%
A.7 (X) edef 2020-03-01 :: \edef\SOME{\cal@Xdateexpand{2020-03-01}} (\SOME) 

A.8 (X) edef 2020-$\backslash$dateMD :: \edef\SOME{\cal@Xdateexpand{2020-\dateMD}} (\SOME) -- fails but compile%% it still fails, but compiles


%% Using the \cal@Y macro
B.1 (Y) 03-01 :: (\cal@Ydateexpand{03-01})

B.2 (Y) 2020-03-01 :: (\cal@Ydateexpand{2020-03-01})

B.3 (Y) macro dateMD :: (\cal@Ydateexpand{\dateMD})

B.4 (Y) 2020-macro dateMD :: (\cal@Ydateexpand{2020-\dateMD}) -- desired behaviour % This is the intended behaviour

B.5 (Y) macro dateYMD :: (\cal@Ydateexpand{\dateYMD})

B.6 (Y) macros dateY-dateMD :: (\cal@Ydateexpand{\dateY-\dateMD}) -- desired behaviour

%% the two edef below fails to compile, with a "missing control sequence inserted"
%B.7 (X) edef 2020-03-01 :: \edef\SOME{\cal@Ydateexpand{2020-03-01}} (\SOME) -- fails to compile

%B.8 (X) edef 2020-macro dateMD :: \edef\SOME{\cal@Ydateexpand{2020-\dateMD}} (\SOME) -- fails to compile


\end{document}

enter image description here

| improve this answer | |
  • Thanks. it does works (of course). I tried to find the definition of \expanded and besides a comment (from egrep) that it is now a primitive in all engines... I haven't found anything else about it. besides the code itself, where else could I find it's definition ? – Alceu Mar 9 at 18:39
  • That's a very useful answer! (IMHO, of course.) – user194703 Mar 9 at 23:51
5

You can use expl3, much more direct.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewExpandableDocumentCommand{\calendardate}{m}
 {
  \alceu_calendar_date:e { #1 }
 }

\cs_new:Nn \alceu_calendar_date:n
 {
  \__alceu_calendar_date:w #1 - - \q_stop
 }
\cs_generate_variant:Nn \alceu_calendar_date:n { e }

\cs_new:Npn \__alceu_calendar_date:w #1 - #2 - #3 - #4 \q_stop
 {
  \tl_if_empty:nT { #3 } { \calYear - }
  #1 - #2
  \tl_if_empty:nF { #3 } { - #3 }
 }

\ExplSyntaxOff

\def\calYear{2020}


\begin{document}

\def\dateMD{03-01} \verb|\dateMD| :: (\dateMD)

\def\dateYMD{2021-03-01} \verb|\dateYMD| :: (\dateYMD)

\def\dateY{2020} \verb|\dateY| :: (\dateY)

A.1 (X) 03-01 :: (\calendardate{03-01})

A.2 (X) 2020-03-01 :: (\calendardate{2020-03-01})

A.3 (X) \verb|\dateMD| :: (\calendardate{\dateMD})

A.4 (X) 2020-\verb|\dateMD| :: (\calendardate{2020-\dateMD})

A.5 (X) \verb|\dateYMD| :: (\calendardate{\dateYMD})

A.6 (X) \verb|\dateY|-\verb|\dateMD| :: (\calendardate{\dateY-\dateMD})

A.7 (X) edef 2020-03-01 :: \edef\SOME{\calendardate{2020-03-01}} (\SOME) 

A.8 (X) edef 2020-\verb|\dateMD| :: \edef\SOME{\calendardate{2020-\dateMD}} (\SOME)

\end{document}

enter image description here

A small change to ensure that months and days are printed as two digits independently of input.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewExpandableDocumentCommand{\calendardate}{m}
 {
  \alceu_calendar_date:e { #1 }
 }

\cs_new:Nn \alceu_calendar_date:n
 {
  \__alceu_calendar_date:w #1 - - \q_stop
 }
\cs_generate_variant:Nn \alceu_calendar_date:n { e }

\cs_new:Npn \__alceu_calendar_date:w #1 - #2 - #3 - #4 \q_stop
 {
  \tl_if_empty:nT { #3 } { \calYear - }
  \alceu_calendar_two:n { #1 } - \alceu_calendar_two:n { #2 }
  \tl_if_empty:nF { #3 } { - #3 }
 }

\cs_new:Nn \alceu_calendar_two:n
 {
  \int_compare:nT { \tl_count:n { #1 } < 2 } { 0 } #1
 }

\ExplSyntaxOff

\def\calYear{2020}


\begin{document}

\def\dateMD{3-1} \verb|\dateMD| :: (\dateMD)

\def\dateYMD{2021-3-01} \verb|\dateYMD| :: (\dateYMD)

\def\dateY{2020} \verb|\dateY| :: (\dateY)

A.1 (X) 03-01 :: (\calendardate{03-01})

A.2 (X) 2020-03-01 :: (\calendardate{2020-03-01})

A.3 (X) \verb|\dateMD| :: (\calendardate{\dateMD})

A.4 (X) 2020-\verb|\dateMD| :: (\calendardate{2020-\dateMD})

A.5 (X) \verb|\dateYMD| :: (\calendardate{\dateYMD})

A.6 (X) \verb|\dateY|-\verb|\dateMD| :: (\calendardate{\dateY-\dateMD})

A.7 (X) edef 2020-03-01 :: \edef\SOME{\calendardate{2020-03-01}} (\SOME) 

A.8 (X) edef 2020-\verb|\dateMD| :: \edef\SOME{\calendardate{2020-\dateMD}} (\SOME)

\end{document}
| improve this answer | |
  • In that regard I'm kind of oldie, I´m sure that, eventually, I will start using the new kernel facilities... but then, I also could have just done that: \RequirePackage{etextools} \def\cal@dateexpand#1{\ifstrmatch{.*-.*-.*}{#1}{#1}{\cal@Year-#1}} it works nicely, and the syntax is easier to read... but it feels like using a cannon to kill some flies.. – Alceu Mar 9 at 18:58
  • But sure thanks for the alternative. – Alceu Mar 9 at 19:06

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.