5

I am trying to apply a expl3 function to the contents of an environment. In LaTeX2e this can be achieved by using \bgroup and \egroup. In expl3, something does not work.

I would expect the following code to print "AAA hey BBB". Instead, it prints "AAA BBB hey".

\ExplSyntaxOn

\cs_new:Npn \bar:n #1 {
    AAA #1 BBB
}

\NewDocumentEnvironment{foo}{}{
    \bar:n \bgroup
}{
    \egroup
}

\ExplSyntaxOff

\begin{document}

\begin{foo}
    hey
\end{foo}

\end{document}

Any thoughts on why this happens and possible workarounds?

Edit

This is my current approach:

\cs_new:Npn \env_new:Nn #1#2 {
    \tl_new:N #1
    \tl_set:Nn #1 { #2 }
}

\cs_new:Npn \env_begin:N #1 {}
\cs_new:Npn \env_end:N #1 {}

\cs_new:Npn \env_capture_on: {
    \global \let \env_begin:N \group_begin:
    \global \let \env_end:N   \group_end:
}

\cs_new:Npn \env_capture_off: {
    \global\let\env_begin:N\env_original_begin:N
    \global\let\env_end:N\env_original_end:N
}

\cs_new:Npn \env_original_begin:N #1 {
    \env_capture_on:
    \tl_use:N #1
    \group_begin:
    \env_capture_off:
}

\cs_new:Npn \env_original_end:N #1 {
}

\env_capture_off:


\cs_new:Npn \foo:n #1 {
    AAA #1 BBB
}

\env_new:Nn \baf {
    \foo:n
}

\env_begin:N \baf
    hey
\env_end:N \baf

This won't work, since \group_begin: and \group_end: cannot be used to capture function arguments (i.e. \foo \group_begin: A \group_end: works differently than \foo { A }).

Is there any way a control sequence can be temporally forced to behave as if it was an explicit brace?

A solution

I think I have a solution. As I mentioned in the comments, one could combine pattern matching with counting the number of \begins to capture the contents of an environment. It was a bit suprising to me that this is precisely what is going on in environ, just below a LOT of notation.

Since I know that environ failed in my intended application, I decided to reimplement it in the simplest possible way, so that I can figure out where does my code fail (now I have an idea about what went wrong, but that's not the point). Below you can find an implementation in expl3. I have not tested it thoroughly, but I believe it works.

Any comments or reviews are hugely appreciated.

\int_new:N \env_count
\cs_new:Npn \env_new:Nn #1#2 {
    \cs_new:cpn { env_defined@ \cs_to_str:N #1 :nn } ##1##2 {
        #2
    }
}

\cs_new:Npn \env_countbegin:w #1\xbegin#2 {
    \cs_if_free:NTF #2 {
        \int_incr:N \env_count
        \env_countbegin:w
    } {
        \cs_if_eq:NNTF #2 \xend {} {
            \int_incr:N \env_count
            \env_countbegin:w
        }
    }
}

\cs_new:Npn \env_collect:w #1#2#3#4\xend#5 {
    \env_countbegin:w #4\xbegin\xend
    \int_compare:nTF { \env_count = 0 } {
        \use:c { env_defined@ \cs_to_str:N #1 :nn } { #3 #4 } { #2 }
    }{
        \int_decr:N \env_count
        \env_collect:w {#1} {#2} { #3 #4 \xend{#5} }
    }
}

\NewDocumentCommand \xbegin {mo} {
    \int_zero:N \env_count
    \env_collect:w {#1} {#2} {}
}

\NewDocumentCommand \xend {} {}

\env_new:Nn \foo {
    AAA #1 BBB
}

\xbegin{\foo}
\xbegin{\foo}
hey
\xend{\foo}
\xend{\foo}
  • 1
    You want to capture the whole content of the the environment, I understood that. I don't think that \begingroup and \endgroup is the correct way – user31729 Apr 21 '17 at 14:43
  • 2
    @ChristianHupfer \group_begin: is the equivalent of \begingroup and not of \bgroup. \begingroup is the primitive while \bgroup has been let to {. The equivalent in expl3 is \c_group_begin_token. – clemens Apr 21 '17 at 15:52
  • 4
    @ChristianHupfer I know. You can't generally exchange { for \bgroup. It works in some cases (like boxes) but not for macros. \def\bar#1{AAA#1BBB} \newenvironment{foo}{\bar\bgroup}{\egroup} would fail the same way… expl3 is no different – clemens Apr 21 '17 at 16:01
  • 1
    As regards to your question in the edit, the answer is no: only explicit characters of category code 1 and 2 can be used for grabbing a token list as an undelimited macro argument. – egreg Apr 22 '17 at 14:03
  • 1
    If you have a self-answer, please post it separately not as an edit to the question – Joseph Wright Apr 22 '17 at 22:00
9

It has never been possible to grab an argument with \bgroup and \egroup. The version

\newenvironment{foo}{%
  \baz\bgroup
}{%
  \egroup
}
\newcommand{\baz}[1]{AAA #1 BBB}

would fail in exactly the same way. The argument to \baz would be \bgroup, so the output of

x\begin{foo}
key
\end{foo}y

would be

xAAA<space><space>BBB<space>key<space>y

One of the unexpected spaces comes from the end-of-line after \begin{foo}, the other one from the end-of-line after key.

It is possible to use environ, as shown by Christian Hupfer:

\usepackage{xparse,environ}

\ExplSyntaxOn
\NewEnviron{foo}
 {
  \jaeya_baz:V \BODY
 }

\cs_new:Nn \jaeya_baz:n { AAA~#1~BBB }
\cs_generate_variant:Nn \jaeya_baz:n { V }
\ExplSyntaxOff

and the output of the code above would be the expected

xAAA<space>key<space}BBBy

Adding features similar to \NewEnviron in xparse should be on the to-do list for the LaTeX team.

  • I second this: "Adding features similar to \NewEnviron in xparse should be on the to-do list for the LaTeX team." – Jonathan Komar Oct 20 '17 at 13:00
  • 1
    @JonathanKomar Now there is! The code can be changed into \NewDocumentEnvironment{foo}{b}{\jaeya_baz:n{#1}}{} – egreg Mar 8 at 13:03
  • Awesome!!! xparse will become a standard package in my repetoire, I think. – Jonathan Komar Mar 8 at 13:13
8

This is a way with environ and \NewEnviron, catching \BODY and applying it within a wrapper environment, defined with xparse's \NewDocumentEnvironment, which means that all features from xparse - argument specifiers can be used still.

\documentclass{article}
\usepackage{environ}
\usepackage{xparse}

\ExplSyntaxOn

\cs_new:Nn \bar:n {
  AAA #1 BBB
}


\NewEnviron{foointernal}{
  \bar:n {\BODY}
}

\ExplSyntaxOff

\let\origfoointernal\foointernal
\let\origendfoointernal\endfoointernal

\NewDocumentEnvironment{foo}{}{%
  \origfoointernal%
}{%
  \origendfoointernal%
}

\begin{document}

\begin{foo}
    hey
\end{foo}


\begin{foo}
   \blindtext

   Hello World
\end{foo}

\end{document}

enter image description here

  • Thanks for your reply. Although in this case it would work, I am afraid the use case is way more complex, and environ was problematic in a previous version (frankly, I don't remember how exactly). – Jaeya Apr 21 '17 at 14:44
  • As I said, environ was very problematic in the past for my code, to the extent that I already had to migrate everything to the \bgroup / \egroup style due to my inability to make it work. I would just like to use something similar with LaTeX3 – Jaeya Apr 21 '17 at 15:07
  • 1
    @Jaeya: I don't know what the problem was with your then... I showed you a working solution, based on the document you gave in your post – user31729 Apr 21 '17 at 15:19
  • And a very appreciated one! I do understand that it is not easy to provide a more suitable solution without more information that, unfortunately, I do not have. – Jaeya Apr 21 '17 at 15:23
  • 2
    I guess the % at the end of the lines between \ExplSyntaxOn and ...Off are due to habit? :) – clemens Apr 21 '17 at 15:54
4

You know you want to use \bar:n in an environment to grab the contents. So just make it scan ahead for \end and insert \end again at the end of the replacement text.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\cs_new:Npn \bar:n #1 \end {
    AAA #1 BBB \end
}

\NewDocumentEnvironment{foo}{}{\bar:n}{}

\ExplSyntaxOff

\begin{document}

\begin{foo}
    hey
\end{foo}

\end{document}

enter image description here

  • 2
    Will this work for nested environments as well? (E.g. \begin{foo} \begin{foobar} Hey \end{foobar}\end{foo}} The idea is thrilling, but I think the nesting is exactly the problem what makes the environ code 'hard' – user31729 Apr 21 '17 at 21:59
  • I already thought of this - I was using a quark for pattern matching, though. As @ChristianHupfer mentioned, the problem here is nesting, but I think there is a workaround - essentially, counting nesting levels. I know, it's ugly. – Jaeya Apr 22 '17 at 3:52
0

I frankly don't know if it will work once the whole package is rewritten, but at least now I can understand what is going on. There are a few limitations: defined environments have only two parameters. #1 represents the contents of the environment; #2 represents a square-bracket parameter, which in principle can be used to contain key-values and so overcome this limitation.

Furthermore, my environments work with commands \xbegin{\foo} and \xend{\foo}. The underlying structure of these commands is different from that of LaTeX environments, so I decided to use different keywords. Anyway, I guess that the approach could be extended to apply to LaTeX environments.

Any review or comments on the code are hugely welcome and appreciated!

\documentclass[10pt]{article}
\usepackage{expl3}

\ExplSyntaxOn

\int_new:N \env_count
\cs_new:Npn \env_new:Nn #1#2 {
    \cs_new:cpn { env_defined@ \cs_to_str:N #1 :nn } ##1##2 {
        #2
    }
}

\cs_new:Npn \env_countbegin:w #1\xbegin#2 {
    \cs_if_free:NTF #2 {
        \int_incr:N \env_count
        \env_countbegin:w
    } {
        \cs_if_eq:NNTF #2 \xend {} {
            \int_incr:N \env_count
            \env_countbegin:w
        }
    }
}

\cs_new:Npn \env_collect:w #1#2#3#4\xend#5 {
    \env_countbegin:w #4\xbegin\xend
    \int_compare:nTF { \env_count = 0 } {
        \use:c { env_defined@ \cs_to_str:N #1 :nn } { #3 #4 } { #2 }
    }{
        \int_decr:N \env_count
        \env_collect:w {#1} {#2} { #3 #4 \xend{#5} }
    }
}

\NewDocumentCommand \xbegin {mo} {
    \int_zero:N \env_count
    \env_collect:w {#1} {#2} {}
}

\NewDocumentCommand \xend {} {}

\env_new:Nn \foo {
    AAA #1 BBB
}

\ExplSyntaxOff

\begin{document}    

\xbegin{\foo}
\xbegin{\foo}
hey
\xend{\foo}
\xend{\foo}

\end{document}

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.