[latex3-commits] [git/LaTeX3-latex3-latex3] xparse: Implement b-type argument for grabbing the body of an environment (481b8ed)
Bruno Le Floch
bruno at le-floch.fr
Wed Feb 27 00:07:32 CET 2019
Repository : https://github.com/latex3/latex3
On branch : xparse
Link : https://github.com/latex3/latex3/commit/481b8ed1ea9f37d3545192a947a1080d8fef7af6
>---------------------------------------------------------------
commit 481b8ed1ea9f37d3545192a947a1080d8fef7af6
Author: Bruno Le Floch <bruno at le-floch.fr>
Date: Tue Feb 26 23:51:15 2019 +0100
Implement b-type argument for grabbing the body of an environment
>---------------------------------------------------------------
481b8ed1ea9f37d3545192a947a1080d8fef7af6
l3packages/xparse/testfiles/xparse005.lvt | 50 ++++++++-
l3packages/xparse/testfiles/xparse005.tlg | 44 ++++++++
l3packages/xparse/xparse.dtx | 163 ++++++++++++++++++++++++++++-
3 files changed, 253 insertions(+), 4 deletions(-)
diff --git a/l3packages/xparse/testfiles/xparse005.lvt b/l3packages/xparse/testfiles/xparse005.lvt
index d51e9b8..09df8fe 100644
--- a/l3packages/xparse/testfiles/xparse005.lvt
+++ b/l3packages/xparse/testfiles/xparse005.lvt
@@ -1,5 +1,5 @@
%
-% Copyright (C) 2018 LaTeX3 Project
+% Copyright (C) 2018,2019 LaTeX3 Project
%
\documentclass{minimal}
@@ -68,4 +68,52 @@
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% The following tests are outside to allow \ExplSyntaxOff and \obeylines
+\TEST { Environment~body~valid } { }
+
+\NewDocumentEnvironment { env1 } { o >{\SplitList{\par}} + b { } }
+ { \TYPE { \tl_to_str:n {|#1|#2|} } }
+ { \TYPE { \tl_to_str:n {|#1|} } }
+\NewDocumentEnvironment { env2 } { ! o ! b { \obeylines } }
+ { \TYPE { \tl_to_str:n {|#1|#2|} } }
+ { \TYPE { \tl_to_str:n {|#1|} } }
+\ExplSyntaxOff
+\begin{env1} [...]
+ \begin{any}
+ \begin{unbalanced}
+ \end{environments}
+ \begin{provided}
+
+ \end{nesting}
+ \end{works}
+ \begin{out}
+ \end{!}
+\end{env1}
+\begin{env2} [...]
+ \begin{any}
+ \begin{unbalanced}
+ \end{environments}
+ \begin{provided}
+ \end{nesting}
+ \end{works}
+ \begin{out}
+ \end{!}
+\end{env2}
+\ExplSyntaxOn
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\TEST { Body~invalid }
+ {
+ \begin{env1}\end{env2}
+ \begin{env2}
+ \par
+ \endgroup % error recovery for runaway args is not great: we need this \endgroup
+ \end{env2}
+ \NewDocumentCommand{\testE} { b{} } { }
+ \NewDocumentEnvironment{env3} { b{} m } { } { }
+ }
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\END
diff --git a/l3packages/xparse/testfiles/xparse005.tlg b/l3packages/xparse/testfiles/xparse005.tlg
index 8038214..f000081 100644
--- a/l3packages/xparse/testfiles/xparse005.tlg
+++ b/l3packages/xparse/testfiles/xparse005.tlg
@@ -113,3 +113,47 @@ l. ... }
<recently read> }
l. ... }
============================================================
+============================================================
+TEST 4: Environment body valid
+============================================================
+============================================================
+|...|{\begin {any} \begin {unbalanced} \end {environments} \begin {provided}}{\end {nesting} \end {works} \begin {out} \end {!}}|
+|...|
+|-NoValue-| [...]^^M\begin {any}^^M\begin {unbalanced}^^M\end {environments}^^M\begin {provided}^^M\end {nesting}^^M\end {works}^^M\begin {out}^^M\end {!}^^M|
+|-NoValue-|
+============================================================
+TEST 5: Body invalid
+============================================================
+|-NoValue-|{}|
+! LaTeX Error: \begin{env1} on input line ... ended by \end{env2}.
+See the LaTeX manual or LaTeX Companion for explanation.
+Type H <return> for immediate help.
+ ...
+l. ... }
+Your command was ignored.
+Type I <command> <return> to replace it with another command,
+or <return> to continue without it.
+Runaway argument?
+\begin
+! Paragraph ended before \environment env2 was complete.
+<to be read again>
+ \par
+l. ... }
+I suspect you've forgotten a `}', causing me to apply this
+control sequence to too much text. How can we recover?
+My plan is to forget the whole thing and hope for the best.
+! LaTeX3 Error: Argument type 'b' not available for command '\testE'.
+For immediate help type H <return>.
+ ...
+l. ... }
+The letter 'b' can only be used in environment argument specifications, not
+for commands.
+LaTeX will ignore this entire definition.
+! LaTeX3 Error: In the definition of 'environment env3', b (body) argument
+(LaTeX3) must be last.
+For immediate help type H <return>.
+ ...
+l. ... }
+The 'body' argument type is followed by 'm' in the argument specification of
+'environment env3'. This is not allowed.
+============================================================
diff --git a/l3packages/xparse/xparse.dtx b/l3packages/xparse/xparse.dtx
index 9502dba..4be1163 100644
--- a/l3packages/xparse/xparse.dtx
+++ b/l3packages/xparse/xparse.dtx
@@ -148,6 +148,12 @@
% A command with a verbatim
% argument will produce an error when it appears within an argument of
% another function.
+% \item[b] Only suitable in the argument specification of an
+% environment, \texttt{b}\marg{code} denotes the body of the
+% environment, between |\begin|\marg{environment} and
+% |\end|\marg{environment}. The \meta{code} is run before
+% \pkg{xparse} reads the environment's body. See
+% Section~\ref{sec:body} for details.
% \end{itemize}
% The types which define optional arguments are:
% \begin{itemize}[font=\ttfamily]
@@ -382,6 +388,47 @@
% |-NoValue-| marker as a default for |_|. This allows mixing of explicit
% defaults with testing for missing values.
%
+% \subsection{Body of an environment}
+% \label{sec:body}
+%
+% While environments |\begin|\marg{environment} \dots{}
+% |\end|\marg{environment} are typically used in cases where the code
+% implementing the \meta{environment} does not need to access the
+% contents of the environment (its \enquote{body}), it is sometimes
+% useful to have the body as a standard argument.
+%
+% This is achieved in \pkg{xparse} by ending the argument specification
+% with \texttt{b}\marg{code}. For instance
+% \begin{verbatim}
+% \NewDocumentEnvironment { twice }
+% { O{\ttfamily} +b{} }
+% {#2#1#2} {}
+% \begin{twice}[\itshape]
+% Hello world!
+% \end{twice}
+% \end{verbatim}
+% typesets \enquote{Hello world!{\itshape Hello world!}}.
+%
+% The prefix |+| is used to allow multiple paragraphs in the
+% environment's body. Argument processors can also be applied to
+% \texttt{b}-type arguments.
+%
+% By default, spaces are trimmed at both ends of the body: in the
+% example there would otherwise be spaces coming from the ends the lines
+% after |[\itshape]| and |world!|. Putting the prefix |!| before
+% \texttt{b} suppresses space-trimming.
+%
+% In \texttt{b}\marg{code} the \meta{code} is some set-up, run before
+% \pkg{xparse} grabs the body of the environment. This can be used to
+% preserve spaces or lines when grabbing environments.
+%
+% When \texttt{b}\marg{code} is used in the argument specification,
+% the last argument of \cs{NewDocumentEnvironment}, which consists of
+% an \meta{end code} to insert at |\end|\marg{environment}, is
+% redundant since one can simply put that code at the end of the
+% \meta{start code}. Nevertheless this (empty) \meta{end code} must be
+% provided.
+%
% \subsection{Backwards Compatibility}
% \label{sec:backwards}
%
@@ -1860,6 +1907,33 @@
% \end{macrocode}
% \end{macro}
%
+% \begin{macro}{\@@_normalize_type_b:w}
+% This argument type is not allowed for commands. This takes one
+% argument and is only allowed at the end of the argument
+% specification, hence we check both |#1| and |#2| for being the end.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_normalize_type_b:w #1#2
+ {
+ \bool_if:NF \l_@@_environment_bool
+ {
+ \__kernel_msg_error:nnxx
+ { xparse } { invalid-command-arg }
+ { \iow_char:N \\ \l_@@_function_tl } { b }
+ \@@_bad_def:wn
+ }
+ \quark_if_recursion_tail_stop_do:nn {#1} { \@@_bad_arg_spec:wn }
+ \int_incr:N \l_@@_mandatory_args_int
+ \tl_clear:N \l_@@_last_delimiters_tl
+ \@@_add_arg_spec:n { b {#1} }
+ \quark_if_recursion_tail_stop:n {#2}
+ \__kernel_msg_error:nnxx
+ { xparse } { arg-after-body }
+ { \l_@@_function_tl } { \tl_to_str:n {#2} }
+ \@@_bad_def:wn
+ }
+% \end{macrocode}
+% \end{macro}
+%
% \begin{macro}{\@@_single_char_check:n}
% Checks that what should be single characters really are single
% characters (possibly surrounded by spaces).
@@ -2036,8 +2110,7 @@
%
% For each known argument type there is an appropriate function to actually
% do the addition to the signature. These are separate for expandable and
-% standard functions, as the approaches are different. Of course, if the type
-% is not known at all then a fall-back is needed.
+% standard functions, as the approaches are different.
% \begin{macrocode}
\cs_new_protected:Npn \@@_prepare_signature:N
{
@@ -2101,7 +2174,7 @@
%
% \begin{macro}{\@@_add_type_>:w}
% When a processor is found, the processor code is stored. It will be
-% used by \cs{@@_args_process:} once arguments are all found. Here,
+% used by \cs{@@_args_process:} once arguments are all found. Here too
% the loop calls \cs{@@_prepare_signature_bypass:N} rather than
% \cs{@@_prepare_signature:N} so that the flag is not reset.
% \begin{macrocode}
@@ -2116,6 +2189,19 @@
% \end{macrocode}
% \end{macro}
%
+% \begin{macro}{\@@_add_type_b:w}
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_add_type_b:w #1
+ {
+ \@@_flush_m_args:
+ \@@_add_default:
+ \@@_add_grabber_mandatory:N b
+ \tl_put_right:Nn \l_@@_signature_tl { {#1} }
+ \@@_prepare_signature:N
+ }
+% \end{macrocode}
+% \end{macro}
+%
% \begin{macro}{\@@_add_type_D:w}
% \begin{macrocode}
\cs_new_protected:Npn \@@_add_type_D:w #1#2#3
@@ -2596,6 +2682,63 @@
% calls \cs{@@_add_arg:n}, responsible for calling processors and
% grabbing further arguments.
%
+% \begin{macro}
+% {
+% \@@_grab_b:w,
+% \@@_grab_b_long:w,
+% \@@_grab_b_obey_spaces:w,
+% \@@_grab_b_long_obey_spaces:w,
+% \@@_grab_b_aux:NNnw,
+% \@@_grab_b_end:Nw
+% }
+% This uses the well-tested code of \texttt{D}-type arguments,
+% skipping the peeking step because the \texttt{b}-type argument is
+% always present, and adding a cleanup stage at the end by hijacking
+% the signature. The user-provided set-up code |#3| is run in a group
+% and the clean-up consists of closing that group while properly
+% dealing with \cs{l_@@_args_tl} and also putting back the \cs{end}
+% that served as an end-delimiter: this \cs{end} receives the
+% environment name as its argument and is run normally. The
+% \texttt{D}-type code stores the argument found (body of the
+% environment) as a brace group in \cs{l_@@_args_tl} and depending on
+% the presence of a prefix~|!| we trim spaces or not before adding
+% this braced argument into \cs{l_@@_args_tl} outside the group.
+% The strange \verb*|\begin | control sequence is there for display
+% purposes only: it has to look like |\begin| in the terminal but
+% not to delimited arguments.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_grab_b:w
+ { \@@_grab_b_aux:NNnw \cs_set_protected_nopar:Npn \tl_trim_spaces:n }
+\cs_new_protected:Npn \@@_grab_b_long:w
+ { \@@_grab_b_aux:NNnw \cs_set_protected:Npn \tl_trim_spaces:n }
+\cs_new_protected:Npn \@@_grab_b_obey_spaces:w
+ { \@@_grab_b_aux:NNnw \cs_set_protected_nopar:Npn \exp_not:n }
+\cs_new_protected:Npn \@@_grab_b_long_obey_spaces:w
+ { \@@_grab_b_aux:NNnw \cs_set_protected:Npn \exp_not:n }
+\cs_new_protected:Npn \@@_grab_b_aux:NNnw #1#2#3#4 \@@_run_code:
+ {
+ \@@_grab_D_aux:NNnN \begin \end {#4} #1
+ \tl_put_left:Nn \l_@@_signature_tl { \@@_grab_b_end:Nw #2 }
+ \group_begin:
+ \tl_clear:N \l_@@_args_tl
+ #3
+ \exp_args:Nc \l_@@_fn_tl { begin ~ }
+ }
+\cs_new_protected:Npn \@@_grab_b_end:Nw #1#2 \@@_run_code:
+ {
+ \use:x
+ {
+ \group_end:
+ \tl_put_right:Nn \exp_not:N \l_@@_args_tl
+ { { \exp_after:wN #1 \l_@@_args_tl } }
+ }
+ #2
+ \@@_run_code:
+ \end
+ }
+% \end{macrocode}
+% \end{macro}
+%
% \begin{macro}{\@@_grab_D:w}
% \begin{macro}{\@@_grab_D_long:w}
% \begin{macro}{\@@_grab_D_obey_spaces:w}
@@ -4122,6 +4265,12 @@
%
% Some messages intended as errors.
% \begin{macrocode}
+\__kernel_msg_new:nnnn { xparse } { arg-after-body }
+ { In~the~definition~of~'#1',~b~(body)~argument~must~be~last. }
+ {
+ The~'body'~argument~type~is~followed~by~'#2'~in~the~argument~
+ specification~of~'#1'.~This~is~not~allowed.
+ }
\__kernel_msg_new:nnnn { xparse } { bad-arg-spec }
{ Bad~argument~specification~'#2'~for~command~'#1'. }
{
@@ -4179,6 +4328,14 @@
The~arguments~for~an~expandable~command~must~not~involve~short~
arguments~after~long~arguments.~You~have~tried~to~mix~the~two~types.
}
+\__kernel_msg_new:nnnn { xparse } { invalid-command-arg }
+ { Argument~type~'#2'~not~available~for~command~'#1'. }
+ {
+ The~letter~'#2'~can~only~be~used~in~environment~argument~
+ specifications,~not~for~commands.
+ \\ \\
+ LaTeX~will~ignore~this~entire~definition.
+ }
\__kernel_msg_new:nnnn { xparse } { invalid-expandable-argument-type }
{ Argument~type~'#2'~not~available~for~expandable~command~'#1'. }
{
More information about the latex3-commits
mailing list