Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I write a document class and want to execute something a certain number of times, depending on a user-defined quantity. So I thought I'd use a foreach loop from PGF together with accessor macros:

\documentclass{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}

\usepackage{xparse,etoolbox,pgffor}

\makeatletter
\csdef{my@test}{3}

\DeclareDocumentCommand{\val}{m g}{%
  \csname #1@#2\endcsname%
}
\makeatother

\begin{document}
  Testing direct: \csname my@test\endcsname

  Testing macro: \val{my}{test}

  \foreach \i in {1,...,\csname my@test\endcsname}{a}

  \foreach \i in {1,...,\val{my}{test}}{a}
\end{document}

Unfortunately, using val breaks foreach:

enter image description here

with error message Illegal unit of measure (pt inserted) (several times).

Funny: if I use parameter specification {m m} instead of {m g}, the problem goes away.

It is feasible to not use the macro inside the class definition -- I do need the optional parameter, though -- but what is going on here?

Background

Users can create a list of Xs by using a macro of the form

\addX{group}{name}{key1=val1,key2=val2,..} 

This macro is implemented with xparse and keyval. It stores the information in macros of the form \X@group@name@key. In order to hide this from users, I provide accessor macros like val above. There are also macros and environments that utilise the created list directly, one of which would contain a loop as defined above, repeating stuff (for every X) depending on user-provided values.

The last parameter of val is to be optional so that a default value can be returned if the user provides no key (which would be the value I suppose will be requested most often).

share|improve this question
2  
Are you really sure you want an optional final argument in braces? I'd consider this bad syntax. –  egreg Nov 8 '13 at 23:10
    
@egreg That depends on your background, I guess. (La)TeX is the only language I know that uses different delimiters for optional arguments, and its treatment of parameters in general is ... baffling. So yea, by LaTeX standards this is weird syntax, however I intend my class to be used by people with minimal if any LaTeX skills so I try to keep LaTeX idiosyncrasies hidden as much as possible. –  Raphael Nov 9 '13 at 14:38
    
@egreg: My point is that my target group (RPG GMs) are unlikely to be aware of these conventions and probably don't want to be. (Besides, having optional parameters before the mandatory ones strikes me as an extremely unfortunate design decision, usage-wise. But that's a discussion for somewhere else.) –  Raphael Nov 9 '13 at 14:49
    
Is the user supposed to type \foreach\i in {1,...,\val{my}{test}}{...}? I don't think so. –  egreg Nov 9 '13 at 14:53
    
@egreg No, but they are supposed to be able to use \val in other contexts. If possible, I'd like both usage cases to work for my and their convenience; if that's not possible, I'll choose theirs. –  Raphael Nov 9 '13 at 14:54

1 Answer 1

up vote 3 down vote accepted

This seems to be an expandability issue. It seems that xparse's manner of defining functions with optional arguments is preventing \foreach from seeing the final value in its domain. In particular, \foreach cannot fully expand the value of your macro because xparse method for creating g type arguments prevents full expansion.

Even if the optional argument precedes the mandatory argument, you run into a similar error:

\documentclass{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}

\usepackage{xparse,etoolbox,pgffor}

\makeatletter
\csdef{my@test}{3}

\DeclareDocumentCommand{\val}{o m}{%
  \csname #1@#2\endcsname%
}
\makeatother

\begin{document}

  Testing direct: \csname my@test\endcsname

  Testing macro: \val[my]{test}

  \foreach \i in {1,...,\csname my@test\endcsname}{a}

  \foreach \i in {1,...,\val[my]{test} }{a}

\end{document}

In the documentation for xparse you can create fully expandable functions with optional arguments, but various restrictions apply:

  • The optional argument may not be of type g

  • The last argument may not be an optional argument.

You can write the following

\documentclass{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}

\usepackage{xparse,etoolbox,pgffor}

\makeatletter
\csdef{my@test}{3}

\DeclareExpandableDocumentCommand{\val}{o m}{%
  \csname #1@#2\endcsname%
}
\makeatother

\begin{document}

  Testing direct: \csname my@test\endcsname

  Testing macro: \val[my]{test}

  \foreach \i in {1,...,\csname my@test\endcsname}{a}

  \foreach \i in {1,...,\val[my]{test} }{a}

\end{document}

Doing a bit of gimmickry you can get a partial glance of what's happening expansion-wise

\documentclass{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}

\usepackage{xparse,etoolbox,pgffor}

\makeatletter
\csdef{my@test}{3}

%% an expandable version of your macro
\DeclareExpandableDocumentCommand{\eval}{o m}{%
  \csname #1@#2\endcsname%
}

%% a non-expandable version of your macro
\DeclareDocumentCommand{\val}{o m}{%
  \csname #1@#2\endcsname%
}
\makeatother
\setlength{\parskip}{2ex}
\begin{document}


 \makebox[1in][r]{Full expanded}: 
    \begin{minipage}[t]{3in}\ttfamily
      \detokenize\expandafter{\romannumeral-`G\eval[my]{test}}
    \end{minipage}

  \makebox[1in][r]{Expanded once}: 
    \begin{minipage}[t]{3in}\ttfamily
      \detokenize\expandafter{\eval[my]{test}}
    \end{minipage}

  \makebox[1in][r]{Not fully expandable}:
    \begin{minipage}[t]{3in}\ttfamily
      \detokenize\expandafter{\romannumeral-`G\val[my]{test}}
    \end{minipage}
\end{document}

enter image description here

Perhaps if you clarify how this macro is to behave, we can come up with a solution that better meets your needs.

UPDATE

You could make both your arguments mandatory, but test whether the second argument has been passed an empty argument.

Here's the code:

\csdef{my@default}{5}

\DeclareDocumentCommand{\eval}{ mm }{%%
  \expandafter\ifx\expandafter\relax\detokenize{#2}\relax
    \csname #1@default\endcsname
  \else
    \csname #1@#2\endcsname
  \fi
}

Then you can write something like

  \foreach \i in {1,...,\eval{my}{}}{D}

  \foreach \i in {1,...,\eval{my}{test}}{c}  

I should point out, though, that \eval{my}{ } will not test true for an empty argument.

share|improve this answer
    
Thank you for this elaborate explanation! Too bad that there is no \expandall with which we could wrap problematic code (or is there?). It seems as if I can not implement what I want, so I have to rethink my approach or deal with the restrictions of val as is. (For background, see my expanded question.) –  Raphael Nov 9 '13 at 14:58
1  
@Raphael Regarding \expandall, that's sort of what \romannumeral is doing above. But you can't force expansion of unexpandable content. LaTeX (TeX) processes material from left to right. A macro with optional arguments requires LaTeX to know what's to the right of the current token before expansion of the current token: so, this irreparably breaks expansion. –  A.Ellett Nov 9 '13 at 15:16
    
Thanks! It think xparse offers commands for trimming whitespace off arguments and xifthen can be used to check for emptiness; or would that break expandability again? (Quick tests make it seem so.) –  Raphael Nov 9 '13 at 16:11

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.