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}
11
  • 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
    Commented Apr 21, 2017 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.
    – cgnieder
    Commented Apr 21, 2017 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
    – cgnieder
    Commented Apr 21, 2017 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
    Commented Apr 22, 2017 at 14:03
  • 1
    If you have a self-answer, please post it separately not as an edit to the question
    – Joseph Wright
    Commented Apr 22, 2017 at 22:00

4 Answers 4

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.

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

6
  • 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
    Commented Apr 21, 2017 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
    Commented Apr 21, 2017 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
    Commented Apr 21, 2017 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
    Commented Apr 21, 2017 at 15:23
  • 2
    I guess the % at the end of the lines between \ExplSyntaxOn and ...Off are due to habit? :)
    – cgnieder
    Commented Apr 21, 2017 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
  • 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
    Commented Apr 21, 2017 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
    Commented Apr 22, 2017 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}

You must log in to answer this question.

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