diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/latex/contrib/l3kernel/l3candidates.dtx |
Initial commit
Diffstat (limited to 'macros/latex/contrib/l3kernel/l3candidates.dtx')
-rw-r--r-- | macros/latex/contrib/l3kernel/l3candidates.dtx | 4480 |
1 files changed, 4480 insertions, 0 deletions
diff --git a/macros/latex/contrib/l3kernel/l3candidates.dtx b/macros/latex/contrib/l3kernel/l3candidates.dtx new file mode 100644 index 0000000000..a92db3ebed --- /dev/null +++ b/macros/latex/contrib/l3kernel/l3candidates.dtx @@ -0,0 +1,4480 @@ +% \iffalse meta-comment +% +%% File: l3candidates.dtx +% +% Copyright (C) 2012-2019 The LaTeX3 Project +% +% It may be distributed and/or modified under the conditions of the +% LaTeX Project Public License (LPPL), either version 1.3c of this +% license or (at your option) any later version. The latest version +% of this license is in the file +% +% https://www.latex-project.org/lppl.txt +% +% This file is part of the "l3kernel bundle" (The Work in LPPL) +% and all files in that bundle must be distributed together. +% +% ----------------------------------------------------------------------- +% +% The development version of the bundle can be found at +% +% https://github.com/latex3/latex3 +% +% for those people who are interested. +% +%<*driver> +\documentclass[full,kernel]{l3doc} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +%</driver> +% \fi +% +% \title{^^A +% The \textsf{l3candidates} package\\ Experimental additions to +% \pkg{l3kernel}^^A +% } +% +% \author{^^A +% The \LaTeX3 Project\thanks +% {^^A +% E-mail: +% \href{mailto:latex-team@latex-project.org} +% {latex-team@latex-project.org}^^A +% }^^A +% } +% +% \date{Released 2019-08-25} +% +% \maketitle +% +% \begin{documentation} +% +% \section{Important notice} +% +% This module provides a space in which functions can be added to +% \pkg{l3kernel} (\pkg{expl3}) while still being experimental. +% \begin{quote} +% \bfseries +% As such, the +% functions here may not remain in their current form, or indeed at all, +% in \pkg{l3kernel} in the future. +% \end{quote} +% In contrast to the material in +% \pkg{l3experimental}, the functions here are all \emph{small} additions to +% the kernel. We encourage programmers to test them out and report back on +% the \texttt{LaTeX-L} mailing list. +% +% \medskip +% +% Thus, if you intend to use any of these functions from the candidate module in a public package +% offered to others for productive use (e.g., being placed on CTAN) please consider the following points carefully: +% \begin{itemize} +% \item Be prepared that your public packages might require updating when such functions +% are being finalized. +% \item Consider informing us that you use a particular function in your public package, e.g., by +% discussing this on the \texttt{LaTeX-L} +% mailing list. This way it becomes easier to coordinate any updates necessary without issues +% for the users of your package. +% \item Discussing and understanding use cases for a particular addition or concept also helps to +% ensure that we provide the right interfaces in the final version so please give us feedback +% if you consider a certain candidate function useful (or not). +% \end{itemize} +% We only add functions in this space if we consider them being serious candidates for a final inclusion +% into the kernel. However, real use sometimes leads to better ideas, so functions from this module are +% \textbf{not necessarily stable} and we may have to adjust them! +% +% \section{Additions to \pkg{l3basics}} +% +% \begin{function}[added = 2017-07-16, updated = 2017-08-02]{\debug_on:n, \debug_off:n} +% \begin{syntax} +% \cs{debug_on:n} |{| \meta{comma-separated list} |}| +% \cs{debug_off:n} |{| \meta{comma-separated list} |}| +% \end{syntax} +% Turn on and off within a group various debugging code, some of which +% is also available as \pkg{expl3} load-time options. The items that +% can be used in the \meta{list} are +% \begin{itemize} +% \item \texttt{check-declarations} that checks all \pkg{expl3} +% variables used were previously declared and that local/global +% variables (based on their name or on their first assignment) are +% only locally/globally assigned; +% \item \texttt{check-expressions} that checks integer, dimension, +% skip, and muskip expressions are not terminated prematurely; +% \item \texttt{deprecation} that makes soon-to-be-deprecated commands produce errors; +% \item \texttt{log-functions} that logs function definitions; +% \item \texttt{all} that does all of the above. +% \end{itemize} +% Providing these as switches rather than options allows testing code +% even if it relies on other packages: load all other packages, call +% \cs{debug_on:n}, and load the code that one is interested in +% testing. These functions can only be used in \LaTeXe{} package mode +% loaded with \texttt{enable-debug} or another option implying it. +% \end{function} +% +% \begin{function}[added = 2017-11-28]{\debug_suspend:, \debug_resume:} +% \begin{syntax} +% \cs{debug_suspend:} \ldots{} \cs{debug_resume:} +% \end{syntax} +% Suppress (locally) errors and logging from \texttt{debug} commands, +% except for the \texttt{deprecation} errors or warnings. These pairs +% of commands can be nested. This can be used around pieces of code +% that are known to fail checks, if such failures should be ignored. +% See for instance \pkg{l3coffins}. +% \end{function} +% +% \begin{function}[added = 2017-07-04]{\mode_leave_vertical:} +% \begin{syntax} +% \cs{mode_leave_vertical:} +% \end{syntax} +% Ensures that \TeX{} is not in vertical (inter-paragraph) mode. In +% horizontal or math mode this command has no effect, in vertical mode it +% switches to horizontal mode, and inserts a box of width +% \tn{parindent}, followed by the \tn{everypar} token list. +% \begin{texnote} +% This results in the contents of the \tn{everypar} token register being +% inserted, after \cs{mode_leave_vertical:} is complete. Notice that in +% contrast to the \LaTeXe{} \tn{leavevmode} approach, no box is used +% by the method implemented here. +% \end{texnote} +% \end{function} +% +% \section{Additions to \pkg{l3box}} +% +% \subsection{Viewing part of a box} +% +% \begin{function}[updated = 2019-01-23] +% {\box_clip:N, \box_clip:c, \box_gclip:N, \box_gclip:c} +% \begin{syntax} +% \cs{box_clip:N} \meta{box} +% \end{syntax} +% Clips the \meta{box} in the output so that only material inside the +% bounding box is displayed in the output. The updated \meta{box} is an +% hbox, irrespective of the nature of the \meta{box} before the clipping is +% applied. +% +% \textbf{These functions require the \LaTeX3 native drivers: they do +% not work with the \LaTeXe{} \pkg{graphics} drivers!} +% +% \begin{texnote} +% Clipping is implemented by the driver, and as such the full content of +% the box is placed in the output file. Thus clipping does not remove +% any information from the raw output, and hidden material can therefore +% be viewed by direct examination of the file. +% \end{texnote} +% \end{function} +% +% \begin{function}[added = 2019-01-23] +% { +% \box_set_trim:Nnnnn, \box_set_trim:cnnnn, +% \box_gset_trim:Nnnnn, \box_gset_trim:cnnnn +% } +% \begin{syntax} +% \cs{box_set_trim:Nnnnn} \meta{box} \Arg{left} \Arg{bottom} \Arg{right} \Arg{top} +% \end{syntax} +% Adjusts the bounding box of the \meta{box} \meta{left} is removed from +% the left-hand edge of the bounding box, \meta{right} from the right-hand +% edge and so fourth. All adjustments are \meta{dimension expressions}. +% Material outside of the bounding box is still displayed in the output +% unless \cs{box_clip:N} is subsequently applied. +% The updated \meta{box} is an +% hbox, irrespective of the nature of the \meta{box} before the trim +% operation is applied. +% The behavior of the operation where the trims requested is +% greater than the size of the box is undefined. +% \end{function} +% +% \begin{function}[added = 2019-01-23] +% { +% \box_set_viewport:Nnnnn, \box_set_viewport:cnnnn, +% \box_gset_viewport:Nnnnn, \box_gset_viewport:cnnnn +% } +% \begin{syntax} +% \cs{box_set_viewport:Nnnnn} \meta{box} \Arg{llx} \Arg{lly} \Arg{urx} \Arg{ury} +% \end{syntax} +% Adjusts the bounding box of the \meta{box} such that it has lower-left +% co-ordinates (\meta{llx}, \meta{lly}) and upper-right co-ordinates +% (\meta{urx}, \meta{ury}). All four co-ordinate positions are +% \meta{dimension expressions}. Material outside of the bounding box is +% still displayed in the output unless \cs{box_clip:N} is +% subsequently applied. +% The updated \meta{box} is an +% hbox, irrespective of the nature of the \meta{box} before the viewport +% operation is applied. +% \end{function} +% +% \section{Additions to \pkg{l3expan}} +% +% \begin{function}[added = 2018-04-04, updated = 2019-02-08] +% {\exp_args_generate:n} +% \begin{syntax} +% \cs{exp_args_generate:n} \Arg{variant argument specifiers} +% \end{syntax} +% Defines \cs[no-index]{exp_args:N\meta{variant}} functions for each +% \meta{variant} given in the comma list \Arg{variant argument +% specifiers}. Each \meta{variant} should consist of the letters |N|, +% |c|, |n|, |V|, |v|, |o|, |f|, |e|, |x|, |p| and the resulting function is +% protected if the letter |x| appears in the \meta{variant}. This is +% only useful for cases where \cs{cs_generate_variant:Nn} is not +% applicable. +% \end{function} +% +% \section{Additions to \pkg{l3fp}} +% +% \begin{function}[EXP, added = 2018-11-03]{logb} +% \begin{syntax} +% \cs{fp_eval:n} \{ |logb(| \meta{fpexpr} |)| \} +% \end{syntax} +% Determines the exponent of the \meta{fpexpr}, namely the floor of +% the base-$10$ logarithm of its absolute value. \enquote{Division by +% zero} occurs when evaluating $\operatorname{logb}(\pm 0) = -\infty$. +% Other special values are $\operatorname{logb}(\pm\infty)=+\infty$ +% and $\operatorname{logb}(\nan{})=\nan{}$. If the operand is a tuple +% or is \nan{}, then \enquote{invalid operation} occurs and the result +% is \nan{}. +% \end{function} +% +% \begin{function}[EXP, added = 2018-11-03]{\fp_sign:n} +% \begin{syntax} +% \cs{fp_sign:n} \Arg{fpexpr} +% \end{syntax} +% Evaluates the \meta{fpexpr} and leaves its sign in the input stream +% using \cs{fp_eval:n} |{sign(|\meta{result}|)}|: $+1$ for positive +% numbers and for $+\infty$, $-1$ for negative numbers and for +% $-\infty$, $\pm 0$ for $\pm 0$. If the operand is a tuple or is +% \nan{}, then \enquote{invalid operation} occurs and the result +% is~$0$. +% \end{function} +% +% \begin{function}[pTF, added = 2019-08-25]{\fp_if_nan:n} +% \begin{syntax} +% \cs{fp_if_nan:n} \Arg{fpexpr} +% \end{syntax} +% Evaluates the \meta{fpexpr} and tests whether the result is exactly +% \nan{}. The test returns \texttt{false} for any other result, even +% a tuple containing \nan{}. +% \end{function} +% +% \section{Additions to \pkg{l3fparray}} +% +% \begin{function}[added = 2018-05-05]{\fparray_new:Nn} +% \begin{syntax} +% \cs{fparray_new:Nn} \meta{fparray~var} \Arg{size} +% \end{syntax} +% Evaluates the integer expression \meta{size} and allocates an +% \meta{floating point array variable} with that number of (zero) +% entries. The variable name should start with |\g_| because +% assignments are always global. +% \end{function} +% +% \begin{function}[EXP, added = 2018-05-05]{\fparray_count:N} +% \begin{syntax} +% \cs{fparray_count:N} \meta{fparray~var} +% \end{syntax} +% Expands to the number of entries in the \meta{floating point array +% variable}. This is performed in constant time. +% \end{function} +% +% \begin{function}[added = 2018-05-05]{\fparray_gset:Nnn} +% \begin{syntax} +% \cs{fparray_gset:Nnn} \meta{fparray~var} \Arg{position} \Arg{value} +% \end{syntax} +% Stores the result of evaluating the floating point expression +% \meta{value} into the \meta{floating point array variable} at the +% (integer expression) \meta{position}. If the \meta{position} is not +% between $1$ and the \cs{fparray_count:N}, an error occurs. +% Assignments are always global. +% \end{function} +% +% \begin{function}[added = 2018-05-05]{\fparray_gzero:N} +% \begin{syntax} +% \cs{fparray_gzero:N} \meta{fparray~var} +% \end{syntax} +% Sets all entries of the \meta{floating point array variable} to +% $+0$. Assignments are always global. +% \end{function} +% +% \begin{function}[EXP, added = 2018-05-05] +% {\fparray_item:Nn, \fparray_item_to_tl:Nn} +% \begin{syntax} +% \cs{fparray_item:Nn} \meta{fparray~var} \Arg{position} +% \end{syntax} +% Applies \cs{fp_use:N} or \cs{fp_to_tl:N} (respectively) to the +% floating point entry stored at the (integer expression) +% \meta{position} in the \meta{floating point array variable}. If the +% \meta{position} is not between $1$ and the \cs{fparray_count:N}, an +% error occurs. +% \end{function} +% +% \section{Additions to \pkg{l3file}} +% +% \begin{function}[added = 2019-05-13, TF]{\file_compare_timestamp:nNn} +% \begin{syntax} +% \cs{file_compare_timestamp:nNn} \Arg{file-1} \meta{comparator} \Arg{file-2} \Arg{true code} \Arg{false code} +% \end{syntax} +% Compares the file stamps on the two \meta{files} as indicated by +% the \meta{comparator}, and inserts either the \meta{true code} +% or \meta{false case} as required. A file which is not found +% is treated as older than any file which is found.This allows for +% example the construct +% \begin{verbatim} +% \file_compare_timestamp:nNnT { source-file } > { derived-file } +% { +% % Code to regenerate derived file +% } +% \end{verbatim} +% to work when the derived file is entirely absent. The timestamp +% of two absent files is regarded as different. +% \end{function} +% +% \begin{function}[added = 2019-01-13]{\ior_map_variable:NNn} +% \begin{syntax} +% \cs{ior_map_variable:NNn} \meta{stream} \meta{tl~var} \Arg{code} +% \end{syntax} +% For each set of \meta{lines} obtained by calling \cs{ior_get:NN} +% until reaching the end of the file, stores the \meta{lines} in the +% \meta{tl~var} then applies the \meta{code}. The \meta{code} will +% usually make use of the \meta{variable}, but this is not enforced. +% The assignments to the \meta{variable} are local. +% Its value after the loop is the last set of \meta{lines}, or its +% original value if the \meta{stream} is empty. \TeX{} ignores +% any trailing new-line marker from the file it reads. +% This function is typically faster than \cs{ior_map_inline:Nn}. +% \end{function} +% +% \begin{function}[added = 2019-01-13]{\ior_str_map_variable:NNn} +% \begin{syntax} +% \cs{ior_str_map_variable:NNn} \meta{stream} \meta{variable} \Arg{code} +% \end{syntax} +% For each \meta{line} in the \meta{stream}, stores the \meta{line} in +% the \meta{variable} then applies the \meta{code}. The material is +% read from the \meta{stream} as a series of tokens with category code +% $12$ (other), with the exception of space characters which are given +% category code $10$ (space). The \meta{code} will usually make use +% of the \meta{variable}, but this is not enforced. The assignments +% to the \meta{variable} are local. Its value after the loop is the +% last \meta{line}, or its original value if the \meta{stream} is +% empty. Note that \TeX{} removes trailing +% space and tab characters (character codes 32 and 9) from every line +% upon input. \TeX{} also ignores any trailing new-line marker from +% the file it reads. +% This function is typically faster than \cs{ior_str_map_inline:Nn}. +% \end{function} +% +% \begin{function}[added = 2018-12-29]{\iow_allow_break:} +% \begin{syntax} +% \cs{iow_allow_break:} +% \end{syntax} +% In the first argument of \cs{iow_wrap:nnnN} (for instance in +% messages), inserts a break-point that allows a line break. +% In other words this is a zero-width breaking space. +% \end{function} +% +% \begin{function}[added = 2019-03-23]{\ior_get_term:nN, \ior_str_get_term:nN} +% \begin{syntax} +% \cs{ior_get_term:nN} \meta{prompt} \meta{token list variable} +% \end{syntax} +% Function that reads one or more lines (until an equal number of left +% and right braces are found) from the terminal and stores +% the result locally in the \meta{token list} variable. Tokenization +% occurs as described for \cs{ior_get:NN} or \cs{ior_str_get:NN}, respectively. +% When the \meta{prompt} +% is empty, \TeX{} will wait for input without any other indication: +% typically the programmer will have provided a suitable text using +% e.g.~\cs{iow_term:n}. Where the \meta{prompt} is given, it will appear +% in the terminal followed by an |=|, e.g. +% \begin{verbatim} +% prompt= +% \end{verbatim} +% \end{function} +% +% \begin{function}[added = 2019-05-08]{\ior_shell_open:Nn} +% \begin{syntax} +% \cs{ior_shell_open:nN} \meta{stream} \Arg{shell~command} +% \end{syntax} +% Opens the \emph{pseudo}-file created by the output of the +% \meta{shell command} for reading using \meta{stream} as the +% control sequence for access. If the \meta{stream} was already +% open it is closed before the new operation begins. The +% \meta{stream} is available for access immediately and will remain +% allocated to \meta{shell command} until a \cs{ior_close:N} instruction +% is given or the \TeX{} run ends. +% If piped system calls are disabled an error is raised. +% +% For details of handling of the \meta{shell command}, see +% \cs{sys_shell_get:nnN(TF)}. +% \end{function} +% +% \section{Additions to \pkg{l3flag}} +% +% \begin{function}[EXP, added = 2018-04-02]{\flag_raise_if_clear:n} +% \begin{syntax} +% \cs{flag_raise_if_clear:n} \Arg{flag name} +% \end{syntax} +% Ensures the \meta{flag} is raised by making its height at least~$1$, +% locally. +% \end{function} +% +% \section{Additions to \pkg{l3int}} +% +% \begin{function}[EXP, added = 2018-11-03]{\int_sign:n} +% \begin{syntax} +% \cs{int_sign:n} \Arg{intexpr} +% \end{syntax} +% Evaluates the \meta{integer expression} then leaves $1$ or $0$ or +% $-1$ in the input stream according to the sign of the result. +% \end{function} +% +% \section{Additions to \pkg{l3intarray}} +% +% \begin{function}[added = 2018-05-05]{\intarray_gset_rand:Nnn, \intarray_gset_rand:Nn} +% \begin{syntax} +% \cs{intarray_gset_rand:Nnn} \meta{intarray~var} \Arg{minimum} \Arg{maximum} +% \cs{intarray_gset_rand:Nn} \meta{intarray~var} \Arg{maximum} +% \end{syntax} +% Evaluates the integer expressions \meta{minimum} and \meta{maximum} +% then sets each entry (independently) of the \meta{integer array +% variable} to a pseudo-random number between the two (with bounds +% included). If the absolute value of either bound is bigger than +% $2^{30}-1$, an error occurs. Entries are generated in the same way +% as repeated calls to \cs{int_rand:nn} or \cs{int_rand:n} +% respectively, in particular for the second function the +% \meta{minimum} is $1$. +% Assignments are always global. +% This is not available in older versions of \XeTeX{}. +% \end{function} +% +% \subsection{Working with contents of integer arrays} +% +% \begin{function}[added = 2018-05-04, rEXP]{\intarray_const_from_clist:Nn} +% \begin{syntax} +% \cs{intarray_const_from_clist:Nn} \meta{intarray~var} \meta{intexpr clist} +% \end{syntax} +% Creates a new constant \meta{integer array variable} or raises an +% error if the name is already taken. The \meta{integer array +% variable} is set (globally) to contain as its items the results of +% evaluating each \meta{integer expression} in the \meta{comma list}. +% \end{function} +% +% \begin{function}[added = 2018-05-04, rEXP]{\intarray_to_clist:N} +% \begin{syntax} +% \cs{intarray_to_clist:N} \meta{intarray~var} +% \end{syntax} +% Converts the \meta{intarray} to integer denotations separated by +% commas. All tokens have category code other. If the +% \meta{intarray} has no entry the result is empty; otherwise the +% result has one fewer comma than the number of items. +% \end{function} +% +% \begin{function}[added = 2018-05-04]{\intarray_show:N, \intarray_log:N} +% \begin{syntax} +% \cs{intarray_show:N} \meta{intarray~var} +% \cs{intarray_log:N} \meta{intarray~var} +% \end{syntax} +% Displays the items in the \meta{integer array variable} in the +% terminal or writes them in the log file. +% \end{function} +% +% \section{Additions to \pkg{l3msg}} +% +% In very rare cases it may be necessary to produce errors in an +% expansion-only context. The functions in this section should only be +% used if there is no alternative approach using \cs{msg_error:nnnnnn} +% or other non-expandable commands from the previous section. Despite +% having a similar interface as non-expandable messages, expandable +% errors must be handled internally very differently from normal error +% messages, as none of the tools to print to the terminal or the log +% file are expandable. As a result, short-hands such as |\{| or |\\| do +% not work, and messages must be very short (with default settings, +% they are truncated after approximately 50 characters). It is +% advisable to ensure that the message is understandable even when +% truncated, by putting the most important information up front. +% Another particularity of expandable messages is that they +% cannot be redirected or turned off by the user. +% +% \begin{function}[EXP, added = 2015-08-06, updated = 2019-02-28] +% { +% \msg_expandable_error:nnnnnn , +% \msg_expandable_error:nnnnn , +% \msg_expandable_error:nnnn , +% \msg_expandable_error:nnn , +% \msg_expandable_error:nn , +% \msg_expandable_error:nnffff , +% \msg_expandable_error:nnfff , +% \msg_expandable_error:nnff , +% \msg_expandable_error:nnf , +% } +% \begin{syntax} +% \cs{msg_expandable_error:nnnnnn} \Arg{module} \Arg{message} \Arg{arg one} \Arg{arg two} \Arg{arg three} \Arg{arg four} +% \end{syntax} +% Issues an \enquote{Undefined error} message from \TeX{} itself +% using the undefined control sequence \cs{::error} then prints +% \enquote{! \meta{module}: }\meta{error message}, which should be +% short. With default settings, anything beyond approximately $60$ +% characters long (or bytes in some engines) is cropped. A leading +% space might be removed as well. +% \end{function} +% +% \begin{function}[added = 2017-12-04]{\msg_show_eval:Nn, \msg_log_eval:Nn} +% \begin{syntax} +% \cs{msg_show_eval:Nn} \meta{function} \Arg{expression} +% \end{syntax} +% Shows or logs the \meta{expression} (turned into a string), an equal +% sign, and the result of applying the \meta{function} to the +% \Arg{expression} (with \texttt{f}-expansion). For instance, if the +% \meta{function} is \cs{int_eval:n} and the \meta{expression} is +% |1+2| then this logs |> 1+2=3.| +% \end{function} +% +% \begin{function}[added = 2017-12-04] +% { +% \msg_show:nnnnnn , +% \msg_show:nnnnn , +% \msg_show:nnnn , +% \msg_show:nnn , +% \msg_show:nn , +% \msg_show:nnxxxx , +% \msg_show:nnxxx , +% \msg_show:nnxx , +% \msg_show:nnx +% } +% \begin{syntax} +% \cs{msg_show:nnnnnn} \Arg{module} \Arg{message} \Arg{arg one} \Arg{arg two} \Arg{arg three} \Arg{arg four} +% \end{syntax} +% Issues \meta{module} information \meta{message}, passing \meta{arg +% one} to \meta{arg four} to the text-creating functions. The +% information text is shown on the terminal and the \TeX{} run is +% interrupted in a manner similar to \cs{tl_show:n}. This is used in +% conjunction with \cs{msg_show_item:n} and similar functions to print +% complex variable contents completely. If the formatted text does +% not contain |>~| at the start of a line, an additional line |>~.| +% will be put at the end. In addition, a final period is added if not +% present. +% \end{function} +% +% \begin{function}[EXP, added = 2017-12-04] +% {\msg_show_item:n, \msg_show_item_unbraced:n, \msg_show_item:nn, \msg_show_item_unbraced:nn} +% \begin{syntax} +% \cs{seq_map_function:NN} \meta{seq} \cs{msg_show_item:n} +% \cs{prop_map_function:NN} \meta{prop} \cs{msg_show_item:nn} +% \end{syntax} +% Used in the text of messages for \cs{msg_show:nnxxxx} to show or log +% a list of items or key--value pairs. The one-argument functions are +% used for sequences, clist or token lists and the others for property +% lists. These functions turn their arguments to strings. +% \end{function} +% +% \section{Additions to \pkg{l3prg}} +% +% \begin{function}[added = 2017-11-28]{\bool_const:Nn, \bool_const:cn} +% \begin{syntax} +% \cs{bool_const:Nn} \meta{boolean} \Arg{boolexpr} +% \end{syntax} +% Creates a new constant \meta{boolean} or raises an error if the name +% is already taken. The value of the \meta{boolean} is set globally to +% the result of evaluating the \meta{boolexpr}. +% \end{function} +% +% \begin{function}[added = 2018-05-10] +% { +% \bool_set_inverse:N , \bool_set_inverse:c , +% \bool_gset_inverse:N, \bool_gset_inverse:c +% } +% \begin{syntax} +% \cs{bool_set_inverse:N} \meta{boolean} +% \end{syntax} +% Toggles the \meta{boolean} from \texttt{true} to \texttt{false} and +% conversely: sets it to the inverse of its current value. +% \end{function} +% +% \section{Additions to \pkg{l3prop}} +% +% \begin{function}[rEXP] +% {\prop_map_tokens:Nn, \prop_map_tokens:cn} +% \begin{syntax} +% \cs{prop_map_tokens:Nn} \meta{property list} \Arg{code} +% \end{syntax} +% Analogue of \cs{prop_map_function:NN} which maps several tokens +% instead of a single function. The \meta{code} receives each +% key--value pair in the \meta{property list} as two trailing brace +% groups. For instance, +% \begin{verbatim} +% \prop_map_tokens:Nn \l_my_prop { \str_if_eq:nnT { mykey } } +% \end{verbatim} +% expands to the value corresponding to \texttt{mykey}: for each +% pair in |\l_my_prop| the function \cs{str_if_eq:nnT} receives +% \texttt{mykey}, the \meta{key} and the \meta{value} as its three +% arguments. For that specific task, \cs{prop_item:Nn} is faster. +% \end{function} +% +% \begin{function}[EXP, added = 2016-12-06] +% {\prop_rand_key_value:N, \prop_rand_key_value:c} +% \begin{syntax} +% \cs{prop_rand_key_value:N} \meta{prop~var} +% \end{syntax} +% Selects a pseudo-random key--value pair from the \meta{property list} +% and returns \Arg{key} and \Arg{value}. If the \meta{property list} is +% empty the result is empty. +% This is not available in older versions of \XeTeX{}. +% \begin{texnote} +% The result is returned within the \tn{unexpanded} +% primitive (\cs{exp_not:n}), which means that the \meta{value} +% does not expand further when appearing in an \texttt{x}-type +% argument expansion. +% \end{texnote} +% \end{function} +% +% \begin{function}[added = 2017-11-28, updated = 2019-08-25] +% { +% \prop_set_from_keyval:Nn, \prop_set_from_keyval:cn, +% \prop_gset_from_keyval:Nn, \prop_gset_from_keyval:cn, +% } +% \begin{syntax} +% \cs{prop_set_from_keyval:Nn} \meta{prop~var} +% \{ +% \meta{key1} |=| \meta{value1} |,| +% \meta{key2} |=| \meta{value2} |,| \ldots{} +% \} +% \end{syntax} +% Sets \meta{prop~var} to contain key--value pairs given in the second +% argument. If duplicate keys appear only one of the values is kept. +% \end{function} +% +% \begin{function}[added = 2017-11-28, updated = 2019-08-25] +% {\prop_const_from_keyval:Nn, \prop_const_from_keyval:cn} +% \begin{syntax} +% \cs{prop_const_from_keyval:Nn} \meta{prop~var} +% \{ +% \meta{key1} |=| \meta{value1} |,| +% \meta{key2} |=| \meta{value2} |,| \ldots{} +% \} +% \end{syntax} +% Creates a new constant \meta{prop~var} or raises an error if the +% name is already taken. The \meta{prop~var} is set globally to +% contain key--value pairs given in the second argument. +% If duplicate keys appear only one of the values is kept. +% \end{function} +% +% \section{Additions to \pkg{l3seq}} +% +% \begin{function}[rEXP] +% { +% \seq_mapthread_function:NNN, \seq_mapthread_function:NcN, +% \seq_mapthread_function:cNN, \seq_mapthread_function:ccN +% } +% \begin{syntax} +% \cs{seq_mapthread_function:NNN} \meta{seq_1} \meta{seq_2} \meta{function} +% \end{syntax} +% Applies \meta{function} to every pair of items +% \meta{seq_1-item}--\meta{seq_2-item} from the two sequences, returning +% items from both sequences from left to right. The \meta{function} +% receives two \texttt{n}-type arguments for each iteration. The mapping +% terminates when +% the end of either sequence is reached (\emph{i.e.}~whichever sequence has +% fewer items determines how many iterations +% occur). +% \end{function} +% +% \begin{function}{\seq_set_filter:NNn, \seq_gset_filter:NNn} +% \begin{syntax} +% \cs{seq_set_filter:NNn} \meta{sequence_1} \meta{sequence_2} \Arg{inline boolexpr} +% \end{syntax} +% Evaluates the \meta{inline boolexpr} for every \meta{item} stored +% within the \meta{sequence_2}. The \meta{inline boolexpr} +% receives the \meta{item} as |#1|. The sequence of all \meta{items} +% for which the \meta{inline boolexpr} evaluated to \texttt{true} +% is assigned to \meta{sequence_1}. +% \begin{texnote} +% Contrarily to other mapping functions, \cs{seq_map_break:} cannot +% be used in this function, and would lead to low-level \TeX{} errors. +% \end{texnote} +% \end{function} +% +% \begin{function}[added = 2011-12-22] +% {\seq_set_map:NNn, \seq_gset_map:NNn} +% \begin{syntax} +% \cs{seq_set_map:NNn} \meta{sequence_1} \meta{sequence_2} \Arg{inline function} +% \end{syntax} +% Applies \meta{inline function} to every \meta{item} stored +% within the \meta{sequence_2}. The \meta{inline function} should +% consist of code which will receive the \meta{item} as |#1|. +% The sequence resulting from \texttt{x}-expanding +% \meta{inline function} applied to each \meta{item} +% is assigned to \meta{sequence_1}. As such, the code +% in \meta{inline function} should be expandable. +% \begin{texnote} +% Contrarily to other mapping functions, \cs{seq_map_break:} cannot +% be used in this function, and would lead to low-level \TeX{} errors. +% \end{texnote} +% \end{function} +% +% \begin{function}[added = 2017-11-28] +% {\seq_const_from_clist:Nn, \seq_const_from_clist:cn} +% \begin{syntax} +% \cs{seq_const_from_clist:Nn} \meta{seq~var} \Arg{comma-list} +% \end{syntax} +% Creates a new constant \meta{seq~var} or raises an error if the name +% is already taken. The \meta{seq~var} is set globally to contain the +% items in the \meta{comma list}. +% \end{function} +% +% \begin{function}[added = 2018-04-06] +% {\seq_set_from_function:NnN, \seq_gset_from_function:NnN} +% \begin{syntax} +% \cs{seq_set_from_function:NnN} \meta{seq~var} \Arg{loop~code} \meta{function} +% \end{syntax} +% Sets the \meta{seq~var} equal to a sequence whose items are obtained +% by \texttt{x}-expanding \meta{loop~code} \meta{function}. This +% expansion must result in successive calls to the \meta{function} +% with no nonexpandable tokens in between. More precisely the +% \meta{function} is replaced by a wrapper function that inserts the +% appropriate separators between items in the sequence. The +% \meta{loop~code} must be expandable; it can be for example +% \cs{tl_map_function:NN} \meta{tl~var} or \cs{clist_map_function:nN} +% \Arg{clist} or \cs{int_step_function:nnnN} \Arg{initial value} +% \Arg{step} \Arg{final value}. +% \end{function} +% +% \begin{function}[added = 2018-04-06] +% {\seq_set_from_inline_x:Nnn, \seq_gset_from_inline_x:Nnn} +% \begin{syntax} +% \cs{seq_set_from_inline_x:Nnn} \meta{seq~var} \Arg{loop~code} \Arg{inline~code} +% \end{syntax} +% Sets the \meta{seq~var} equal to a sequence whose items are obtained +% by \texttt{x}-expanding \meta{loop~code} applied to a +% \meta{function} derived from the \meta{inline~code}. A +% \meta{function} is defined, that takes one argument, +% \texttt{x}-expands the \meta{inline~code} with that argument +% as~|#1|, then adds appropriate separators to turn the result into an +% item of the sequence. The \texttt{x}-expansion of \meta{loop~code} +% \meta{function} must result in successive calls to the +% \meta{function} with no nonexpandable tokens in between. The +% \meta{loop~code} must be expandable; it can be for example +% \cs{tl_map_function:NN} \meta{tl~var} or \cs{clist_map_function:nN} +% \Arg{clist} or \cs{int_step_function:nnnN} \Arg{initial value} +% \Arg{step} \Arg{final value}, but not the analogous \enquote{inline} +% mappings. +% \end{function} +% +% \begin{function}[added = 2018-04-29]{\seq_shuffle:N, \seq_gshuffle:N} +% \begin{syntax} +% \cs{seq_shuffle:N} \meta{seq~var} +% \end{syntax} +% Sets the \meta{seq~var} to the result of placing the items of the +% \meta{seq~var} in a random order. Each item is (roughly) as likely +% to end up in any given position. +% \begin{texnote} +% For sequences with more than $13$ items or so, only a small +% proportion of all possible permutations can be reached, because +% the random seed \cs{sys_rand_seed:} only has $28$-bits. The use +% of \tn{toks} internally means that sequences with more than +% $32767$ or $65535$ items (depending on the engine) cannot be +% shuffled. +% \end{texnote} +% \end{function} +% +% \begin{function}[added = 2018-05-03]{\seq_indexed_map_function:NN} +% \begin{syntax} +% \cs{seq_indexed_map_function:NN} \meta{seq~var} \meta{function} +% \end{syntax} +% Applies \meta{function} to every entry in the \meta{sequence +% variable}. The \meta{function} should have signature |:nn|. It +% receives two arguments for each iteration: the \meta{index} (namely +% |1| for the first entry, then |2| and so on) and the \meta{item}. +% \end{function} +% +% \begin{function}[added = 2018-05-03]{\seq_indexed_map_inline:Nn} +% \begin{syntax} +% \cs{seq_indexed_map_inline:Nn} \meta{seq~var} \Arg{inline function} +% \end{syntax} +% Applies \meta{inline function} to every entry in the \meta{sequence +% variable}. The \meta{inline function} should consist of code which +% receives the \meta{index} (namely |1| for the first entry, then |2| +% and so on) as~|#1| and the \meta{item} as~|#2|. +% \end{function} +% +% \section{Additions to \pkg{l3skip}} +% +% \begin{function}[EXP, added = 2018-11-03]{\dim_sign:n} +% \begin{syntax} +% \cs{dim_sign:n} \Arg{dimexpr} +% \end{syntax} +% Evaluates the \meta{dimexpr} then leaves $1$ or $0$ or $-1$ in the +% input stream according to the sign of the result. +% \end{function} +% +% \section{Additions to \pkg{l3sys}} +% +% \begin{function}[noTF, added = 2019-01-16, updated = 2019-02-16] +% {\sys_shell_get:nnN} +% \begin{syntax} +% \cs{sys_shell_get:nnN} \Arg{shell~command} \Arg{setup} \meta{tl~var} +% \cs{sys_shell_get:nnNTF} \Arg{shell~command} \Arg{setup} \meta{tl~var} \Arg{true code} \Arg{false code} +% \end{syntax} +% Defines \meta{tl} to the text returned by the \meta{shell command}. +% The \meta{shell command} is converted to a string using +% \cs{tl_to_str:n}. Category codes may need to be set appropriately +% via the \meta{setup} argument, which is run just before running the +% \meta{shell command} (in a group). +% If shell escape is disabled, the \meta{tl~var} will be set to +% \cs{q_no_value} in the non-branching version. +% Note that quote characters (|"|) \emph{cannot} be used inside the +% \meta{shell command}. The \cs{sys_shell_get:nnNTF} conditional +% returns \texttt{true} if the shell is available and no quote is +% detected, and \texttt{false} otherwise. +% \end{function} +% +% \begin{variable}[added = 2018-05-02]{\c_sys_engine_version_str} +% The version string of the current engine, in the same form as +% given in the banner issued when running a job. For \pdfTeX{} +% and \LuaTeX{} this is of the form +% \begin{quote} +% \meta{major}.\meta{minor}.\meta{revision} +% \end{quote} +% For \XeTeX{}, the form is +% \begin{quote} +% \meta{major}.\meta{minor} +% \end{quote} +% For \pTeX{} and \upTeX{}, only releases since \TeX{} Live 2018 +% make the data available, and the form is more complex, as it comprises +% the \pTeX{} version, the \upTeX{} version and the e-\pTeX{} version. +% \begin{quote} +% p\meta{major}.\meta{minor}.\meta{revision}-u\meta{major}.\meta{minor}^^A +% -\meta{epTeX} +% \end{quote} +% where the |u| part is only present for \upTeX{}. +% \end{variable} +% +% \begin{function}[added = 2017-05-27, EXP, pTF]{\sys_if_rand_exist:} +% \begin{syntax} +% \cs{sys_if_rand_exist_p:} +% \cs{sys_if_rand_exist:TF} \Arg{true code} \Arg{false code} +% \end{syntax} +% Tests if the engine has a pseudo-random number generator. Currently +% this is the case in \pdfTeX{}, \LuaTeX{}, \pTeX{} and \upTeX{}. +% \end{function} +% +% \begin{function}[added = 2017-05-27, EXP]{\sys_rand_seed:} +% \begin{syntax} +% \cs{sys_rand_seed:} +% \end{syntax} +% Expands to the current value of the engine's random seed, a +% non-negative integer. In engines without random number support this +% expands to $0$. +% \end{function} +% +% \begin{function}[added = 2017-05-27]{\sys_gset_rand_seed:n} +% \begin{syntax} +% \cs{sys_gset_rand_seed:n} \Arg{intexpr} +% \end{syntax} +% Globally sets the seed for the engine's pseudo-random number +% generator to the \meta{integer expression}. This random seed +% affects all \cs[no-index]{\ldots{}_rand} functions (such as +% \cs{int_rand:nn} or \cs{clist_rand_item:n}) as well as other +% packages relying on the engine's random number generator. In +% engines without random number support this produces an error. +% \begin{texnote} +% While a $32$-bit (signed) integer can be given as a seed, only the +% absolute value is used and any number beyond $2^{28}$ is divided +% by an appropriate power of~$2$. We recommend using an integer in +% $[0,2^{28}-1]$. +% \end{texnote} +% \end{function} +% +% \begin{function}[added = 2018-07-27, EXP, pTF] +% { +% \sys_if_platform_unix:, +% \sys_if_platform_windows: +% } +% \begin{syntax} +% \cs{sys_if_platform_unix:TF} \Arg{true code} \Arg{false code} +% \end{syntax} +% Conditionals which allow platform-specific code to be used. The names +% follow the \Lua{} |os.type()| function, \emph{i.e.}~all Unix-like systems +% are |unix| (including Linux and MacOS). +% \end{function} +% +% \begin{variable}[added = 2018-07-27]{\c_sys_platform_str} +% The current platform given as a lower case string: one of +% |unix|, |windows| or |unknown|. +% \end{variable} +% +% \begin{variable}[added = 2017-05-27]{\c_sys_shell_escape_int} +% This variable exposes the internal triple of the shell escape +% status. The possible values are +% \begin{description} +% \item[0] Shell escape is disabled +% \item[1] Unrestricted shell escape is enabled +% \item[2] Restricted shell escape is enabled +% \end{description} +% \end{variable} +% +% \begin{function}[added = 2017-05-27, EXP, pTF]{\sys_if_shell:} +% \begin{syntax} +% \cs{sys_if_shell_p:} +% \cs{sys_if_shell:TF} \Arg{true code} \Arg{false code} +% \end{syntax} +% Performs a check for whether shell escape is enabled. This +% returns true if either of restricted or unrestricted shell escape +% is enabled. +% \end{function} +% +% \begin{function}[added = 2017-05-27, EXP, pTF]{\sys_if_shell_unrestricted:} +% \begin{syntax} +% \cs{sys_if_shell_unrestricted_p:} +% \cs{sys_if_shell_unrestricted:TF} \Arg{true code} \Arg{false code} +% \end{syntax} +% Performs a check for whether \emph{unrestricted} shell escape is +% enabled. +% \end{function} +% +% \begin{function}[added = 2017-05-27, EXP, pTF]{\sys_if_shell_restricted:} +% \begin{syntax} +% \cs{sys_if_shell_restricted_p:} +% \cs{sys_if_shell_restricted:TF} \Arg{true code} \Arg{false code} +% \end{syntax} +% Performs a check for whether \emph{restricted} shell escape is +% enabled. This returns false if unrestricted shell escape is +% enabled. Unrestricted shell escape is not considered a superset +% of restricted shell escape in this case. To find whether any +% shell escape is enabled use \cs{sys_if_shell:}. +% \end{function} +% +% \begin{function}[added = 2017-05-27]{\sys_shell_now:n, \sys_shell_now:x} +% \begin{syntax} +% \cs{sys_shell_now:n} \Arg{tokens} +% \end{syntax} +% Execute \meta{tokens} through shell escape immediately. +% \end{function} +% +% \begin{function}[added = 2017-05-27]{\sys_shell_shipout:n, \sys_shell_shipout:x} +% \begin{syntax} +% \cs{sys_shell_shipout:n} \Arg{tokens} +% \end{syntax} +% Execute \meta{tokens} through shell escape at shipout. +% \end{function} +% +% \section{Additions to \pkg{l3tl}} +% +% \begin{function}[EXP, added = 2014-06-30, updated = 2016-01-12] +% { +% \tl_lower_case:n, \tl_upper_case:n, \tl_mixed_case:n, +% \tl_lower_case:nn, \tl_upper_case:nn, \tl_mixed_case:nn +% } +% \begin{syntax} +% \cs{tl_upper_case:n} \Arg{tokens} +% \cs{tl_upper_case:nn} \Arg{language} \Arg{tokens} +% \end{syntax} +% These functions are intended to be applied to input which may be +% regarded broadly as \enquote{text}. They traverse the \meta{tokens} and +% change the case of characters as discussed below. The character code of +% the characters replaced may be arbitrary: the replacement characters +% have standard document-level category codes ($11$ for letters, $12$ for +% letter-like characters which can also be case-changed). Begin-group and +% end-group characters in the \meta{tokens} are normalized and become |{| +% and |}|, respectively. +% +% Importantly, notice that these functions are intended for working with +% user text for typesetting. For case changing programmatic data see the +% \pkg{l3str} module and discussion there of \cs{str_lower_case:n}, +% \cs{str_upper_case:n} and \cs{str_fold_case:n}. +% \end{function} +% +% The functions perform expansion on the input in most cases. In particular, +% input in the form of token lists or expandable functions is expanded +% \emph{unless} it falls within one of the special handling classes described +% below. This expansion approach means that in general the result of case +% changing matches the \enquote{natural} outcome expected from a +% \enquote{functional} approach to case modification. For example +% \begin{verbatim} +% \tl_set:Nn \l_tmpa_tl { hello } +% \tl_upper_case:n { \l_tmpa_tl \c_space_tl world } +% \end{verbatim} +% produces +% \begin{verbatim} +% HELLO WORLD +% \end{verbatim} +% The expansion approach taken means that in package mode any \LaTeXe{} +% \enquote{robust} commands which may appear in the input should be converted +% to engine-protected versions using for example the \tn{robustify} command +% from the \pkg{etoolbox} package. +% +% \begin{variable}{\l_tl_case_change_math_tl} +% Case changing does not take place within math mode material so for example +% \begin{verbatim} +% \tl_upper_case:n { Some~text~$y = mx + c$~with~{Braces} } +% \end{verbatim} +% becomes +% \begin{verbatim} +% SOME TEXT $y = mx + c$ WITH {BRACES} +% \end{verbatim} +% Material inside math mode is left entirely unchanged: in particular, no +% expansion is undertaken. +% +% Detection of math mode is controlled by the list of tokens in +% \cs{l_tl_case_change_math_tl}, which should be in open--close pairs. In +% package mode the standard settings is +% \begin{verbatim} +% $ $ \( \) +% \end{verbatim} +% +% Note that while expansion occurs when searching the text it does not +% apply to math mode material (which should be unaffected by case changing). +% As such, whilst the opening token for math mode may be \enquote{hidden} +% inside a command/macro, the closing one cannot be as this is being +% searched for in math mode. Typically, in the types of \enquote{text} +% the case changing functions are intended to apply to this should not be +% an issue. +% \end{variable} +% +% \begin{variable}{\l_tl_case_change_exclude_tl} +% Case changing can be prevented by using any command on the list +% \cs{l_tl_case_change_exclude_tl}. Each entry should be a function +% to be followed by one argument: the latter will be preserved as-is +% with no expansion. Thus for example following +% \begin{verbatim} +% \tl_put_right:Nn \l_tl_case_change_exclude_tl { \NoChangeCase } +% \end{verbatim} +% the input +% \begin{verbatim} +% \tl_upper_case:n +% { Some~text~$y = mx + c$~with~\NoChangeCase {Protection} } +% \end{verbatim} +% will result in +% \begin{verbatim} +% SOME TEXT $y = mx + c$ WITH \NoChangeCase {Protection} +% \end{verbatim} +% Notice that the case changing mapping preserves the inclusion of +% the escape functions: it is left to other code to provide suitable +% definitions (typically equivalent to \cs{use:n}). In particular, the +% result of case changing is returned protected by \cs{exp_not:n}. +% +% When used with \LaTeXe{} the commands |\cite|, |\ensuremath|, |\label| +% and |\ref| are automatically included in the list for exclusion from +% case changing. +% \end{variable} +% +% \begin{variable}{\l_tl_case_change_accents_tl} +% This list specifies accent commands which should be left unexpanded +% in the output. This allows for example +% \begin{verbatim} +% \tl_upper_case:n { \" { a } } +% \end{verbatim} +% to yield +% \begin{verbatim} +% \" { A } +% \end{verbatim} +% irrespective of the expandability of |\"|. +% +% The standard contents of this variable is |\"|, |\'|, |\.|, |\^|, |\`|, +% |\~|, |\c|, |\H|, |\k|, |\r|, |\t|, |\u| and |\v|. +% \end{variable} +% +% \enquote{Mixed} case conversion may be regarded informally as converting the +% first character of the \meta{tokens} to upper case and the rest to lower +% case. However, the process is more complex than this as there are some +% situations where a single lower case character maps to a special form, for +% example \texttt{ij} in Dutch which becomes \texttt{IJ}. As such, +% \cs[index=tl_mixed_case:n]{tl_mixed_case:n(n)} +% implement a more sophisticated mapping which accounts +% for this and for modifying accents on the first letter. Spaces at the start +% of the \meta{tokens} are ignored when finding the first \enquote{letter} for +% conversion. +% \begin{verbatim} +% \tl_mixed_case:n { hello~WORLD } % => "Hello world" +% \tl_mixed_case:n { ~hello~WORLD } % => " Hello world" +% \tl_mixed_case:n { {hello}~WORLD } % => "{Hello} world" +% \end{verbatim} +% When finding the first \enquote{letter} for this process, any content in +% math mode or covered by \cs{l_tl_case_change_exclude_tl} is ignored. +% +% (Note that the Unicode Consortium describe this as \enquote{title case}, but +% that in English title case applies on a word-by-word basis. The +% \enquote{mixed} case implemented here is a lower level concept needed for +% both \enquote{title} and \enquote{sentence} casing of text.) +% +% \begin{variable}{\l_tl_mixed_case_ignore_tl} +% The list of characters to ignore when searching for the first +% \enquote{letter} in mixed-casing is determined by +% \cs{l_tl_mixed_change_ignore_tl}. This has the standard setting +% \begin{verbatim} +% ( [ { ` - +% \end{verbatim} +% where comparisons are made on a character basis. +% \end{variable} +% +% As is generally true for \pkg{expl3}, these functions are designed to +% work with Unicode input only. As such, UTF-8 input is assumed for +% \emph{all} engines. When used with \XeTeX{} or \LuaTeX{} a full range of +% Unicode transformations are enabled. Specifically, the standard mappings +% here follow those defined by the \href{http://www.unicode.org}^^A +% {Unicode Consortium} in \texttt{UnicodeData.txt} and +% \texttt{SpecialCasing.txt}. In the case of $8$-bit engines, mappings +% are provided for characters which can be represented in output typeset +% using the |T1| font encoding. Thus for example |ä| can be case-changed +% using \pdfTeX{}. For \pTeX{} only the ASCII range is covered as the +% engine treats input outside of this range as east Asian. +% +% Context-sensitive mappings are enabled: language-dependent cases are +% discussed below. Context detection expands input but treats any +% unexpandable control sequences as \enquote{failures} to match a context. +% +% Language-sensitive conversions are enabled using the \meta{language} +% argument, and follow Unicode Consortium guidelines. Currently, the +% languages recognised for special handling are as follows. +% \begin{itemize} +% \item Azeri and Turkish (\texttt{az} and \texttt{tr}). +% The case pairs I/i-dotless and I-dot/i are activated for these +% languages. The combining dot mark is removed when lower +% casing I-dot and introduced when upper casing i-dotless. +% \item German (\texttt{de-alt}). +% An alternative mapping for German in which the lower case +% \emph{Eszett} maps to a \emph{gro\ss{}es Eszett}. +% \item Lithuanian (\texttt{lt}). +% The lower case letters i and j should retain a dot above when the +% accents grave, acute or tilde are present. This is implemented for +% lower casing of the relevant upper case letters both when input as +% single Unicode codepoints and when using combining accents. The +% combining dot is removed when upper casing in these cases. Note that +% \emph{only} the accents used in Lithuanian are covered: the behaviour +% of other accents are not modified. +% \item Dutch (\texttt{nl}). +% Capitalisation of \texttt{ij} at the beginning of mixed cased +% input produces \texttt{IJ} rather than \texttt{Ij}. The output +% retains two separate letters, thus this transformation \emph{is} +% available using \pdfTeX{}. +% \end{itemize} +% +% Creating additional context-sensitive mappings requires knowledge +% of the underlying mapping implementation used here. The team are happy +% to add these to the kernel where they are well-documented +% (\emph{e.g.}~in Unicode Consortium or relevant government publications). +% +% \begin{function}[EXP, added = 2017-07-15] +% { +% \tl_range_braced:Nnn, \tl_range_braced:cnn, \tl_range_braced:nnn, +% \tl_range_unbraced:Nnn, \tl_range_unbraced:cnn, \tl_range_unbraced:nnn +% } +% \begin{syntax} +% \cs{tl_range_braced:Nnn} \meta{tl~var} \Arg{start index} \Arg{end index} +% \cs{tl_range_braced:nnn} \Arg{token list} \Arg{start index} \Arg{end index} +% \cs{tl_range_unbraced:Nnn} \meta{tl~var} \Arg{start index} \Arg{end index} +% \cs{tl_range_unbraced:nnn} \Arg{token list} \Arg{start index} \Arg{end index} +% \end{syntax} +% Leaves in the input stream the items from the \meta{start index} to +% the \meta{end index} inclusive, using the same indexing as +% \cs{tl_range:nnn}. Spaces are ignored. Regardless of whether items +% appear with or without braces in the \meta{token list}, the +% \cs{tl_range_braced:nnn} function wraps each item in braces, while +% \cs{tl_range_unbraced:nnn} does not (overall it removes an outer set +% of braces). For instance, +% \begin{verbatim} +% \iow_term:x { \tl_range_braced:nnn { abcd~{e{}}f } { 2 } { 5 } } +% \iow_term:x { \tl_range_braced:nnn { abcd~{e{}}f } { -4 } { -1 } } +% \iow_term:x { \tl_range_braced:nnn { abcd~{e{}}f } { -2 } { -1 } } +% \iow_term:x { \tl_range_braced:nnn { abcd~{e{}}f } { 0 } { -1 } } +% \end{verbatim} +% prints \verb*|{b}{c}{d}{e{}}|, \verb*|{c}{d}{e{}}{f}|, \verb*|{e{}}{f}|, and an empty +% line to the terminal, while +% \begin{verbatim} +% \iow_term:x { \tl_range_unbraced:nnn { abcd~{e{}}f } { 2 } { 5 } } +% \iow_term:x { \tl_range_unbraced:nnn { abcd~{e{}}f } { -4 } { -1 } } +% \iow_term:x { \tl_range_unbraced:nnn { abcd~{e{}}f } { -2 } { -1 } } +% \iow_term:x { \tl_range_unbraced:nnn { abcd~{e{}}f } { 0 } { -1 } } +% \end{verbatim} +% prints \verb*|bcde{}|, \verb*|cde{}f|, \verb*|e{}f|, and an empty +% line to the terminal. Because braces are removed, the result of +% \cs{tl_range_unbraced:nnn} may have a different number of items as +% for \cs{tl_range:nnn} or \cs{tl_range_braced:nnn}. In cases where +% preserving spaces is important, consider the slower function +% \cs{tl_range:nnn}. +% \begin{texnote} +% The result is returned within the \tn{unexpanded} +% primitive (\cs{exp_not:n}), which means that the \meta{item} +% does not expand further when appearing in an \texttt{x}-type +% argument expansion. +% \end{texnote} +% \end{function} +% +% \begin{function}[added = 2018-04-01]{\tl_build_begin:N, \tl_build_gbegin:N} +% \begin{syntax} +% \cs{tl_build_begin:N} \meta{tl~var} +% \end{syntax} +% Clears the \meta{tl~var} and sets it up to support other +% \cs[no-index]{tl_build_\ldots{}} functions, which allow accumulating +% large numbers of tokens piece by piece much more efficiently than +% standard \pkg{l3tl} functions. Until \cs{tl_build_end:N} +% \meta{tl~var} is called, applying any function from \pkg{l3tl} other +% than \cs[no-index]{tl_build_\ldots{}} will lead to incorrect +% results. The |begin| and |gbegin| functions must be used for local +% and global \meta{tl~var} respectively. +% \end{function} +% +% \begin{function}[added = 2018-04-01]{\tl_build_clear:N, \tl_build_gclear:N} +% \begin{syntax} +% \cs{tl_build_clear:N} \meta{tl~var} +% \end{syntax} +% Clears the \meta{tl~var} and sets it up to support other +% \cs[no-index]{tl_build_\ldots{}} functions. The |clear| and +% |gclear| functions must be used for local and global \meta{tl~var} +% respectively. +% \end{function} +% +% \begin{function}[added = 2018-04-01] +% { +% \tl_build_put_left:Nn, \tl_build_put_left:Nx, +% \tl_build_gput_left:Nn, \tl_build_gput_left:Nx, +% \tl_build_put_right:Nn, \tl_build_put_right:Nx, +% \tl_build_gput_right:Nn, \tl_build_gput_right:Nx, +% } +% \begin{syntax} +% \cs{tl_build_put_left:Nn} \meta{tl~var} \Arg{tokens} +% \cs{tl_build_put_right:Nn} \meta{tl~var} \Arg{tokens} +% \end{syntax} +% Adds \meta{tokens} to the left or right side of the current contents +% of \meta{tl~var}. The \meta{tl~var} must have been set up with +% \cs{tl_build_begin:N} or \cs{tl_build_gbegin:N}. The |put| and +% |gput| functions must be used for local and global \meta{tl~var} +% respectively. The |right| functions are about twice faster than the +% |left| functions. +% \end{function} +% +% \begin{function}[added = 2018-04-01]{\tl_build_get:NN} +% \begin{syntax} +% \cs{tl_build_get:N} \meta{tl~var_1} \meta{tl~var_2} +% \end{syntax} +% Stores the contents of the \meta{tl~var_1} in the \meta{tl~var_2}. +% The \meta{tl~var_1} must have been set up with \cs{tl_build_begin:N} +% or \cs{tl_build_gbegin:N}. The \meta{tl~var_2} is a +% \enquote{normal} token list variable, assigned locally using +% \cs{tl_set:Nn}. +% \end{function} +% +% \begin{function}[added = 2018-04-01]{\tl_build_end:N, \tl_build_gend:N} +% \begin{syntax} +% \cs{tl_build_end:N} \meta{tl~var} +% \end{syntax} +% Gets the contents of \meta{tl~var} and stores that into the +% \meta{tl~var} using \cs{tl_set:Nn}. The \meta{tl~var} must have +% been set up with \cs{tl_build_begin:N} or \cs{tl_build_gbegin:N}. +% The |end| and |gend| functions must be used for local and global +% \meta{tl~var} respectively. These functions completely remove the +% setup code that enabled \meta{tl~var} to be used for other +% \cs[no-index]{tl_build_\ldots{}} functions. +% \end{function} +% +% \section{Additions to \pkg{l3token}} +% +% \begin{variable}[added = 2017-08-07]{\c_catcode_active_space_tl} +% Token list containing one character with category code $13$, +% (\enquote{active}), and character code $32$ (space). +% \end{variable} +% +% \begin{function}[added = 2018-04-06, updated = 2019-05-03, EXP] +% { +% \char_lower_case:N, \char_upper_case:N, +% \char_mixed_case:N, \char_fold_case:N, +% \char_str_lower_case:N, \char_str_upper_case:N, +% \char_str_mixed_case:N, \char_str_fold_case:N, +% } +% \begin{syntax} +% \cs{char_lower_case:N} \meta{char} +% \end{syntax} +% Converts the \meta{char} to the equivalent case-changed character +% as detailed by the function name (see \cs{str_fold_case:n} +% and \cs{tl_mixed_case:n} for details of these terms). The case mapping +% is carried out with no context-dependence (\emph{cf.}~\cs{tl_upper_case:n}, +% \emph{etc.}) The \texttt{str} versions always generate \enquote{other} +% (category code $12$) characters, whilst the standard versions generate +% characters with the currently-active category code (\emph{i.e.}~as if +% the character had been read directly here). +% \end{function} +% +% \begin{function}[added = 2018-06-01, EXP]{\char_codepoint_to_bytes:n} +% \begin{syntax} +% \cs{char_codepoint_to_bytes:n} \Arg{codepoint} +% \end{syntax} +% Converts the (Unicode) \meta{codepoint} to UTF-8 bytes. The expansion +% of this function comprises four brace groups, each of which will contain +% a hexadecimal value: the appropriate byte. As UTF-8 is a variable-length, +% one or more of the grouos may be empty: the bytes read in the logical order, +% such that a two-byte codepoint will have groups |#1| and |#2| filled +% and |#3| and |#4| empty. +% \end{function} +% +% \begin{function}[added = 2018-09-23] +% { +% \peek_catcode_collect_inline:Nn, +% \peek_charcode_collect_inline:Nn, +% \peek_meaning_collect_inline:Nn +% } +% \begin{syntax} +% \cs{peek_catcode_collect_inline:Nn} \meta{test token} \Arg{inline code} +% \cs{peek_charcode_collect_inline:Nn} \meta{test token} \Arg{inline code} +% \cs{peek_meaning_collect_inline:Nn} \meta{test token} \Arg{inline code} +% \end{syntax} +% Collects and removes tokens from the input stream until finding a +% token that does not match the \meta{test token} (as defined by the +% test \cs{token_if_eq_catcode:NNTF} or \cs{token_if_eq_charcode:NNTF} +% or \cs{token_if_eq_meaning:NNTF}). The collected tokens are passed +% to the \meta{inline code} as~|#1|. When begin-group or end-group +% tokens (usually |{| or~|}|) are collected they are replaced by +% implicit \cs{c_group_begin_token} and \cs{c_group_end_token}, and +% when spaces (including \cs{c_space_token}) are collected they are +% replaced by explicit spaces. +% +% For example the following code prints ``Hello'' to the terminal and +% leave ``, world!'' in the input stream. +% \begin{verbatim} +% \peek_catcode_collect_inline:Nn A { \iow_term:n {#1} } Hello,~world! +% \end{verbatim} +% Another example is that the following code tests if the next token is |*|, ignoring intervening spaces, but putting them back using |#1| if there is no~|*|. +% \begin{verbatim} +% \peek_meaning_collect_inline:Nn \c_space_token +% { \peek_charcode:NTF * { star } { no~star #1 } } +% \end{verbatim} +% \end{function} +% +% \begin{function}[added = 2018-10-01]{\peek_remove_spaces:n} +% \begin{syntax} +% \cs{peek_remove_spaces:n} \Arg{code} +% \end{syntax} +% Removes explicit and implicit space tokens (category code~$10$ and +% character code~$32$) from the input stream, then inserts \meta{code}. +% \end{function} +% +% \end{documentation} +% +% \begin{implementation} +% +% \section{\pkg{l3candidates} Implementation} +% +% \begin{macrocode} +%<*initex|package> +% \end{macrocode} +% +% \subsection{Additions to \pkg{l3basics}} +% +% \begin{macro}{\mode_leave_vertical:} +% The approach here is different to that used by \LaTeXe{} or plain \TeX{}, +% which unbox a void box to force horizontal mode. That inserts the +% \tn{everypar} tokens \emph{before} the re-inserted unboxing tokens. The +% approach here uses either the \tn{quitvmode} primitive or the equivalent +% protected macro. In vertical mode, the \tn{indent} primitive is inserted: +% this will switch to horizontal mode and insert \tn{everypar} tokens and +% nothing else. Unlike the \LaTeXe{} version, the availability of \eTeX{} +% means using a mode test can be done at for example the start of an +% \tn{halign}. +% \begin{macrocode} +\cs_new_protected:Npn \mode_leave_vertical: + { + \if_mode_vertical: + \exp_after:wN \tex_indent:D + \fi: + } +% \end{macrocode} +% \end{macro} +% +% \subsection{Additions to \pkg{l3box}} +% +% \begin{macrocode} +%<@@=box> +% \end{macrocode} +% +% \subsubsection{Viewing part of a box} +% +% \begin{macro}{\box_clip:N, \box_clip:c, \box_gclip:N, \box_gclip:c} +% A wrapper around the driver-dependent code. +% \begin{macrocode} +\cs_new_protected:Npn \box_clip:N #1 + { \hbox_set:Nn #1 { \@@_backend_clip:N #1 } } +\cs_generate_variant:Nn \box_clip:N { c } +\cs_new_protected:Npn \box_gclip:N #1 + { \hbox_gset:Nn #1 { \@@_backend_clip:N #1 } } +\cs_generate_variant:Nn \box_gclip:N { c } +% \end{macrocode} +% \end{macro} +% +% \begin{macro} +% { +% \box_set_trim:Nnnnn, \box_set_trim:cnnnn, +% \box_gset_trim:Nnnnn, \box_gset_trim:cnnnn +% } +% \begin{macro}{\@@_set_trim:NnnnnN} +% Trimming from the left- and right-hand edges of the box is easy: kern the +% appropriate parts off each side. +% \begin{macrocode} +\cs_new_protected:Npn \box_set_trim:Nnnnn #1#2#3#4#5 + { \@@_set_trim:NnnnnN #1 {#2} {#3} {#4} {#5} \box_set_eq:NN } +\cs_generate_variant:Nn \box_set_trim:Nnnnn { c } +\cs_new_protected:Npn \box_gset_trim:Nnnnn #1#2#3#4#5 + { \@@_set_trim:NnnnnN #1 {#2} {#3} {#4} {#5} \box_gset_eq:NN } +\cs_generate_variant:Nn \box_gset_trim:Nnnnn { c } +\cs_new_protected:Npn \@@_set_trim:NnnnnN #1#2#3#4#5#6 + { + \hbox_set:Nn \l_@@_internal_box + { + \tex_kern:D - \@@_dim_eval:n {#2} + \box_use:N #1 + \tex_kern:D - \@@_dim_eval:n {#4} + } +% \end{macrocode} +% For the height and depth, there is a need to watch the baseline is +% respected. Material always has to stay on the correct side, so trimming +% has to check that there is enough material to trim. First, the bottom +% edge. If there is enough depth, simply set the depth, or if not move +% down so the result is zero depth. \cs{box_move_down:nn} is used in both +% cases so the resulting box always contains a \tn{lower} primitive. +% The internal box is used here as it allows safe use of \cs{box_set_dp:Nn}. +% \begin{macrocode} + \dim_compare:nNnTF { \box_dp:N #1 } > {#3} + { + \hbox_set:Nn \l_@@_internal_box + { + \box_move_down:nn \c_zero_dim + { \box_use_drop:N \l_@@_internal_box } + } + \box_set_dp:Nn \l_@@_internal_box { \box_dp:N #1 - (#3) } + } + { + \hbox_set:Nn \l_@@_internal_box + { + \box_move_down:nn { (#3) - \box_dp:N #1 } + { \box_use_drop:N \l_@@_internal_box } + } + \box_set_dp:Nn \l_@@_internal_box \c_zero_dim + } +% \end{macrocode} +% Same thing, this time from the top of the box. +% \begin{macrocode} + \dim_compare:nNnTF { \box_ht:N \l_@@_internal_box } > {#5} + { + \hbox_set:Nn \l_@@_internal_box + { + \box_move_up:nn \c_zero_dim + { \box_use_drop:N \l_@@_internal_box } + } + \box_set_ht:Nn \l_@@_internal_box + { \box_ht:N \l_@@_internal_box - (#5) } + } + { + \hbox_set:Nn \l_@@_internal_box + { + \box_move_up:nn { (#5) - \box_ht:N \l_@@_internal_box } + { \box_use_drop:N \l_@@_internal_box } + } + \box_set_ht:Nn \l_@@_internal_box \c_zero_dim + } + #6 #1 \l_@@_internal_box + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro} +% { +% \box_set_viewport:Nnnnn, \box_set_viewport:cnnnn, +% \box_gset_viewport:Nnnnn, \box_gset_viewport:cnnnn +% } +% \begin{macro}{\@@_viewport:NnnnnN} +% The same general logic as for the trim operation, but with absolute +% dimensions. As a result, there are some things to watch out for in the +% vertical direction. +% \begin{macrocode} +\cs_new_protected:Npn \box_set_viewport:Nnnnn #1#2#3#4#5 + { \@@_set_viewport:NnnnnN #1 {#2} {#3} {#4} {#5} \box_set_eq:NN } +\cs_generate_variant:Nn \box_set_viewport:Nnnnn { c } +\cs_new_protected:Npn \box_gset_viewport:Nnnnn #1#2#3#4#5 + { \@@_set_viewport:NnnnnN #1 {#2} {#3} {#4} {#5} \box_gset_eq:NN } +\cs_generate_variant:Nn \box_gset_viewport:Nnnnn { c } +\cs_new_protected:Npn \@@_set_viewport:NnnnnN #1#2#3#4#5#6 + { + \hbox_set:Nn \l_@@_internal_box + { + \tex_kern:D - \@@_dim_eval:n {#2} + \box_use:N #1 + \tex_kern:D \@@_dim_eval:n { #4 - \box_wd:N #1 } + } + \dim_compare:nNnTF {#3} < \c_zero_dim + { + \hbox_set:Nn \l_@@_internal_box + { + \box_move_down:nn \c_zero_dim + { \box_use_drop:N \l_@@_internal_box } + } + \box_set_dp:Nn \l_@@_internal_box { - \@@_dim_eval:n {#3} } + } + { + \hbox_set:Nn \l_@@_internal_box + { \box_move_down:nn {#3} { \box_use_drop:N \l_@@_internal_box } } + \box_set_dp:Nn \l_@@_internal_box \c_zero_dim + } + \dim_compare:nNnTF {#5} > \c_zero_dim + { + \hbox_set:Nn \l_@@_internal_box + { + \box_move_up:nn \c_zero_dim + { \box_use_drop:N \l_@@_internal_box } + } + \box_set_ht:Nn \l_@@_internal_box + { + (#5) + \dim_compare:nNnT {#3} > \c_zero_dim + { - (#3) } + } + } + { + \hbox_set:Nn \l_@@_internal_box + { + \box_move_up:nn { - \@@_dim_eval:n {#5} } + { \box_use_drop:N \l_@@_internal_box } + } + \box_set_ht:Nn \l_@@_internal_box \c_zero_dim + } + #6 #1 \l_@@_internal_box + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsection{Additions to \pkg{l3fp-convert}} +% +% \begin{macrocode} +%<@@=fp> +% \end{macrocode} +% +% \begin{macro}{\fp_sign:n} +% Trivial but useful. See the implementation of \cs{fp_add:Nn} for an +% explanation of why to use \cs{@@_parse:n}, namely, for better error +% reporting. +% \begin{macrocode} +\cs_new:Npn \fp_sign:n #1 + { \fp_to_decimal:n { sign \@@_parse:n {#1} } } +% \end{macrocode} +% \end{macro} +% +% \subsection{Additions to \pkg{l3flag}} +% +% \begin{macrocode} +%<@@=flag> +% \end{macrocode} +% +% \begin{macro}[EXP]{\flag_raise_if_clear:n} +% It might be faster to just call the \enquote{trap} function in all +% cases but conceptually the function name suggests we should only run +% it if the flag is zero in case the \enquote{trap} made customizable +% in the future. +% \begin{macrocode} +\__kernel_patch:nnNNpn { \@@_chk_exist:n {#1} } { } +\cs_new:Npn \flag_raise_if_clear:n #1 + { + \if_cs_exist:w flag~#1~0 \cs_end: + \else: + \cs:w flag~#1 \cs_end: 0 ; + \fi: + } +% \end{macrocode} +% \end{macro} +% +% \subsection{Additions to \pkg{l3int}} +% +% \begin{macrocode} +%<@@=int> +% \end{macrocode} +% +% \begin{macro}[EXP]{\int_sign:n, \@@_sign:Nw} +% See \cs{int_abs:n}. Evaluate the expression once (and when +% debugging is enabled, check that the expression is well-formed), +% then test the first character to determine the sign. This is +% wrapped in \cs{int_value:w} \ldots{} \cs{exp_stop_f:} to ensure a +% fixed number of expansions and to avoid dealing with closing the +% conditionals. +% \begin{macrocode} +\__kernel_patch_args:nNNpn + { { \__kernel_chk_expr:nNnN {#1} \@@_eval:w { } \int_sign:n } } +\cs_new:Npn \int_sign:n #1 + { + \int_value:w \exp_after:wN \@@_sign:Nw + \int_value:w \@@_eval:w #1 \@@_eval_end: ; + \exp_stop_f: + } +\cs_new:Npn \@@_sign:Nw #1#2 ; + { + \if_meaning:w 0 #1 + 0 + \else: + \if_meaning:w - #1 - \fi: 1 + \fi: + } +% \end{macrocode} +% \end{macro} +% +% \subsection{Additions to \pkg{l3msg}} +% +% \begin{macrocode} +%<@@=msg> +% \end{macrocode} +% +% \begin{macro}[EXP] +% { +% \msg_expandable_error:nnnnnn , +% \msg_expandable_error:nnnnn , +% \msg_expandable_error:nnnn , +% \msg_expandable_error:nnn , +% \msg_expandable_error:nn , +% \msg_expandable_error:nnffff , +% \msg_expandable_error:nnfff , +% \msg_expandable_error:nnff , +% \msg_expandable_error:nnf +% } +% \begin{macro}{\@@_expandable_error_module:nn} +% Pass to an auxiliary the message to display and the module name +% \begin{macrocode} +\cs_new:Npn \msg_expandable_error:nnnnnn #1#2#3#4#5#6 + { + \exp_args:Ne \@@_expandable_error_module:nn + { + \exp_args:Nc \exp_args:Noooo + { \c_@@_text_prefix_tl #1 / #2 } + { \tl_to_str:n {#3} } + { \tl_to_str:n {#4} } + { \tl_to_str:n {#5} } + { \tl_to_str:n {#6} } + } + {#1} + } +\cs_new:Npn \msg_expandable_error:nnnnn #1#2#3#4#5 + { \msg_expandable_error:nnnnnn {#1} {#2} {#3} {#4} {#5} { } } +\cs_new:Npn \msg_expandable_error:nnnn #1#2#3#4 + { \msg_expandable_error:nnnnnn {#1} {#2} {#3} {#4} { } { } } +\cs_new:Npn \msg_expandable_error:nnn #1#2#3 + { \msg_expandable_error:nnnnnn {#1} {#2} {#3} { } { } { } } +\cs_new:Npn \msg_expandable_error:nn #1#2 + { \msg_expandable_error:nnnnnn {#1} {#2} { } { } { } { } } +\cs_generate_variant:Nn \msg_expandable_error:nnnnnn { nnffff } +\cs_generate_variant:Nn \msg_expandable_error:nnnnn { nnfff } +\cs_generate_variant:Nn \msg_expandable_error:nnnn { nnff } +\cs_generate_variant:Nn \msg_expandable_error:nnn { nnf } +\cs_new:Npn \@@_expandable_error_module:nn #1#2 + { + \exp_after:wN \exp_after:wN + \exp_after:wN \use_none_delimit_by_q_stop:w + \use:n { \::error ! ~ #2 : ~ #1 } \q_stop + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\msg_show_eval:Nn, \msg_log_eval:Nn, \@@_show_eval:nnN} +% A short-hand used for \cs{int_show:n} and similar functions that +% passes to \cs{tl_show:n} the result of applying |#1| (a +% function such as \cs{int_eval:n}) to the expression |#2|. The use of +% \texttt{f}-expansion ensures that |#1| is expanded in the scope in which the +% show command is called, rather than in the group created by +% \cs{iow_wrap:nnnN}. This is only important for expressions +% involving the \tn{currentgrouplevel} or \tn{currentgrouptype}. +% On the other hand we want the expression to be converted to a string +% with the usual escape character, hence within the wrapping code. +% \begin{macrocode} +\cs_new_protected:Npn \msg_show_eval:Nn #1#2 + { \exp_args:Nf \@@_show_eval:nnN { #1 {#2} } {#2} \tl_show:n } +\cs_new_protected:Npn \msg_log_eval:Nn #1#2 + { \exp_args:Nf \@@_show_eval:nnN { #1 {#2} } {#2} \tl_log:n } +\cs_new_protected:Npn \@@_show_eval:nnN #1#2#3 { #3 { #2 = #1 } } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\msg_show_item:n} +% \begin{macro}[EXP]{\msg_show_item_unbraced:n} +% \begin{macro}[EXP]{\msg_show_item:nn} +% \begin{macro}[EXP]{\msg_show_item_unbraced:nn} +% Each item in the variable is formatted using one of the following +% functions. We cannot use |\\| and so on because these short-hands +% cannot be used inside the arguments of messages, only when defining +% the messages. +% \begin{macrocode} +\cs_new:Npx \msg_show_item:n #1 + { \iow_newline: > ~ \c_space_tl \exp_not:N \tl_to_str:n { {#1} } } +\cs_new:Npx \msg_show_item_unbraced:n #1 + { \iow_newline: > ~ \c_space_tl \exp_not:N \tl_to_str:n {#1} } +\cs_new:Npx \msg_show_item:nn #1#2 + { + \iow_newline: > \use:nn { ~ } { ~ } + \exp_not:N \tl_to_str:n { {#1} } + \use:nn { ~ } { ~ } => \use:nn { ~ } { ~ } + \exp_not:N \tl_to_str:n { {#2} } + } +\cs_new:Npx \msg_show_item_unbraced:nn #1#2 + { + \iow_newline: > \use:nn { ~ } { ~ } + \exp_not:N \tl_to_str:n {#1} + \use:nn { ~ } { ~ } => \use:nn { ~ } { ~ } + \exp_not:N \tl_to_str:n {#2} + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \subsection{Additions to \pkg{l3prg}} +% +% \begin{macro}[added = 2017-11-28]{\bool_const:Nn, \bool_const:cn} +% A merger between \cs{tl_const:Nn} and \cs{bool_set:Nn}. +% \begin{macrocode} +\__kernel_patch:nnNNpn { \__kernel_chk_var_scope:NN c #1 } { } +\cs_new_protected:Npn \bool_const:Nn #1#2 + { + \__kernel_chk_if_free_cs:N #1 + \tex_global:D \tex_chardef:D #1 = \bool_if_p:n {#2} + } +\cs_generate_variant:Nn \bool_const:Nn { c } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[added = 2018-05-10] +% {\bool_set_inverse:N, \bool_set_inverse:c, \bool_gset_inverse:N, \bool_gset_inverse:c} +% Set to \texttt{false} or \texttt{true} locally or globally. +% \begin{macrocode} +\cs_new_protected:Npn \bool_set_inverse:N #1 + { \bool_if:NTF #1 { \bool_set_false:N } { \bool_set_true:N } #1 } +\cs_generate_variant:Nn \bool_set_inverse:N { c } +\cs_new_protected:Npn \bool_gset_inverse:N #1 + { \bool_if:NTF #1 { \bool_gset_false:N } { \bool_gset_true:N } #1 } +\cs_generate_variant:Nn \bool_gset_inverse:N { c } +% \end{macrocode} +% \end{macro} +% +% \subsection{Additions to \pkg{l3prop}} +% +% \begin{macrocode} +%<@@=prop> +% \end{macrocode} +% +% \begin{macro}[rEXP]{\prop_map_tokens:Nn, \prop_map_tokens:cn} +% \begin{macro}{\@@_map_tokens:nwwn} +% The mapping is very similar to \cs{prop_map_function:NN}. The +% \cs{use_i:nn} removes the leading \cs{s_@@}. The odd construction +% |\use:n {#1}| allows |#1| to contain any token without interfering +% with \cs{prop_map_break:}. The loop stops when the argument +% delimited by \cs{@@_pair:wn} is \cs{prg_break:} instead of being +% empty. +% \begin{macrocode} +\cs_new:Npn \prop_map_tokens:Nn #1#2 + { + \exp_last_unbraced:Nno + \use_i:nn { \@@_map_tokens:nwwn {#2} } #1 + \prg_break: \@@_pair:wn \s_@@ { } \prg_break_point: + \prg_break_point:Nn \prop_map_break: { } + } +\cs_new:Npn \@@_map_tokens:nwwn #1#2 \@@_pair:wn #3 \s_@@ #4 + { + #2 + \use:n {#1} {#3} {#4} + \@@_map_tokens:nwwn {#1} + } +\cs_generate_variant:Nn \prop_map_tokens:Nn { c } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP] +% {\prop_rand_key_value:N, \prop_rand_key_value:c} +% \begin{macro}[EXP]{\@@_rand_item:w} +% Contrarily to |clist|, |seq| and |tl|, there is no function to get +% an item of a |prop| given an integer between $1$ and the number of +% items, so we write the appropriate code. There is no bounds +% checking because \cs{int_rand:nn} is always within bounds. The +% initial \cs{int_value:w} is stopped by the first \cs{s_@@} in~|#1|. +% \begin{macrocode} +\cs_new:Npn \prop_rand_key_value:N #1 + { + \prop_if_empty:NF #1 + { + \exp_after:wN \@@_rand_item:w + \int_value:w \int_rand:nn { 1 } { \prop_count:N #1 } + #1 \q_stop + } + } +\cs_generate_variant:Nn \prop_rand_key_value:N { c } +\cs_new:Npn \@@_rand_item:w #1 \s_@@ \@@_pair:wn #2 \s_@@ #3 + { + \int_compare:nNnF {#1} > 1 + { \use_i_delimit_by_q_stop:nw { \exp_not:n { {#2} {#3} } } } + \exp_after:wN \@@_rand_item:w + \int_value:w \int_eval:n { #1 - 1 } \s_@@ + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsection{Additions to \pkg{l3seq}} +% +% \begin{macrocode} +%<@@=seq> +% \end{macrocode} +% +% \begin{macro} +% { +% \seq_mapthread_function:NNN, \seq_mapthread_function:NcN, +% \seq_mapthread_function:cNN, \seq_mapthread_function:ccN +% } +% \begin{macro} +% { +% \@@_mapthread_function:wNN, \@@_mapthread_function:wNw, +% \@@_mapthread_function:Nnnwnn +% } +% The idea is to first expand both sequences, adding the +% usual |{ ? \prg_break: } { }| to the end of each one. This is +% most conveniently done in two steps using an auxiliary function. +% The mapping then throws away the first tokens of |#2| and |#5|, +% which for items in both sequences are \cs{s_@@} +% \cs{@@_item:n}. The function to be mapped are then be applied to +% the two entries. When the code hits the end of one of the +% sequences, the break material stops the entire loop and tidy up. +% This avoids needing to find the count of the two sequences, or +% worrying about which is longer. +% \begin{macrocode} +\cs_new:Npn \seq_mapthread_function:NNN #1#2#3 + { \exp_after:wN \@@_mapthread_function:wNN #2 \q_stop #1 #3 } +\cs_new:Npn \@@_mapthread_function:wNN \s_@@ #1 \q_stop #2#3 + { + \exp_after:wN \@@_mapthread_function:wNw #2 \q_stop #3 + #1 { ? \prg_break: } { } + \prg_break_point: + } +\cs_new:Npn \@@_mapthread_function:wNw \s_@@ #1 \q_stop #2 + { + \@@_mapthread_function:Nnnwnn #2 + #1 { ? \prg_break: } { } + \q_stop + } +\cs_new:Npn \@@_mapthread_function:Nnnwnn #1#2#3#4 \q_stop #5#6 + { + \use_none:n #2 + \use_none:n #5 + #1 {#3} {#6} + \@@_mapthread_function:Nnnwnn #1 #4 \q_stop + } +\cs_generate_variant:Nn \seq_mapthread_function:NNN { Nc , c , cc } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\seq_set_filter:NNn, \seq_gset_filter:NNn} +% \begin{macro}{\@@_set_filter:NNNn} +% Similar to \cs{seq_map_inline:Nn}, without a +% \cs{prg_break_point:} because the user's code +% is performed within the evaluation of a boolean expression, +% and skipping out of that would break horribly. +% The \cs{@@_wrap_item:n} function inserts the relevant +% \cs{@@_item:n} without expansion in the input stream, +% hence in the \texttt{x}-expanding assignment. +% \begin{macrocode} +\cs_new_protected:Npn \seq_set_filter:NNn + { \@@_set_filter:NNNn \tl_set:Nx } +\cs_new_protected:Npn \seq_gset_filter:NNn + { \@@_set_filter:NNNn \tl_gset:Nx } +\cs_new_protected:Npn \@@_set_filter:NNNn #1#2#3#4 + { + \@@_push_item_def:n { \bool_if:nT {#4} { \@@_wrap_item:n {##1} } } + #1 #2 { #3 } + \@@_pop_item_def: + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\seq_set_map:NNn, \seq_gset_map:NNn} +% \begin{macro}{\@@_set_map:NNNn} +% Very similar to \cs{seq_set_filter:NNn}. We could actually +% merge the two within a single function, but it would have weird +% semantics. +% \begin{macrocode} +\cs_new_protected:Npn \seq_set_map:NNn + { \@@_set_map:NNNn \tl_set:Nx } +\cs_new_protected:Npn \seq_gset_map:NNn + { \@@_set_map:NNNn \tl_gset:Nx } +\cs_new_protected:Npn \@@_set_map:NNNn #1#2#3#4 + { + \@@_push_item_def:n { \exp_not:N \@@_item:n {#4} } + #1 #2 { #3 } + \@@_pop_item_def: + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\seq_set_from_inline_x:Nnn, \seq_gset_from_inline_x:Nnn} +% \begin{macro}{\@@_set_from_inline_x:NNnn} +% Set \cs{@@_item:n} then map it using the loop code. +% \begin{macrocode} +\cs_new_protected:Npn \seq_set_from_inline_x:Nnn + { \@@_set_from_inline_x:NNnn \tl_set:Nx } +\cs_new_protected:Npn \seq_gset_from_inline_x:Nnn + { \@@_set_from_inline_x:NNnn \tl_gset:Nx } +\cs_new_protected:Npn \@@_set_from_inline_x:NNnn #1#2#3#4 + { + \@@_push_item_def:n { \exp_not:N \@@_item:n {#4} } + #1 #2 { \s_@@ #3 \@@_item:n } + \@@_pop_item_def: + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\seq_set_from_function:NnN, \seq_gset_from_function:NnN} +% Reuse \cs{seq_set_from_inline_x:Nnn}. +% \begin{macrocode} +\cs_new_protected:Npn \seq_set_from_function:NnN #1#2#3 + { \seq_set_from_inline_x:Nnn #1 {#2} { #3 {##1} } } +\cs_new_protected:Npn \seq_gset_from_function:NnN #1#2#3 + { \seq_gset_from_inline_x:Nnn #1 {#2} { #3 {##1} } } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\seq_const_from_clist:Nn, \seq_const_from_clist:cn} +% Almost identical to \cs{seq_set_from_clist:Nn}. +% \begin{macrocode} +\cs_new_protected:Npn \seq_const_from_clist:Nn #1#2 + { + \tl_const:Nx #1 + { \s_@@ \clist_map_function:nN {#2} \@@_wrap_item:n } + } +\cs_generate_variant:Nn \seq_const_from_clist:Nn { c } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\seq_shuffle:N, \seq_gshuffle:N, \@@_shuffle:NN, \@@_shuffle_item:n} +% \begin{variable}{\g_@@_internal_seq, \l_@@_internal_a_int, \l_@@_internal_b_int} +% We apply the Fisher–Yates shuffle, storing items in \tn{toks} +% registers. We use the primitive \cs{tex_uniformdeviate:D} for +% speed reasons. Its non-uniformity is of order its argument divided +% by $2^{28}$, not too bad for small lists. For sequences with more +% than $13$ elements there are more possible permutations than +% possible seeds ($13!>2^{28}$) so the question of uniformity is +% somewhat moot. +% \begin{macrocode} +\cs_if_exist:NTF \tex_uniformdeviate:D + { + \int_new:N \l_@@_internal_a_int + \int_new:N \l_@@_internal_b_int + \seq_new:N \g_@@_internal_seq + \cs_new_protected:Npn \seq_shuffle:N { \@@_shuffle:NN \seq_set_eq:NN } + \cs_new_protected:Npn \seq_gshuffle:N { \@@_shuffle:NN \seq_gset_eq:NN } + \cs_new_protected:Npn \@@_shuffle:NN #1#2 + { + \int_compare:nNnTF { \seq_count:N #2 } > \c_max_register_int + { + \__kernel_msg_error:nnx { kernel } { shuffle-too-large } + { \token_to_str:N #2 } + } + { + \group_begin: + \cs_set_eq:NN \@@_item:n \@@_shuffle_item:n + \int_zero:N \l_@@_internal_a_int + #2 + \seq_gset_from_inline_x:Nnn \g_@@_internal_seq + { \int_step_function:nN { \l_@@_internal_a_int } } + { \tex_the:D \tex_toks:D ##1 } + \group_end: + #1 #2 \g_@@_internal_seq + \seq_gclear:N \g_@@_internal_seq + } + } + \cs_new_protected:Npn \@@_shuffle_item:n + { + \int_incr:N \l_@@_internal_a_int + \int_set:Nn \l_@@_internal_b_int + { 1 + \tex_uniformdeviate:D \l_@@_internal_a_int } + \tex_toks:D \l_@@_internal_a_int + = \tex_toks:D \l_@@_internal_b_int + \tex_toks:D \l_@@_internal_b_int + } + \__kernel_msg_new:nnnn { kernel } { shuffle-too-large } + { The~sequence~#1~is~too~long~to~be~shuffled~by~TeX. } + { + TeX~has~ \int_eval:n { \c_max_register_int + 1 } ~ + toks~registers:~this~only~allows~to~shuffle~up~to~ + \int_use:N \c_max_register_int \ items.~ + The~list~will~not~be~shuffled. + } + } + { + \cs_new_protected:Npn \seq_shuffle:N #1 + { + \__kernel_msg_error:nnn { kernel } { fp-no-random } + { \seq_shuffle:N #1 } + } + \cs_new_eq:NN \seq_gshuffle:N \seq_shuffle:N + } +% \end{macrocode} +% \end{variable} +% \end{macro} +% +% \begin{macro} +% { +% \seq_indexed_map_function:NN, \seq_indexed_map_inline:Nn, +% \@@_indexed_map:nNN, \@@_indexed_map:Nw +% } +% Similar to \cs{seq_map_function:NN} but we keep track of the item +% index as a |;|-delimited argument of \cs{@@_indexed_map:Nw}. +% \begin{macrocode} +\cs_new:Npn \seq_indexed_map_function:NN #1#2 + { + \@@_indexed_map:NN #1#2 + \prg_break_point:Nn \seq_map_break: { } + } +\cs_new_protected:Npn \seq_indexed_map_inline:Nn #1#2 + { + \int_gincr:N \g__kernel_prg_map_int + \cs_gset_protected:cpn + { @@_map_ \int_use:N \g__kernel_prg_map_int :w } ##1##2 {#2} + \exp_args:NNc \@@_indexed_map:NN #1 + { @@_map_ \int_use:N \g__kernel_prg_map_int :w } + \prg_break_point:Nn \seq_map_break: + { \int_gdecr:N \g__kernel_prg_map_int } + } +\cs_new:Npn \@@_indexed_map:NN #1#2 + { + \exp_after:wN \@@_indexed_map:Nw + \exp_after:wN #2 + \int_value:w 1 + \exp_after:wN \use_i:nn + \exp_after:wN ; + #1 + \prg_break: \@@_item:n { } \prg_break_point: + } +\cs_new:Npn \@@_indexed_map:Nw #1#2 ; #3 \@@_item:n #4 + { + #3 + #1 {#2} {#4} + \exp_after:wN \@@_indexed_map:Nw + \exp_after:wN #1 + \int_value:w \int_eval:w 1 + #2 ; + } +% \end{macrocode} +% \end{macro} +% +% \subsection{Additions to \pkg{l3skip}} +% +% \begin{macrocode} +%<@@=dim> +% \end{macrocode} +% +% \begin{macro}[EXP]{\dim_sign:n, \@@_sign:Nw} +% See \cs{dim_abs:n}. Contrarily to \cs{int_sign:n} the case of a +% zero dimension cannot be distinguished from a positive dimension by +% looking only at the first character, since |0.2pt| and |0pt| start +% the same way. We need explicit comparisons. We start by +% distinguishing the most common case of a positive dimension. +% \begin{macrocode} +\__kernel_patch_args:nNNpn + { { \__kernel_chk_expr:nNnN {#1} \@@_eval:w { } \dim_sign:n } } +\cs_new:Npn \dim_sign:n #1 + { + \int_value:w \exp_after:wN \@@_sign:Nw + \dim_use:N \@@_eval:w #1 \@@_eval_end: ; + \exp_stop_f: + } +\cs_new:Npn \@@_sign:Nw #1#2 ; + { + \if_dim:w #1#2 > \c_zero_dim + 1 + \else: + \if_meaning:w - #1 + -1 + \else: + 0 + \fi: + \fi: + } +% \end{macrocode} +% \end{macro} +% +% \subsection{Additions to \pkg{l3sys}} +% +% \begin{macrocode} +%<@@=sys> +% \end{macrocode} +% +% \begin{variable}{\c_sys_engine_version_str} +% Various different engines, various different ways to extract the +% data! +% \begin{macrocode} +\str_const:Nx \c_sys_engine_version_str + { + \str_case:on \c_sys_engine_str + { + { pdftex } + { + \fp_eval:n { round(\int_use:N \tex_pdftexversion:D / 100 , 2) } + . + \tex_pdftexrevision:D + } + { ptex } + { + \cs_if_exist:NT \tex_ptexversion:D + { + p + \int_use:N \tex_ptexversion:D + \int_use:N \tex_ptexminorversion:D + \tex_ptexrevision:D + - + \int_use:N \tex_epTeXversion:D + } + } + { luatex } + { + \fp_eval:n { round(\int_use:N \tex_luatexversion:D / 100, 2) } + . + \tex_luatexrevision:D + } + { uptex } + { + \cs_if_exist:NT \tex_ptexversion:D + { + p + \int_use:N \tex_ptexversion:D + \int_use:N \tex_ptexminorversion:D + \tex_ptexrevision:D + - + u + \int_use:N \tex_uptexversion:D + \tex_uptexrevision:D + - + \int_use:N \tex_epTeXversion:D + } + } + { xetex } + { + \int_use:N \tex_XeTeXversion:D + \tex_XeTeXrevision:D + } + } + } +% \end{macrocode} +% \end{variable} +% +% \begin{macro}[EXP]{\sys_rand_seed:} +% Unpack the primitive. When random numbers are not available, we +% return zero after an error (and incidentally make sure the number of +% expansions needed is the same as with random numbers available). +% \begin{macrocode} +\sys_if_rand_exist:TF + { \cs_new:Npn \sys_rand_seed: { \tex_the:D \tex_randomseed:D } } + { + \cs_new:Npn \sys_rand_seed: + { + \int_value:w + \__kernel_msg_expandable_error:nnn { kernel } { fp-no-random } + { \sys_rand_seed: } + \c_zero_int + } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\sys_gset_rand_seed:n} +% The primitive always assigns the seed globally. +% \begin{macrocode} +\sys_if_rand_exist:TF + { + \cs_new_protected:Npn \sys_gset_rand_seed:n #1 + { \tex_setrandomseed:D \int_eval:n {#1} \exp_stop_f: } + } + { + \cs_new_protected:Npn \sys_gset_rand_seed:n #1 + { + \__kernel_msg_error:nnn { kernel } { fp-no-random } + { \sys_gset_rand_seed:n {#1} } + } + } +% \end{macrocode} +% \end{macro} +% +% \begin{variable}{\c_sys_shell_escape_int} +% Expose the engine's shell escape status to the user. +% \begin{macrocode} +\int_const:Nn \c_sys_shell_escape_int + { + \sys_if_engine_luatex:TF + { + \tex_directlua:D + { tex.sprint(status.shell_escape~or~os.execute()) } + } + { + \tex_shellescape:D + } + } +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_@@_internal_tl} +% \begin{macrocode} +\tl_new:N \l_@@_internal_tl +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\c_@@_marker_tl} +% The same idea as the marker for rescanning token lists. +% \begin{macrocode} +\tl_const:Nx \c_@@_marker_tl { : \token_to_str:N : } +% \end{macrocode} +% \end{variable} +% +% \begin{macro}[TF]{\sys_shell_get:nnN} +% \begin{macro}{\sys_shell_get:nnN,\@@_get:nnN,\@@_get_do:Nw} +% Setting using a shell is at this level just a slightly specialised file +% operation, with an additional check for quotes, as these are not supported. +% \begin{macrocode} +\cs_new_protected:Npn \sys_shell_get:nnN #1#2#3 + { + \sys_shell_get:nnNF {#1} {#2} #3 + { \tl_set:Nn #3 { \q_no_value } } + } +\prg_new_protected_conditional:Npnn \sys_shell_get:nnN #1#2#3 { T , F , TF } + { + \sys_if_shell:TF + { \exp_args:No \@@_get:nnN { \tl_to_str:n {#1} } {#2} #3 } + { \prg_return_false: } + } +\cs_new_protected:Npn \@@_get:nnN #1#2#3 + { + \tl_if_in:nnTF {#1} { " } + { + \__kernel_msg_error:nnx + { kernel } { quote-in-shell } {#1} + \prg_return_false: + } + { + \group_begin: + \if_false: { \fi: + \int_set_eq:NN \tex_tracingnesting:D \c_zero_int + \exp_args:No \tex_everyeof:D { \c_@@_marker_tl } + #2 \scan_stop: + \exp_after:wN \@@_get_do:Nw + \exp_after:wN #3 + \exp_after:wN \prg_do_nothing: + \tex_input:D | "#1" \scan_stop: + \if_false: } \fi: + \prg_return_true: + } + } +\exp_args:Nno \use:nn + { \cs_new_protected:Npn \@@_get_do:Nw #1#2 } + { \c_@@_marker_tl } + { + \group_end: + \tl_set:No #1 {#2} + } +\__kernel_msg_new:nnnn { kernel } { quote-in-shell } + { Quotes~in~shell~command~'#1'. } + { Shell~commands~cannot~contain~quotes~("). } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{variable}{\c_sys_platform_str} +% Detecting the platform on \LuaTeX{} is easy: for other engines, we use +% the fact that the two common cases have special null files. It is possible +% to probe further (see package \pkg{platform}), but that requires shell +% escape and seems unlikely to be useful. +% \begin{macrocode} +\sys_if_engine_luatex:TF + { + \str_const:Nx \c_sys_platform_str + { \lua_now:n { tex.print(os.type) } } + } + { + \file_if_exist:nTF { nul: } + { + \file_if_exist:nF { /dev/null } + { \str_const:Nn \c_sys_platform_str { windows } } + } + { + \file_if_exist:nT { /dev/null } + { \str_const:Nn \c_sys_platform_str { unix } } + } + } +\cs_if_exist:NF \c_sys_platform_str + { \str_const:Nn \c_sys_platform_str { unknown } } +% \end{macrocode} +% \end{variable} +% \begin{macro}[pTF]{\sys_if_platform_unix:} +% \begin{macro}[pTF]{\sys_if_platform_windows:} +% We can now set up the tests. +% \begin{macrocode} +\clist_map_inline:nn { unix , windows } + { + \@@_const:nn { sys_if_platform_ #1 } + { \str_if_eq_p:Vn \c_sys_platform_str { #1 } } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP, pTF]{\sys_if_shell:, \sys_if_shell_unrestricted:, \sys_if_shell_restricted:} +% Performs a check for whether shell escape is enabled. The first set +% of functions returns true if either of restricted or unrestricted +% shell escape is enabled, while the other two sets of functions +% return true in only one of these two cases. +% \begin{macrocode} +\@@_const:nn { sys_if_shell } + { \int_compare_p:nNn \c_sys_shell_escape_int > 0 } +\@@_const:nn { sys_if_shell_unrestricted } + { \int_compare_p:nNn \c_sys_shell_escape_int = 1 } +\@@_const:nn { sys_if_shell_restricted } + { \int_compare_p:nNn \c_sys_shell_escape_int = 2 } +% \end{macrocode} +% \end{macro} +% +% \begin{variable}{\c_@@_shell_stream_int} +% This is not needed for \LuaTeX{}: shell escape there isn't done using +% a \TeX{} interface. +% \begin{macrocode} +\sys_if_engine_luatex:F + { \int_const:Nn \c_@@_shell_stream_int { 18 } } +% \end{macrocode} +% \end{variable} +% +% \begin{macro}{\sys_shell_now:n} +% Execute commands through shell escape immediately. +% \begin{macrocode} +\sys_if_engine_luatex:TF + { + \cs_new_protected:Npn \sys_shell_now:n #1 + { + \lua_now:e + { os.execute(" \lua_escape:e { \tl_to_str:n {#1} } ") } + } + } + { + \cs_new_protected:Npn \sys_shell_now:n #1 + { \iow_now:Nn \c_@@_shell_stream_int {#1} } + } +\cs_generate_variant:Nn \sys_shell_now:n { x } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\sys_shell_shipout:n} +% Execute commands through shell escape at shipout. +% \begin{macrocode} +\sys_if_engine_luatex:TF + { + \cs_new_protected:Npn \sys_shell_shipout:n #1 + { + \lua_shipout_e:n + { os.execute(" \lua_escape:e { \tl_to_str:n {#1} } ") } + } + } + { + \cs_new_protected:Npn \sys_shell_shipout:n #1 + { \iow_shipout:Nn \c_@@_shell_stream_int {#1} } + } +\cs_generate_variant:Nn \sys_shell_shipout:n { x } +% \end{macrocode} +% \end{macro} +% +% \subsection{Additions to \pkg{l3file}} +% +% \begin{macrocode} +%<@@=file> +% \end{macrocode} +% +% \begin{macro}[EXP]{\@@_str_cmp:nn} +% \begin{macro}[EXP]{\@@_str_escape:n} +% As we are doing a fixed-length \enquote{big} integer comparison, it +% is easiest to use the low-level behavior of string comparisons. +% \begin{macrocode} +\cs_new:Npn \@@_str_cmp:nn #1#2 { \tex_strcmp:D {#1} {#2} } +\sys_if_engine_luatex:T + { + \cs_set:Npn \@@_str_cmp:nn #1#2 + { + \lua_now:e + { + l3kernel.strcmp + ( + " \@@_str_escape:n {#1}", + " \@@_str_escape:n {#2}" + ) + } + } + \cs_new:Npn \@@_str_escape:n #1 + { + \lua_escape:e + { \__kernel_tl_to_str:w \use:e { {#1} } } + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{variable}{\l_@@_first_tl, \l_@@_second_tl} +% \begin{macrocode} +\tl_new:N \l_@@_first_tl +\tl_new:N \l_@@_second_tl +% \end{macrocode} +% \end{variable} +% +% \begin{macro}[TF]{\file_compare_timestamp:nNn} +% \begin{macro}[EXP]{\@@_timestamp:n} +% Comparison of file date can be done by using the low-level nature of the +% string comparison functions. This could be expandable but that would +% skip any file look-up: to stay consistent we retain that at the cost of +% expansion. +% \begin{macrocode} +\prg_new_protected_conditional:Npnn \file_compare_timestamp:nNn #1#2#3 + { T , F , TF } + { + \group_begin: + \file_get_full_name:nNTF {#1} \l_@@_first_tl + { + \file_get_full_name:nNTF {#3} \l_@@_second_tl + { + \if_int_compare:w + \@@_str_cmp:nn + { \@@_timestamp:n \exp_after:wN { \l_@@_first_tl } } + { \@@_timestamp:n \exp_after:wN { \l_@@_second_tl } } + #2 0 \exp_stop_f: + \group_end: + \prg_return_true: + \else: + \group_end: + \prg_return_false: + \fi: + } + { + \group_end: + \if_charcode:w #2 > + \prg_return_true: + \else: + \prg_return_false: + \fi: + } + } + { + \group_end: + \if_charcode:w #2 < + \prg_return_true: + \else: + \prg_return_false: + \fi: + } + } +\sys_if_engine_luatex:TF + { + \cs_new:Npn \@@_timestamp:n #1 + { + \lua_now:e + { l3kernel.filemoddate ( " \lua_escape:e {#1} " ) } + } + } + { \cs_new_eq:NN \@@_timestamp:n \tex_filemoddate:D } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macrocode} +%<@@=ior> +% \end{macrocode} +% +% \begin{macro}{\ior_shell_open:Nn} +% \begin{macro}{\@@_shell_open:nN} +% Actually much easier than either the standard open or input versions! +% \begin{macrocode} +\cs_new_protected:Npn \ior_shell_open:Nn #1#2 + { + \sys_if_shell:TF + { \exp_args:No \@@_shell_open:nN { \tl_to_str:n {#2} } #1 } + { \__kernel_msg_error:nn { kernel } { pipe-failed } } + } +\cs_new_protected:Npn \@@_shell_open:nN #1#2 + { + \tl_if_in:nnTF {#1} { " } + { + \__kernel_msg_error:nnx + { kernel } { quote-in-shell } {#1} + } + { \__kernel_ior_open:Nn #2 { "|#1" } } + } +\__kernel_msg_new:nnnn { kernel } { pipe-failed } + { Cannot~run~piped~system~commands. } + { + LaTeX~tried~to~call~a~system~process~but~this~was~not~possible.\\ + Try~the~"--shell-escape"~(or~"--enable-pipes")~option. + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsection{Additions to \pkg{l3tl}} +% +% \subsubsection{Unicode case changing} +% +% The mechanisms needed for case changing are somewhat involved, particularly +% to allow for all of the special cases. These functions also require the +% appropriate data extracted from the Unicode documentation (either manually +% or automatically). +% +% First, some code which \enquote{belongs} in \pkg{l3tokens} but has to come +% here. +% \begin{macrocode} +%<@@=char> +% \end{macrocode} +% +% \begin{macro}[EXP] +% { +% \char_lower_case:N, \char_upper_case:N, +% \char_mixed_case:N, \char_fold_case:N +% } +% \begin{macro}[EXP]{\@@_change_case:nNN} +% \begin{macro}[EXP]{\@@_change_case:nN} +% \begin{macro}[EXP]{\@@_change_case_multi:n, \@@_change_case_multi:v} +% \begin{macro}[EXP]{\@@_change_case_multi:NNNw} +% \begin{macro}[EXP]{\@@_change_case:NN} +% \begin{macro}[EXP]{\@@_change_case:NNN} +% \begin{macro}[EXP]{\@@_change_case:N} +% \begin{macro}[EXP] +% { +% \char_str_lower_case:N, \char_str_upper_case:N, +% \char_str_mixed_case:N, \char_str_fold_case:N +% } +% \begin{macro}[EXP]{\@@_str_change_case:nNN} +% \begin{macro}[EXP]{\@@_str_change_case:nN} +% Expandable character generation is done using a two-part approach. +% First, see if the current character has a special mapping for the current +% transformation. If it does, insert that. Otherwise, use the \TeX{} data +% to look up the one-to-one mapping, and generate the appropriate character +% with the appropriate category code. Mixed case needs an extra step as it +% may be special-cased or might be a special upper case outcome. The internal +% when using non-Unicode engines has to be set up to only do anything +% with ASCII characters. +% +% To ensure that the category codes produced are predictable, every character +% is re-generated even if it is otherwise unchanged. This makes life a little +% interesting when we might have multiple output characters: we have to +% grab each of them and case change them in reverse order to maintain +% \texttt{f}-type expandability. +% \begin{macrocode} +\cs_new:Npn \char_lower_case:N #1 + { \@@_change_case:nNN { lower } \char_value_lccode:n #1 } +\cs_new:Npn \char_upper_case:N #1 + { \@@_change_case:nNN { upper } \char_value_uccode:n #1 } +\cs_new:Npn \char_mixed_case:N #1 + { + \tl_if_exist:cTF { c_@@_mixed_case_ \token_to_str:N #1 _tl } + { + \@@_change_case_multi:v + { c_@@_mixed_case_ \token_to_str:N #1 _tl } + } + { \char_upper_case:N #1 } + } +\cs_new:Npn \char_fold_case:N #1 + { \@@_change_case:nNN { fold } \char_value_lccode:n #1 } +\cs_new:Npn \@@_change_case:nNN #1#2#3 + { + \tl_if_exist:cTF { c_@@_ #1 _case_ \token_to_str:N #3 _tl } + { + \@@_change_case_multi:v + { c_@@_ #1 _case_ \token_to_str:N #3 _tl } + } + { \exp_args:Nf \@@_change_case:nN { #2 { `#3 } } #3 } + } +\cs_new:Npn \@@_change_case:nN #1#2 + { + \int_compare:nNnTF {#1} = 0 + { \@@_change_case:N #2 } + { \char_generate:nn {#1} { \char_value_catcode:n {#1} } } + } +\cs_new:Npn \@@_change_case_multi:n #1 + { \@@_change_case_multi:NNNw #1 \q_no_value \q_no_value \q_stop } +\cs_new:Npn \@@_change_case_multi:NNNw #1#2#3#4 \q_stop + { + \quark_if_no_value:NTF #3 + { + \quark_if_no_value:NTF #2 + { \@@_change_case:N #1 } + { \@@_change_case:NN #1#2 } + } + { \@@_change_case:NNN #1#2#3 } + } +\cs_generate_variant:Nn \@@_change_case_multi:n { v } +\cs_new:Npn \@@_change_case:NN #1#2 + { + \exp_args:Nnf \use:nn + { \@@_change_case:N #1 } + { \@@_change_case:N #2 } + } +\cs_new:Npn \@@_change_case:NNN #1#2#3 + { + \exp_args:Nnff \use:nnn + { \@@_change_case:N #1 } + { \@@_change_case:N #2 } + { \@@_change_case:N #3 } + } +\cs_new:Npn \@@_change_case:N #1 + { \char_generate:nn { `#1 } { \char_value_catcode:n { `#1 } } } +\bool_lazy_or:nnF { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } + { + \cs_set_eq:NN \@@_change_case:nN \use_ii:nn + } +% \end{macrocode} +% Same story for the string version, except category code is easier +% to follow. This of course makes this version significantly faster. +% \begin{macrocode} +\cs_new:Npn \char_str_lower_case:N #1 + { \@@_str_change_case:nNN { lower } \char_value_lccode:n #1 } +\cs_new:Npn \char_str_upper_case:N #1 + { \@@_str_change_case:nNN { upper } \char_value_uccode:n #1 } +\cs_new:Npn \char_str_mixed_case:N #1 + { + \tl_if_exist:cTF { c_@@_mixed_case_ \token_to_str:N #1 _tl } + { \tl_to_str:c { c_@@_mixed_case_ \token_to_str:N #1 _tl } } + { \char_str_upper_case:N #1 } + } +\cs_new:Npn \char_str_fold_case:N #1 + { \@@_str_change_case:nNN { fold } \char_value_lccode:n #1 } +\cs_new:Npn \@@_str_change_case:nNN #1#2#3 + { + \tl_if_exist:cTF { c_@@_ #1 _case_ \token_to_str:N #3 _tl } + { \tl_to_str:c { c_@@_ #1 _case_ \token_to_str:N #3 _tl } } + { \exp_args:Nf \@@_str_change_case:nN { #2 { `#3 } } #3 } + } +\cs_new:Npn \@@_str_change_case:nN #1#2 + { + \int_compare:nNnTF {#1} = 0 + { \tl_to_str:n {#2} } + { \char_generate:nn {#1} { 12 } } + } +\bool_lazy_or:nnF { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } + { + \cs_set:Npn \@@_str_change_case:nN #1#2 + { \tl_to_str:n {#2} } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\char_codepoint_to_bytes:n} +% \begin{macro}[EXP]{\@@_codepoint_to_bytes_auxi:n} +% \begin{macro}[EXP]{\@@_codepoint_to_bytes_auxii:Nnn} +% \begin{macro}[EXP]{\@@_codepoint_to_bytes_auxiii:n} +% \begin{macro}[EXP] +% { +% \@@_codepoint_to_bytes_outputi:nw , +% \@@_codepoint_to_bytes_outputii:nw , +% \@@_codepoint_to_bytes_outputiii:nw , +% \@@_codepoint_to_bytes_outputiv:nw +% } +% \begin{macro}[EXP] +% {\@@_codepoint_to_bytes_output:nnn, \@@_codepoint_to_bytes_output:fnn} +% \begin{macro}[EXP]{\@@_codepoint_to_bytes_end:} +% This code converts a codepoint into the correct UTF-8 representation. +% In terms of the algorithm itself, see +% \url{https://en.wikipedia.org/wiki/UTF-8} for the octet pattern. +% \begin{macrocode} +\cs_new:Npn \char_codepoint_to_bytes:n #1 + { + \exp_args:Nf \@@_codepoint_to_bytes_auxi:n + { \int_eval:n {#1} } + } +\cs_new:Npn \@@_codepoint_to_bytes_auxi:n #1 + { + \if_int_compare:w #1 > "80 \exp_stop_f: + \if_int_compare:w #1 < "800 \exp_stop_f: + \@@_codepoint_to_bytes_outputi:nw + { \@@_codepoint_to_bytes_auxii:Nnn C {#1} { 64 } } + \@@_codepoint_to_bytes_outputii:nw + { \@@_codepoint_to_bytes_auxiii:n {#1} } + \else: + \if_int_compare:w #1 < "10000 \exp_stop_f: + \@@_codepoint_to_bytes_outputi:nw + { \@@_codepoint_to_bytes_auxii:Nnn E {#1} { 64 * 64 } } + \@@_codepoint_to_bytes_outputii:nw + { + \@@_codepoint_to_bytes_auxiii:n + { \int_div_truncate:nn {#1} { 64 } } + } + \@@_codepoint_to_bytes_outputiii:nw + { \@@_codepoint_to_bytes_auxiii:n {#1} } + \else: + \@@_codepoint_to_bytes_outputi:nw + { + \@@_codepoint_to_bytes_auxii:Nnn F + {#1} { 64 * 64 * 64 } + } + \@@_codepoint_to_bytes_outputii:nw + { + \@@_codepoint_to_bytes_auxiii:n + { \int_div_truncate:nn {#1} { 64 * 64 } } + } + \@@_codepoint_to_bytes_outputiii:nw + { + \@@_codepoint_to_bytes_auxiii:n + { \int_div_truncate:nn {#1} { 64 } } + } + \@@_codepoint_to_bytes_outputiv:nw + { \@@_codepoint_to_bytes_auxiii:n {#1} } + \fi: + \fi: + \else: + \@@_codepoint_to_bytes_outputi:nw {#1} + \fi: + \@@_codepoint_to_bytes_end: { } { } { } { } + } +\cs_new:Npn \@@_codepoint_to_bytes_auxii:Nnn #1#2#3 + { "#10 + \int_div_truncate:nn {#2} {#3} } +\cs_new:Npn \@@_codepoint_to_bytes_auxiii:n #1 + { \int_mod:nn {#1} { 64 } + 128 } +\cs_new:Npn \@@_codepoint_to_bytes_outputi:nw + #1 #2 \@@_codepoint_to_bytes_end: #3 + { \@@_codepoint_to_bytes_output:fnn { \int_eval:n {#1} } { } {#2} } +\cs_new:Npn \@@_codepoint_to_bytes_outputii:nw + #1 #2 \@@_codepoint_to_bytes_end: #3#4 + { \@@_codepoint_to_bytes_output:fnn { \int_eval:n {#1} } { {#3} } {#2} } +\cs_new:Npn \@@_codepoint_to_bytes_outputiii:nw + #1 #2 \@@_codepoint_to_bytes_end: #3#4#5 + { + \@@_codepoint_to_bytes_output:fnn + { \int_eval:n {#1} } { {#3} {#4} } {#2} + } +\cs_new:Npn \@@_codepoint_to_bytes_outputiv:nw + #1 #2 \@@_codepoint_to_bytes_end: #3#4#5#6 + { + \@@_codepoint_to_bytes_output:fnn + { \int_eval:n {#1} } { {#3} {#4} {#5} } {#2} + } +\cs_new:Npn \@@_codepoint_to_bytes_output:nnn #1#2#3 + { + #3 + \@@_codepoint_to_bytes_end: #2 {#1} + } +\cs_generate_variant:Nn \@@_codepoint_to_bytes_output:nnn { f } +\cs_new:Npn \@@_codepoint_to_bytes_end: { } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macrocode} +%<@@=tl> +% \end{macrocode} +% +% \begin{macro}[EXP, documented-as=\tl_if_head_eq_catcode:nNTF] +% {\tl_if_head_eq_catcode:oNTF} +% Extra variants. +% \begin{macrocode} +\cs_generate_variant:Nn \tl_if_head_eq_catcode:nNTF { o } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\tl_lower_case:n, \tl_upper_case:n, \tl_mixed_case:n} +% \begin{macro}[EXP]{\tl_lower_case:nn, \tl_upper_case:nn, \tl_mixed_case:nn} +% The user level functions here are all wrappers around the internal +% functions for case changing. +% \begin{macrocode} +\cs_new:Npn \tl_lower_case:n { \@@_change_case:nnn { lower } { } } +\cs_new:Npn \tl_upper_case:n { \@@_change_case:nnn { upper } { } } +\cs_new:Npn \tl_mixed_case:n { \@@_change_case:nnn { mixed } { } } +\cs_new:Npn \tl_lower_case:nn { \@@_change_case:nnn { lower } } +\cs_new:Npn \tl_upper_case:nn { \@@_change_case:nnn { upper } } +\cs_new:Npn \tl_mixed_case:nn { \@@_change_case:nnn { mixed } } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_change_case:nnn} +% \begin{macro}[EXP]{\@@_change_case_aux:nnn} +% \begin{macro}[EXP]{\@@_change_case_loop:wnn} +% \begin{macro}[EXP] +% { +% \@@_change_case_output:nwn , +% \@@_change_case_output:Vwn , +% \@@_change_case_output:own , +% \@@_change_case_output:vwn , +% \@@_change_case_output:fwn , +% } +% \begin{macro}[EXP]{\@@_change_case_end:wn} +% \begin{macro}[EXP]{\@@_change_case_group:nwnn} +% \begin{macro}[EXP] +% { +% \@@_change_case_group_lower:nnnn , +% \@@_change_case_group_upper:nnnn , +% \@@_change_case_group_mixed:nnnn +% } +% \begin{macro}[EXP]{\@@_change_case_space:wnn} +% \begin{macro}[EXP]{\@@_change_case_N_type:Nwnn} +% \begin{macro}[EXP]{\@@_change_case_N_type:NNNnnn} +% \begin{macro}[EXP]{\@@_change_case_math:NNNnnn} +% \begin{macro}[EXP]{\@@_change_case_math_loop:wNNnn} +% \begin{macro}[EXP]{\@@_change_case_math:NwNNnn} +% \begin{macro}[EXP]{\@@_change_case_math_group:nwNNnn} +% \begin{macro}[EXP]{\@@_change_case_math_space:wNNnn} +% \begin{macro}[EXP]{\@@_change_case_N_type:Nnnn} +% \begin{macro}[EXP] +% { +% \@@_change_case_char_lower:Nnn , +% \@@_change_case_char_upper:Nnn , +% \@@_change_case_char_mixed:Nnn +% } +% \begin{macro}[EXP]{\@@_change_case_char:nN} +% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nNN} +% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nNNN} +% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nNNNN} +% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nn} +% \begin{macro}[EXP]{\@@_change_case_cs_letterlike:Nn} +% \begin{macro}[EXP]{\@@_change_case_cs_letterlike:NnN} +% \begin{macro}[EXP]{\@@_change_case_cs_accents:NN} +% \begin{macro}[EXP]{\@@_change_case_cs:N} +% \begin{macro}[EXP]{\@@_change_case_cs:NN} +% \begin{macro}[EXP]{\@@_change_case_cs:NNn} +% \begin{macro}[EXP]{\@@_change_case_protect:wNN} +% \begin{macro}[EXP]{\@@_change_case_if_expandable:NTF} +% \begin{macro}[EXP]{\@@_change_case_cs_expand:Nnw} +% \begin{macro}[EXP]{\@@_change_case_cs_expand:NN} +% \begin{macro}[EXP]{\@@_change_case_mixed_skip:N} +% \begin{macro}[EXP]{\@@_change_case_mixed_skip:NN} +% \begin{macro}[EXP]{\@@_change_case_mixed_skip_tidy:Nwn} +% \begin{macro}[EXP]{\@@_change_case_mixed_switch:w} +% The mechanism for the core conversion of case is based on the idea that +% we can use a loop to grab the entire token list plus a quark: the latter is +% used as an end marker and to avoid any brace stripping. Depending on the +% nature of the first item in the grabbed argument, it can either processed +% as a single token, treated as a group or treated as a space. These +% different cases all work by re-reading |#1| in the appropriate way, hence +% the repetition of |#1 \q_recursion_stop|. +% \begin{macrocode} +\cs_new:Npn \@@_change_case:nnn #1#2#3 + { + \__kernel_exp_not:w \exp_after:wN + { + \exp:w + \@@_change_case_aux:nnn {#1} {#2} {#3} + } + } +\cs_new:Npn \@@_change_case_aux:nnn #1#2#3 + { + \group_align_safe_begin: + \@@_change_case_loop:wnn + #3 \q_recursion_tail \q_recursion_stop {#1} {#2} + \@@_change_case_result:n { } + } +\cs_new:Npn \@@_change_case_loop:wnn #1 \q_recursion_stop + { + \tl_if_head_is_N_type:nTF {#1} + { \@@_change_case_N_type:Nwnn } + { + \tl_if_head_is_group:nTF {#1} + { \@@_change_case_group:nwnn } + { \@@_change_case_space:wnn } + } + #1 \q_recursion_stop + } +% \end{macrocode} +% Earlier versions of the code where only \texttt{x}-type expandable rather +% than \texttt{f}-type: this causes issues with nesting and so the slight +% performance hit is taken for a better outcome in usability terms. Setting +% up for \texttt{f}-type expandability has two requirements: a marker +% token after the main loop (see above) and a mechanism to \enquote{load} +% and finalise the result. That is handled in the code below, which includes +% the necessary material to end the \cs{exp:w} expansion. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_output:nwn #1#2 \@@_change_case_result:n #3 + { #2 \@@_change_case_result:n { #3 #1 } } +\cs_generate_variant:Nn \@@_change_case_output:nwn { V , o , v , f } +\cs_new:Npn \@@_change_case_end:wn #1 \@@_change_case_result:n #2 + { + \group_align_safe_end: + \exp_end: + #2 + } +% \end{macrocode} +% Handling for the cases where the current argument is a brace group or +% a space is relatively easy. For the brace case, the routine works +% recursively, using the expandability of the mechanism to ensure that the +% result is finalised before storage. For the space case it is simply a +% question of removing the space in the input and storing it in the output. +% In both cases, and indeed for the \texttt{N}-type grabber, after removing +% the current item from the input \cs{@@_change_case_loop:wnn} is inserted +% in front of the remaining tokens. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_group:nwnn #1#2 \q_recursion_stop #3#4 + { + \use:c { @@_change_case_group_ #3 : nnnn } {#1} {#2} {#3} {#4} + } +\cs_new:Npn \@@_change_case_group_lower:nnnn #1#2#3#4 + { + \@@_change_case_output:own + { + \exp_after:wN + { + \exp:w + \@@_change_case_aux:nnn {#3} {#4} {#1} + } + } + \@@_change_case_loop:wnn #2 \q_recursion_stop {#3} {#4} + } +\cs_new_eq:NN \@@_change_case_group_upper:nnnn + \@@_change_case_group_lower:nnnn +% \end{macrocode} +% For the \enquote{mixed} case, a group is taken as forcing a switch to lower +% casing. That means we need a separate auxiliary. (Tracking whether we have +% found a first character inside a group and transferring the information out +% looks pretty horrible.) +% \begin{macrocode} +\cs_new:Npn \@@_change_case_group_mixed:nnnn #1#2#3#4 + { + \@@_change_case_output:own + { + \exp_after:wN + { + \exp:w + \@@_change_case_aux:nnn {#3} {#4} {#1} + } + } + \@@_change_case_loop:wnn #2 \q_recursion_stop { lower } {#4} + } +\exp_last_unbraced:NNo \cs_new:Npn \@@_change_case_space:wnn \c_space_tl + { + \@@_change_case_output:nwn { ~ } + \@@_change_case_loop:wnn + } +% \end{macrocode} +% For \texttt{N}-type arguments there are several stages to the approach. +% First, a simply check for the end-of-input marker, which if found triggers +% the final clean up and output step. Assuming that is not the case, the +% first check is for math-mode escaping: this test can encompass control +% sequences or other \texttt{N}-type tokens so is handled up front. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_N_type:Nwnn #1#2 \q_recursion_stop + { + \quark_if_recursion_tail_stop_do:Nn #1 + { \@@_change_case_end:wn } + \exp_after:wN \@@_change_case_N_type:NNNnnn + \exp_after:wN #1 \l_tl_case_change_math_tl + \q_recursion_tail ? \q_recursion_stop {#2} + } +% \end{macrocode} +% Looking for math mode escape first requires a loop over the possible +% token pairs to see if the current input (|#1|) matches an open-math case +% (|#2|). If if does then this test loop is ended and a new input-gathering +% one is begun. The latter simply transfers material from the input to the +% output without any expansion, testing each \texttt{N}-type token to see +% if it matches the close-math case required. If that is the situation then +% the \enquote{math loop} stops and resumes the main loop: as that might +% be either the standard case-changing one or the mixed-case alternative, +% it is not hard-coded into the math loop but is rather passed as argument +% |#3| to \cs{@@_change_case_math:NNNnnn}. If no close-math token is found +% then the final clean-up is forced (\emph{i.e.}~there is no assumption +% of \enquote{well-behaved} input in terms of math mode). +% \begin{macrocode} +\cs_new:Npn \@@_change_case_N_type:NNNnnn #1#2#3 + { + \quark_if_recursion_tail_stop_do:Nn #2 + { \@@_change_case_N_type:Nnnn #1 } + \token_if_eq_meaning:NNTF #1 #2 + { + \use_i_delimit_by_q_recursion_stop:nw + { + \@@_change_case_math:NNNnnn + #1 #3 \@@_change_case_loop:wnn + } + } + { \@@_change_case_N_type:NNNnnn #1 } + } +\cs_new:Npn \@@_change_case_math:NNNnnn #1#2#3#4 + { + \@@_change_case_output:nwn {#1} + \@@_change_case_math_loop:wNNnn #4 \q_recursion_stop #2 #3 + } +\cs_new:Npn \@@_change_case_math_loop:wNNnn #1 \q_recursion_stop + { + \tl_if_head_is_N_type:nTF {#1} + { \@@_change_case_math:NwNNnn } + { + \tl_if_head_is_group:nTF {#1} + { \@@_change_case_math_group:nwNNnn } + { \@@_change_case_math_space:wNNnn } + } + #1 \q_recursion_stop + } +\cs_new:Npn \@@_change_case_math:NwNNnn #1#2 \q_recursion_stop #3#4 + { + \token_if_eq_meaning:NNTF \q_recursion_tail #1 + { \@@_change_case_end:wn } + { + \@@_change_case_output:nwn {#1} + \token_if_eq_meaning:NNTF #1 #3 + { #4 #2 \q_recursion_stop } + { \@@_change_case_math_loop:wNNnn #2 \q_recursion_stop #3#4 } + } + } +\cs_new:Npn \@@_change_case_math_group:nwNNnn #1#2 \q_recursion_stop + { + \@@_change_case_output:nwn { {#1} } + \@@_change_case_math_loop:wNNnn #2 \q_recursion_stop + } +\exp_last_unbraced:NNo + \cs_new:Npn \@@_change_case_math_space:wNNnn \c_space_tl + { + \@@_change_case_output:nwn { ~ } + \@@_change_case_math_loop:wNNnn + } +% \end{macrocode} +% Once potential math-mode cases are filtered out the next stage is to +% test if the token grabbed is a control sequence: they cannot be used in +% the lookup table and also may require expansion. At this stage the loop +% code starting \cs{@@_change_case_loop:wnn} is inserted: all subsequent +% steps in the code which need a look-ahead are coded to rely on this and +% thus have \texttt{w}-type arguments if they may do a look-ahead. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_N_type:Nnnn #1#2#3#4 + { + \token_if_cs:NTF #1 + { \@@_change_case_cs_letterlike:Nn #1 {#3} } + { \use:c { @@_change_case_char_ #3 :Nnn } #1 {#3} {#4} } + \@@_change_case_loop:wnn #2 \q_recursion_stop {#3} {#4} + } +% \end{macrocode} +% For character tokens there are some special cases to deal with then +% the majority of changes are covered by using the \TeX{} data as a lookup +% along with expandable character generation. This avoids needing a very +% large number of macros or (as seen in earlier versions) a somewhat tricky +% split of the characters into various blocks. Notice that the special case +% code may do a look-ahead so requires a final \texttt{w}-type argument +% whereas the core lookup table does not and also guarantees an output so +% \texttt{f}-type expansion may be used to obtain the case-changed result. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_char_lower:Nnn #1#2#3 + { + \cs_if_exist_use:cF { @@_change_case_ #2 _ #3 :Nnw } + { \use_ii:nn } + #1 + { + \use:c { @@_change_case_ #2 _ sigma:Nnw } #1 + { \@@_change_case_char:nN {#2} #1 } + } + } +\cs_new_eq:NN \@@_change_case_char_upper:Nnn + \@@_change_case_char_lower:Nnn +% \end{macrocode} +% For mixed case, the code is somewhat different: there is a need to +% look up both mixed and upper case chars and we have to cover the +% situation where there is a character to skip over. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_char_mixed:Nnn #1#2#3 + { + \@@_change_case_mixed_switch:w + \cs_if_exist_use:cF { @@_change_case_mixed_ #3 :Nnw } + { + \cs_if_exist_use:cF { @@_change_case_upper_ #3 :Nnw } + { \use_ii:nn } + } + #1 + { \@@_change_case_mixed_skip:N #1 } + } +% \end{macrocode} +% For Unicode engines we can handle all characters directly. However, for +% the $8$-bit engines the aim is to deal with (a subset of) Unicode (UTF-8) +% input. They deal with that by making the upper half of the range active, +% so we look for that and if found work out how many UTF-8 octets there +% are to deal with. Those can then be grabbed to reconstruct the full +% Unicode character, which is then used in a lookup. (As will become +% obvious below, there is no intention here of covering all of Unicode.) +% \begin{macrocode} +\bool_lazy_or:nnTF + { \sys_if_engine_luatex_p: } + { \sys_if_engine_xetex_p: } + { + \cs_new:Npn \@@_change_case_char:nN #1#2 + { + \@@_change_case_output:fwn + { \use:c { char_ #1 _case:N } #2 } + } + } + { + \cs_new:Npn \@@_change_case_char:nN #1#2 + { + \int_compare:nNnTF { `#2 } > { "80 } + { + \int_compare:nNnTF { `#2 } < { "E0 } + { \@@_change_case_char_UTFviii:nNNN {#1} #2 } + { + \int_compare:nNnTF { `#2 } < { "F0 } + { \@@_change_case_char_UTFviii:nNNNN {#1} #2 } + { \@@_change_case_char_UTFviii:nNNNNN {#1} #2 } + } + } + { + \@@_change_case_output:fwn + { \use:c { char_ #1 _case:N } #2 } + } + } + } +% \end{macrocode} +% To allow for the special case of mixed case, we insert here a +% action-dependent auxiliary. +% \begin{macrocode} +\bool_lazy_or:nnF + { \sys_if_engine_luatex_p: } + { \sys_if_engine_xetex_p: } + { + \cs_new:Npn \@@_change_case_char_UTFviii:nNNN #1#2#3#4 + { \@@_change_case_char_UTFviii:nnN {#1} {#2#4} #3 } + \cs_new:Npn \@@_change_case_char_UTFviii:nNNNN #1#2#3#4#5 + { \@@_change_case_char_UTFviii:nnN {#1} {#2#4#5} #3 } + \cs_new:Npn \@@_change_case_char_UTFviii:nNNNNN #1#2#3#4#5#6 + { \@@_change_case_char_UTFviii:nnN {#1} {#2#4#5#6} #3 } + \cs_new:Npn \@@_change_case_char_UTFviii:nnN #1#2#3 + { + \cs_if_exist:cTF { c_@@_ #1 _case_ \tl_to_str:n {#2} _tl } + { + \@@_change_case_output:vwn + { c_@@_ #1 _case_ \tl_to_str:n {#2} _tl } + } + { \@@_change_case_output:nwn {#2} } + #3 + } + } +% \end{macrocode} +% Before dealing with general control sequences there are the special +% ones to deal with. Letter-like control sequences are a simple look-up, +% while for accents the loop is much as done elsewhere. Notice that +% we have a no-op test to make sure there is no unexpected expansion of +% letter-like input. The split into two parts here allows us to insert +% the \enquote{switch} code for mixed casing. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_cs_letterlike:Nn #1#2 + { + \str_if_eq:nnTF {#2} { mixed } + { + \@@_change_case_cs_letterlike:NnN #1 { upper } + \@@_change_case_mixed_switch:w + } + { \@@_change_case_cs_letterlike:NnN #1 {#2} \prg_do_nothing: } + } +\cs_new:Npn \@@_change_case_cs_letterlike:NnN #1#2#3 + { + \cs_if_exist:cTF { c_@@_change_case_ #2 _ \token_to_str:N #1 _tl } + { + \@@_change_case_output:vwn + { c_@@_change_case_ #2 _ \token_to_str:N #1 _tl } + #3 + } + { + \cs_if_exist:cTF + { + c_@@_change_case_ + \str_if_eq:nnTF {#2} { lower } { upper } { lower } + _ \token_to_str:N #1 _tl + } + { + \@@_change_case_output:nwn {#1} + #3 + } + { + \exp_after:wN \@@_change_case_cs_accents:NN + \exp_after:wN #1 \l_tl_case_change_accents_tl + \q_recursion_tail \q_recursion_stop + } + } + } +\cs_new:Npn \@@_change_case_cs_accents:NN #1#2 + { + \quark_if_recursion_tail_stop_do:Nn #2 + { \@@_change_case_cs:N #1 } + \str_if_eq:nnTF {#1} {#2} + { + \use_i_delimit_by_q_recursion_stop:nw + { \@@_change_case_output:nwn {#1} } + } + { \@@_change_case_cs_accents:NN #1 } + } +% \end{macrocode} +% To deal with a control sequence there is first a need to test if it is +% on the list which indicate that case changing should be skipped. That's +% done using a loop as for the other special cases. If a hit is found then +% the argument is grabbed: that comes \emph{after} the loop function which +% is therefore rearranged. In a \LaTeXe{} context, \tn{protect} needs +% to be treated specially, to prevent expansion of the next token but +% output it without braces. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_cs:N #1 + { +%<*package> + \str_if_eq:nnTF {#1} { \protect } { \@@_change_case_protect:wNN } +%</package> + \exp_after:wN \@@_change_case_cs:NN + \exp_after:wN #1 \l_tl_case_change_exclude_tl + \q_recursion_tail \q_recursion_stop + } +\cs_new:Npn \@@_change_case_cs:NN #1#2 + { + \quark_if_recursion_tail_stop_do:Nn #2 + { + \@@_change_case_cs_expand:Nnw #1 + { \@@_change_case_output:nwn {#1} } + } + \str_if_eq:nnTF {#1} {#2} + { + \use_i_delimit_by_q_recursion_stop:nw + { \@@_change_case_cs:NNn #1 } + } + { \@@_change_case_cs:NN #1 } + } +\cs_new:Npn \@@_change_case_cs:NNn #1#2#3 + { + \@@_change_case_output:nwn { #1 {#3} } + #2 + } +%<*package> +\cs_new:Npn \@@_change_case_protect:wNN #1 \q_recursion_stop #2 #3 + { \@@_change_case_output:nwn { \protect #3 } #2 } +%</package> +% \end{macrocode} +% When a control sequence is not on the exclude list the other test if +% to see if it is expandable. Once again, if there is a hit then the loop +% function is grabbed as part of the clean-up and reinserted before the +% now expanded material. The test for expandability has to check for +% end-of-recursion as it is needed by the look-ahead code which might hit +% the end of the input. The test is done in two parts as \cs{bool_if:nTF} +% would choke if |#1| was |(|! +% \begin{macrocode} +\cs_new:Npn \@@_change_case_if_expandable:NTF #1 + { + \token_if_expandable:NTF #1 + { + \bool_lazy_any:nTF + { + { \token_if_eq_meaning_p:NN \q_recursion_tail #1 } + { \token_if_protected_macro_p:N #1 } + { \token_if_protected_long_macro_p:N #1 } + } + { \use_ii:nn } + { \use_i:nn } + } + { \use_ii:nn } + } +\cs_new:Npn \@@_change_case_cs_expand:Nnw #1#2 + { + \@@_change_case_if_expandable:NTF #1 + { \@@_change_case_cs_expand:NN #1 } + { #2 } + } +\cs_new:Npn \@@_change_case_cs_expand:NN #1#2 + { \exp_after:wN #2 #1 } +% \end{macrocode} +% For mixed case, there is an additional list of exceptions to deal with: +% once that is sorted, we can move on back to the main loop. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_mixed_skip:N #1 + { + \exp_after:wN \@@_change_case_mixed_skip:NN + \exp_after:wN #1 \l_tl_mixed_case_ignore_tl + \q_recursion_tail \q_recursion_stop + } +\cs_new:Npn \@@_change_case_mixed_skip:NN #1#2 + { + \quark_if_recursion_tail_stop_do:nn {#2} + { \@@_change_case_char:nN { mixed } #1 } + \int_compare:nNnT { `#1 } = { `#2 } + { + \use_i_delimit_by_q_recursion_stop:nw + { + \@@_change_case_output:nwn {#1} + \@@_change_case_mixed_skip_tidy:Nwn + } + } + \@@_change_case_mixed_skip:NN #1 + } +\cs_new:Npn \@@_change_case_mixed_skip_tidy:Nwn #1#2 \q_recursion_stop #3 + { + \@@_change_case_loop:wnn #2 \q_recursion_stop { mixed } + } +% \end{macrocode} +% Needed to switch from mixed to lower casing when we have found a +% first character in the former mode. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_mixed_switch:w + #1 \@@_change_case_loop:wnn #2 \q_recursion_stop #3 + { + #1 + \@@_change_case_loop:wnn #2 \q_recursion_stop { lower } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_change_case_lower_sigma:Nnw} +% \begin{macro}[EXP]{\@@_change_case_lower_sigma:w} +% \begin{macro}[EXP]{\@@_change_case_lower_sigma:Nw} +% \begin{macro}[EXP]{\@@_change_case_upper_sigma:Nnw} +% If the current char is an upper case sigma, the a check is made on the next +% item in the input. If it is \texttt{N}-type and not a control sequence +% then there is a look-ahead phase. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_lower_sigma:Nnw #1#2#3#4 \q_recursion_stop + { + \int_compare:nNnTF { `#1 } = { "03A3 } + { + \@@_change_case_output:fwn + { \@@_change_case_lower_sigma:w #4 \q_recursion_stop } + } + {#2} + #3 #4 \q_recursion_stop + } +\cs_new:Npn \@@_change_case_lower_sigma:w #1 \q_recursion_stop + { + \tl_if_head_is_N_type:nTF {#1} + { \@@_change_case_lower_sigma:Nw #1 \q_recursion_stop } + { \c_@@_final_sigma_tl } + } +\cs_new:Npn \@@_change_case_lower_sigma:Nw #1#2 \q_recursion_stop + { + \@@_change_case_if_expandable:NTF #1 + { + \exp_after:wN \@@_change_case_lower_sigma:w #1 + #2 \q_recursion_stop + } + { + \token_if_letter:NTF #1 + { \c_@@_std_sigma_tl } + { \c_@@_final_sigma_tl } + } + } +% \end{macrocode} +% Simply skip to the final step for upper casing. +% \begin{macrocode} +\cs_new_eq:NN \@@_change_case_upper_sigma:Nnw \use_ii:nn +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_change_case_lower_tr:Nnw} +% \begin{macro}[EXP]{\@@_change_case_lower_tr_auxi:Nw} +% \begin{macro}[EXP]{\@@_change_case_lower_tr_auxii:Nw} +% \begin{macro}[EXP]{\@@_change_case_upper_tr:Nnw} +% \begin{macro}[EXP]{\@@_change_case_lower_az:Nnw} +% \begin{macro}[EXP]{\@@_change_case_upper_az:Nnw} +% The Turkic languages need special treatment for dotted-i and dotless-i. +% The lower casing rule can be expressed in terms of searching first for +% either a dotless-I or a dotted-I. In the latter case the mapping is +% easy, but in the former there is a second stage search. +% \begin{macrocode} +\bool_lazy_or:nnTF + { \sys_if_engine_luatex_p: } + { \sys_if_engine_xetex_p: } + { + \cs_new:Npn \@@_change_case_lower_tr:Nnw #1#2 + { + \int_compare:nNnTF { `#1 } = { "0049 } + { \@@_change_case_lower_tr_auxi:Nw } + { + \int_compare:nNnTF { `#1 } = { "0130 } + { \@@_change_case_output:nwn { i } } + {#2} + } + } +% \end{macrocode} +% After a dotless-I there may be a dot-above character. If there is then +% a dotted-i should be produced, otherwise output a dotless-i. When the +% combination is found both the dotless-I and the dot-above char have to +% be removed from the input, which is done by the \cs{use_i:nn} +% (it grabs \cs{@@_change_case_loop:wn} and the dot-above char and +% discards the latter). +% \begin{macrocode} + \cs_new:Npn \@@_change_case_lower_tr_auxi:Nw #1#2 \q_recursion_stop + { + \tl_if_head_is_N_type:nTF {#2} + { \@@_change_case_lower_tr_auxii:Nw #2 \q_recursion_stop } + { \@@_change_case_output:Vwn \c_@@_dotless_i_tl } + #1 #2 \q_recursion_stop + } + \cs_new:Npn \@@_change_case_lower_tr_auxii:Nw #1#2 \q_recursion_stop + { + \@@_change_case_if_expandable:NTF #1 + { + \exp_after:wN \@@_change_case_lower_tr_auxi:Nw #1 + #2 \q_recursion_stop + } + { + \bool_lazy_or:nnTF + { \token_if_cs_p:N #1 } + { ! \int_compare_p:nNn { `#1 } = { "0307 } } + { \@@_change_case_output:Vwn \c_@@_dotless_i_tl } + { + \@@_change_case_output:nwn { i } + \use_i:nn + } + } + } + } +% \end{macrocode} +% For $8$-bit engines, dot-above is not available so there is a simple +% test for an upper-case I. Then we can look for the UTF-8 representation of +% an upper case dotted-I without the combining char. If it's not there, +% preserve the UTF-8 sequence as-is. +% \begin{macrocode} + { + \cs_new:Npn \@@_change_case_lower_tr:Nnw #1#2 + { + \int_compare:nNnTF { `#1 } = { "0049 } + { \@@_change_case_output:Vwn \c_@@_dotless_i_tl } + { + \int_compare:nNnTF { `#1 } = { 196 } + { \@@_change_case_lower_tr_auxi:Nw #1 {#2} } + {#2} + } + } + \cs_new:Npn \@@_change_case_lower_tr_auxi:Nw #1#2#3#4 + { + \int_compare:nNnTF { `#4 } = { 176 } + { + \@@_change_case_output:nwn { i } + #3 + } + { + #2 + #3 #4 + } + } + } +% \end{macrocode} +% Upper casing is easier: just one exception with no context. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_upper_tr:Nnw #1#2 + { + \int_compare:nNnTF { `#1 } = { "0069 } + { \@@_change_case_output:Vwn \c_@@_dotted_I_tl } + {#2} + } +% \end{macrocode} +% Straight copies. +% \begin{macrocode} +\cs_new_eq:NN \@@_change_case_lower_az:Nnw \@@_change_case_lower_tr:Nnw +\cs_new_eq:NN \@@_change_case_upper_az:Nnw \@@_change_case_upper_tr:Nnw +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_change_case_lower_lt:Nnw} +% \begin{macro}[EXP]{\@@_change_case_lower_lt:nNnw} +% \begin{macro}[EXP]{\@@_change_case_lower_lt:nnw} +% \begin{macro}[EXP]{\@@_change_case_lower_lt:Nw} +% \begin{macro}[EXP]{\@@_change_case_lower_lt:NNw} +% \begin{macro}[EXP]{\@@_change_case_upper_lt:Nnw} +% \begin{macro}[EXP]{\@@_change_case_upper_lt:nnw} +% \begin{macro}[EXP]{\@@_change_case_upper_lt:Nw} +% \begin{macro}[EXP]{\@@_change_case_upper_lt:NNw} +% For Lithuanian, the issue to be dealt with is dots over lower case +% letters: these should be present if there is another accent. That means +% that there is some work to do when lower casing I and J. The first step +% is a simple match attempt: \cs{c_@@_accents_lt_tl} contains +% accented upper case letters which should gain a dot-above char in their +% lower case form. This is done using \texttt{f}-type expansion so only one +% pass is needed to find if it works or not. If there was no hit, the second +% stage is to check for I, J and I-ogonek, and if the current char is a +% match to look for a following accent. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_lower_lt:Nnw #1 + { + \exp_args:Nf \@@_change_case_lower_lt:nNnw + { \str_case:nVF #1 \c_@@_accents_lt_tl \exp_stop_f: } + #1 + } +\cs_new:Npn \@@_change_case_lower_lt:nNnw #1#2 + { + \tl_if_blank:nTF {#1} + { + \exp_args:Nf \@@_change_case_lower_lt:nnw + { + \int_case:nnF {`#2} + { + { "0049 } i + { "004A } j + { "012E } \c_@@_i_ogonek_tl + } + \exp_stop_f: + } + } + { + \@@_change_case_output:nwn {#1} + \use_none:n + } + } +\cs_new:Npn \@@_change_case_lower_lt:nnw #1#2 + { + \tl_if_blank:nTF {#1} + {#2} + { + \@@_change_case_output:nwn {#1} + \@@_change_case_lower_lt:Nw + } + } +% \end{macrocode} +% Grab the next char and see if it is one of the accents used in Lithuanian: +% if it is, add the dot-above char into the output. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_lower_lt:Nw #1#2 \q_recursion_stop + { + \tl_if_head_is_N_type:nT {#2} + { \@@_change_case_lower_lt:NNw } + #1 #2 \q_recursion_stop + } +\cs_new:Npn \@@_change_case_lower_lt:NNw #1#2#3 \q_recursion_stop + { + \@@_change_case_if_expandable:NTF #2 + { + \exp_after:wN \@@_change_case_lower_lt:Nw \exp_after:wN #1 #2 + #3 \q_recursion_stop + } + { + \bool_lazy_and:nnT + { ! \token_if_cs_p:N #2 } + { + \bool_lazy_any_p:n + { + { \int_compare_p:nNn { `#2 } = { "0300 } } + { \int_compare_p:nNn { `#2 } = { "0301 } } + { \int_compare_p:nNn { `#2 } = { "0303 } } + } + } + { \@@_change_case_output:Vwn \c_@@_dot_above_tl } + #1 #2#3 \q_recursion_stop + } + } +% \end{macrocode} +% For upper casing, the test required is for a dot-above char after an I, +% J or I-ogonek. First a test for the appropriate letter, and if found a +% look-ahead and potentially one token dropped. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_upper_lt:Nnw #1 + { + \exp_args:Nf \@@_change_case_upper_lt:nnw + { + \int_case:nnF {`#1} + { + { "0069 } I + { "006A } J + { "012F } \c_@@_I_ogonek_tl + } + \exp_stop_f: + } + } +\cs_new:Npn \@@_change_case_upper_lt:nnw #1#2 + { + \tl_if_blank:nTF {#1} + {#2} + { + \@@_change_case_output:nwn {#1} + \@@_change_case_upper_lt:Nw + } + } +\cs_new:Npn \@@_change_case_upper_lt:Nw #1#2 \q_recursion_stop + { + \tl_if_head_is_N_type:nT {#2} + { \@@_change_case_upper_lt:NNw } + #1 #2 \q_recursion_stop + } +\cs_new:Npn \@@_change_case_upper_lt:NNw #1#2#3 \q_recursion_stop + { + \@@_change_case_if_expandable:NTF #2 + { + \exp_after:wN \@@_change_case_upper_lt:Nw \exp_after:wN #1 #2 + #3 \q_recursion_stop + } + { + \bool_lazy_and:nnTF + { ! \token_if_cs_p:N #2 } + { \int_compare_p:nNn { `#2 } = { "0307 } } + { #1 } + { #1 #2 } + #3 \q_recursion_stop + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\@@_change_case_upper_de-alt:Nnw} +% A simple alternative version for German. +% \begin{macrocode} +\cs_new:cpn { @@_change_case_upper_de-alt:Nnw } #1#2 + { + \int_compare:nNnTF { `#1 } = { 223 } + { \@@_change_case_output:Vwn \c_@@_upper_Eszett_tl } + {#2} + } +% \end{macrocode} +% \end{macro} +% +% \begin{variable} +% { +% \c_@@_std_sigma_tl , +% \c_@@_final_sigma_tl , +% \c_@@_accents_lt_tl , +% \c_@@_dot_above_tl , +% \c_@@_upper_Eszett_tl +% } +% The above needs various special token lists containing pre-formed characters. +% This set are only available in Unicode engines, with no-op definitions +% for $8$-bit use. +% \begin{macrocode} +\bool_lazy_or:nnTF + { \sys_if_engine_luatex_p: } + { \sys_if_engine_xetex_p: } + { + \group_begin: + \cs_set:Npn \@@_tmp:n #1 + { \char_generate:nn {#1} { \char_value_catcode:n {#1} } } + \tl_const:Nx \c_@@_std_sigma_tl { \@@_tmp:n { "03C3 } } + \tl_const:Nx \c_@@_final_sigma_tl { \@@_tmp:n { "03C2 } } + \tl_const:Nx \c_@@_accents_lt_tl + { + \@@_tmp:n { "00CC } + { + \@@_tmp:n { "0069 } + \@@_tmp:n { "0307 } + \@@_tmp:n { "0300 } + } + \@@_tmp:n { "00CD } + { + \@@_tmp:n { "0069 } + \@@_tmp:n { "0307 } + \@@_tmp:n { "0301 } + } + \@@_tmp:n { "0128 } + { + \@@_tmp:n { "0069 } + \@@_tmp:n { "0307 } + \@@_tmp:n { "0303 } + } + } + \tl_const:Nx \c_@@_dot_above_tl { \@@_tmp:n { "0307 } } + \tl_const:Nx \c_@@_upper_Eszett_tl { \@@_tmp:n { "1E9E } } + \group_end: + } + { + \tl_const:Nn \c_@@_std_sigma_tl { } + \tl_const:Nn \c_@@_final_sigma_tl { } + \tl_const:Nn \c_@@_accents_lt_tl { } + \tl_const:Nn \c_@@_dot_above_tl { } + \tl_const:Nn \c_@@_upper_Eszett_tl { } + } +% \end{macrocode} +% \end{variable} +% \begin{variable} +% { +% \c_@@_dotless_i_tl , +% \c_@@_dotted_I_tl , +% \c_@@_i_ogonek_tl , +% \c_@@_I_ogonek_tl , +% } +% For cases where there is an $8$-bit option in the |T1| font set up, +% a variant is provided in both cases. +% \begin{macrocode} +\group_begin: + \bool_lazy_or:nnTF + { \sys_if_engine_luatex_p: } + { \sys_if_engine_xetex_p: } + { + \cs_set_protected:Npn \@@_tmp:w #1#2 + { + \tl_const:Nx #1 + { + \exp_after:wN \exp_after:wN \exp_after:wN + \exp_not:N \char_generate:nn + {"#2} { \char_value_catcode:n {"#2} } + } + } + } + { + \cs_set_protected:Npn \@@_tmp:w #1#2 + { + \group_begin: + \cs_set_protected:Npn \@@_tmp:w ##1##2##3##4 + { + \tl_const:Nx #1 + { + \exp_after:wN \exp_after:wN \exp_after:wN + \exp_not:N \char_generate:nn {##1} { 13 } + \exp_after:wN \exp_after:wN \exp_after:wN + \exp_not:N \char_generate:nn {##2} { 13 } + } + } + \tl_set:Nx \l_@@_internal_a_tl + { \char_codepoint_to_bytes:n {"#2} } + \exp_after:wN \@@_tmp:w \l_@@_internal_a_tl + \group_end: + } + } + \@@_tmp:w \c_@@_dotless_i_tl { 0131 } + \@@_tmp:w \c_@@_dotted_I_tl { 0130 } + \@@_tmp:w \c_@@_i_ogonek_tl { 012F } + \@@_tmp:w \c_@@_I_ogonek_tl { 012E } +\group_end: +% \end{macrocode} +% \end{variable} +% +% For $8$-bit engines we now need to define the case-change data for +% the multi-octet mappings. These need a list of what code points are +% doable in |T1| so the list is hard coded (there's no saving in loading +% the mappings dynamically). All of the straight-forward ones have two +% octets, so that is taken as read. +% \begin{macrocode} +\group_begin: + \bool_lazy_or:nnT + { \sys_if_engine_pdftex_p: } + { \sys_if_engine_uptex_p: } + { + \cs_set_protected:Npn \@@_loop:nn #1#2 + { + \quark_if_recursion_tail_stop:n {#1} + \tl_set:Nx \l_@@_internal_a_tl + { + \char_codepoint_to_bytes:n {"#1} + \char_codepoint_to_bytes:n {"#2} + } + \exp_after:wN \@@_tmp:w \l_@@_internal_a_tl + \@@_loop:nn + } + \cs_set_protected:Npn \@@_tmp:nnnn #1#2#3#4#5 + { + \tl_const:cx + { + c_@@_ #1 _case_ + \char_generate:nn {#2} { 12 } + \char_generate:nn {#3} { 12 } + _tl + } + { + \exp_after:wN \exp_after:wN \exp_after:wN + \exp_not:N \char_generate:nn {#4} { 13 } + \exp_after:wN \exp_after:wN \exp_after:wN + \exp_not:N \char_generate:nn {#5} { 13 } + } + } + \cs_set_protected:Npn \@@_tmp:w #1#2#3#4#5#6#7#8 + { + \tl_const:cx + { + c_@@_lower_case_ + \char_generate:nn {#1} { 12 } + \char_generate:nn {#2} { 12 } + _tl + } + { + \exp_after:wN \exp_after:wN \exp_after:wN + \exp_not:N \char_generate:nn {#5} { 13 } + \exp_after:wN \exp_after:wN \exp_after:wN + \exp_not:N \char_generate:nn {#6} { 13 } + } + \@@_tmp:nnnn { upper } {#5} {#6} {#1} {#2} + \@@_tmp:nnnn { mixed } {#5} {#6} {#1} {#2} + } + \@@_loop:nn + { 00C0 } { 00E0 } + { 00C2 } { 00E2 } + { 00C3 } { 00E3 } + { 00C4 } { 00E4 } + { 00C5 } { 00E5 } + { 00C6 } { 00E6 } + { 00C7 } { 00E7 } + { 00C8 } { 00E8 } + { 00C9 } { 00E9 } + { 00CA } { 00EA } + { 00CB } { 00EB } + { 00CC } { 00EC } + { 00CD } { 00ED } + { 00CE } { 00EE } + { 00CF } { 00EF } + { 00D0 } { 00F0 } + { 00D1 } { 00F1 } + { 00D2 } { 00F2 } + { 00D3 } { 00F3 } + { 00D4 } { 00F4 } + { 00D5 } { 00F5 } + { 00D6 } { 00F6 } + { 00D8 } { 00F8 } + { 00D9 } { 00F9 } + { 00DA } { 00FA } + { 00DB } { 00FB } + { 00DC } { 00FC } + { 00DD } { 00FD } + { 00DE } { 00FE } + { 0100 } { 0101 } + { 0102 } { 0103 } + { 0104 } { 0105 } + { 0106 } { 0107 } + { 0108 } { 0109 } + { 010A } { 010B } + { 010C } { 010D } + { 010E } { 010F } + { 0110 } { 0111 } + { 0112 } { 0113 } + { 0114 } { 0115 } + { 0116 } { 0117 } + { 0118 } { 0119 } + { 011A } { 011B } + { 011C } { 011D } + { 011E } { 011F } + { 0120 } { 0121 } + { 0122 } { 0123 } + { 0124 } { 0125 } + { 0128 } { 0129 } + { 012A } { 012B } + { 012C } { 012D } + { 012E } { 012F } + { 0132 } { 0133 } + { 0134 } { 0135 } + { 0136 } { 0137 } + { 0139 } { 013A } + { 013B } { 013C } + { 013E } { 013F } + { 0141 } { 0142 } + { 0143 } { 0144 } + { 0145 } { 0146 } + { 0147 } { 0148 } + { 014A } { 014B } + { 014C } { 014D } + { 014E } { 014F } + { 0150 } { 0151 } + { 0152 } { 0153 } + { 0154 } { 0155 } + { 0156 } { 0157 } + { 0158 } { 0159 } + { 015A } { 015B } + { 015C } { 015D } + { 015E } { 015F } + { 0160 } { 0161 } + { 0162 } { 0163 } + { 0164 } { 0165 } + { 0168 } { 0169 } + { 016A } { 016B } + { 016C } { 016D } + { 016E } { 016F } + { 0170 } { 0171 } + { 0172 } { 0173 } + { 0174 } { 0175 } + { 0176 } { 0177 } + { 0178 } { 00FF } + { 0179 } { 017A } + { 017B } { 017C } + { 017D } { 017E } + { 01CD } { 01CE } + { 01CF } { 01D0 } + { 01D1 } { 01D2 } + { 01D3 } { 01D4 } + { 01E2 } { 01E3 } + { 01E6 } { 01E7 } + { 01E8 } { 01E9 } + { 01EA } { 01EB } + { 01F4 } { 01F5 } + { 0218 } { 0219 } + { 021A } { 021B } + \q_recursion_tail ? + \q_recursion_stop + \cs_set_protected:Npn \@@_tmp:w #1#2#3 + { + \group_begin: + \cs_set_protected:Npn \@@_tmp:w ##1##2##3##4 + { + \tl_const:cx + { + c_@@_ #3 _case_ + \char_generate:nn {##1} { 12 } + \char_generate:nn {##2} { 12 } + _tl + } + {#2} + } + \tl_set:Nx \l_@@_internal_a_tl + { \char_codepoint_to_bytes:n { "#1 } } + \exp_after:wN \@@_tmp:w \l_@@_internal_a_tl + \group_end: + } + \@@_tmp:w { 00DF } { SS } { upper } + \@@_tmp:w { 00DF } { Ss } { mixed } + \@@_tmp:w { 0131 } { I } { upper } + } + \group_end: +% \end{macrocode} +% +% The (fixed) look-up mappings for letter-like control sequences. +% \begin{macrocode} +\group_begin: + \cs_set_protected:Npn \@@_change_case_setup:NN #1#2 + { + \quark_if_recursion_tail_stop:N #1 + \tl_const:cn { c_@@_change_case_lower_ \token_to_str:N #1 _tl } + { #2 } + \tl_const:cn { c_@@_change_case_upper_ \token_to_str:N #2 _tl } + { #1 } + \@@_change_case_setup:NN + } + \@@_change_case_setup:NN + \AA \aa + \AE \ae + \DH \dh + \DJ \dj + \IJ \ij + \L \l + \NG \ng + \O \o + \OE \oe + \SS \ss + \TH \th + \q_recursion_tail ? + \q_recursion_stop + \tl_const:cn { c_@@_change_case_upper_ \token_to_str:N \i _tl } { I } + \tl_const:cn { c_@@_change_case_upper_ \token_to_str:N \j _tl } { J } +\group_end: +% \end{macrocode} +% +% \begin{variable}{\l_tl_case_change_accents_tl} +% A list of accents to leave alone. +% \begin{macrocode} +\tl_new:N \l_tl_case_change_accents_tl +\tl_set:Nn \l_tl_case_change_accents_tl + { \" \' \. \^ \` \~ \c \H \k \r \t \u \v } +% \end{macrocode} +% \end{variable} +% +% \begin{macro}[EXP]{\@@_change_case_mixed_nl:Nnw} +% \begin{macro}[EXP]{\@@_change_case_mixed_nl:Nw} +% \begin{macro}[EXP]{\@@_change_case_mixed_nl:NNw} +% For Dutch, there is a single look-ahead test for \texttt{ij} when +% title casing. If the appropriate letters are found, produce \texttt{IJ} +% and gobble the \texttt{j}/\texttt{J}. +% \begin{macrocode} +\cs_new:Npn \@@_change_case_mixed_nl:Nnw #1 + { + \bool_lazy_or:nnTF + { \int_compare_p:nNn { `#1 } = { `i } } + { \int_compare_p:nNn { `#1 } = { `I } } + { + \@@_change_case_output:nwn { I } + \@@_change_case_mixed_nl:Nw + } + } +\cs_new:Npn \@@_change_case_mixed_nl:Nw #1#2 \q_recursion_stop + { + \tl_if_head_is_N_type:nT {#2} + { \@@_change_case_mixed_nl:NNw } + #1 #2 \q_recursion_stop + } +\cs_new:Npn \@@_change_case_mixed_nl:NNw #1#2#3 \q_recursion_stop + { + \@@_change_case_if_expandable:NTF #2 + { + \exp_after:wN \@@_change_case_mixed_nl:Nw \exp_after:wN #1 #2 + #3 \q_recursion_stop + } + { + \bool_lazy_and:nnTF + { ! ( \token_if_cs_p:N #2 ) } + { + \bool_lazy_or_p:nn + { \int_compare_p:nNn { `#2 } = { `j } } + { \int_compare_p:nNn { `#2 } = { `J } } + } + { + \@@_change_case_output:nwn { J } + #1 + } + { #1 #2 } + #3 \q_recursion_stop + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{variable}{\l_tl_case_change_math_tl} +% The list of token pairs which are treated as math mode and so +% not case changed. +% \begin{macrocode} +\tl_new:N \l_tl_case_change_math_tl +%<*package> +\tl_set:Nn \l_tl_case_change_math_tl + { $ $ \( \) } +%</package> +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_tl_case_change_exclude_tl} +% The list of commands for which an argument is not case changed. +% \begin{macrocode} +\tl_new:N \l_tl_case_change_exclude_tl +%<*package> +\tl_set:Nn \l_tl_case_change_exclude_tl + { \cite \ensuremath \label \ref } +%</package> +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_tl_mixed_case_ignore_tl} +% Characters to skip over when finding the first letter in a word to be +% mixed cased. +% \begin{macrocode} +\tl_new:N \l_tl_mixed_case_ignore_tl +\tl_set:Nx \l_tl_mixed_case_ignore_tl + { + ( % ) + [ % ] + \cs_to_str:N \{ % \} + ` + - + } +% \end{macrocode} +% \end{variable} +% +% \subsubsection{Building a token list} +% +% Between \cs{tl_build_begin:N} \meta{tl~var} and \cs{tl_build_end:N} +% \meta{tl~var}, the \meta{tl~var} has the structure +% \begin{quote} +% \cs{exp_end:} \ldots{} \cs{exp_end:} \cs{@@_build_last:NNn} +% \meta{assignment} \meta{next~tl} \Arg{left} \meta{right} +% \end{quote} +% where \meta{right} is not braced. The \enquote{data} it represents is +% \meta{left} followed by the \enquote{data} of \meta{next~tl} followed +% by \meta{right}. The \meta{next~tl} is a token list variable whose +% name is that of \meta{tl~var} followed by~|'|. There are between $0$ +% and $4$ \cs{exp_end:} to keep track of when \meta{left} and +% \meta{right} should be put into the \meta{next~tl}. The +% \meta{assignment} is \cs{cs_set_nopar:Npx} if the variable is local, +% and \cs{cs_gset_nopar:Npx} if it is global. +% +% \begin{macro}{\tl_build_begin:N, \tl_build_gbegin:N} +% \begin{macro}{\@@_build_begin:NN, \@@_build_begin:NNN} +% First construct the \meta{next~tl}: using a prime here conflicts +% with the usual \pkg{expl3} convention but we need a name that can be +% derived from |#1| without any external data such as a counter. +% Empty that \meta{next~tl} and setup the structure. The local and +% global versions only differ by a single function +% \cs[no-index]{cs_(g)set_nopar:Npx} used for all assignments: this is +% important because only that function is stored in the \meta{tl~var} +% and \meta{next~tl} for subsequent assignments. In principle +% \cs{@@_build_begin:NNN} could use \cs[no-index]{tl_(g)clear_new:N} +% to empty |#1| and make sure it is defined, but logging the +% definition does not seem useful so we just do |#3| |#1| |{}| to +% clear it locally or globally as appropriate. +% \begin{macrocode} +\__kernel_patch:nnNNpn { \__kernel_chk_var_local:N #1 } { } +\cs_new_protected:Npn \tl_build_begin:N #1 + { \@@_build_begin:NN \cs_set_nopar:Npx #1 } +\__kernel_patch:nnNNpn { \__kernel_chk_var_global:N #1 } { } +\cs_new_protected:Npn \tl_build_gbegin:N #1 + { \@@_build_begin:NN \cs_gset_nopar:Npx #1 } +\cs_new_protected:Npn \@@_build_begin:NN #1#2 + { \exp_args:Nc \@@_build_begin:NNN { \cs_to_str:N #2 ' } #2 #1 } +\cs_new_protected:Npn \@@_build_begin:NNN #1#2#3 + { + #3 #1 { } + #3 #2 + { + \exp_not:n { \exp_end: \exp_end: \exp_end: \exp_end: } + \exp_not:n { \@@_build_last:NNn #3 #1 { } } + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\tl_build_clear:N, \tl_build_gclear:N} +% The |begin| and |gbegin| functions already clear enough to make the +% token list variable effectively empty. Eventually the |begin| and +% |gbegin| functions should check that |#1'| is empty or undefined, +% while the |clear| and |gclear| functions ought to empty |#1'|, +% |#1''| and so on, similar to \cs{tl_build_end:N}. This only affects +% memory usage. +% \begin{macrocode} +\cs_new_eq:NN \tl_build_clear:N \tl_build_begin:N +\cs_new_eq:NN \tl_build_gclear:N \tl_build_gbegin:N +% \end{macrocode} +% \end{macro} +% +% \begin{macro} +% { +% \tl_build_put_right:Nn, \tl_build_put_right:Nx, +% \tl_build_gput_right:Nn, \tl_build_gput_right:Nx, +% \@@_build_last:NNn, \@@_build_put:nn, \@@_build_put:nw +% } +% Similar to \cs{tl_put_right:Nn}, but apply \cs{exp:w} to |#1|. Most +% of the time this just removes one \cs{exp_end:}. When there are +% none left, \cs{@@_build_last:NNn} is expanded instead. It resets +% the definition of the \meta{tl~var} by ending the \cs{exp_not:n} and +% the definition early. Then it makes sure the \meta{next~tl} (its +% argument |#1|) is set-up and starts a new definition. Then +% \cs{@@_build_put:nn} and \cs{@@_build_put:nw} place the \meta{left} +% part of the original \meta{tl~var} as appropriate for the definition +% of the \meta{next~tl} (the \meta{right} part is left in the right +% place without ever becoming a macro argument). We use +% \cs{exp_after:wN} rather than some \cs{exp_args:No} to avoid reading +% arguments that are likely very long token lists. We use +% \cs[no-index]{cs_(g)set_nopar:Npx} rather than +% \cs[no-index]{tl_(g)set:Nx} partly for the same reason and partly +% because the assignments are interrupted by brace tricks, which +% implies that the assignment does not simply set the token list to an +% |x|-expansion of the second argument. +% \begin{macrocode} +\__kernel_patch:nnNNpn { \__kernel_chk_var_local:N #1 } { } +\cs_new_protected:Npn \tl_build_put_right:Nn #1#2 + { + \cs_set_nopar:Npx #1 + { \exp_after:wN \exp_not:n \exp_after:wN { \exp:w #1 #2 } } + } +\__kernel_patch:nnNNpn { \__kernel_chk_var_local:N #1 } { } +\cs_new_protected:Npn \tl_build_put_right:Nx #1#2 + { + \cs_set_nopar:Npx #1 + { \exp_after:wN \exp_not:n \exp_after:wN { \exp:w #1 } #2 } + } +\__kernel_patch:nnNNpn { \__kernel_chk_var_global:N #1 } { } +\cs_new_protected:Npn \tl_build_gput_right:Nn #1#2 + { + \cs_gset_nopar:Npx #1 + { \exp_after:wN \exp_not:n \exp_after:wN { \exp:w #1 #2 } } + } +\__kernel_patch:nnNNpn { \__kernel_chk_var_global:N #1 } { } +\cs_new_protected:Npn \tl_build_gput_right:Nx #1#2 + { + \cs_gset_nopar:Npx #1 + { \exp_after:wN \exp_not:n \exp_after:wN { \exp:w #1 } #2 } + } +\cs_new_protected:Npn \@@_build_last:NNn #1#2 + { + \if_false: { { \fi: + \exp_end: \exp_end: \exp_end: \exp_end: \exp_end: + \@@_build_last:NNn #1 #2 { } + } + } + \if_meaning:w \c_empty_tl #2 + \@@_build_begin:NN #1 #2 + \fi: + #1 #2 + { + \exp_after:wN \exp_not:n \exp_after:wN + { + \exp:w \if_false: } } \fi: + \exp_after:wN \@@_build_put:nn \exp_after:wN {#2} + } +\cs_new_protected:Npn \@@_build_put:nn #1#2 { \@@_build_put:nw {#2} #1 } +\cs_new_protected:Npn \@@_build_put:nw #1#2 \@@_build_last:NNn #3#4#5 + { #2 \@@_build_last:NNn #3 #4 { #1 #5 } } +% \end{macrocode} +% \end{macro} +% +% \begin{macro} +% { +% \tl_build_put_left:Nn, \tl_build_put_left:Nx, +% \tl_build_gput_left:Nn, \tl_build_gput_left:Nx, \@@_build_put_left:NNn +% } +% See \cs{tl_build_put_right:Nn} for all the machinery. We could +% easily provide \cs[no-index]{tl_build_put_left_right:Nnn}, by just +% add the \meta{right} material after the \Arg{left} in the +% |x|-expanding assignment. +% \begin{macrocode} +\__kernel_patch:nnNNpn { \__kernel_chk_var_local:N #1 } { } +\cs_new_protected:Npn \tl_build_put_left:Nn #1 + { \@@_build_put_left:NNn \cs_set_nopar:Npx #1 } +\cs_generate_variant:Nn \tl_build_put_left:Nn { Nx } +\__kernel_patch:nnNNpn { \__kernel_chk_var_global:N #1 } { } +\cs_new_protected:Npn \tl_build_gput_left:Nn #1 + { \@@_build_put_left:NNn \cs_gset_nopar:Npx #1 } +\cs_generate_variant:Nn \tl_build_gput_left:Nn { Nx } +\cs_new_protected:Npn \@@_build_put_left:NNn #1#2#3 + { + #1 #2 + { + \exp_after:wN \exp_not:n \exp_after:wN + { + \exp:w \exp_after:wN \@@_build_put:nn + \exp_after:wN {#2} {#3} + } + } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\tl_build_get:NN} +% \begin{macro}{\@@_build_get:NNN, \@@_build_get:w, \@@_build_get_end:w} +% The idea is to expand the \meta{tl~var} then the \meta{next~tl} and +% so on, all within an |x|-expanding assignment, and wrap as +% appropriate in \cs{exp_not:n}. The various \meta{left} parts are +% left in the assignment as we go, which enables us to expand the +% \meta{next~tl} at the right place. The various \meta{right} parts +% are eventually picked up in one last \cs{exp_not:n}, with a brace +% trick to wrap all the \meta{right} parts together. +% \begin{macrocode} +\cs_new_protected:Npn \tl_build_get:NN + { \@@_build_get:NNN \tl_set:Nx } +\cs_new_protected:Npn \@@_build_get:NNN #1#2#3 + { #1 #3 { \if_false: { \fi: \exp_after:wN \@@_build_get:w #2 } } } +\cs_new:Npn \@@_build_get:w #1 \@@_build_last:NNn #2#3#4 + { + \exp_not:n {#4} + \if_meaning:w \c_empty_tl #3 + \exp_after:wN \@@_build_get_end:w + \fi: + \exp_after:wN \@@_build_get:w #3 + } +\cs_new:Npn \@@_build_get_end:w #1#2#3 + { \exp_after:wN \exp_not:n \exp_after:wN { \if_false: } \fi: } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\tl_build_end:N, \tl_build_gend:N, \@@_build_end_loop:NN} +% Get the data then clear the \meta{next~tl} recursively until finding +% an empty one. It is perhaps wasteful to repeatedly use +% \cs{cs_to_sr:N}. The local/global scope is checked by +% \cs{tl_set:Nx} or \cs{tl_gset:Nx}. +% \begin{macrocode} +\cs_new_protected:Npn \tl_build_end:N #1 + { + \@@_build_get:NNN \tl_set:Nx #1 #1 + \exp_args:Nc \@@_build_end_loop:NN { \cs_to_str:N #1 ' } \tl_clear:N + } +\cs_new_protected:Npn \tl_build_gend:N #1 + { + \@@_build_get:NNN \tl_gset:Nx #1 #1 + \exp_args:Nc \@@_build_end_loop:NN { \cs_to_str:N #1 ' } \tl_gclear:N + } +\cs_new_protected:Npn \@@_build_end_loop:NN #1#2 + { + \if_meaning:w \c_empty_tl #1 + \exp_after:wN \use_none:nnnnnn + \fi: + #2 #1 + \exp_args:Nc \@@_build_end_loop:NN { \cs_to_str:N #1 ' } #2 + } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Other additions to \pkg{l3tl}} +% +% \begin{macro}{\tl_range_braced:Nnn, \tl_range_braced:cnn, \tl_range_braced:nnn} +% \begin{macro} +% {\tl_range_unbraced:Nnn, \tl_range_unbraced:cnn, \tl_range_unbraced:nnn} +% \begin{macro} +% { +% \@@_range_braced:w, \@@_range_collect_braced:w, +% \@@_range_unbraced:w, \@@_range_collect_unbraced:w, +% } +% For the braced version \cs{@@_range_braced:w} sets up +% \cs{@@_range_collect_braced:w} which stores items one by one in an +% argument after the semicolon. The unbraced version is almost +% identical. The version preserving braces and spaces starts by +% deleting spaces before the argument to avoid collecting them, and +% sets up \cs{@@_range_collect:nn} with a first argument of the form +% |{| \Arg{collected} \meta{tokens} |}|, whose head is the collected +% tokens and whose tail is what remains of the original token list. +% This form makes it easier to move tokens to the \meta{collected} +% tokens. +% \begin{macrocode} +\cs_new:Npn \tl_range_braced:Nnn { \exp_args:No \tl_range_braced:nnn } +\cs_generate_variant:Nn \tl_range_braced:Nnn { c } +\cs_new:Npn \tl_range_braced:nnn { \@@_range:Nnnn \@@_range_braced:w } +\cs_new:Npn \tl_range_unbraced:Nnn + { \exp_args:No \tl_range_unbraced:nnn } +\cs_generate_variant:Nn \tl_range_unbraced:Nnn { c } +\cs_new:Npn \tl_range_unbraced:nnn + { \@@_range:Nnnn \@@_range_unbraced:w } +\cs_new:Npn \@@_range_braced:w #1 ; #2 + { \@@_range_collect_braced:w #1 ; { } #2 } +\cs_new:Npn \@@_range_unbraced:w #1 ; #2 + { \@@_range_collect_unbraced:w #1 ; { } #2 } +\cs_new:Npn \@@_range_collect_braced:w #1 ; #2#3 + { + \if_int_compare:w #1 > 1 \exp_stop_f: + \exp_after:wN \@@_range_collect_braced:w + \int_value:w \int_eval:n { #1 - 1 } \exp_after:wN ; + \fi: + { #2 {#3} } + } +\cs_new:Npn \@@_range_collect_unbraced:w #1 ; #2#3 + { + \if_int_compare:w #1 > 1 \exp_stop_f: + \exp_after:wN \@@_range_collect_unbraced:w + \int_value:w \int_eval:n { #1 - 1 } \exp_after:wN ; + \fi: + { #2 #3 } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \subsection{Additions to \pkg{l3token}} +% +% \begin{variable}{\c_catcode_active_space_tl} +% While \cs{char_generate:nn} can produce active characters in some +% engines it cannot in general. It would be possible to simply change +% the catcode of space but then the code would need to avoid all +% spaces, making it quite unreadable. Instead we use the primitive +% \cs{tex_lowercase:D} trick. +% \begin{macrocode} +\group_begin: + \char_set_catcode_active:N * + \char_set_lccode:nn { `* } { `\ } + \tex_lowercase:D { \tl_const:Nn \c_catcode_active_space_tl { * } } +\group_end: +% \end{macrocode} +% \end{variable} +% +% \begin{macrocode} +%<@@=peek> +% \end{macrocode} +% +% \begin{variable}{\l_@@_collect_tl} +% \begin{macrocode} +\tl_new:N \l_@@_collect_tl +% \end{macrocode} +% \end{variable} +% +% \begin{macro} +% { +% \peek_catcode_collect_inline:Nn, +% \peek_charcode_collect_inline:Nn, +% \peek_meaning_collect_inline:Nn +% } +% \begin{macro} +% { +% \@@_collect:NNn, \@@_collect_true:w, +% \@@_collect_remove:nw, \@@_collect:N +% } +% Most of the work is done by \cs{@@_execute_branches_\ldots{}:}, +% which calls either \cs{@@_true:w} or \cs{@@_false:w} according to +% whether the next token \cs{l_peek_token} matches the search token +% (stored in \cs{l_@@_search_token} and \cs{l_@@_search_tl}). +% Here, in the \texttt{true} case we run \cs{@@_collect_true:w}, +% which generally calls \cs{@@_collect:N} to store the peeked token +% into \cs{l_@@_collect_tl}, except in special non-\texttt{N}-type +% cases (begin-group, end-group, or space), where a frozen token is +% stored. The \texttt{true} branch calls +% \cs{@@_execute_branches_\ldots{}:} to fetch more matching tokens. +% Once there are no more, \cs{@@_false_aux:n} closes the safe-align +% group and runs the user's inline code. +% \begin{macrocode} +\cs_new_protected:Npn \peek_catcode_collect_inline:Nn + { \@@_collect:NNn \@@_execute_branches_catcode: } +\cs_new_protected:Npn \peek_charcode_collect_inline:Nn + { \@@_collect:NNn \@@_execute_branches_charcode: } +\cs_new_protected:Npn \peek_meaning_collect_inline:Nn + { \@@_collect:NNn \@@_execute_branches_meaning: } +\cs_new_protected:Npn \@@_collect:NNn #1#2#3 + { + \group_align_safe_begin: + \cs_set_eq:NN \l_@@_search_token #2 + \tl_set:Nn \l_@@_search_tl {#2} + \tl_clear:N \l_@@_collect_tl + \cs_set:Npn \@@_false:w + { \exp_args:No \@@_false_aux:n \l_@@_collect_tl } + \cs_set:Npn \@@_false_aux:n ##1 + { + \group_align_safe_end: + #3 + } + \cs_set_eq:NN \@@_true:w \@@_collect_true:w + \cs_set:Npn \@@_true_aux:w { \peek_after:Nw #1 } + \@@_true_aux:w + } +\cs_new_protected:Npn \@@_collect_true:w + { + \if_case:w + \if_catcode:w \exp_not:N \l_peek_token { 1 \exp_stop_f: \fi: + \if_catcode:w \exp_not:N \l_peek_token } 2 \exp_stop_f: \fi: + \if_meaning:w \l_peek_token \c_space_token 3 \exp_stop_f: \fi: + 0 \exp_stop_f: + \exp_after:wN \@@_collect:N + \or: \@@_collect_remove:nw { \c_group_begin_token } + \or: \@@_collect_remove:nw { \c_group_end_token } + \or: \@@_collect_remove:nw { ~ } + \fi: + } +\cs_new_protected:Npn \@@_collect:N #1 + { + \tl_put_right:Nn \l_@@_collect_tl {#1} + \@@_true_aux:w + } +\cs_new_protected:Npn \@@_collect_remove:nw #1 + { + \tl_put_right:Nn \l_@@_collect_tl {#1} + \exp_after:wN \@@_true_remove:w + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macrocode} +%</initex|package> +% \end{macrocode} +% +% \end{implementation} +% +% \PrintIndex |