1

I would like to define nested environments that have the same nested definitions of sub-commands and sub-environments. This is how it would be used:

\begin{myenva}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{}
    \end{myenvz}
  \end{myenvx}
\end{myenva}

\begin{myenvb}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{}
    \end{myenvz}
  \end{myenvx}
\end{myenvb}

I would like for myenvx to be a totally different environment with different functionality when used under myenva vs. myenvb. Likewise, I would like myenvz underneath myenva/myenvx to be totally different than myenvz under myenvb/myenvx.

I have the following MWE demonstrating how I tried getting it to work:

\documentclass[a4paper]{article}

\usepackage{expl3}
\usepackage{xparse}

\ExplSyntaxOn

\begin{document}

\NewDocumentEnvironment{myenva}{} {
  \NewDocumentEnvironment{myenvx}{} {
    \NewDocumentEnvironment{myenvz}{} {
      \NewDocumentCommand{mycmdz} {
        \mycmda{}
        \mycmdx{}
        \typeout{mycmdz}
      }
    }{}

    \NewDocumentCommand{mycmdx} {
      \typeout{mycmdx}
    }
  }{}

  \NewDocumentCommand{mycmda} {
    \typeout{mycmda}
  }
}{}

\NewDocumentEnvironment{myenvb}{} {
  \NewDocumentEnvironment{myenvx}{} {
    \NewDocumentEnvironment{myenvz}{} {
      \NewDocumentCommand{mycmdz} {
        \mycmdb{}
        \mycmdx{}
        \typeout{mycmdz}
      }
    }{}

    \NewDocumentCommand{mycmdx} {
      \typeout{mycmdx}
    }
  }{}

  \NewDocumentCommand{mycmdb} {
    \typeout{mycmdb}
  }
}{}

\begin{myenva}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{}
    \end{myenvz}
  \end{myenvx}
\end{myenva}

\begin{myenvb}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{}
    \end{myenvz}
  \end{myenvx}
\end{myenvb}

\end{document}

But I get this error when trying to compile:

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! LaTeX error: "xparse/command-already-defined"
!
! Command 'mycmda' already defined!
!
! See the LaTeX3 documentation for further information.
!
! For immediate help type H <return>.
!...............................................

l.51   \begin
             {myenvx}

I am not sure how exactly to define nested environments. It used to work in LaTeX2 with \newenvironment, but it doesn't seem to be working with LaTeX3. Not sure what might be going wrong, seems like there could be several things. The goal is to be able to define nested environments and commands where the name is the same in subcommands in different environments.

(I would like for the environments to be scoped like this, so they can be handled differently.)

I don't think I should be using \DeclareDocumentCommand anywhere. Maybe this is where I should be using the new LaTeX3 functions, but not sure. I am able to get a little further by using \cs_new:Npn, as in this MWE:

\documentclass[a4paper]{article}

\usepackage{expl3}
\usepackage{xparse}

\ExplSyntaxOn

\begin{document}

\NewDocumentEnvironment{myenva}{} {
  \NewDocumentEnvironment{myenvx}{} {
    \NewDocumentEnvironment{myenvz}{} {
      \cs_new:Npn \mycmdz {
        \mycmda{}
        \mycmdx{}
        \typeout{mycmdz}
      }
    }{}

    \cs_new:Npn \mycmdx {
      \typeout{mycmdx}
    }
  }{}

  \cs_new:Npn \mycmda {
    \typeout{mycmda}
  }
}{}

\NewDocumentEnvironment{myenvb}{} {
  \NewDocumentEnvironment{myenvx}{} {
    \NewDocumentEnvironment{myenvz}{} {
      \cs_new:Npn \mycmdz {
        \mycmda{}
        \mycmdx{}
        \typeout{mycmdz}
      }
    }{}

    \cs_new:Npn \mycmdx {
      \typeout{mycmdx}
    }
  }{}

  \cs_new:Npn \mycmdb {
    \typeout{mycmdb}
  }
}{}

\begin{myenva}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{}
    \end{myenvz}
  \end{myenvx}
\end{myenva}

\begin{myenvb}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{}
    \end{myenvz}
  \end{myenvx}
\end{myenvb}

\end{document}

But it still errors with:

mycmda
mycmdx
mycmdz

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! LaTeX error: "kernel/command-already-defined"
!
! Control sequence \mycmdx already defined.
!
! See the LaTeX3 documentation for further information.
!
! For immediate help type H <return>.
!...............................................

I am able to accomplish the desired behavior when using LaTeX2 functionality, demonstrated in the following MWE:

\documentclass[a4paper]{article}

\usepackage{expl3}
\usepackage{xparse}

\begin{document}

\newenvironment{myenva} {
  \newenvironment{myenvx} {
    \newenvironment{myenvz} {
      \newcommand{\mycmdz} {
        \mycmda{}
        \mycmdx{}
        \typeout{mycmdz}
      }
    }{}

    \newcommand{\mycmdx} {
      \typeout{mycmdx}
    }
  }{}

  \newcommand{\mycmda} {
    \typeout{mycmda}
  }
}{}

\newenvironment{myenvb} {
  \newenvironment{myenvx} {
    \newenvironment{myenvz} {
      \newcommand{\mycmdz} {
        \mycmdb{}
        \mycmdx{}
        \typeout{mycmdz}
      }
    }{}

    \newcommand{\mycmdx} {
      \typeout{mycmdx}
    }
  }{}

  \newcommand{\mycmdb} {
    \typeout{mycmdb}
  }
}{}

\begin{myenva}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{}
    \end{myenvz}
  \end{myenvx}
\end{myenva}

\begin{myenvb}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{}
    \end{myenvz}
  \end{myenvx}
\end{myenvb}

\end{document}

However, I would like to take advantage of the following features while accomplishing the nested environments/commands:

  1. Use LaTeX3 features.
  2. Have dynamic/overloaded argument parsing thanks to xparse.

Update

I tried some more permutations to make sure it covered the cases I was looking for and it all seems to work, yay!

\documentclass[a4paper]{article}

\usepackage[english]{babel}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{expl3}
\usepackage{xparse}

\ExplSyntaxOn


\NewDocumentEnvironment{myenvx}{}{}{}
\NewDocumentEnvironment{myenvz}{}{}{}

\NewDocumentCommand{\mycmdz}{}{}

\NewDocumentEnvironment{myenva}{}{%
  \NewDocumentCommand{\mycmda}{mm}{%
    \typeout{mycmda}
    \typeout{##1}
    \typeout{##2}
  }%
  \RenewDocumentEnvironment{myenvx}{}{%
    \NewDocumentCommand{\mycmdx}{mm}{%
      \typeout{mycmdx}
      \typeout{####1}
      \typeout{####2}
    }%
    \RenewDocumentEnvironment{myenvz}{}{%
      \RenewDocumentCommand{\mycmdz}{mm}{%
        \mycmda{mycmda:a}{mycmda:b}
        \mycmdx{mycmda:mycmdx:a}{mycmda:mycmdx:b}
        \typeout{mycmdz}
        \typeout{########1}
        \typeout{########2}
      }%
    }{}%
    %
  }{}%
 %
}{}

\NewDocumentEnvironment{myenvb}{} {%
  \NewDocumentCommand{\mycmdb}{}{%
    \typeout{mycmdb}
  }%
  \RenewDocumentEnvironment{myenvx}{} {%
    \NewDocumentCommand{\mycmdx}{mmm}{%
      \typeout{mycmdx}
      \typeout{####1}
      \typeout{####2}
      \typeout{####3}
    }%
    \RenewDocumentEnvironment{myenvz}{} {%
      \RenewDocumentCommand{\mycmdz}{mmm}{%
        \mycmdb{}
        \mycmdx{mycmdb:mycmdx:a}{mycmdb:mycmdx:b}{mycmdb:mycmdx:c}
        \typeout{mycmdz}
        \typeout{########1}
        \typeout{########2}
        \typeout{########3}
      }%
    }{}%
    %
  }{}%
  %
}{}

\begin{document}
\begin{myenva}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{mycmda:mycmdx:mycmdz:a}{mycmda:mycmdx:mycmdz:b}
    \end{myenvz}
  \end{myenvx}
\end{myenva}

\begin{myenvb}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{mycmdb:mycmdx:mycmdz:a}{mycmdb:mycmdx:mycmdz:b}{mycmdb:mycmdx:mycmdz:c}
    \end{myenvz}
  \end{myenvx}
\end{myenvb}

\begin{myenva}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{mycmda:mycmdx:mycmdz:a}{mycmda:mycmdx:mycmdz:b}
    \end{myenvz}
  \end{myenvx}
\end{myenva}
\end{document}

Outputs:

mycmda
mycmda:a
mycmda:b
mycmdx
mycmda:mycmdx:a
mycmda:mycmdx:b
mycmdz
mycmda:mycmdx:mycmdz:a
mycmda:mycmdx:mycmdz:b
mycmdb
mycmdx
mycmdb:mycmdx:a
mycmdb:mycmdx:b
mycmdb:mycmdx:c
mycmdz
mycmdb:mycmdx:mycmdz:a
mycmdb:mycmdx:mycmdz:b
mycmdb:mycmdx:mycmdz:c
mycmda
mycmda:a
mycmda:b
mycmdx
mycmda:mycmdx:a
mycmda:mycmdx:b
mycmdz
mycmda:mycmdx:mycmdz:a
mycmda:mycmdx:mycmdz:b

Update 2

It seems to work without RenewDocumentCommand and RenewDocumentEnvironment.

\documentclass[a4paper]{article}

\usepackage[english]{babel}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{expl3}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentEnvironment{myenva}{}{
  \NewDocumentCommand{\mycmda}{mm}{
    \typeout{mycmda}
    \typeout{##1}
    \typeout{##2}
  }
  \NewDocumentEnvironment{myenvx}{}{
    \NewDocumentCommand{\mycmdx}{mm}{
      \typeout{mycmdx}
      \typeout{####1}
      \typeout{####2}
    }
    \NewDocumentEnvironment{myenvz}{}{
      \NewDocumentCommand{\mycmdz}{mm}{
        \mycmda{mycmda:a}{mycmda:b}
        \mycmdx{mycmda:mycmdx:a}{mycmda:mycmdx:b}
        \typeout{mycmdz}
        \typeout{########1}
        \typeout{########2}
      }
    }{}

  }{}

}{}

\NewDocumentEnvironment{myenvb}{} {
  \NewDocumentCommand{\mycmdb}{}{
    \typeout{mycmdb}
  }
  \NewDocumentEnvironment{myenvx}{} {
    \NewDocumentCommand{\mycmdx}{mmm}{
      \typeout{mycmdx}
      \typeout{####1}
      \typeout{####2}
      \typeout{####3}
    }
    \NewDocumentEnvironment{myenvz}{} {
      \NewDocumentCommand{\mycmdz}{mmm}{
        \mycmdb{}
        \mycmdx{mycmdb:mycmdx:a}{mycmdb:mycmdx:b}{mycmdb:mycmdx:c}
        \typeout{mycmdz}
        \typeout{########1}
        \typeout{########2}
        \typeout{########3}
      }
    }{}

  }{}

}{}

\begin{document}
\begin{myenva}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{mycmda:mycmdx:mycmdz:a}{mycmda:mycmdx:mycmdz:b}
    \end{myenvz}
  \end{myenvx}
\end{myenva}

\begin{myenvb}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{mycmdb:mycmdx:mycmdz:a}{mycmdb:mycmdx:mycmdz:b}{mycmdb:mycmdx:mycmdz:c}
    \end{myenvz}
  \end{myenvx}
\end{myenvb}

\begin{myenva}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{mycmda:mycmdx:mycmdz:a}{mycmda:mycmdx:mycmdz:b}
    \end{myenvz}
  \end{myenvx}
\end{myenva}
\end{document}
  • I think you need to define the macros and environment outside first (definition body can be empty at this stage). Then use \RenewDocumentCommand and \RenewDocumentEnvironment within your environments to redefine them with the desired definitions. – Peter Grill Mar 25 '18 at 17:44
  • The team are still thinking about 'scopes'. There are places that people like to use them, but broadly each document command should exist everywhere even if it doesn't work everywhere. A good example of why this can be an issue is the accent commands and tabbing (the one place there is some 'leakage' in the kernel) ... – Joseph Wright Mar 25 '18 at 20:42
2

I think this is what you need:

enter image description here

Notes:

  • Define each environment/macro first and then redefine them as needed. In this case it appears that the definitions are only local so you don't necessailyr need to define them first and then renrew them. I tend to always define them first and redefine them is to ensure that the same name is not used elsewhere. Renewing the definitions just checks that the name was already defined, it does not check that the arg specs match, so you are free to change them ocally if needed.

  • Macro names begin with a leading slash \, environment names do not.

  • \NewDocumentEnvironment/\RenewDocumentCommand require an arg spec, so if there are no arguments (as is the case here), you must specify an empy arg spec as {}. These were missing in your MWE. So, the general format is

    \NewDocumentCommand{<macro name starting with a slash>}{<arg spec>}{<body>}
    
  • Similary, \NewDocumentEnvironment/\RenewDocumentEnvironment also require an arg spec. These were missing in your MWE. So, the general format is

    \NewDocumentEnvironment{<env name without leading slash>}{<arg spec>}{<begin env code>}{<end env code>}
    
  • When you want a macro only to be defined within another environemnt/macro, you should define it before using it. So, I moved those up definitions up.

  • Also, be careful with spurious spaces. See this Tex Capacity Exceeded (if remove % after use of macro) as an example of what can happen if you are not.

    As I am not 100% sure where I don't need them, I tend to add more than actually required. However, I have been chastised for overdoing this When is it harmful to add percent character at end of lines in a \newcommand, or similar. However, I also automatically to add a \relax folowing any \nuxper, \dimexpr type so have not yet encoutered any problems with over doing it (but I am not recommending this).

  • My recommendation when a problem is encoutered is to keep the intial version as simple as possible before trying something more complicated. For instance, you could have started with a single (not nested) macro definitions to make sure you have that correct. This would have shown the problem with the missing arg spec and macro name. Then a single (not nested) environment to make sure you have that correct. Then from there build a nested enviroment (just two levels to start, no point in starting at three levels).
  • If the nested macros/enviroment have arguments, to access them you need to double up the use of #. For more information see:

  • AFAIK, using \ExplSyntaxOn allows you to leave off the %, but then any spaces in the text in the macros will also get removed. Up to you where you perfer to deal with the spaces: Either within the definitions, or within the text.

Code:

\documentclass{article}
\usepackage{expl3}
\usepackage{xparse}

\NewDocumentEnvironment{myenvx}{}{}{}
\NewDocumentEnvironment{myenvz}{}{}{}

\NewDocumentCommand{\mycmdz}{}{}

\NewDocumentEnvironment{myenva}{}{%
  \NewDocumentCommand{\mycmda}{}{%
    \typeout{mycmda}
  }%
  \RenewDocumentEnvironment{myenvx}{}{%
    \NewDocumentCommand{\mycmdx}{}{%
      \typeout{mycmdx}
    }%
    \RenewDocumentEnvironment{myenvz}{}{%
      \RenewDocumentCommand{\mycmdz}{}{%
        \mycmda{}
        \mycmdx{}
        \typeout{mycmdz}
      }%
    }{}%
    %
  }{}%
 %
}{}

\NewDocumentEnvironment{myenvb}{} {%
  \NewDocumentCommand{\mycmdb}{}{%
    \typeout{mycmdb}
  }%
  \RenewDocumentEnvironment{myenvx}{} {%
    \NewDocumentCommand{\mycmdx}{}{%
      \typeout{mycmdx}
    }%
    \RenewDocumentEnvironment{myenvz}{} {%
      \RenewDocumentCommand{\mycmdz}{}{%
        \mycmdb{}
        \mycmdx{}
        \typeout{mycmdz}
      }%
    }{}%
    %
  }{}%
  %
}{}

\begin{document}
\begin{myenva}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{}
    \end{myenvz}
  \end{myenvx}
\end{myenva}

\begin{myenvb}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz{}
    \end{myenvz}
  \end{myenvx}
\end{myenvb}
\end{document}
| improve this answer | |
  • @LancePollard: Does it make sense now? Please let me know if something is not clear. If you add a MWE that shows how you want to overload the arg specs, I can adapt it to show how to double up the ## as per the last point in the Notes section. – Peter Grill Mar 25 '18 at 18:31
  • First question is why NewDocumentCommand{\mycmdx} instead of RenewDocumentCommand{\mycmdx}, when there is RenewDocumentCommand{\mycmdz} instead of NewDocumentCommand{\mycmdz}. – Lance Pollard Mar 25 '18 at 18:54
  • I added an update to the question with an experiment demonstrating all the edge cases I could think of, seems to work great, thank you! What I still don't understand is: (1) Why define the \NewDocumentCommand{\mycmdz} first, while not defining \NewDocumentCommand{\mycmdx} first, since it is also nested/repeated. (2) It seems like kind of a hack having to do RenewDocumentCommand in this way, how is it not a hack (just want to understand better). – Lance Pollard Mar 25 '18 at 19:05
  • (3) I don't understand why doing \NewDocumentCommand{\mycmdz}{} (with no args) and \RenewDocumentCommand{\mycmdz}{mmm} (with 3 args) is a compatible override. Makes me wonder why defining it first is even necessary if the API is different. Part of my question regarding why it's not a hack ("When you want a macro only to be defined within another environemnt/macro, you should define it before using it. So, I moved those up definitions up.") – Lance Pollard Mar 25 '18 at 19:06
  • 1
    @LancePollard: I tried to address that in the update to the first bullet point. – Peter Grill Mar 25 '18 at 21:11
1

It is often simpler to make the inner commands adapt to their context rather than make the outer commands redefine everything.

here I just typeout, producing

myenva:myenvx:myenvz:mycmdz
myenvb:myenvx:myenvz:mycmdz

but given those different sequences, \mycmdz could execute arbitrarily different code.

\documentclass[a4paper]{article}

\usepackage{expl3}
\usepackage{xparse}

\ExplSyntaxOn
\makeatletter
\cs_new:Nn\colonify:n{#1:}

\seq_new:N\zz_seq{}

\NewDocumentEnvironment{myenva}{}{\seq_put_right:Nx\zz_seq{\@currenvir}} {}
\NewDocumentEnvironment{myenvb}{}{\seq_put_right:Nx\zz_seq{\@currenvir}} {}
\NewDocumentEnvironment{myenvx}{}{\seq_put_right:Nx\zz_seq{\@currenvir}} {}
\NewDocumentEnvironment{myenvz}{}{\seq_put_right:Nx\zz_seq{\@currenvir}} {}

\NewDocumentCommand\mycmdz{}{
\typeout{
  \seq_map_function:NN\zz_seq\colonify:n mycmdz
  }}

\ExplSyntaxOff

\begin{document}

\begin{myenva}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz
    \end{myenvz}
  \end{myenvx}
\end{myenva}

\begin{myenvb}
  \begin{myenvx}
    \begin{myenvz}
      \mycmdz
    \end{myenvz}
  \end{myenvx}
\end{myenvb}

\end{document}
| improve this answer | |
  • Not sure exactly what you mean by here: "It is often simpler to make the inner commands adapt to their context" – Lance Pollard Mar 25 '18 at 22:19
  • @LancePollard you suggested making your outer environments redefine \mycmdz so it acts differently , but I suggest here not doing that and instead define \mycmdz so it detects which environment it is in and acts accordingly – David Carlisle Mar 25 '18 at 22:41

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.