8

The \NewDocumentCommand command uses a clever trick to make better error messages -- if the arguments of \mycommand are messed up, it would be undesirable for the resulting error to be something like "File ended while scanning use of \__xparse_grab_D:w", so whenever xparse is going to grab another argument, it defines \mycommand<space> to do the argument grabbing and then uses that to do the work so that if something goes wrong the error is instead something like "Paragraph ended before \mycommand<space> was complete".

I want a command like \NewDocumentCommandAs that takes two arguments -- one the name of the macro to be defined, and the second the name of the command to be used in error messages.

I have two use cases for this. One is that I have a package that defines commands that are only to be used inside a certain environment. So it defines the commands in the package's private namespace and \let's the public name for the command to the private version at the start of the environment. Obviously I want to base all the error messages around the public name of the command, so I would like to say something like

\NewDocumentCommandAs \__my_pkg_cmd:args \cmd {<args>}{<body>}

The second use case is that I have a command with an optional argument, an argument that is everything up to the first open parenthesis, and then an argument delimited by balanced parentheses starting with that first open parenthesis. Because the "until" argument type absorbs the tokens it is scanning up to, I need to reinsert the open parenthesis and use an auxilary:

\NewDocumentCommand \mycommand { o u( } { \mycommand_aux{#1}{#2}( }
\NewDocumentCommandAs \mycommand_aux \mycommand { m m r() } { 
    % do stuff here
}

Of course this could be fixed by making a version of u (maybe U?) that reinserts the tokens it removes, but that might be an unusual use case.

For formatting purposes, I guess I'll submit my two implementations as an answer. Has anyone else dealt with this issue? Is there some better approach that I am missing? Should I not do this and solve these problems in a different way? Is this a feature that might be added to a future version of xparse?

  • I'm sorry if I wasn't clear, but \__mypkg_cmd:args is supposed to be a private version of a document command that can only be used in a specific environment. So in the startup code for that environment, I'll say \let\cmd\__my_pkg_cmd:args. I think it's appropriate to use this command to define a document-level macro in a private namespace? – Hood Chatham Feb 1 '17 at 17:25
  • In response to your second comment, I think I see what you are talking about and both of those things are just mistakes and I think I fixed them. – Hood Chatham Feb 1 '17 at 17:27
  • So my concern, in response to your last comment, is that the name of the user level commands are very short and could quite well be used to do something else outside of my environment (e.g., \class). I was following the design of tikz where it has a bunch of lines like \let\path=\tikz@command@path to ensure that someone could define \path to do something else outside of tikz drawing environments. This seems to me like a good way to do things (especially because code inside tikz environments is very different from code outside of them so there is limited risk of confusion). – Hood Chatham Feb 2 '17 at 18:46
  • As far as your complaint about me using _aux for a non LaTeX3 style command, could you explain what the "best practices" solution to that particular problem would be? You say that the way I did it is wrong, but haven't suggested an alternative. Honestly I don't see that it matters so much as long as name reasonably communicates its function. I would be perfectly happy deleting the _aux and throwing in an @. – Hood Chatham Feb 2 '17 at 19:01
  • I could also define both the outer command and the auxilary command in LaTeX3 style using \NewDocumentCommandAs and then \let\mycommand to the outer one. Though I'm not sure what the argspec would be. \__mypkg_mycmd:ww and \__mypkg_mycmd_aux:mmw? – Hood Chatham Feb 2 '17 at 19:05
1

My first attempt was:

\cs_new_protected:Npn \NewDocumentCommandAs#1#2#3#4{
    \group_begin:
    % Store the original value of #2
    \cs_set_eq:cN { tempsave_ \cs_to_str:N #2 } #2
    \cs_set_eq:cc { tempsave_ \cs_to_str:N #2 \c_space_tl code } 
                  { \cs_to_str:N #2 \c_space_tl code }
    % Use \DeclareDocumentCommand with #2 so that error messages work
    \DeclareDocumentCommand#2{#3}{\group_end: #4}
    % Define #1 to be a wrapper command that sets #2<space>code equal to #1<space>code
    \cs_new:Npx #1{
        \group_begin:
        \exp_not:N \cs_set_eq:NN
            \exp_not:c { \cs_to_str:N #2 \c_space_tl code }
            \exp_not:c { \cs_to_str:N #1 \c_space_tl code }
        \exp_not:c { \cs_to_str:N #1 \c_space_tl inner }
    }
    % Save the value of #2 set by DeclareDocumentCommand
    \cs_gset_eq:cN { \cs_to_str:N #1 \c_space_tl inner } #2
    \cs_gset_eq:cc{ \cs_to_str:N #1 \c_space_tl code } { \cs_to_str:N #2 \c_space_tl code }
    % Restore the original value of #2
    \cs_gset_eq:Nc #2 { tempsave_ \cs_to_str:N #2 }
    \cs_gset_eq:cc { \cs_to_str:N #2 \c_space_tl code } { tempsave_ \cs_to_str:N #2 \c_space_tl code }
    \group_end:
}

This is a bit messy, but works alright. The big benefit of this is that it doesn't depend on the implementation of \NewDocumentCommand beyond the detail that the guts of the macro are stored in #1<space>code and no other auxilary commands are used. One limitation is that it won't work if all arguments are type "m", but this doesn't bother me. This approach also can't work for expandable macros.

Another possible approach is to make a patched version of \__xparse_declare_cmd_mixed_aux:Nn that uses a different name for the error command (stored in \l__xparse_fn_tl) and install that definition temporarily for the sake of using \NewDocumentCommand. This has the advantage that you can do a similar thing to \__xparse_declare_cmd_mixed_expandable:Nn and get an expandable version working. It also works even if all arguments are mandatory. The disadvantage is that it depends on the exact implementation of \__xparse_declare_cmd_mixed_aux:Nn.

% Make two copies of \__xparse_declare_cmd_aux:Nn, one to patch, one as a save
% If \l_..._environment_bool is true, it forces xparse to use argument grabbers even when
% all arguments are mandatory. So we'll set it to be true for \NewDocumentCommandAs
\cs_new_eq:NN \__my_xparse_declare_cmd_aux_as:Nnn \__xparse_declare_cmd_aux:Nnn
\cs_new_eq:NN \__my_xparse_declare_cmd_aux_save:Nnn \__xparse_declare_cmd_aux:Nnn
\patchcmd \__my_xparse_declare_cmd_aux_as:Nnn { \bool_set_false:N \l__xparse_environment_bool } { \bool_set_true:N \l__xparse_environment_bool }{}{\oops}

\cs_new_eq:NN \__my_xparse_declare_cmd_mixed_aux_as:Nn   \__xparse_declare_cmd_mixed_aux:Nn
\cs_new_eq:NN \__my_xparse_declare_cmd_mixed_aux_save:Nn \__xparse_declare_cmd_mixed_aux:Nn

% Replace \l__xparse_function_tl with my own token list that I can set independently
\tl_new:N \l__my_xparse_function_as_tl
\patchcmd \__my_xparse_declare_as_cmd_mixed_aux:Nn 
    {{ \l__xparse_function_tl \c_space_tl }} 
    {{ \l__my_xparse_function_as_tl \c_space_tl }}{}{\oops}

\cs_new:Npn \NewDocumentCommandAs #1#2#3#4 {
    % Patch in my modified version of \__xparse_declare_cmd_mixed_aux
    \cs_set_eq:NN \__xparse_declare_cmd_mixed_aux:Nn \__my_xparse_declare_cmd_mixed_aux_as:Nn
    \tl_set:Nn \l__my_xparse_function_as_tl { \cs_to_str:N #2 } % Use #2 as the error name
    \NewDocumentCommand#1{#3}{#4}
    \cs_set_eq:NN \__xparse_declare_cmd_mixed_aux:Nn \__my_xparse_declare_as_cmd_mixed_aux_save:Nn
}
  • Ok will do. As you can see, I'm not super well versed in the LaTeX3 world. What code is supposed to go in the wrapper? Can I just \cs_new_eq the wrapper to the expl3 syntax command? Note that in xparse, the definition of \DeclareDocumentCommand uses \cs_new_protected:Npn (of course it is a wrapper in the sense that it just checks whether the first argument is definable and if so calls \__xparse_declare_cmd:Nnn). – Hood Chatham Feb 2 '17 at 18: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.