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}
6
  • 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. Mar 9, 2020 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, 2020 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. Mar 9, 2020 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, 2020 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). Mar 9, 2020 at 19:47

2 Answers 2

6

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

1
6

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}
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .