summaryrefslogtreecommitdiff
path: root/macros/latex/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/contrib')
-rw-r--r--macros/latex/contrib/l3kernel/CHANGELOG.md17
-rw-r--r--macros/latex/contrib/l3kernel/README.md2
-rw-r--r--macros/latex/contrib/l3kernel/expl3.dtx30
-rw-r--r--macros/latex/contrib/l3kernel/expl3.pdfbin585109 -> 590134 bytes
-rw-r--r--macros/latex/contrib/l3kernel/interface3.pdfbin1558149 -> 1571071 bytes
-rw-r--r--macros/latex/contrib/l3kernel/interface3.tex2
-rw-r--r--macros/latex/contrib/l3kernel/l3basics.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3bootstrap.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3box.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3candidates.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3cctab.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3clist.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3coffins.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3color-base.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3debug.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3deprecation.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3doc.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3docstrip.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3docstrip.pdfbin263178 -> 263035 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3expan.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3file.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3flag.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fp-assign.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fp-aux.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fp-basics.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fp-convert.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fp-expo.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fp-extended.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fp-logic.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fp-parse.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fp-random.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fp-round.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fp-traps.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fp-trig.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fp.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3fparray.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3int.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3intarray.dtx47
-rw-r--r--macros/latex/contrib/l3kernel/l3kernel-functions.dtx27
-rw-r--r--macros/latex/contrib/l3kernel/l3keys.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3legacy.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3luatex.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3msg.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3names.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3news.pdfbin384356 -> 379340 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3news01.pdfbin113527 -> 113533 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3news02.pdfbin140776 -> 140873 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3news03.pdfbin144785 -> 138073 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3news04.pdfbin107115 -> 107122 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3news05.pdfbin105090 -> 105090 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3news06.pdfbin140795 -> 140905 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3news07.pdfbin119094 -> 119094 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3news08.pdfbin152413 -> 152504 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3news09.pdfbin235322 -> 235877 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3news10.pdfbin147097 -> 140388 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3news11.pdfbin165946 -> 159248 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3news12.pdfbin199139 -> 192502 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3prefixes.csv13
-rw-r--r--macros/latex/contrib/l3kernel/l3prefixes.pdfbin75933 -> 76183 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3prg.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3prop.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3quark.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3regex.dtx1219
-rw-r--r--macros/latex/contrib/l3kernel/l3seq.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3skip.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3sort.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3str-convert.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3str.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3styleguide.pdfbin279954 -> 279773 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3styleguide.tex2
-rw-r--r--macros/latex/contrib/l3kernel/l3syntax-changes.pdfbin228250 -> 228077 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3syntax-changes.tex2
-rw-r--r--macros/latex/contrib/l3kernel/l3sys.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3term-glossary.pdfbin217627 -> 217446 bytes
-rw-r--r--macros/latex/contrib/l3kernel/l3term-glossary.tex2
-rw-r--r--macros/latex/contrib/l3kernel/l3text-case.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3text-purify.dtx4
-rw-r--r--macros/latex/contrib/l3kernel/l3text.dtx22
-rw-r--r--macros/latex/contrib/l3kernel/l3tl-analysis.dtx444
-rw-r--r--macros/latex/contrib/l3kernel/l3tl.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/l3token.dtx229
-rw-r--r--macros/latex/contrib/l3kernel/l3unicode.dtx2
-rw-r--r--macros/latex/contrib/l3kernel/source3.pdfbin5903982 -> 5985840 bytes
-rw-r--r--macros/latex/contrib/l3kernel/source3.tex2
-rw-r--r--macros/latex/contrib/l3kernel/source3body.tex18
-rw-r--r--macros/latex/contrib/pbalance/LICENSE11
-rw-r--r--macros/latex/contrib/pbalance/README.md14
-rw-r--r--macros/latex/contrib/pbalance/pbalance.dtx1069
-rw-r--r--macros/latex/contrib/pbalance/pbalance.ins49
-rw-r--r--macros/latex/contrib/pbalance/pbalance.pdfbin0 -> 307144 bytes
-rw-r--r--macros/latex/contrib/rest-api/README.md2
-rw-r--r--macros/latex/contrib/rest-api/rest-api.dtx210
-rw-r--r--macros/latex/contrib/rest-api/rest-api.ins4
-rw-r--r--macros/latex/contrib/rest-api/rest-api.pdfbin142219 -> 154481 bytes
94 files changed, 2994 insertions, 541 deletions
diff --git a/macros/latex/contrib/l3kernel/CHANGELOG.md b/macros/latex/contrib/l3kernel/CHANGELOG.md
index cfca3155d0..5e967d3607 100644
--- a/macros/latex/contrib/l3kernel/CHANGELOG.md
+++ b/macros/latex/contrib/l3kernel/CHANGELOG.md
@@ -7,10 +7,22 @@ this project uses date-based 'snapshot' version identifiers.
## [Unreleased]
-## [2020-10-27]
+## [2020-12-03]
### Added
+- `\peek_analysis_map_inline:n`
+- `\peek_regex:nTF`, `\peek_regex_remove_once:nTF`, and
+ `\peek_regex_replace_once:nnTF`
+- `\token_case_catcode:NnTF`, `\token_case_charcode:NnTF`, and
+ `\token_case_meaning:NnTF`
+
+### Changed
+- Extend `\text_expand:n` to cover `\@protected@testopt`
+- Extend `\text_purify:n` to cover `\@protected@testopt`
+## [2020-10-27]
+
+### Added
- `\token_if_font_selection:N(TF)` (see #806)
### Fixed
@@ -793,7 +805,8 @@ this project uses date-based 'snapshot' version identifiers.
- Step func­tions have been added for dim vari­ables,
e.g. `\dim_step_in­line:nnnn`
-[Unreleased]: https://github.com/latex3/latex3/compare/2020-10-27...HEAD
+[Unreleased]: https://github.com/latex3/latex3/compare/2020-12-03...HEAD
+[2020-12-03]: https://github.com/latex3/latex3/compare/2020-10-27...2020-12-03
[2020-10-27]: https://github.com/latex3/latex3/compare/2020-10-05...2020-10-27
[2020-10-05]: https://github.com/latex3/latex3/compare/2020-09-24...2020-10-05
[2020-09-24]: https://github.com/latex3/latex3/compare/2020-09-06...2020-09-24
diff --git a/macros/latex/contrib/l3kernel/README.md b/macros/latex/contrib/l3kernel/README.md
index 2361e047a8..5a0e2ee3a0 100644
--- a/macros/latex/contrib/l3kernel/README.md
+++ b/macros/latex/contrib/l3kernel/README.md
@@ -1,7 +1,7 @@
LaTeX3 Programming Conventions
==============================
-Release 2020-10-27
+Release 2020-12-03
Overview
--------
diff --git a/macros/latex/contrib/l3kernel/expl3.dtx b/macros/latex/contrib/l3kernel/expl3.dtx
index 59b8cad3a7..fc49c0aa90 100644
--- a/macros/latex/contrib/l3kernel/expl3.dtx
+++ b/macros/latex/contrib/l3kernel/expl3.dtx
@@ -24,7 +24,7 @@
%
%<*driver|generic|package|2ekernel>
%</driver|generic|package|2ekernel>
-\def\ExplFileDate{2020-10-27}%
+\def\ExplFileDate{2020-12-03}%
%<*driver>
\documentclass[full]{l3doc}
\usepackage{graphicx}
@@ -51,7 +51,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
@@ -526,9 +526,20 @@
% \end{arg-description}
% There are two other specifiers with more general meanings:
% \begin{arg-description}
-% \item[D] This means: \textbf{Do not use}. This special case is used
-% for \TeX{} primitives. Programmers outside the kernel team should
-% not use these functions!
+% \item[D] Stands for \textbf{Do not use}. This special case is used
+% for \TeX{} primitives. These functions have no standardized
+% syntax, they are engine dependent and their name can change
+% without warning, thus their use is \emph{strongly discouraged} in
+% package code: programmers should instead use the interfaces
+% documented in \href{interface3.pdf}{interface3.pdf}%^^A
+% \footnote{If a primitive offers a functionality not yet in the
+% kernel, programmers and users are encouraged to write to the
+% \texttt{LaTeX-L} mailing list
+% (\url{mailto:LATEX-L@listserv.uni-heidelberg.de}) describing
+% their use-case and intended behaviour, so that a possible
+% interface can be discussed. Temporarily, while an interface is
+% not provided, programmers may use the procedure described in the
+% \href{l3styleguide.pdf}{l3styleguide.pdf}.}.
% \item[w] This means that the argument syntax is \enquote{weird} in that it
% does not follow any standard rule. It is used for functions with
% arguments that take non standard forms: examples are \TeX{}-level
@@ -1072,13 +1083,10 @@
% \begin{macrocode}
%<*2ekernel|generic>
\begingroup
- \catcode`\>=12
- \def\aux#1>{}
- \def\auxi{c__kernel_expl_date_tl}%
- \edef\auxi{\expandafter\aux\meaning\auxi}%
+ \catcode`\_=11
\expandafter
- \ifx\csname\auxi\endcsname\relax
- \global\expandafter\let\csname\auxi\endcsname\ExplFileDate
+ \ifx\csname c__kernel_expl_date_tl\endcsname\relax
+ \global\let\c__kernel_expl_date_tl\ExplFileDate
\fi
\endgroup
%</2ekernel|generic>
diff --git a/macros/latex/contrib/l3kernel/expl3.pdf b/macros/latex/contrib/l3kernel/expl3.pdf
index c249956331..6667f5c234 100644
--- a/macros/latex/contrib/l3kernel/expl3.pdf
+++ b/macros/latex/contrib/l3kernel/expl3.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/interface3.pdf b/macros/latex/contrib/l3kernel/interface3.pdf
index b7b0dbf9a9..a8ced65b24 100644
--- a/macros/latex/contrib/l3kernel/interface3.pdf
+++ b/macros/latex/contrib/l3kernel/interface3.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/interface3.tex b/macros/latex/contrib/l3kernel/interface3.tex
index 071ddd4bdf..cb7bb6e7eb 100644
--- a/macros/latex/contrib/l3kernel/interface3.tex
+++ b/macros/latex/contrib/l3kernel/interface3.tex
@@ -54,7 +54,7 @@ for those people who are interested.
{latex-team@latex-project.org}%
}%
}
-\date{Released 2020-10-27}
+\date{Released 2020-12-03}
\pagenumbering{roman}
\maketitle
diff --git a/macros/latex/contrib/l3kernel/l3basics.dtx b/macros/latex/contrib/l3kernel/l3basics.dtx
index e7f2c4fd63..025f016eb8 100644
--- a/macros/latex/contrib/l3kernel/l3basics.dtx
+++ b/macros/latex/contrib/l3kernel/l3basics.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3bootstrap.dtx b/macros/latex/contrib/l3kernel/l3bootstrap.dtx
index eba03dca56..2139dfae9f 100644
--- a/macros/latex/contrib/l3kernel/l3bootstrap.dtx
+++ b/macros/latex/contrib/l3kernel/l3bootstrap.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3box.dtx b/macros/latex/contrib/l3kernel/l3box.dtx
index a866d6324b..3bd2f1de85 100644
--- a/macros/latex/contrib/l3kernel/l3box.dtx
+++ b/macros/latex/contrib/l3kernel/l3box.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3candidates.dtx b/macros/latex/contrib/l3kernel/l3candidates.dtx
index 17f7f33891..f376f0e0a4 100644
--- a/macros/latex/contrib/l3kernel/l3candidates.dtx
+++ b/macros/latex/contrib/l3kernel/l3candidates.dtx
@@ -44,7 +44,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3cctab.dtx b/macros/latex/contrib/l3kernel/l3cctab.dtx
index 6e57d4a1bf..2918f8cae3 100644
--- a/macros/latex/contrib/l3kernel/l3cctab.dtx
+++ b/macros/latex/contrib/l3kernel/l3cctab.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3clist.dtx b/macros/latex/contrib/l3kernel/l3clist.dtx
index 779062a3cf..f45ff58a92 100644
--- a/macros/latex/contrib/l3kernel/l3clist.dtx
+++ b/macros/latex/contrib/l3kernel/l3clist.dtx
@@ -44,7 +44,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3coffins.dtx b/macros/latex/contrib/l3kernel/l3coffins.dtx
index 0c0c3c776c..f4d6810d9a 100644
--- a/macros/latex/contrib/l3kernel/l3coffins.dtx
+++ b/macros/latex/contrib/l3kernel/l3coffins.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3color-base.dtx b/macros/latex/contrib/l3kernel/l3color-base.dtx
index 1d88e3d6ff..fb04d8eee0 100644
--- a/macros/latex/contrib/l3kernel/l3color-base.dtx
+++ b/macros/latex/contrib/l3kernel/l3color-base.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3debug.dtx b/macros/latex/contrib/l3kernel/l3debug.dtx
index 30f0f78b5e..d05991e37d 100644
--- a/macros/latex/contrib/l3kernel/l3debug.dtx
+++ b/macros/latex/contrib/l3kernel/l3debug.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3deprecation.dtx b/macros/latex/contrib/l3kernel/l3deprecation.dtx
index 6d13ae1b38..e60766e876 100644
--- a/macros/latex/contrib/l3kernel/l3deprecation.dtx
+++ b/macros/latex/contrib/l3kernel/l3deprecation.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3doc.dtx b/macros/latex/contrib/l3kernel/l3doc.dtx
index 25ad20f7dd..3eff299b53 100644
--- a/macros/latex/contrib/l3kernel/l3doc.dtx
+++ b/macros/latex/contrib/l3kernel/l3doc.dtx
@@ -79,7 +79,7 @@ and all files in that bundle must be distributed together.
%
% \title{The \cls{l3doc} class}
% \author{\Team}
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
% \maketitle
% \tableofcontents
%
diff --git a/macros/latex/contrib/l3kernel/l3docstrip.dtx b/macros/latex/contrib/l3kernel/l3docstrip.dtx
index 2e455845db..7dc8dbe355 100644
--- a/macros/latex/contrib/l3kernel/l3docstrip.dtx
+++ b/macros/latex/contrib/l3kernel/l3docstrip.dtx
@@ -63,7 +63,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3docstrip.pdf b/macros/latex/contrib/l3kernel/l3docstrip.pdf
index 9a63be0ebe..91cfd0b37b 100644
--- a/macros/latex/contrib/l3kernel/l3docstrip.pdf
+++ b/macros/latex/contrib/l3kernel/l3docstrip.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3expan.dtx b/macros/latex/contrib/l3kernel/l3expan.dtx
index 49fafac296..544887627c 100644
--- a/macros/latex/contrib/l3kernel/l3expan.dtx
+++ b/macros/latex/contrib/l3kernel/l3expan.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3file.dtx b/macros/latex/contrib/l3kernel/l3file.dtx
index fc6f7260a0..132741ccda 100644
--- a/macros/latex/contrib/l3kernel/l3file.dtx
+++ b/macros/latex/contrib/l3kernel/l3file.dtx
@@ -44,7 +44,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3flag.dtx b/macros/latex/contrib/l3kernel/l3flag.dtx
index 2475241f91..f2df290cb5 100644
--- a/macros/latex/contrib/l3kernel/l3flag.dtx
+++ b/macros/latex/contrib/l3kernel/l3flag.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3fp-assign.dtx b/macros/latex/contrib/l3kernel/l3fp-assign.dtx
index 918c7b9941..b629576584 100644
--- a/macros/latex/contrib/l3kernel/l3fp-assign.dtx
+++ b/macros/latex/contrib/l3kernel/l3fp-assign.dtx
@@ -40,7 +40,7 @@
% {latex-team@latex-project.org}^^A
% }^^A
% }
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
% \maketitle
%
% \begin{documentation}
diff --git a/macros/latex/contrib/l3kernel/l3fp-aux.dtx b/macros/latex/contrib/l3kernel/l3fp-aux.dtx
index 60662e3560..ece95374b5 100644
--- a/macros/latex/contrib/l3kernel/l3fp-aux.dtx
+++ b/macros/latex/contrib/l3kernel/l3fp-aux.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3fp-basics.dtx b/macros/latex/contrib/l3kernel/l3fp-basics.dtx
index 4fa89b6355..634c5f6f11 100644
--- a/macros/latex/contrib/l3kernel/l3fp-basics.dtx
+++ b/macros/latex/contrib/l3kernel/l3fp-basics.dtx
@@ -40,7 +40,7 @@
% {latex-team@latex-project.org}^^A
% }^^A
% }
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3fp-convert.dtx b/macros/latex/contrib/l3kernel/l3fp-convert.dtx
index 1b28754a82..ccfaba6475 100644
--- a/macros/latex/contrib/l3kernel/l3fp-convert.dtx
+++ b/macros/latex/contrib/l3kernel/l3fp-convert.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3fp-expo.dtx b/macros/latex/contrib/l3kernel/l3fp-expo.dtx
index b30fd883bd..77bc35d84d 100644
--- a/macros/latex/contrib/l3kernel/l3fp-expo.dtx
+++ b/macros/latex/contrib/l3kernel/l3fp-expo.dtx
@@ -40,7 +40,7 @@
% {latex-team@latex-project.org}^^A
% }^^A
% }
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3fp-extended.dtx b/macros/latex/contrib/l3kernel/l3fp-extended.dtx
index 2ff5305b4c..337875215c 100644
--- a/macros/latex/contrib/l3kernel/l3fp-extended.dtx
+++ b/macros/latex/contrib/l3kernel/l3fp-extended.dtx
@@ -40,7 +40,7 @@
% {latex-team@latex-project.org}^^A
% }^^A
% }
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3fp-logic.dtx b/macros/latex/contrib/l3kernel/l3fp-logic.dtx
index 28ed6bba76..c2422c1a5e 100644
--- a/macros/latex/contrib/l3kernel/l3fp-logic.dtx
+++ b/macros/latex/contrib/l3kernel/l3fp-logic.dtx
@@ -40,7 +40,7 @@
% {latex-team@latex-project.org}^^A
% }^^A
% }
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3fp-parse.dtx b/macros/latex/contrib/l3kernel/l3fp-parse.dtx
index 894cf439d9..cc8e6520d3 100644
--- a/macros/latex/contrib/l3kernel/l3fp-parse.dtx
+++ b/macros/latex/contrib/l3kernel/l3fp-parse.dtx
@@ -40,7 +40,7 @@
% {latex-team@latex-project.org}^^A
% }^^A
% }
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3fp-random.dtx b/macros/latex/contrib/l3kernel/l3fp-random.dtx
index e8682c57a8..8ba199e5e4 100644
--- a/macros/latex/contrib/l3kernel/l3fp-random.dtx
+++ b/macros/latex/contrib/l3kernel/l3fp-random.dtx
@@ -40,7 +40,7 @@
% {latex-team@latex-project.org}^^A
% }^^A
% }
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3fp-round.dtx b/macros/latex/contrib/l3kernel/l3fp-round.dtx
index 03de976829..5187a83a8e 100644
--- a/macros/latex/contrib/l3kernel/l3fp-round.dtx
+++ b/macros/latex/contrib/l3kernel/l3fp-round.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3fp-traps.dtx b/macros/latex/contrib/l3kernel/l3fp-traps.dtx
index 377596fb59..1bd6a3ad4f 100644
--- a/macros/latex/contrib/l3kernel/l3fp-traps.dtx
+++ b/macros/latex/contrib/l3kernel/l3fp-traps.dtx
@@ -40,7 +40,7 @@
% {latex-team@latex-project.org}^^A
% }^^A
% }
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
% \maketitle
%
% \begin{documentation}
diff --git a/macros/latex/contrib/l3kernel/l3fp-trig.dtx b/macros/latex/contrib/l3kernel/l3fp-trig.dtx
index eefc4c7ccb..d602e51b6e 100644
--- a/macros/latex/contrib/l3kernel/l3fp-trig.dtx
+++ b/macros/latex/contrib/l3kernel/l3fp-trig.dtx
@@ -40,7 +40,7 @@
% {latex-team@latex-project.org}^^A
% }^^A
% }
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3fp.dtx b/macros/latex/contrib/l3kernel/l3fp.dtx
index 51f1a30c18..2f2dddd645 100644
--- a/macros/latex/contrib/l3kernel/l3fp.dtx
+++ b/macros/latex/contrib/l3kernel/l3fp.dtx
@@ -49,7 +49,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3fparray.dtx b/macros/latex/contrib/l3kernel/l3fparray.dtx
index 5d48154ae8..96a02296b8 100644
--- a/macros/latex/contrib/l3kernel/l3fparray.dtx
+++ b/macros/latex/contrib/l3kernel/l3fparray.dtx
@@ -44,7 +44,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3int.dtx b/macros/latex/contrib/l3kernel/l3int.dtx
index f246d8400b..90ae8a493e 100644
--- a/macros/latex/contrib/l3kernel/l3int.dtx
+++ b/macros/latex/contrib/l3kernel/l3int.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3intarray.dtx b/macros/latex/contrib/l3kernel/l3intarray.dtx
index 7c9d2372d9..fc9f8bf2c4 100644
--- a/macros/latex/contrib/l3kernel/l3intarray.dtx
+++ b/macros/latex/contrib/l3kernel/l3intarray.dtx
@@ -44,7 +44,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
@@ -461,6 +461,51 @@
% \end{macro}
% \end{macro}
%
+% \begin{macro}[rEXP]{\__kernel_intarray_range_to_clist:Nnn, \@@_range_to_clist:ww}
+% Loop through part of the array.
+% \begin{macrocode}
+\cs_new:Npn \__kernel_intarray_range_to_clist:Nnn #1#2#3
+ {
+ \exp_last_unbraced:Nf \use_none:n
+ {
+ \exp_after:wN \@@_range_to_clist:ww
+ \int_value:w \int_eval:w #2 \exp_after:wN ;
+ \int_value:w \int_eval:w #3 ;
+ #1 \prg_break_point:
+ }
+ }
+\cs_new:Npn \@@_range_to_clist:ww #1 ; #2 ; #3
+ {
+ \if_int_compare:w #1 > #2 \exp_stop_f:
+ \prg_break:n
+ \fi:
+ , \__kernel_intarray_item:Nn #3 {#1}
+ \exp_after:wN \@@_range_to_clist:ww
+ \int_value:w \int_eval:w #1 + \c_one_int ; #2 ; #3
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\__kernel_intarray_gset_range_from_clist:Nnn, \@@_gset_range:Nw}
+% Loop through part of the array.
+% \begin{macrocode}
+\cs_new_protected:Npn \__kernel_intarray_gset_range_from_clist:Nnn #1#2#3
+ {
+ \int_set:Nn \l_@@_loop_int {#2}
+ \@@_gset_range:Nw #1 #3 , , \prg_break_point:
+ }
+\cs_new_protected:Npn \@@_gset_range:Nw #1 #2 ,
+ {
+ \if_catcode:w \scan_stop: \tl_to_str:n {#2} \scan_stop:
+ \prg_break:n
+ \fi:
+ \__kernel_intarray_gset:Nnn #1 \l_@@_loop_int {#2}
+ \int_incr:N \l_@@_loop_int
+ \@@_gset_range:Nw #1
+ }
+% \end{macrocode}
+% \end{macro}
+%
% \begin{macro}{\intarray_show:N, \intarray_show:c, \intarray_log:N, \intarray_log:c}
% Convert the list to a comma list (with spaces after each comma)
% \begin{macrocode}
diff --git a/macros/latex/contrib/l3kernel/l3kernel-functions.dtx b/macros/latex/contrib/l3kernel/l3kernel-functions.dtx
index b844c89385..76778ce5c2 100644
--- a/macros/latex/contrib/l3kernel/l3kernel-functions.dtx
+++ b/macros/latex/contrib/l3kernel/l3kernel-functions.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
@@ -207,6 +207,31 @@
% the \cs{intarray_count:N}, lest a low-level \TeX{} error occur.
% \end{function}
%
+% \begin{function}[rEXP, added = 2020-07-12]{\__kernel_intarray_range_to_clist:Nnn}
+% \begin{syntax}
+% \cs{__kernel_intarray_range_to_clist:Nnn} \meta{intarray~var} \Arg{start index} \Arg{end index}
+% \end{syntax}
+% Converts to integer denotations separated by commas the entries of
+% the \meta{intarray} from positions \meta{start index} to \meta{end
+% index} included. The \meta{start index} and \meta{end index} must
+% be suitable for a direct assignment to a \TeX{} count register, must
+% be between $1$ and the \cs{intarray_count:N}, and be suitably
+% ordered. All tokens have category code other.
+% \end{function}
+%
+% \begin{function}[added = 2020-07-12]{\__kernel_intarray_gset_range_from_clist:Nnn}
+% \begin{syntax}
+% \cs{__kernel_intarray_gset_range_from_clist:Nnn} \meta{intarray~var} \Arg{start index} \Arg{integer clist}
+% \end{syntax}
+% Stores the entries of the \meta{clist} as entries of the
+% \meta{intarray~var} starting from the \meta{start index}, upwards.
+% This is done without any bound checking. The \meta{start index} and
+% all entries of the \meta{integer comma list} (which do not undergo
+% space trimming and brace stripping as in normal clist mappings) must
+% be suitable for a direct assignment to a \TeX{} count register. An
+% empty entry may stop the loop.
+% \end{function}
+%
% \begin{function}{\__kernel_ior_open:Nn, \__kernel_ior_open:No}
% \begin{syntax}
% \cs{__kernel_ior_open:Nn} \meta{stream} \Arg{file name}
diff --git a/macros/latex/contrib/l3kernel/l3keys.dtx b/macros/latex/contrib/l3kernel/l3keys.dtx
index ea9b1f9d04..495346fa20 100644
--- a/macros/latex/contrib/l3kernel/l3keys.dtx
+++ b/macros/latex/contrib/l3kernel/l3keys.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3legacy.dtx b/macros/latex/contrib/l3kernel/l3legacy.dtx
index 041c39c333..f640d04cd1 100644
--- a/macros/latex/contrib/l3kernel/l3legacy.dtx
+++ b/macros/latex/contrib/l3kernel/l3legacy.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3luatex.dtx b/macros/latex/contrib/l3kernel/l3luatex.dtx
index 880eb79f9e..891b7e0654 100644
--- a/macros/latex/contrib/l3kernel/l3luatex.dtx
+++ b/macros/latex/contrib/l3kernel/l3luatex.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3msg.dtx b/macros/latex/contrib/l3kernel/l3msg.dtx
index cde386e371..96bc2ca447 100644
--- a/macros/latex/contrib/l3kernel/l3msg.dtx
+++ b/macros/latex/contrib/l3kernel/l3msg.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3names.dtx b/macros/latex/contrib/l3kernel/l3names.dtx
index b75c20eb08..5333720124 100644
--- a/macros/latex/contrib/l3kernel/l3names.dtx
+++ b/macros/latex/contrib/l3kernel/l3names.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3news.pdf b/macros/latex/contrib/l3kernel/l3news.pdf
index cd9eead890..5dc815800c 100644
--- a/macros/latex/contrib/l3kernel/l3news.pdf
+++ b/macros/latex/contrib/l3kernel/l3news.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3news01.pdf b/macros/latex/contrib/l3kernel/l3news01.pdf
index 4919fac2b1..a3dc4f6315 100644
--- a/macros/latex/contrib/l3kernel/l3news01.pdf
+++ b/macros/latex/contrib/l3kernel/l3news01.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3news02.pdf b/macros/latex/contrib/l3kernel/l3news02.pdf
index f5a33f51b4..f4fb786dd3 100644
--- a/macros/latex/contrib/l3kernel/l3news02.pdf
+++ b/macros/latex/contrib/l3kernel/l3news02.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3news03.pdf b/macros/latex/contrib/l3kernel/l3news03.pdf
index 9ee630bddb..a31cd89b97 100644
--- a/macros/latex/contrib/l3kernel/l3news03.pdf
+++ b/macros/latex/contrib/l3kernel/l3news03.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3news04.pdf b/macros/latex/contrib/l3kernel/l3news04.pdf
index 843b757d41..14a59aa796 100644
--- a/macros/latex/contrib/l3kernel/l3news04.pdf
+++ b/macros/latex/contrib/l3kernel/l3news04.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3news05.pdf b/macros/latex/contrib/l3kernel/l3news05.pdf
index b6e9ce58cf..9ffb4f8ba7 100644
--- a/macros/latex/contrib/l3kernel/l3news05.pdf
+++ b/macros/latex/contrib/l3kernel/l3news05.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3news06.pdf b/macros/latex/contrib/l3kernel/l3news06.pdf
index 0a9530e337..7f3a5bfeff 100644
--- a/macros/latex/contrib/l3kernel/l3news06.pdf
+++ b/macros/latex/contrib/l3kernel/l3news06.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3news07.pdf b/macros/latex/contrib/l3kernel/l3news07.pdf
index 2a946711ea..d610b0f7ec 100644
--- a/macros/latex/contrib/l3kernel/l3news07.pdf
+++ b/macros/latex/contrib/l3kernel/l3news07.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3news08.pdf b/macros/latex/contrib/l3kernel/l3news08.pdf
index b4e91f3196..aff2eb1a9c 100644
--- a/macros/latex/contrib/l3kernel/l3news08.pdf
+++ b/macros/latex/contrib/l3kernel/l3news08.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3news09.pdf b/macros/latex/contrib/l3kernel/l3news09.pdf
index 43ee33b148..325bef60cc 100644
--- a/macros/latex/contrib/l3kernel/l3news09.pdf
+++ b/macros/latex/contrib/l3kernel/l3news09.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3news10.pdf b/macros/latex/contrib/l3kernel/l3news10.pdf
index d8cbe38b33..07ce82bad6 100644
--- a/macros/latex/contrib/l3kernel/l3news10.pdf
+++ b/macros/latex/contrib/l3kernel/l3news10.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3news11.pdf b/macros/latex/contrib/l3kernel/l3news11.pdf
index 65240af456..18f5740e94 100644
--- a/macros/latex/contrib/l3kernel/l3news11.pdf
+++ b/macros/latex/contrib/l3kernel/l3news11.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3news12.pdf b/macros/latex/contrib/l3kernel/l3news12.pdf
index 37d8e10aa1..8dc788a1e6 100644
--- a/macros/latex/contrib/l3kernel/l3news12.pdf
+++ b/macros/latex/contrib/l3kernel/l3news12.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3prefixes.csv b/macros/latex/contrib/l3kernel/l3prefixes.csv
index b51851ffec..46ce3a046b 100644
--- a/macros/latex/contrib/l3kernel/l3prefixes.csv
+++ b/macros/latex/contrib/l3kernel/l3prefixes.csv
@@ -11,7 +11,7 @@ alloc,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,http
ampersand,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
apfs,apfontspec,Qing Lee,https://github.com/CTeX-org/apfontspec,https://github.com/CTeX-org/apfontspec.git,https://github.com/CTeX-org/apfontspec/issues,2020-05-17,2020-05-17,
arch,archaeologie,Lukas C. Bossert,http://www.biblatex-archaeologie.de,https://github.com/LukasCBossert/biblatex-archaeologie.git,https://github.com/LukasCBossert/biblatex-archaeologie/issues,2017-03-24,2017-03-24,
-array,hobby,Andrew Stacey,,,,2013-03-16,2013-03-16,
+array,hobby,Andrew Stacey,https://github.com/loopspace/hobby,https://github.com/loopspace/hobby,https://github.com/loopspace/hobby/issues,2013-03-16,2020-10-29,
atsign,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
avm,langsci-avm,Felix Kopecky,https://ctan.org/pkg/langsci-avm,https://github.com/langsci/langsci-avm,https://github.com/langsci/langsci-avm/issues,2020-03-11,2020-03-11,
backend,l3backend,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2019-06-04,2019-06-04,
@@ -70,6 +70,7 @@ fdudoc,fduthesis,Xiangdong Zeng,https://github.com/stone-zeng/fduthesis,https://
fdulogo,fduthesis,Xiangdong Zeng,https://github.com/stone-zeng/fduthesis,https://github.com/stone-zeng/fduthesis.git,https://github.com/stone-zeng/fduthesis/issues,2018-06-14,2020-03-08,
fi,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-28,2012-09-28,
file,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
+filehook,ltfilehook,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex2e.git,https://github.com/latex3/latex2e/issues,2020-10-01,2020-10-01,
flag,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2017-02-13,
fltr,newlfm,Paul Thomson,,,,2013-01-29,2013-01-29,
fmdug,dashundergaps,Frank Mittelbach,https://www.latex-project.org/,https://github.com/FrankMittelbach/fmitex.git,https://github.com/FrankMittelbach/fmitex/issues,2018-06-24,2018-06-24,
@@ -92,6 +93,7 @@ hbox,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https
hcoffin,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-28,2012-09-28,
hobete,hobete,Tobias Görlach,http://www.disk0s1.de,,,2012-11-07,2012-11-07,
hook,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2019-06-03,2019-06-03,
+hyp,hyperref,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/hyperref.git,https://github.com/latex3/hyperref/issues,2020-11-27,2020-11-27,
if,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
inf,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
insert,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-28,2012-09-28,
@@ -99,6 +101,7 @@ int,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https:
intarray,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-04-06,2018-04-06,
ior,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
iow,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
+jiazhu,jiazhu,Qing Lee,https://github.com/CTeX-org/ctex-kit,https://github.com/CTeX-org/ctex-kit.git,https://github.com/CTeX-org/ctex-kit/issues,2020-05-17,2020-05-17,
kernel,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
keys,"l3kernel,l3keys2e",The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
keyval,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
@@ -108,13 +111,13 @@ lltxmath,lualatex-math,Philipp Stephani,https://github.com/phst/lualatex-math,ht
log,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
lua,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
luatex,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
-jiazhu,jiazhu,Qing Lee,https://github.com/CTeX-org/ctex-kit,https://github.com/CTeX-org/ctex-kit.git,https://github.com/CTeX-org/ctex-kit/issues,2020-05-17,2020-05-17,
mark,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
marks,l3kernel/xmarks,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2020-02-17,2020-02-17,
math,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
max,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
mcrule,multicolrule,Karl Hagen,https://github.com/polysyllabic/multicolrule,https://github.com/polysyllabic/multicolrule.git,https://github.com/polysyllabic/multicolrule/issues,2018-12-24,2018-12-24,
mermap,mercatormap,Thomas F. Sturm,https://github.com/T-F-S/mercatormap,https://github.com/T-F-S/mercatormap.git,https://github.com/T-F-S/mercatormap/issues,2020-02-19,2020-02-19,
+metrix,metrix,Tobias Weh,https://github.com/tweh/metrix,git@github.com:tweh/metrix.git,https://github.com/tweh/metrix/issues,2020-10-31,2019-10-09,
mhchem,mhchem,Martin Hensel,,,,2014-02-05,2014-02-05,
minibox,minibox,Will Robertson,,https://github.com/wspr/will2e.git,https://github.com/wspr/will2e/issues,2020-04-24,2020-04-24,
minus,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
@@ -140,6 +143,7 @@ overleaf,overleaf,Overleaf,https://www.overleaf.com/about,,support+tex-dev@overl
parameter,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
pbs,media9,Alexander Grahn,,https://gitlab.com/agrahn/media9,https://gitlab.com/agrahn/media9/issues,2016-02-26,2020-04-15,
pdf,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2019-06-02,2019-06-02,
+pdfmanagement,l3pdfmanagement,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2020-11-27,2020-11-27,
pdfoverlay,pdfoverlay,David Purton,https://github.com/dcpurton/pdfoverlay,https://github.com/dcpurton/pdfoverlay.git,https://github.com/dcpurton/pdfoverlay/issues,2020-06-22,2020-06-22,
pdftex,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
peek,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
@@ -149,14 +153,15 @@ pi,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https:/
pkgploader,pkgploader,Michiel Helvensteijn,,,,2014-02-05,2014-02-05,
platex,platex,Japanese TeX Development Community,https://github.com/texjporg/platex,https://github.com/texjporg/platex.git,https://github.com/texjporg/platex/issues,2020-09-30,2020-09-30,
polyglossia,polyglossia,Arthur Reutenauer,https://www.polyglossia.org/,https://github.com/reutenauer/polyglossia,https://github.com/reutenauer/polyglossia/issues,2019-09-03,,
+prelim,prelim2e,Marei Peischl,https://github.com/TeXhackse/prelim2e,https://github.com/TeXhackse/prelim2e.git,https://github.com/TeXhackse/prelim2e/issues,2020-11-24,2020-11-24,
prg,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
primargs,morewrites,Bruno Le Floch,https://github.com/blefloch/latex-morewrites,https://github.com/blefloch/latex-morewrites.git,https://github.com/blefloch/latex-morewrites/issues,2013-03-16,2015-09-22,
prop,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
pseudo,pseudo,Magnus Lie Hetland,https://github.com/mlhetland/pseudo.sty,https://github.com/mlhetland/pseudo.sty.git,https://github.com/mlhetland/pseudo.sty/issues,2019-06-24,2019-06-24,
ptex,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2015-07-28,2015-07-28,
ptxcd,ptxcd, Marei Peischl,,,,2020-07-27,2020-07-27,Used for specific corporate design templates
-quark,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
qrbill,qrbill,Marei Peischl,https://github.com/peiTeX/qrbill,https://github.com/peiTeX/qrbill.git,https://github.com/peiTeX/qrbill/issues,2020-06-27,2020-06-27,
+quark,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
randomwalk,randomwalk,Bruno Le Floch,https://github.com/blefloch/latex-randomwalk,https://github.com/blefloch/latex-randomwalk.git,https://github.com/blefloch/latex-randomwalk/issues,2013-03-16,2015-09-22,
recursion,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
regex,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2018-04-06,
@@ -168,6 +173,7 @@ scan,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https
scontents,scontents,Pablo González,https://github.com/pablgonz/scontents,git@github.com:pablgonz/scontents.git,https://github.com/pablgonz/scontents/issues,2019-12-05,2019-12-05,
sdaps,sdaps,Benjamin Berg,https://sdaps.org,https://github.com/sdaps/sdaps-class.git,https://github.com/sdaps/sdaps-class/issues,2020-02-17,2020-02-17,
seq,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
+shipout,ltshipout,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex2e.git,https://github.com/latex3/latex2e/issues,2020-10-01,2020-10-01,
siunitx,siunitx,Joseph Wright,https://github.com/josephwright/siunitx,https://github.com/josephwright/siunitx.git,https://github.com/josephwright/siunitx/issues,2012-11-04,2012-11-04,
skip,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
sort,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2017-02-13,
@@ -190,6 +196,7 @@ tl,"l3kernel,l3tl-analysis,l3tl-build",The LaTeX3 Project,https://www.latex-proj
tmpa,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
tmpb,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
token,l3kernel,The LaTeX3 Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
+twmk,menukeys,Tobias Weh,https://github.com/tweh/menukeys,git@github.com:tweh/menukeys.git,https://github.com/tweh/menukeys/issues,2020-10-31,2020-10-31,“classic” L2 package using only some expl3 code
ufcombo,combofont,Ulrike Fischer,https://github.com/u-fischer/combofont,https://github.com/u-fischer/combofont,https://github.com/u-fischer/combofont/issues,2020-04-24,2020-04-24,
ufgrid,returntogrid,Ulrike Fischer,https://github.com/u-fischer/returntogrid,https://github.com/u-fischer/returntogrid,https://github.com/u-fischer/returntogrid/issues,2020-04-24,2020-04-24,
uftag,tagpdf,Ulrike Fischer,,,,2018-07-15,2018-07-15,
diff --git a/macros/latex/contrib/l3kernel/l3prefixes.pdf b/macros/latex/contrib/l3kernel/l3prefixes.pdf
index 50089cf13c..78022e7601 100644
--- a/macros/latex/contrib/l3kernel/l3prefixes.pdf
+++ b/macros/latex/contrib/l3kernel/l3prefixes.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3prg.dtx b/macros/latex/contrib/l3kernel/l3prg.dtx
index 3f1280f23e..b5b1e4246e 100644
--- a/macros/latex/contrib/l3kernel/l3prg.dtx
+++ b/macros/latex/contrib/l3kernel/l3prg.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3prop.dtx b/macros/latex/contrib/l3kernel/l3prop.dtx
index 9419a5ce6c..84b41be5a0 100644
--- a/macros/latex/contrib/l3kernel/l3prop.dtx
+++ b/macros/latex/contrib/l3kernel/l3prop.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3quark.dtx b/macros/latex/contrib/l3kernel/l3quark.dtx
index 16a9b0c447..487393eb6a 100644
--- a/macros/latex/contrib/l3kernel/l3quark.dtx
+++ b/macros/latex/contrib/l3kernel/l3quark.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3regex.dtx b/macros/latex/contrib/l3kernel/l3regex.dtx
index 2086b5c3ec..5e64d73980 100644
--- a/macros/latex/contrib/l3kernel/l3regex.dtx
+++ b/macros/latex/contrib/l3kernel/l3regex.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
@@ -713,7 +713,7 @@
% |{n,}| quantifier? (I think not.)
% \item Quantifiers for |\u| and assertions.
% \item When matching, keep track of an explicit stack of
-% \texttt{current_state} and \texttt{current_submatches}.
+% \texttt{curr_state} and \texttt{curr_submatches}.
% \item If possible, when a state is reused by the same thread, kill
% other subthreads.
% \item Use an array rather than \cs[no-index]{l__regex_balance_tl}
@@ -845,12 +845,14 @@
% names).
% \begin{itemize}
% \item \emph{Group}: index of the capturing group, $-1$ for
-% non-capturing groups.
+% non-capturing groups. ^^A start/end index?
% \item \emph{Position}: each token in the query is labelled by an
% integer \meta{position}, with $\texttt{min_pos} - 1 \leq
% \meta{position} \leq \texttt{max_pos}$. The lowest and highest
-% positions correspond to imaginary begin and end markers (with
-% inaccessible category code and character code).
+% positions $\texttt{min_pos} - 1$ and $\texttt{max_pos}$
+% correspond to imaginary begin and end markers (with
+% non-existent category code and character code).
+% $\texttt{max_pos}$ is only set quite late in the processing.
% \item \emph{Query}: the token list to which we apply the regular
% expression.
% \item \emph{State}: each state of the \textsc{nfa} is labelled by an
@@ -865,8 +867,8 @@
% unique id for all the steps of the matching algorithm.
% \end{itemize}
%
-% We use \pkg{l3intarray} to manipulate arrays of integers (stored into
-% some dimension registers in scaled points). We also abuse \TeX{}'s
+% We use \pkg{l3intarray} to manipulate arrays of integers.
+% We also abuse \TeX{}'s
% \tn{toks} registers, by accessing them directly by number rather than
% tying them to control sequence using the \tn{newtoks} allocation
% functions. Specifically, these arrays and \tn{toks} are used as
@@ -876,21 +878,14 @@
% \begin{itemize}
% \item \cs{g_@@_state_active_intarray} holds the last \meta{step} in
% which each \meta{state} was active.
-% \item \cs{g_@@_thread_state_intarray} maps each \meta{thread} (with
-% $\texttt{min_active} \leq \meta{thread} < \texttt{max_active}$) to
-% the \meta{state} in which the \meta{thread} currently is. The
+% \item \cs{g_@@_thread_info_intarray} consists of blocks for each
+% \meta{thread} (with $\texttt{min_thread} \leq \meta{thread} <
+% \texttt{max_thread}$). Each block has
+% $1+2\cs{l_@@_capturing_group_int}$ entries: the \meta{state} in
+% which the \meta{thread} currently is, followed by the beginnings
+% of all submatches, and then the ends of all submatches. The
% \meta{threads} are ordered starting from the best to the least
% preferred.
-% \item \tn{toks}\meta{thread} holds the submatch information for the
-% \meta{thread}, as the contents of a property list.
-% \item \cs{g_@@_charcode_intarray} and \cs{g_@@_catcode_intarray} hold the
-% character codes and category codes of tokens at each
-% \meta{position} in the query.
-% \item \cs{g_@@_balance_intarray} holds the balance of begin-group and
-% end-group character tokens which appear before that point in the
-% token list.
-% \item \tn{toks}\meta{position} holds \meta{tokens} which \texttt{o}-
-% and \texttt{x}-expand to the \meta{position}-th token in the query.
% \item \cs{g_@@_submatch_prev_intarray}, \cs{g_@@_submatch_begin_intarray}
% and \cs{g_@@_submatch_end_intarray} hold, for each submatch (as would
% be extracted by \cs{regex_extract_all:nnN}), the place where the
@@ -901,6 +896,14 @@
% block corresponding to one match with all its submatches stored in
% consecutive entries.
% \end{itemize}
+% When actually building the result,
+% \begin{itemize}
+% \item \tn{toks}\meta{position} holds \meta{tokens} which \texttt{o}-
+% and \texttt{x}-expand to the \meta{position}-th token in the query.
+% \item \cs{g_@@_balance_intarray} holds the balance of begin-group and
+% end-group character tokens which appear before that point in the
+% token list.
+% \end{itemize}
%
% The code is structured as follows. Variables are introduced in the
% relevant section. First we present some generic helper functions. Then
@@ -944,7 +947,7 @@
{ \@@_toks_set:Nn #1 { } }
\cs_new_eq:NN \@@_toks_set:Nn \tex_toks:D
\cs_new_protected:Npn \@@_toks_set:No #1
- { \@@_toks_set:Nn #1 \exp_after:wN }
+ { \tex_toks:D #1 \exp_after:wN }
% \end{macrocode}
% \end{macro}
%
@@ -975,13 +978,13 @@
% \begin{macrocode}
\cs_new_protected:Npn \@@_toks_put_left:Nx #1#2
{
- \cs_set:Npx \@@_tmp:w { #2 }
+ \cs_set_nopar:Npx \@@_tmp:w { #2 }
\tex_toks:D #1 \exp_after:wN \exp_after:wN \exp_after:wN
{ \exp_after:wN \@@_tmp:w \tex_the:D \tex_toks:D #1 }
}
\cs_new_protected:Npn \@@_toks_put_right:Nx #1#2
{
- \cs_set:Npx \@@_tmp:w {#2}
+ \cs_set_nopar:Npx \@@_tmp:w {#2}
\tex_toks:D #1 \exp_after:wN
{ \tex_the:D \tex_toks:D \exp_after:wN #1 \@@_tmp:w }
}
@@ -1000,11 +1003,38 @@
\cs_new:Npn \@@_curr_cs_to_str:
{
\exp_after:wN \exp_after:wN \exp_after:wN \cs_to_str:N
- \tex_the:D \tex_toks:D \l_@@_curr_pos_int
+ \l_@@_curr_token_tl
}
% \end{macrocode}
% \end{macro}
%
+% \begin{macro}{\@@_intarray_item:NnF, \@@_intarray_item_aux:nNF}
+% Item of intarray, with a default value.
+% \begin{macrocode}
+\cs_new:Npn \@@_intarray_item:NnF #1#2
+ { \exp_args:Nf \@@_intarray_item_aux:nNF { \int_eval:n {#2} } #1 }
+\cs_new:Npn \@@_intarray_item_aux:nNF #1#2
+ {
+ \if_int_compare:w #1 > \c_zero_int
+ \exp_after:wN \use_i:nn
+ \else:
+ \exp_after:wN \use_ii:nn
+ \fi:
+ { \__kernel_intarray_item:Nn #2 {#1} }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_maplike_break:}
+% Analogous to \cs{tl_map_break:}, this correctly exits
+% \cs{tl_map_inline:nn} and similar constructions and jumps to the
+% matching \cs{prg_break_point:Nn} \cs{@@_maplike_break:} |{| |}|.
+% \begin{macrocode}
+\cs_new:Npn \@@_maplike_break:
+ { \prg_map_break:Nn \@@_maplike_break: { } }
+% \end{macrocode}
+% \end{macro}
+%
% \subsubsection{Constants and variables}
%
% \begin{macro}{\@@_tmp:w}
@@ -1056,19 +1086,6 @@
% \end{macrocode}
% \end{variable}
%
-% \begin{variable}{\g_@@_charcode_intarray, \g_@@_catcode_intarray, \g_@@_balance_intarray}
-% The first thing we do when matching is to go once through the query
-% token list and store the information for each token into
-% \cs{g_@@_charcode_intarray}, \cs{g_@@_catcode_intarray} and \tn{toks}
-% registers. We also store the balance of begin-group/end-group
-% characters into \cs{g_@@_balance_intarray}.
-% \begin{macrocode}
-\intarray_new:Nn \g_@@_charcode_intarray { 65536 }
-\intarray_new:Nn \g_@@_catcode_intarray { 65536 }
-\intarray_new:Nn \g_@@_balance_intarray { 65536 }
-% \end{macrocode}
-% \end{variable}
-%
% \begin{variable}{\l_@@_balance_int}
% During this phase, \cs{l_@@_balance_int} counts the balance of
% begin-group and end-group character tokens which appear before a
@@ -1079,15 +1096,6 @@
% \end{macrocode}
% \end{variable}
%
-% \begin{variable}{\l_@@_cs_name_tl}
-% This variable is used in \cs{@@_item_cs:n} to store the csname of
-% the currently-tested token when the regex contains a sub-regex for
-% testing csnames.
-% \begin{macrocode}
-\tl_new:N \l_@@_cs_name_tl
-% \end{macrocode}
-% \end{variable}
-%
% \subsubsection{Testing characters}
%
% \begin{macro}{\c_@@_ascii_min_int, \c_@@_ascii_max_control_int, \c_@@_ascii_max_int}
@@ -1201,7 +1209,7 @@
% \begin{macro}
% {\@@_item_caseless_equal:n, \@@_item_caseless_range:nn}
% For caseless matching, we perform the test both on the
-% \texttt{current_char} and on the \texttt{case_changed_char}. Before
+% \texttt{curr_char} and on the \texttt{case_changed_char}. Before
% doing the second set of tests, we make sure that
% \texttt{case_changed_char} has been computed.
% \begin{macrocode}
@@ -1308,7 +1316,8 @@
%
% \begin{macro}{\@@_item_exact:nn, \@@_item_exact_cs:n}
% This matches an exact \meta{category}-\meta{character code} pair, or
-% an exact control sequence, more precisely one of several possible control sequences.
+% an exact control sequence, more precisely one of several possible
+% control sequences, separated by \cs{scan_stop:}.
% \begin{macrocode}
\cs_new_protected:Npn \@@_item_exact:nn #1#2
{
@@ -1337,24 +1346,19 @@
% Match a control sequence (the argument is a compiled regex).
% First test the catcode of the current token to be zero.
% Then perform the matching test, and break if the csname
-% indeed matches. The three \cs{exp_after:wN} expand the contents
-% of the \tn{toks}\meta{current position} (of the form \cs{exp_not:n}
-% \Arg{control sequence}) to \meta{control sequence}.
-% We store the cs name before building states for the cs, as those
-% states may overlap with toks registers storing the user's input.
+% indeed matches.
% \begin{macrocode}
\cs_new_protected:Npn \@@_item_cs:n #1
{
\int_compare:nNnT \l_@@_curr_catcode_int = 0
{
\group_begin:
- \__kernel_tl_set:Nx \l_@@_cs_name_tl { \@@_curr_cs_to_str: }
\@@_single_match:
\@@_disable_submatches:
\@@_build_for_cs:n {#1}
\bool_set_eq:NN \l_@@_saved_success_bool
\g_@@_success_bool
- \exp_args:NV \@@_match_cs:n \l_@@_cs_name_tl
+ \exp_args:Nx \@@_match_cs:n { \@@_curr_cs_to_str: }
\if_meaning:w \c_true_bool \g_@@_success_bool
\group_insert_after:N \@@_break_true:w
\fi:
@@ -1822,7 +1826,7 @@
% \item \cs{@@_command_K:}
% \item \cs{@@_assertion:Nn} \meta{boolean} \Arg{assertion test},
% where the \meta{assertion test} is \cs{@@_b_test:} or
-% |{|\cs{@@_anchor:N} \meta{integer}|}|
+% \cs{@@_Z_test:} or \cs{@@_A_test:} or \cs{@@_G_test:}
% \end{itemize}
% Tests can be the following:
% \begin{itemize}
@@ -2663,73 +2667,56 @@
%
% \subsubsection{Anchoring and simple assertions}
%
-% \begin{macro}{\@@_compile_anchor:NF}
+% \begin{macro}{\@@_compile_anchor_letter:NNN}
+% \begin{macro}{\@@_compile_/A:, \@@_compile_/G:, \@@_compile_/Z:, \@@_compile_/z:, \@@_compile_/b:, \@@_compile_/B:}
% \begin{macro}+\@@_compile_^:+
-% \begin{macro}{\@@_compile_/A:, \@@_compile_/G:}
% \begin{macro}+\@@_compile_$:+
-% \begin{macro}{\@@_compile_/Z:, \@@_compile_/z:}
-% In modes where assertions are allowed, anchor to the start of the
-% query, the start of the match, or the end of the query, depending on
-% the integer |#1|. In other modes, |#2| treats the character as raw,
-% with an error for escaped letters (|$| is valid in a class, but |\A|
-% is definitely a mistake on the user's part).
+% In modes where assertions are forbidden, anchors such as |\A|
+% produce an error (|\A|~is invalid in classes); otherwise they add an
+% \cs{@@_assertion:Nn} test as appropriate (the only negative
+% assertion is~|\B|). The test functions are defined later. The
+% implementation for
+% |$| and |^| is only different from |\A| etc because these are valid
+% in a class.
% \begin{macrocode}
-\cs_new_protected:Npn \@@_compile_anchor:NF #1#2
+\cs_new_protected:Npn \@@_compile_anchor_letter:NNN #1#2#3
{
- \@@_if_in_class_or_catcode:TF {#2}
+ \@@_if_in_class_or_catcode:TF { \@@_compile_raw_error:N #1 }
{
\tl_build_put_right:Nn \l_@@_build_tl
- { \@@_assertion:Nn \c_true_bool { \@@_anchor:N #1 } }
+ { \@@_assertion:Nn #2 {#3} }
}
}
-\cs_set_protected:Npn \@@_tmp:w #1#2
- {
- \cs_new_protected:cpn { @@_compile_/#1: }
- { \@@_compile_anchor:NF #2 { \@@_compile_raw_error:N #1 } }
- }
-\@@_tmp:w A \l_@@_min_pos_int
-\@@_tmp:w G \l_@@_start_pos_int
-\@@_tmp:w Z \l_@@_max_pos_int
-\@@_tmp:w z \l_@@_max_pos_int
+\cs_new_protected:cpn { @@_compile_/A: }
+ { \@@_compile_anchor_letter:NNN A \c_true_bool \@@_A_test: }
+\cs_new_protected:cpn { @@_compile_/G: }
+ { \@@_compile_anchor_letter:NNN G \c_true_bool \@@_G_test: }
+\cs_new_protected:cpn { @@_compile_/Z: }
+ { \@@_compile_anchor_letter:NNN Z \c_true_bool \@@_Z_test: }
+\cs_new_protected:cpn { @@_compile_/z: }
+ { \@@_compile_anchor_letter:NNN z \c_true_bool \@@_Z_test: }
+\cs_new_protected:cpn { @@_compile_/b: }
+ { \@@_compile_anchor_letter:NNN b \c_true_bool \@@_b_test: }
+\cs_new_protected:cpn { @@_compile_/B: }
+ { \@@_compile_anchor_letter:NNN B \c_false_bool \@@_b_test: }
\cs_set_protected:Npn \@@_tmp:w #1#2
{
\cs_new_protected:cpn { @@_compile_#1: }
- { \@@_compile_anchor:NF #2 { \@@_compile_raw:N #1 } }
+ {
+ \@@_if_in_class_or_catcode:TF { \@@_compile_raw:N #1 }
+ {
+ \tl_build_put_right:Nn \l_@@_build_tl
+ { \@@_assertion:Nn \c_true_bool {#2} }
+ }
+ }
}
-\exp_args:Nx \@@_tmp:w { \iow_char:N \^ } \l_@@_min_pos_int
-\exp_args:Nx \@@_tmp:w { \iow_char:N \$ } \l_@@_max_pos_int
+\exp_args:Nx \@@_tmp:w { \iow_char:N \^ } { \@@_A_test: }
+\exp_args:Nx \@@_tmp:w { \iow_char:N \$ } { \@@_Z_test: }
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
-% \end{macro}
-%
-% \begin{macro}{\@@_compile_/b:, \@@_compile_/B:}
-% Contrarily to |^| and |$|, which could be implemented without really
-% knowing what precedes in the token list, this requires more
-% information, namely, the knowledge of the last character code.
-% \begin{macrocode}
-\cs_new_protected:cpn { @@_compile_/b: }
- {
- \@@_if_in_class_or_catcode:TF
- { \@@_compile_raw_error:N b }
- {
- \tl_build_put_right:Nn \l_@@_build_tl
- { \@@_assertion:Nn \c_true_bool { \@@_b_test: } }
- }
- }
-\cs_new_protected:cpn { @@_compile_/B: }
- {
- \@@_if_in_class_or_catcode:TF
- { \@@_compile_raw_error:N B }
- {
- \tl_build_put_right:Nn \l_@@_build_tl
- { \@@_assertion:Nn \c_false_bool { \@@_b_test: } }
- }
- }
-% \end{macrocode}
-% \end{macro}
%
% \subsubsection{Character classes}
%
@@ -3512,7 +3499,9 @@
{ \bool_if:NF ##1 { negative~ } assertion:~##2 }
}
\cs_set:Npn \@@_b_test: { word~boundary }
- \cs_set_eq:NN \@@_anchor:N \@@_show_anchor_to_str:N
+ \cs_set:Npn \@@_Z_test: { anchor~at~end~(\iow_char:N\\Z) }
+ \cs_set:Npn \@@_A_test: { anchor~at~start~(\iow_char:N\\A) }
+ \cs_set:Npn \@@_G_test: { anchor~at~start~of~match~(\iow_char:N\\G) }
\cs_set_protected:Npn \@@_item_caseful_equal:n ##1
{ \@@_show_one:n { char~code~\int_eval:n{##1} } }
\cs_set_protected:Npn \@@_item_caseful_range:nn ##1##2
@@ -3655,24 +3644,6 @@
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}[rEXP]{\@@_show_anchor_to_str:N}
-% The argument is an integer telling us where the anchor is. We
-% convert that to the relevant info.
-% \begin{macrocode}
-\cs_new:Npn \@@_show_anchor_to_str:N #1
- {
- anchor~at~
- \str_case:nnF { #1 }
- {
- { \l_@@_min_pos_int } { start~(\iow_char:N\\A) }
- { \l_@@_start_pos_int } { start~of~match~(\iow_char:N\\G) }
- { \l_@@_max_pos_int } { end~(\iow_char:N\\Z) }
- }
- { <error:~'#1'~not~recognized> }
- }
-% \end{macrocode}
-% \end{macro}
-%
% \begin{macro}{\@@_show_item_catcode:NnT}
% Produce a sequence of categories which the catcode bitmap |#2|
% contains, and show it, indenting the tests on which this catcode
@@ -3757,8 +3728,9 @@
% Each state of the \textsc{nfa} is stored in a \tn{toks}. The
% operations which can appear in the \tn{toks} are
% \begin{itemize}
-% \item \cs{@@_action_start_wildcard:} inserted at the start
-% of the regular expression to make it unanchored.
+% \item \cs{@@_action_start_wildcard:N} \meta{boolean} inserted at the
+% start of the regular expression, where a \texttt{true}
+% \meta{boolean} makes it unanchored.
% \item \cs{@@_action_success:} marks the exit state of the
% \textsc{nfa}.
% \item \cs{@@_action_cost:n} \Arg{shift} is a transition from the
@@ -3772,10 +3744,11 @@
% how they detect and avoid infinite loops. For now, we just need to
% know that the \texttt{group} variant must be used for transitions
% back to the start of a group.
-% \item \cs{@@_action_submatch:n} \Arg{key} where the \meta{key} is
-% a group number followed by |<| or |>| for the beginning or end of
-% group. This causes the current position in the query to be stored
-% as the \meta{key} submatch boundary.
+% \item \cs{@@_action_submatch:nN} \Arg{group} \meta{key} where the
+% \meta{key} is |<| or |>| for the beginning or end of group
+% numbered \meta{group}. This causes the current position in the
+% query to be stored as the \meta{key} submatch boundary.
+% \item One of these actions, within a conditional.
% \end{itemize}
%
% We strive to preserve the following properties while building.
@@ -3793,20 +3766,26 @@
% corresponding end-points of nested groups.
% \end{itemize}
%
-% \begin{macro}{\@@_build:n, \@@_build:N}
+% \begin{macro}{\@@_build:n, \@@_build_aux:Nn, \@@_build:N, \@@_build_aux:NN}
% The \texttt{n}-type function first compiles its argument. Reset some
% variables. Allocate two states, and put a wildcard in state $0$
% (transitions to state $1$ and $0$ state). Then build the regex
% within a (capturing) group numbered $0$ (current
% value of \texttt{capturing_group}). Finally, if the match reaches the
-% last state, it is successful.
+% last state, it is successful. A \texttt{false} boolean for argument
+% |#1| for the auxiliaries will suppress the wildcard and make the
+% match anchored: used for \cs{peek_regex:nTF} and similar.
% \begin{macrocode}
-\cs_new_protected:Npn \@@_build:n #1
+\cs_new_protected:Npn \@@_build:n
+ { \@@_build_aux:Nn \c_true_bool }
+\cs_new_protected:Npn \@@_build:N
+ { \@@_build_aux:NN \c_true_bool }
+\cs_new_protected:Npn \@@_build_aux:Nn #1#2
{
- \@@_compile:n {#1}
- \@@_build:N \l_@@_internal_regex
+ \@@_compile:n {#2}
+ \@@_build_aux:NN #1 \l_@@_internal_regex
}
-\cs_new_protected:Npn \@@_build:N #1
+\cs_new_protected:Npn \@@_build_aux:NN #1#2
{
\@@_standard_escapechar:
\int_zero:N \l_@@_capturing_group_int
@@ -3814,8 +3793,8 @@
\@@_build_new_state:
\@@_build_new_state:
\@@_toks_put_right:Nn \l_@@_left_state_int
- { \@@_action_start_wildcard: }
- \@@_group:nnnN {#1} { 1 } { 0 } \c_false_bool
+ { \@@_action_start_wildcard:N #1 }
+ \@@_group:nnnN {#2} { 1 } { 0 } \c_false_bool
\@@_toks_put_right:Nn \l_@@_right_state_int
{ \@@_action_success: }
}
@@ -3826,19 +3805,14 @@
% The matching code relies on some global intarray variables, but only
% uses a range of their entries. Specifically,
% \begin{itemize}
-% \item \cs{g_@@_state_active_intarray} from \cs{l_@@_min_state_int}
-% to $\cs{l_@@_max_state_int}-1$;
-% \item \cs{g_@@_thread_state_intarray} from \cs{l_@@_min_active_int}
-% to $\cs{l_@@_max_active_int}-1$.
+% \item \cs{g_@@_state_active_intarray} from \cs{l_@@_min_state_int}
+% to $\cs{l_@@_max_state_int}-1$;
% \end{itemize}
-% In fact, some data is stored in \tn{toks} registers (local) in the
-% same ranges so these ranges mustn't overlap. This is done by
-% setting \cs{l_@@_min_active_int} to \cs{l_@@_max_state_int} after
-% building the \textsc{nfa}. Here, in this nested call to the
-% matching code, we need the new versions of these ranges to involve
+% Here, in this nested call to the
+% matching code, we need the new versions of this range to involve
% completely new entries of the intarray variables, so we begin by
% setting (the new) \cs{l_@@_min_state_int} to (the old)
-% \cs{l_@@_max_active_int} to use higher entries.
+% \cs{l_@@_max_state_int} to use higher entries.
%
% When using a regex to match a cs, we don't insert a wildcard, we
% anchor at the end, and since we ignore submatches, there is no need
@@ -3848,8 +3822,7 @@
% \begin{macrocode}
\cs_new_protected:Npn \@@_build_for_cs:n #1
{
- \int_set_eq:NN \l_@@_min_state_int \l_@@_max_active_int
- \int_set_eq:NN \l_@@_max_state_int \l_@@_min_state_int
+ \int_set_eq:NN \l_@@_min_state_int \l_@@_max_state_int
\@@_build_new_state:
\@@_build_new_state:
\@@_push_lr_states:
@@ -3857,7 +3830,7 @@
\@@_pop_lr_states:
\@@_toks_put_right:Nn \l_@@_right_state_int
{
- \if_int_compare:w \l_@@_curr_pos_int = \l_@@_max_pos_int
+ \if_int_compare:w -2 = \l_@@_curr_char_int
\exp_after:wN \@@_action_success:
\fi:
}
@@ -4195,8 +4168,8 @@
\cs_new_protected:Npn \@@_group_submatches:nNN #1#2#3
{
\if_int_compare:w #1 > - 1 \exp_stop_f:
- \@@_toks_put_left:Nx #2 { \@@_action_submatch:n { #1 < } }
- \@@_toks_put_left:Nx #3 { \@@_action_submatch:n { #1 > } }
+ \@@_toks_put_left:Nx #2 { \@@_action_submatch:nN {#1} < }
+ \@@_toks_put_left:Nx #3 { \@@_action_submatch:nN {#1} > }
\fi:
}
% \end{macrocode}
@@ -4345,16 +4318,14 @@
%
% \subsubsection{Others}
%
-% \begin{macro}{\@@_assertion:Nn, \@@_b_test:, \@@_anchor:N}
+% \begin{macro}{\@@_assertion:Nn, \@@_b_test:, \@@_A_test:, \@@_G_test:, \@@_Z_test:}
% Usage: \cs{@@_assertion:Nn} \meta{boolean} \Arg{test}, where the
% \meta{test} is either of the two other functions. Add a free
% transition to a new state, conditionally to the assertion test. The
% \cs{@@_b_test:} test is used by the |\b| and |\B| escape: check
% if the last character was a word character or not, and do the same
% to the current character. The boundary-markers of the string are
-% non-word characters for this purpose. Anchors at the start or end
-% of match use \cs{@@_anchor:N}, with a position controlled by the
-% integer |#1|.
+% non-word characters for this purpose.
% \begin{macrocode}
\cs_new_protected:Npn \@@_assertion:Nn #1#2
{
@@ -4374,12 +4345,6 @@
\bool_if:NT #1 { { } }
}
}
-\cs_new_protected:Npn \@@_anchor:N #1
- {
- \if_int_compare:w #1 = \l_@@_curr_pos_int
- \exp_after:wN \@@_break_true:w
- \fi:
- }
\cs_new_protected:Npn \@@_b_test:
{
\group_begin:
@@ -4389,6 +4354,24 @@
{ \group_end: \@@_item_reverse:n \@@_prop_w: }
{ \group_end: \@@_prop_w: }
}
+\cs_new_protected:Npn \@@_Z_test:
+ {
+ \if_int_compare:w -2 = \l_@@_curr_char_int
+ \exp_after:wN \@@_break_true:w
+ \fi:
+ }
+\cs_new_protected:Npn \@@_A_test:
+ {
+ \if_int_compare:w -2 = \l_@@_last_char_int
+ \exp_after:wN \@@_break_true:w
+ \fi:
+ }
+\cs_new_protected:Npn \@@_G_test:
+ {
+ \if_int_compare:w \l_@@_curr_pos_int = \l_@@_start_pos_int
+ \exp_after:wN \@@_break_true:w
+ \fi:
+ }
% \end{macrocode}
% \end{macro}
%
@@ -4401,7 +4384,7 @@
\@@_build_new_state:
\@@_toks_put_right:Nx \l_@@_left_state_int
{
- \@@_action_submatch:n { 0< }
+ \@@_action_submatch:nN { 0 } <
\bool_set_true:N \l_@@_fresh_thread_bool
\@@_action_free:n
{
@@ -4423,7 +4406,8 @@
% transitions, the instruction at the new state of the \textsc{nfa} is
% performed immediately. When a transition consumes a character, the
% new state is appended to a list of \enquote{active states}, stored in
-% \cs{g_@@_thread_state_intarray}: this thread is made active again when the next
+% \cs{g_@@_thread_info_intarray} (together with submatch information):
+% this thread is made active again when the next
% token is read from the query. At every step (for each token in the
% query), we unpack that list of active states and the corresponding
% submatch props, and empty those.
@@ -4467,11 +4451,10 @@
% }
% The tokens in the query are indexed from \texttt{min_pos} for the
% first to $\texttt{max_pos}-1$ for the last, and their information is
-% stored in several arrays and \tn{toks} registers with those numbers. We
-% don't start from $0$ because the \tn{toks} registers with low
-% numbers are used to hold the states of the \textsc{nfa}. We match
+% stored in several arrays and \tn{toks} registers with those numbers.
+% We match
% without backtracking, keeping all threads in lockstep at the
-% \texttt{current_pos} in the query. The starting point of the current
+% \texttt{curr_pos} in the query. The starting point of the current
% match attempt is \texttt{start_pos}, and \texttt{success_pos},
% updated whenever a thread succeeds, is used as the next starting
% position.
@@ -4488,20 +4471,26 @@
% {
% \l_@@_curr_char_int,
% \l_@@_curr_catcode_int,
+% \l_@@_curr_token_tl,
% \l_@@_last_char_int,
+% \l_@@_last_char_success_int,
% \l_@@_case_changed_char_int
% }
% The character and category codes of the token at the current
-% position; the character code of the token at the previous position;
+% position and a token list expanding to that token; the character
+% code of the token at the previous position;
+% the character code of the token just before a successful match;
% and the character code of the result of changing the case of the
% current token (|A-Z|$\leftrightarrow$|a-z|). This last integer is
% only computed when necessary, and is otherwise \cs{c_max_int}. The
-% \texttt{current_char} variable is also used in various other phases
+% \texttt{curr_char} variable is also used in various other phases
% to hold a character code.
% \begin{macrocode}
\int_new:N \l_@@_curr_char_int
\int_new:N \l_@@_curr_catcode_int
+\tl_new:N \l_@@_curr_token_tl
\int_new:N \l_@@_last_char_int
+\int_new:N \l_@@_last_char_success_int
\int_new:N \l_@@_case_changed_char_int
% \end{macrocode}
% \end{variable}
@@ -4517,18 +4506,17 @@
% \end{variable}
%
% \begin{variable}
-% {\l_@@_curr_submatches_prop, \l_@@_success_submatches_prop}
+% {\l_@@_curr_submatches_tl, \l_@@_success_submatches_tl}
% The submatches for the thread which is currently active are stored
-% in the \texttt{current_submatches} property list variable. This
-% property list is stored by \cs{@@_action_cost:n} into the
-% \tn{toks} register for the target state of the transition, to be
-% retrieved when matching at the next position. When a thread
-% succeeds, this property list is copied to
-% \cs{l_@@_success_submatches_prop}: only the last successful thread
+% in the \texttt{curr_submatches} list, which is almost a comma list,
+% but ends with a comma. This list is stored by \cs{@@_store_state:n}
+% into an intarray variable, to be retrieved when matching at the next
+% position. When a thread succeeds, this list is copied to
+% \cs{l_@@_success_submatches_tl}: only the last successful thread
% remains there.
% \begin{macrocode}
-\prop_new:N \l_@@_curr_submatches_prop
-\prop_new:N \l_@@_success_submatches_prop
+\tl_new:N \l_@@_curr_submatches_tl
+\tl_new:N \l_@@_success_submatches_tl
% \end{macrocode}
% \end{variable}
%
@@ -4551,28 +4539,44 @@
% \end{macrocode}
% \end{variable}
%
-% \begin{variable}{\l_@@_min_active_int, \l_@@_max_active_int}
+% \begin{variable}{\l_@@_min_thread_int, \l_@@_max_thread_int}
% All the currently active threads are kept in order of precedence in
-% \cs{g_@@_thread_state_intarray}, and the corresponding submatches in the
-% \tn{toks}. For our purposes, those serve as an array, indexed from
-% \texttt{min_active} (inclusive) to \texttt{max_active} (excluded).
-% At the start of every step, the whole array is unpacked, so that the
-% space can immediately be reused, and \texttt{max_active} is reset to
-% \texttt{min_active}, effectively clearing the array.
+% \cs{g_@@_thread_info_intarray} together with the corresponding
+% submatch information. Data in this intarray is organized as blocks
+% from \texttt{min_thread} (included) to \texttt{max_thread}
+% (excluded). At the start of every step, the whole array is
+% unpacked, so that the space can immediately be reused, and
+% \texttt{max_thread} is reset to \texttt{min_thread}, effectively
+% clearing the array.
% \begin{macrocode}
-\int_new:N \l_@@_min_active_int
-\int_new:N \l_@@_max_active_int
+\int_new:N \l_@@_min_thread_int
+\int_new:N \l_@@_max_thread_int
% \end{macrocode}
% \end{variable}
%
-% \begin{variable}{\g_@@_state_active_intarray, \g_@@_thread_state_intarray}
+% \begin{variable}{\g_@@_state_active_intarray, \g_@@_thread_info_intarray}
% \cs{g_@@_state_active_intarray} stores the last \meta{step} in which
-% each \meta{state} was active. \cs{g_@@_thread_state_intarray} stores
+% each \meta{state} was active. \cs{g_@@_thread_info_intarray} stores
% threads to be considered in the next step, more precisely the
% states in which these threads are.
% \begin{macrocode}
\intarray_new:Nn \g_@@_state_active_intarray { 65536 }
-\intarray_new:Nn \g_@@_thread_state_intarray { 65536 }
+\intarray_new:Nn \g_@@_thread_info_intarray { 65536 }
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_matched_analysis_tl, \l_@@_curr_analysis_tl}
+% The list \cs{l_@@_curr_analysis_tl} consists of a brace group
+% containing three brace groups corresponding to the current token,
+% with the same syntax as \cs{tl_analysis_map_inline:nn}. The list
+% \cs{l_@@_matched_analysis_tl} (constructed under the
+% \texttt{tl\_build} machinery) has one item for each token that has
+% already been treated so far in a given match attempt: each item
+% consists of three brace groups with the same syntax as
+% \cs{tl_analysis_map_inline:nn}.
+% \begin{macrocode}
+\tl_new:N \l_@@_matched_analysis_tl
+\tl_new:N \l_@@_curr_analysis_tl
% \end{macrocode}
% \end{variable}
%
@@ -4635,8 +4639,7 @@
%
% \begin{macro}{\@@_match:n, \@@_match_cs:n}
% \begin{macro}{\@@_match_init:}
-% First store the query into \tn{toks} registers and arrays (see
-% \cs{@@_query_set:nnn}). Then initialize the variables that should
+% Initialize the variables that should
% be set once for each user function (even for multiple
% matches). Namely, the overall matching is not yet successful; none of
% the states should be marked as visited (\cs{g_@@_state_active_intarray}), and
@@ -4647,38 +4650,26 @@
% \begin{macrocode}
\cs_new_protected:Npn \@@_match:n #1
{
- \int_zero:N \l_@@_balance_int
- \int_set:Nn \l_@@_curr_pos_int { 2 * \l_@@_max_state_int }
- \@@_query_set:nnn { } { -1 } { -2 }
- \int_set_eq:NN \l_@@_min_pos_int \l_@@_curr_pos_int
- \tl_analysis_map_inline:nn {#1}
- { \@@_query_set:nnn {##1} {"##3} {##2} }
- \int_set_eq:NN \l_@@_max_pos_int \l_@@_curr_pos_int
- \@@_query_set:nnn { } { -1 } { -2 }
\@@_match_init:
- \@@_match_once:
+ \@@_match_once_init:
+ \tl_analysis_map_inline:nn {#1}
+ { \@@_match_one_token:nnN {##1} {##2} ##3 }
+ \@@_match_one_token:nnN { } { -2 } F
+ \prg_break_point:Nn \@@_maplike_break: { }
}
\cs_new_protected:Npn \@@_match_cs:n #1
{
- \int_zero:N \l_@@_balance_int
- \int_set:Nn \l_@@_curr_pos_int
- {
- \int_max:nn { 2 * \l_@@_max_state_int - \l_@@_min_state_int }
- { \l_@@_max_pos_int }
- + 1
- }
- \@@_query_set:nnn { } { -1 } { -2 }
- \int_set_eq:NN \l_@@_min_pos_int \l_@@_curr_pos_int
+ \int_set_eq:NN \l_@@_min_thread_int \l_@@_max_thread_int
+ \@@_match_init:
+ \@@_match_once_init:
\str_map_inline:nn {#1}
{
- \@@_query_set:nnn { \exp_not:n {##1} }
- { \tl_if_blank:nTF {##1} { 10 } { 12 } }
- { `##1 }
+ \tl_if_blank:nTF {##1}
+ { \@@_match_one_token:nnN {##1} {`##1} A }
+ { \@@_match_one_token:nnN {##1} {`##1} C }
}
- \int_set_eq:NN \l_@@_max_pos_int \l_@@_curr_pos_int
- \@@_query_set:nnn { } { -1 } { -2 }
- \@@_match_init:
- \@@_match_once:
+ \@@_match_one_token:nnN { } { -2 } F
+ \prg_break_point:Nn \@@_maplike_break: { }
}
\cs_new_protected:Npn \@@_match_init:
{
@@ -4689,11 +4680,13 @@
\__kernel_intarray_gset:Nnn
\g_@@_state_active_intarray {##1} { 1 }
}
- \int_set_eq:NN \l_@@_min_active_int \l_@@_max_state_int
\int_zero:N \l_@@_step_int
+ \int_set:Nn \l_@@_min_pos_int { 2 }
\int_set_eq:NN \l_@@_success_pos_int \l_@@_min_pos_int
- \int_set:Nn \l_@@_min_submatch_int
- { 2 * \l_@@_max_state_int }
+ \int_set:Nn \l_@@_last_char_success_int { -2 }
+ \tl_build_begin:N \l_@@_matched_analysis_tl
+ \tl_clear:N \l_@@_curr_analysis_tl
+ \int_set:Nn \l_@@_min_submatch_int { 1 }
\int_set_eq:NN \l_@@_submatch_int \l_@@_min_submatch_int
\bool_set_false:N \l_@@_empty_success_bool
}
@@ -4701,21 +4694,24 @@
% \end{macro}
% \end{macro}
%
-% \begin{macro}{\@@_match_once:}
-% This function finds one match, then does some action defined by the
-% \texttt{every_match} token list, which may recursively call
-% \cs{@@_match_once:}. First initialize some variables: set the
+% \begin{macro}{\@@_match_once_init:}
+% This function resets various variables used when finding one match.
+% It is called before the loop through characters, and every time we
+% find a match, before searching for another match (this is controlled
+% by the \texttt{every_match} token list).
+%
+% First initialize some variables: set the
% conditional which detects identical empty matches; this match
% attempt starts at the previous \texttt{success_pos}, is not yet
% successful, and has no submatches yet; clear the array of active
% threads, and put the starting state $0$ in it. We are then almost
% ready to read our first token in the query, but we actually start
-% one position earlier than the start, and \texttt{get} that token, to
-% set \texttt{last_char} properly for word
-% boundaries. Then call \cs{@@_match_loop:}, which runs through the
-% query until the end or until a successful match breaks early.
+% one position earlier than the start because
+% \cs{@@_match_one_token:nnN} increments \cs{l_@@_curr_pos_int} and
+% saves \cs{l_@@_curr_char_int} as the \texttt{last_char} so that word
+% boundaries can be correctly identified.
% \begin{macrocode}
-\cs_new_protected:Npn \@@_match_once:
+\cs_new_protected:Npn \@@_match_once_init:
{
\if_meaning:w \c_true_bool \l_@@_empty_success_bool
\cs_set:Npn \@@_if_two_empty_matches:F
@@ -4728,14 +4724,24 @@
\fi:
\int_set_eq:NN \l_@@_start_pos_int \l_@@_success_pos_int
\bool_set_false:N \l_@@_match_success_bool
- \prop_clear:N \l_@@_curr_submatches_prop
- \int_set_eq:NN \l_@@_max_active_int \l_@@_min_active_int
+ \tl_set:Nx \l_@@_curr_submatches_tl
+ { \prg_replicate:nn { 2 * \l_@@_capturing_group_int } { 0 , } }
+ \int_set_eq:NN \l_@@_max_thread_int \l_@@_min_thread_int
\@@_store_state:n { \l_@@_min_state_int }
\int_set:Nn \l_@@_curr_pos_int
{ \l_@@_start_pos_int - 1 }
- \@@_query_get:
- \@@_match_loop:
- \l_@@_every_match_tl
+ \int_set_eq:NN \l_@@_curr_char_int \l_@@_last_char_success_int
+ \tl_build_get:NN \l_@@_matched_analysis_tl \l_@@_internal_a_tl
+ \exp_args:NNf \@@_match_once_init_aux:
+ \tl_map_inline:nn
+ { \exp_after:wN \l_@@_internal_a_tl \l_@@_curr_analysis_tl }
+ { \@@_match_one_token:nnN ##1 }
+ \prg_break_point:Nn \@@_maplike_break: { }
+ }
+\cs_new_protected:Npn \@@_match_once_init_aux:
+ {
+ \tl_build_clear:N \l_@@_matched_analysis_tl
+ \tl_clear:N \l_@@_curr_analysis_tl
}
% \end{macrocode}
% \end{macro}
@@ -4753,110 +4759,80 @@
\bool_gset_eq:NN
\g_@@_success_bool
\l_@@_match_success_bool
+ \@@_maplike_break:
}
}
\cs_new_protected:Npn \@@_multi_match:n #1
{
\tl_set:Nn \l_@@_every_match_tl
{
- \if_meaning:w \c_true_bool \l_@@_match_success_bool
- \bool_gset_true:N \g_@@_success_bool
- #1
- \exp_after:wN \@@_match_once:
+ \if_meaning:w \c_false_bool \l_@@_match_success_bool
+ \exp_after:wN \@@_maplike_break:
\fi:
+ \bool_gset_true:N \g_@@_success_bool
+ #1
+ \@@_match_once_init:
}
}
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}{\@@_match_loop:}
+% \begin{macro}{\@@_match_one_token:nnN}
% \begin{macro}[rEXP]{\@@_match_one_active:n}
% At each new position, set some variables and get the new character
% and category from the query. Then unpack the array of active
% threads, and clear it by resetting its length
-% (\texttt{max_active}). This results in a sequence of
-% \cs{@@_use_state_and_submatches:nn} \Arg{state} \Arg{prop}, and
+% (\texttt{max_thread}). This results in a sequence of
+% \cs{@@_use_state_and_submatches:w} \meta{state}|,|\meta{submatch-clist}|;| and
% we consider those states one by one in order. As soon as a thread
% succeeds, exit the step, and, if there are threads to consider at the
% next position, and we have not reached the end of the string,
-% repeat the loop. Otherwise, the last thread that succeeded is what
-% \cs{@@_match_once:} matches. We explain the \texttt{fresh_thread}
-% business when describing \cs{@@_action_wildcard:}.
+% repeat the loop. Otherwise, the last thread that succeeded is the
+% match. We explain the \texttt{fresh_thread} business when
+% describing \cs{@@_action_wildcard:}.
% \begin{macrocode}
-\cs_new_protected:Npn \@@_match_loop:
+\cs_new_protected:Npn \@@_match_one_token:nnN #1#2#3
{
\int_add:Nn \l_@@_step_int { 2 }
\int_incr:N \l_@@_curr_pos_int
\int_set_eq:NN \l_@@_last_char_int \l_@@_curr_char_int
\int_set_eq:NN \l_@@_case_changed_char_int \c_max_int
- \@@_query_get:
+ \tl_set:Nn \l_@@_curr_token_tl {#1}
+ \int_set:Nn \l_@@_curr_char_int {#2}
+ \int_set:Nn \l_@@_curr_catcode_int { "#3 }
+ \tl_build_put_right:Nx \l_@@_matched_analysis_tl
+ { \exp_not:o \l_@@_curr_analysis_tl }
+ \tl_set:Nn \l_@@_curr_analysis_tl { { {#1} {#2} #3 } }
\use:x
{
- \int_set_eq:NN \l_@@_max_active_int \l_@@_min_active_int
+ \int_set_eq:NN \l_@@_max_thread_int \l_@@_min_thread_int
\int_step_function:nnN
- { \l_@@_min_active_int }
- { \l_@@_max_active_int - 1 }
+ { \l_@@_min_thread_int }
+ { \l_@@_max_thread_int - 1 }
\@@_match_one_active:n
}
\prg_break_point:
\bool_set_false:N \l_@@_fresh_thread_bool
- \if_int_compare:w \l_@@_max_active_int > \l_@@_min_active_int
- \if_int_compare:w \l_@@_curr_pos_int < \l_@@_max_pos_int
- \exp_after:wN \exp_after:wN \exp_after:wN \@@_match_loop:
+ \if_int_compare:w \l_@@_max_thread_int > \l_@@_min_thread_int
+ \if_int_compare:w -2 < \l_@@_curr_char_int
+ \exp_after:wN \exp_after:wN \exp_after:wN \use_none:n
\fi:
\fi:
+ \l_@@_every_match_tl
}
\cs_new:Npn \@@_match_one_active:n #1
{
- \@@_use_state_and_submatches:nn
- { \__kernel_intarray_item:Nn \g_@@_thread_state_intarray {#1} }
- { \@@_toks_use:w #1 }
+ \@@_use_state_and_submatches:w
+ \__kernel_intarray_range_to_clist:Nnn
+ \g_@@_thread_info_intarray
+ { 1 + #1 * (\l_@@_capturing_group_int * 2 + 1) }
+ { (1 + #1) * (\l_@@_capturing_group_int * 2 + 1) }
+ ;
}
% \end{macrocode}
% \end{macro}
% \end{macro}
%
-% \begin{macro}{\@@_query_set:nnn}
-% The arguments are: tokens that \texttt{o} and \texttt{x} expand to
-% one token of the query, the catcode, and the character code. Store
-% those, and the current brace balance (used later to check for
-% overall brace balance) in a \tn{toks} register and some arrays,
-% then update the \texttt{balance}.
-% \begin{macrocode}
-\cs_new_protected:Npn \@@_query_set:nnn #1#2#3
- {
- \__kernel_intarray_gset:Nnn \g_@@_charcode_intarray
- { \l_@@_curr_pos_int } {#3}
- \__kernel_intarray_gset:Nnn \g_@@_catcode_intarray
- { \l_@@_curr_pos_int } {#2}
- \__kernel_intarray_gset:Nnn \g_@@_balance_intarray
- { \l_@@_curr_pos_int } { \l_@@_balance_int }
- \@@_toks_set:Nn \l_@@_curr_pos_int {#1}
- \int_incr:N \l_@@_curr_pos_int
- \if_case:w #2 \exp_stop_f:
- \or: \int_incr:N \l_@@_balance_int
- \or: \int_decr:N \l_@@_balance_int
- \fi:
- }
-% \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\@@_query_get:}
-% Extract the current character and category codes at the current
-% position from the appropriate arrays.
-% \begin{macrocode}
-\cs_new_protected:Npn \@@_query_get:
- {
- \l_@@_curr_char_int
- = \__kernel_intarray_item:Nn \g_@@_charcode_intarray
- { \l_@@_curr_pos_int } \scan_stop:
- \l_@@_curr_catcode_int
- = \__kernel_intarray_item:Nn \g_@@_catcode_intarray
- { \l_@@_curr_pos_int } \scan_stop:
- }
-% \end{macrocode}
-% \end{macro}
-%
% \subsubsection{Using states of the \textsc{nfa}}
%
% \begin{macro}{\@@_use_state:}
@@ -4879,20 +4855,20 @@
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}{\@@_use_state_and_submatches:nn}
+% \begin{macro}{\@@_use_state_and_submatches:w}
% This function is called as one item in the array of active threads
% after that array has been unpacked for a new step. Update the
-% \texttt{current_state} and \texttt{current_submatches} and use the
+% \texttt{curr_state} and \texttt{curr_submatches} and use the
% state if it has not yet been encountered at this step.
% \begin{macrocode}
-\cs_new_protected:Npn \@@_use_state_and_submatches:nn #1 #2
+\cs_new_protected:Npn \@@_use_state_and_submatches:w #1 , #2 ;
{
\int_set:Nn \l_@@_curr_state_int {#1}
\if_int_compare:w
\__kernel_intarray_item:Nn \g_@@_state_active_intarray
{ \l_@@_curr_state_int }
< \l_@@_step_int
- \tl_set:Nn \l_@@_curr_submatches_prop {#2}
+ \tl_set:Nn \l_@@_curr_submatches_tl { #2 , }
\exp_after:wN \@@_use_state:
\fi:
\scan_stop:
@@ -4902,20 +4878,20 @@
%
% \subsubsection{Actions when matching}
%
-% \begin{macro}{\@@_action_start_wildcard:}
+% \begin{macro}{\@@_action_start_wildcard:N}
% For an unanchored match, state $0$ has a free transition to the next
% and a costly one to itself, to repeat at the next position. To catch
% repeated identical empty matches, we need to know if a successful
% thread corresponds to an empty match. The instruction resetting
% \cs{l_@@_fresh_thread_bool} may be skipped by a successful
-% thread, hence we had to add it to \cs{@@_match_loop:} too.
+% thread, hence we had to add it to \cs{@@_match_one_token:nnN} too.
% \begin{macrocode}
-\cs_new_protected:Npn \@@_action_start_wildcard:
+\cs_new_protected:Npn \@@_action_start_wildcard:N #1
{
\bool_set_true:N \l_@@_fresh_thread_bool
\@@_action_free:n {1}
\bool_set_false:N \l_@@_fresh_thread_bool
- \@@_action_cost:n {0}
+ \bool_if:NT #1 { \@@_action_cost:n {0} }
}
% \end{macrocode}
% \end{macro}
@@ -4954,8 +4930,8 @@
}
\int_set:Nn \l_@@_curr_state_int
{ \int_use:N \l_@@_curr_state_int }
- \tl_set:Nn \exp_not:N \l_@@_curr_submatches_prop
- { \exp_not:o \l_@@_curr_submatches_prop }
+ \tl_set:Nn \exp_not:N \l_@@_curr_submatches_tl
+ { \exp_not:o \l_@@_curr_submatches_tl }
}
}
% \end{macrocode}
@@ -4978,21 +4954,26 @@
%
% \begin{macro}{\@@_store_state:n}
% \begin{macro}{\@@_store_submatches:}
-% Put the given state in \cs{g_@@_thread_state_intarray}, and increment
-% the length of the array. Also store the current submatch in the
-% appropriate \tn{toks}.
+% Put the given state and current submatch information in
+% \cs{g_@@_thread_info_intarray}, and increment the length of the
+% array.
% \begin{macrocode}
\cs_new_protected:Npn \@@_store_state:n #1
{
- \@@_store_submatches:
- \__kernel_intarray_gset:Nnn \g_@@_thread_state_intarray
- { \l_@@_max_active_int } {#1}
- \int_incr:N \l_@@_max_active_int
+ \exp_args:No \@@_store_submatches:nn
+ \l_@@_curr_submatches_tl {#1}
+ \int_incr:N \l_@@_max_thread_int
}
-\cs_new_protected:Npn \@@_store_submatches:
+\cs_new_protected:Npn \@@_store_submatches:nn #1#2
{
- \@@_toks_set:No \l_@@_max_active_int
- { \l_@@_curr_submatches_prop }
+ \__kernel_intarray_gset_range_from_clist:Nnn
+ \g_@@_thread_info_intarray
+ {
+ \@@_int_eval:w
+ 1 + \l_@@_max_thread_int *
+ (\l_@@_capturing_group_int * 2 + 1)
+ }
+ { #2 , #1 }
}
% \end{macrocode}
% \end{macro}
@@ -5006,21 +4987,37 @@
% \begin{macrocode}
\cs_new_protected:Npn \@@_disable_submatches:
{
- \cs_set_protected:Npn \@@_store_submatches: { }
- \cs_set_protected:Npn \@@_action_submatch:n ##1 { }
+ \cs_set_protected:Npn \@@_store_submatches:n ##1 { }
+ \cs_set_protected:Npn \@@_action_submatch:nN ##1##2 { }
}
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}{\@@_action_submatch:n}
+% \begin{macro}{\@@_action_submatch:nN, \@@_action_submatch_aux:w, \@@_action_submatch_auxii:w, \@@_action_submatch_auxiii:w, \@@_action_submatch_auxiv:w}
% Update the current submatches with the information from the current
% position. Maybe a bottleneck.
% \begin{macrocode}
-\cs_new_protected:Npn \@@_action_submatch:n #1
+\cs_new_protected:Npn \@@_action_submatch:nN #1#2
+ {
+ \exp_after:wN \@@_action_submatch_aux:w
+ \l_@@_curr_submatches_tl ; {#1} #2
+ }
+\cs_new_protected:Npn \@@_action_submatch_aux:w #1 ; #2#3
{
- \prop_put:Nno \l_@@_curr_submatches_prop {#1}
- { \int_use:N \l_@@_curr_pos_int }
+ \tl_set:Nx \l_@@_curr_submatches_tl
+ {
+ \prg_replicate:nn
+ { #2 \if_meaning:w > #3 + \l_@@_capturing_group_int \fi: }
+ { \@@_action_submatch_auxii:w }
+ \@@_action_submatch_auxiii:w
+ #1
+ }
}
+\cs_new:Npn \@@_action_submatch_auxii:w
+ #1 \@@_action_submatch_auxiii:w #2 ,
+ { #2 , #1 \@@_action_submatch_auxiii:w }
+\cs_new:Npn \@@_action_submatch_auxiii:w #1 ,
+ { \int_use:N \l_@@_curr_pos_int , }
% \end{macrocode}
% \end{macro}
%
@@ -5042,8 +5039,10 @@
\bool_set_eq:NN \l_@@_empty_success_bool
\l_@@_fresh_thread_bool
\int_set_eq:NN \l_@@_success_pos_int \l_@@_curr_pos_int
- \prop_set_eq:NN \l_@@_success_submatches_prop
- \l_@@_curr_submatches_prop
+ \int_set_eq:NN \l_@@_last_char_success_int \l_@@_last_char_int
+ \tl_build_clear:N \l_@@_matched_analysis_tl
+ \tl_set_eq:NN \l_@@_success_submatches_tl
+ \l_@@_curr_submatches_tl
\prg_break:
}
}
@@ -5134,6 +5133,14 @@
% \end{macrocode}
% \end{macro}
%
+% \begin{macro}{\@@_replacement_exp_not:V}
+% This is used for the implementation of~|\u|, and it gets redefined
+% for \cs{peek_regex_replace_once:nnTF}.
+% \begin{macrocode}
+\cs_new_eq:NN \@@_replacement_exp_not:V \exp_not:V
+% \end{macrocode}
+% \end{macro}
+%
% \subsubsection{Query and brace balance}
%
% \begin{macro}[rEXP]{\@@_query_range:nn}
@@ -5189,42 +5196,25 @@
% range is the difference between the brace balances at the
% \meta{max~pos} and \meta{min~pos}. These two positions are found in
% the corresponding \enquote{submatch} arrays.
-%^^A todo: understand when these int_compare are needed
% \begin{macrocode}
\cs_new_protected:Npn \@@_submatch_balance:n #1
{
\int_eval:n
- {
- \int_compare:nNnTF
- {
- \__kernel_intarray_item:Nn
- \g_@@_submatch_end_intarray {#1}
- }
- = 0
- { 0 }
- {
- \__kernel_intarray_item:Nn \g_@@_balance_intarray
- {
- \__kernel_intarray_item:Nn
- \g_@@_submatch_end_intarray {#1}
- }
- }
- -
- \int_compare:nNnTF
- {
- \__kernel_intarray_item:Nn
- \g_@@_submatch_begin_intarray {#1}
- }
- = 0
- { 0 }
- {
- \__kernel_intarray_item:Nn \g_@@_balance_intarray
- {
- \__kernel_intarray_item:Nn
- \g_@@_submatch_begin_intarray {#1}
- }
- }
- }
+ {
+ \@@_intarray_item:NnF \g_@@_balance_intarray
+ {
+ \__kernel_intarray_item:Nn
+ \g_@@_submatch_end_intarray {#1}
+ }
+ { 0 }
+ -
+ \@@_intarray_item:NnF \g_@@_balance_intarray
+ {
+ \__kernel_intarray_item:Nn
+ \g_@@_submatch_begin_intarray {#1}
+ }
+ { 0 }
+ }
}
% \end{macrocode}
% \end{macro}
@@ -5305,6 +5295,14 @@
% \end{macro}
% \end{macro}
%
+% \begin{macro}{\@@_replacement_put:n}
+% This gets redefined for \cs{peek_regex_replace_once:nnTF}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_replacement_put:n
+ { \tl_build_put_right:Nn \l_@@_build_tl }
+% \end{macrocode}
+% \end{macro}
+%
% \begin{macro}{\@@_replacement_normal:n}
% Most characters are simply sent to the output by
% \cs{tl_build_put_right:Nn}, unless a particular category code has been
@@ -5314,11 +5312,13 @@
% sequence is non-empty there: it contains an empty entry
% corresponding to the initial value of
% \cs{l_@@_replacement_category_tl}.
+% The argument |#1| can be a space, otherwise it is a single
+% character.
% \begin{macrocode}
\cs_new_protected:Npn \@@_replacement_normal:n #1
{
\tl_if_empty:NTF \l_@@_replacement_category_tl
- { \tl_build_put_right:Nn \l_@@_build_tl {#1} }
+ { \@@_replacement_put:n {#1} }
{ % (
\token_if_eq_charcode:NNTF #1 )
{
@@ -5361,7 +5361,7 @@
%
% \subsubsection{Submatches}
%
-% \begin{macro}{\@@_replacement_put_submatch:n}
+% \begin{macro}{\@@_replacement_put_submatch:n, \@@_replacement_put_submatch_aux:n}
% Insert a submatch in the replacement text. This is dropped if the
% submatch number is larger than the number of capturing groups.
% Unless the submatch appears inside a |\c{...}| or |\u{...}|
@@ -5374,15 +5374,19 @@
\cs_new_protected:Npn \@@_replacement_put_submatch:n #1
{
\if_int_compare:w #1 < \l_@@_capturing_group_int
- \tl_build_put_right:Nn \l_@@_build_tl
- { \@@_query_submatch:n { \int_eval:n { #1 + ##1 } } }
- \if_int_compare:w \l_@@_replacement_csnames_int = 0 \exp_stop_f:
- \tl_put_right:Nn \l_@@_balance_tl
- {
- + \@@_submatch_balance:n
- { \exp_not:N \int_eval:n { #1 + ##1 } }
- }
- \fi:
+ \@@_replacement_put_submatch_aux:n {#1}
+ \fi:
+ }
+\cs_new_protected:Npn \@@_replacement_put_submatch_aux:n #1
+ {
+ \tl_build_put_right:Nn \l_@@_build_tl
+ { \@@_query_submatch:n { \int_eval:n { #1 + ##1 } } }
+ \if_int_compare:w \l_@@_replacement_csnames_int = 0 \exp_stop_f:
+ \tl_put_right:Nn \l_@@_balance_tl
+ {
+ + \@@_submatch_balance:n
+ { \exp_not:N \int_eval:n { #1 + ##1 } }
+ }
\fi:
}
% \end{macrocode}
@@ -5488,7 +5492,7 @@
{
\@@_two_if_eq:NNNNTF
#1 #2 \@@_replacement_normal:n \c_left_brace_str
- { \@@_replacement_cu_aux:Nw \exp_not:V }
+ { \@@_replacement_cu_aux:Nw \@@_replacement_exp_not:V }
{ \@@_replacement_error:NNN u #1#2 }
}
% \end{macrocode}
@@ -5579,7 +5583,7 @@
\cs_new_protected:Npn \@@_replacement_char:nNN #1#2#3
{
\tex_lccode:D 0 = `#3 \scan_stop:
- \tex_lowercase:D { \tl_build_put_right:Nn \l_@@_build_tl {#1} }
+ \tex_lowercase:D { \@@_replacement_put:n {#1} }
}
% \end{macrocode}
% \end{macro}
@@ -5625,7 +5629,7 @@
\cs_new_protected:Npn \@@_replacement_c_C:w #1#2
{
\tl_build_put_right:Nn \l_@@_build_tl
- { \exp_not:N \exp_not:N \exp_not:c {#2} }
+ { \exp_not:N \@@_replacement_exp_not:N \exp_not:c {#2} }
}
% \end{macrocode}
% \end{macro}
@@ -5712,7 +5716,7 @@
\__kernel_msg_error:nn { kernel } { replacement-null-space }
\fi:
\tex_lccode:D `\ = `#2 \scan_stop:
- \tex_lowercase:D { \tl_build_put_right:Nn \l_@@_build_tl {~} }
+ \tex_lowercase:D { \@@_replacement_put:n {~} }
}
% \end{macrocode}
% \end{macro}
@@ -5940,6 +5944,14 @@
% \end{macrocode}
% \end{variable}
%
+% \begin{variable}{\g_@@_balance_intarray}
+% The first thing we do when matching is to store the balance of
+% begin-group/end-group characters into \cs{g_@@_balance_intarray}.
+% \begin{macrocode}
+\intarray_new:Nn \g_@@_balance_intarray { 65536 }
+% \end{macrocode}
+% \end{variable}
+%
% \begin{macro}{\@@_return:}
% This function triggers either \cs{prg_return_false:} or
% \cs{prg_return_true:} as appropriate to whether a match was found or
@@ -5956,6 +5968,36 @@
% \end{macrocode}
% \end{macro}
%
+% \begin{macro}{\@@_query_set:n, \@@_query_set_aux:nN}
+% To easily extract subsets of the input once we found the positions
+% at which to cut, store the input tokens one by one into successive
+% \tn{toks} registers. Also store the brace balance (used to check
+% for overall brace balance) in an array.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_query_set:n #1
+ {
+ \int_zero:N \l_@@_balance_int
+ \int_zero:N \l_@@_curr_pos_int
+ \@@_query_set_aux:nN { } F
+ \tl_analysis_map_inline:nn {#1}
+ { \@@_query_set_aux:nN {##1} ##3 }
+ \@@_query_set_aux:nN { } F
+ \int_set_eq:NN \l_@@_max_pos_int \l_@@_curr_pos_int
+ }
+\cs_new_protected:Npn \@@_query_set_aux:nN #1#2
+ {
+ \int_incr:N \l_@@_curr_pos_int
+ \@@_toks_set:Nn \l_@@_curr_pos_int {#1}
+ \__kernel_intarray_gset:Nnn \g_@@_balance_intarray
+ { \l_@@_curr_pos_int } { \l_@@_balance_int }
+ \if_case:w "#2 \exp_stop_f:
+ \or: \int_incr:N \l_@@_balance_int
+ \or: \int_decr:N \l_@@_balance_int
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+%
% \subsubsection{Matching}
%
% \begin{macro}{\@@_if_match:nn}
@@ -6011,6 +6053,7 @@
#1
\@@_match:n {#2}
\@@_extract:
+ \@@_query_set:n {#2}
\@@_group_end_extract_seq:N #3
}
\cs_new_protected:Npn \@@_extract_all:nnN #1#2#3
@@ -6019,6 +6062,7 @@
\@@_multi_match:n { \@@_extract: }
#1
\@@_match:n {#2}
+ \@@_query_set:n {#2}
\@@_group_end_extract_seq:N #3
}
% \end{macrocode}
@@ -6058,7 +6102,7 @@
}
#1
\@@_match:n {#2}
-%<assert>\assert_int:n { \l_@@_curr_pos_int = \l_@@_max_pos_int }
+ \@@_query_set:n {#2}
\__kernel_intarray_gset:Nnn \g_@@_submatch_prev_intarray
{ \l_@@_submatch_int } { 0 }
\__kernel_intarray_gset:Nnn \g_@@_submatch_end_intarray
@@ -6143,18 +6187,15 @@
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}
-% {\@@_extract:, \@@_extract_b:wn, \@@_extract_e:wn}
-% Our task here is to extract from the property list
-% \cs{l_@@_success_submatches_prop} the list of end-points of
-% submatches, and store them in appropriate array entries, from
-% \cs{l_@@_zeroth_submatch_int} upwards. We begin by emptying those
-% entries. Then for each \meta{key}--\meta{value} pair in
-% the property list update the appropriate entry. This
-% is somewhat a hack: the \meta{key} is a non-negative integer
-% followed by |<| or |>|, which we use in a comparison to $-1$. At the
-% end, store the information about the position at which the match
-% attempt started, in \cs{g_@@_submatch_prev_intarray}.
+% \begin{macro}{\@@_extract:}
+% Our task here is to store the list of end-points of submatches, and
+% store them in appropriate array entries, from
+% \cs{l_@@_zeroth_submatch_int} upwards. First, we store in
+% \cs{g_@@_submatch_prev_intarray} the position at which the match
+% attempt started. We extract the rest from the comma list
+% \cs{l_@@_success_submatches_tl}, which starts with entries to be
+% stored in \cs{g_@@_submatch_begin_intarray} and continues with
+% entries for \cs{g_@@_submatch_end_intarray}.
% \begin{macrocode}
\cs_new_protected:Npn \@@_extract:
{
@@ -6162,34 +6203,26 @@
\int_set_eq:NN \l_@@_zeroth_submatch_int \l_@@_submatch_int
\prg_replicate:nn \l_@@_capturing_group_int
{
- \__kernel_intarray_gset:Nnn \g_@@_submatch_begin_intarray
- { \l_@@_submatch_int } { 0 }
- \__kernel_intarray_gset:Nnn \g_@@_submatch_end_intarray
- { \l_@@_submatch_int } { 0 }
\__kernel_intarray_gset:Nnn \g_@@_submatch_prev_intarray
{ \l_@@_submatch_int } { 0 }
\int_incr:N \l_@@_submatch_int
}
- \prop_map_inline:Nn \l_@@_success_submatches_prop
+ \__kernel_intarray_gset:Nnn \g_@@_submatch_prev_intarray
+ { \l_@@_zeroth_submatch_int } { \l_@@_start_pos_int }
+ \int_zero:N \l_@@_internal_a_int
+ \clist_map_inline:Nn \l_@@_success_submatches_tl
{
- \if_int_compare:w ##1 - 1 \exp_stop_f:
- \exp_after:wN \@@_extract_e:wn \int_value:w
+ \if_int_compare:w \l_@@_internal_a_int < \l_@@_capturing_group_int
+ \__kernel_intarray_gset:Nnn \g_@@_submatch_begin_intarray
+ { \@@_int_eval:w \l_@@_zeroth_submatch_int + \l_@@_internal_a_int } {##1}
\else:
- \exp_after:wN \@@_extract_b:wn \int_value:w
+ \__kernel_intarray_gset:Nnn \g_@@_submatch_end_intarray
+ { \@@_int_eval:w \l_@@_zeroth_submatch_int + \l_@@_internal_a_int - \l_@@_capturing_group_int } {##1}
\fi:
- \@@_int_eval:w \l_@@_zeroth_submatch_int + ##1 {##2}
+ \int_incr:N \l_@@_internal_a_int
}
- \__kernel_intarray_gset:Nnn \g_@@_submatch_prev_intarray
- { \l_@@_zeroth_submatch_int } { \l_@@_start_pos_int }
\fi:
}
-\cs_new_protected:Npn \@@_extract_b:wn #1 < #2
- {
- \__kernel_intarray_gset:Nnn
- \g_@@_submatch_begin_intarray {#1} {#2}
- }
-\cs_new_protected:Npn \@@_extract_e:wn #1 > #2
- { \__kernel_intarray_gset:Nnn \g_@@_submatch_end_intarray {#1} {#2} }
% \end{macrocode}
% \end{macro}
%
@@ -6214,12 +6247,13 @@
\group_begin:
\@@_single_match:
#1
- \@@_replacement:n {#2}
- \exp_args:No \@@_match:n { #3 }
+ \exp_args:No \@@_match:n {#3}
\if_meaning:w \c_false_bool \g_@@_success_bool
\group_end:
\else:
\@@_extract:
+ \exp_args:No \@@_query_set:n {#3}
+ \@@_replacement:n {#2}
\int_set:Nn \l_@@_balance_int
{
\@@_replacement_balance_one_match:n
@@ -6259,8 +6293,9 @@
\group_begin:
\@@_multi_match:n { \@@_extract: }
#1
- \@@_replacement:n {#2}
\exp_args:No \@@_match:n {#3}
+ \exp_args:No \@@_query_set:n {#3}
+ \@@_replacement:n {#2}
\int_set:Nn \l_@@_balance_int
{
0
@@ -6320,7 +6355,371 @@
% \end{macrocode}
% \end{macro}
%
-% \subsubsection{Storing and showing compiled patterns}
+% \subsubsection{Peeking ahead}
+%
+% \begin{variable}{\l_@@_peek_true_tl, \l_@@_peek_false_tl}
+% True/false code arguments of \cs{peek_regex:nTF} or similar.
+% \begin{macrocode}
+\tl_new:N \l_@@_peek_true_tl
+\tl_new:N \l_@@_peek_false_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_replacement_tl}
+% When peeking in \cs{peek_regex_replace_once:nnTF} we need to store
+% the replacement text.
+% \begin{macrocode}
+\tl_new:N \l_@@_replacement_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_input_tl}
+% \begin{macro}{\@@_input_item:n}
+% Stores each token found as \cs{@@_input_item:n} \Arg{tokens}, where
+% the \meta{tokens} \texttt{o}-expand to the token found, as for
+% \cs{tl_analysis_map_inline:nn}.
+% \begin{macrocode}
+\tl_new:N \l_@@_input_tl
+\cs_new_eq:NN \@@_input_item:n ?
+% \end{macrocode}
+% \end{macro}
+% \end{variable}
+%
+% \begin{macro}[TF]
+% {\peek_regex:n, \peek_regex:N, \peek_regex_remove_once:n, \peek_regex_remove_once:N}
+% The |T| and |F| functions just call the corresponding |TF| function.
+% The four |TF| functions differ along two axes: whether to remove the
+% token or not, distinguished by using \cs{@@_peek_end:} or
+% \cs{@@_peek_remove_end:n} (the latter case needs an argument, as we
+% will see), and whether the regex has to be compiled or is already in
+% an |N|-type variable, distinguished by calling \cs{@@_build_aux:Nn}
+% or \cs{@@_build_aux:NN}. The first argument of these functions is
+% \cs{c_false_bool} to indicate that there should be no implicit
+% insertion of a wildcard at the start of the pattern: otherwise the
+% code would keep looking further into the input stream until matching
+% the regex.
+% \begin{macrocode}
+\cs_new_protected:Npn \peek_regex:nTF #1
+ {
+ \@@_peek:nnTF
+ { \@@_build_aux:Nn \c_false_bool {#1} }
+ { \@@_peek_end: }
+ }
+\cs_new_protected:Npn \peek_regex:nT #1#2
+ { \peek_regex:nTF {#1} {#2} { } }
+\cs_new_protected:Npn \peek_regex:nF #1 { \peek_regex:nTF {#1} { } }
+\cs_new_protected:Npn \peek_regex:NTF #1
+ {
+ \@@_peek:nnTF
+ { \@@_build_aux:NN \c_false_bool #1 }
+ { \@@_peek_end: }
+ }
+\cs_new_protected:Npn \peek_regex:NT #1#2
+ { \peek_regex:NTF #1 {#2} { } }
+\cs_new_protected:Npn \peek_regex:NF #1 { \peek_regex:NTF {#1} { } }
+\cs_new_protected:Npn \peek_regex_remove_once:nTF #1
+ {
+ \@@_peek:nnTF
+ { \@@_build_aux:Nn \c_false_bool {#1} }
+ { \@@_peek_remove_end:n {##1} }
+ }
+\cs_new_protected:Npn \peek_regex_remove_once:nT #1#2
+ { \peek_regex_remove_once:nTF {#1} {#2} { } }
+\cs_new_protected:Npn \peek_regex_remove_once:nF #1
+ { \peek_regex_remove_once:nTF {#1} { } }
+\cs_new_protected:Npn \peek_regex_remove_once:NTF #1
+ {
+ \@@_peek:nnTF
+ { \@@_build_aux:NN \c_false_bool #1 }
+ { \@@_peek_remove_end:n {##1} }
+ }
+\cs_new_protected:Npn \peek_regex_remove_once:NT #1#2
+ { \peek_regex_remove_once:NTF #1 {#2} { } }
+\cs_new_protected:Npn \peek_regex_remove_once:NF #1
+ { \peek_regex_remove_once:NTF #1 { } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_peek:nnTF, \@@_peek_aux:nnTF}
+% Store the user's true/false codes (plus \cs{group_end:}) into two
+% token lists. Then build the automaton with |#1|, without submatch
+% tracking, and aiming for a single match. Then start matching by
+% setting up a few variables like for any regex matching like
+% \cs{regex_match:nnTF}, with the addition of \cs{l_@@_input_tl}
+% that keeps track of the tokens seen, to reinsert them at the
+% end. Instead of \cs{tl_analysis_map_inline:nn} on the input, we
+% call \cs{peek_analysis_map_inline:n} to go through tokens in the
+% input stream. Since \cs{@@_match_one_token:nnN} calls
+% \cs{@@_maplike_break:} we need to catch that and break the
+% \cs{peek_analysis_map_inline:n} loop instead.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek:nnTF #1
+ {
+ \@@_peek_aux:nnTF
+ {
+ \@@_disable_submatches:
+ #1
+ }
+ }
+\cs_new_protected:Npn \@@_peek_aux:nnTF #1#2#3#4
+ {
+ \group_begin:
+ \tl_set:Nn \l_@@_peek_true_tl { \group_end: #3 }
+ \tl_set:Nn \l_@@_peek_false_tl { \group_end: #4 }
+ \@@_single_match:
+ #1
+ \@@_match_init:
+ \tl_build_clear:N \l_@@_input_tl
+ \@@_match_once_init:
+ \peek_analysis_map_inline:n
+ {
+ \tl_build_put_right:Nn \l_@@_input_tl
+ { \@@_input_item:n {##1} }
+ \@@_match_one_token:nnN {##1} {##2} ##3
+ \use_none:nnn
+ \prg_break_point:Nn \@@_maplike_break:
+ { \peek_analysis_map_break:n {#2} }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_peek_end:, \@@_peek_remove_end:n}
+% Once the regex matches (or permanently fails to match) we call
+% \cs{@@_peek_end:}, or \cs{@@_peek_remove_end:n} with argument the
+% last token seen (or rather tokens that \texttt{o}-expand and
+% \texttt{x}-expand to it). For \cs{peek_regex:nTF} we reinsert
+% tokens seen by calling \cs{@@_peek_reinsert:N} regardless of the
+% result of the match. For \cs{peek_regex_remove_once:nTF} we reinsert the
+% tokens seen only if the match failed; otherwise we just reinsert the
+% tokens~|#1|, with one expansion.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_end:
+ {
+ \bool_if:NTF \g_@@_success_bool
+ { \@@_peek_reinsert:N \l_@@_peek_true_tl }
+ { \@@_peek_reinsert:N \l_@@_peek_false_tl }
+ }
+\cs_new_protected:Npn \@@_peek_remove_end:n #1
+ {
+ \bool_if:NTF \g_@@_success_bool
+ { \exp_after:wN \l_@@_peek_true_tl #1 }
+ { \@@_peek_reinsert:N \l_@@_peek_false_tl }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_peek_reinsert:N, \@@_reinsert_item:n}
+% Insert the true/false code |#1|, followed by the tokens found, which
+% were stored in \cs{l_@@_input_tl}. For this, loop through that
+% token list using \cs{@@_reinsert_item:n}, which expands |#1| once to
+% get a single token, and jumps over it to expand what follows, with
+% suitable \cs{exp:w} and \cs{exp_end:}. We cannot just use
+% \cs{use:e} on the whole token list because the result may be
+% unbalanced, which would stop the primitive prematurely, or let it
+% continue beyond where we would like.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_reinsert:N #1
+ {
+ \tl_build_end:N \l_@@_input_tl
+ \cs_set_eq:NN \@@_input_item:n \@@_reinsert_item:n
+ \exp_after:wN #1 \exp:w \l_@@_input_tl \exp_end:
+ }
+\cs_new_protected:Npn \@@_reinsert_item:n #1
+ {
+ \exp_after:wN \exp_after:wN
+ \exp_after:wN \exp_end:
+ \exp_after:wN \exp_after:wN
+ #1
+ \exp:w
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[noTF]
+% {\peek_regex_replace_once:nn, \peek_regex_replace_once:Nn}
+% Similar to \cs{peek_regex:nTF} above.
+% \begin{macrocode}
+\cs_new_protected:Npn \peek_regex_replace_once:nnTF #1
+ { \@@_peek_replace:nnTF { \@@_build_aux:Nn \c_false_bool {#1} } }
+\cs_new_protected:Npn \peek_regex_replace_once:nnT #1#2#3
+ { \peek_regex_replace_once:nnTF {#1} {#2} {#3} { } }
+\cs_new_protected:Npn \peek_regex_replace_once:nnF #1#2
+ { \peek_regex_replace_once:nnTF {#1} {#2} { } }
+\cs_new_protected:Npn \peek_regex_replace_once:nn #1#2
+ { \peek_regex_replace_once:nnTF {#1} {#2} { } { } }
+\cs_new_protected:Npn \peek_regex_replace_once:NnTF #1
+ { \@@_peek_replace:nnTF { \@@_build_aux:NN \c_false_bool #1 } }
+\cs_new_protected:Npn \peek_regex_replace_once:NnT #1#2#3
+ { \peek_regex_replace_once:NnTF #1 {#2} {#3} { } }
+\cs_new_protected:Npn \peek_regex_replace_once:NnF #1#2
+ { \peek_regex_replace_once:NnTF #1 {#2} { } }
+\cs_new_protected:Npn \peek_regex_replace_once:Nn #1#2
+ { \peek_regex_replace_once:NnTF #1 {#2} { } { } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_peek_replace:nnTF}
+% Same as \cs{@@_peek:nnTF} (used for \cs{peek_regex:nTF} above), but
+% without disabling submatches, and with a different end. The
+% replacement text |#2| is stored, to be analyzed later.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_replace:nnTF #1#2
+ {
+ \tl_set:Nn \l_@@_replacement_tl {#2}
+ \@@_peek_aux:nnTF {#1} { \@@_peek_replace_end: }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_peek_replace_end:}
+% If the match failed \cs{@@_peek_reinsert:N} reinserts the tokens
+% found. Otherwise, finish storing the submatch information using
+% \cs{@@_extract:}, and store the input into \tn{toks}. Redefine a
+% few auxiliaries to change slightly their expansion behaviour as
+% explained below. Analyse the replacement text with
+% \cs{@@_replacement:n}, which as usual defines
+% \cs{@@_replacement_do_one_match:n} to insert the tokens from the
+% start of the match attempt to the beginning of the match, followed
+% by the replacement text. The \cs{use:x} expands for instance the
+% trailing \cs{@@_query_range:nn} down to a sequence of
+% \cs{@@_reinsert_item:n} \Arg{tokens} where \meta{tokens}
+% \texttt{o}-expand to a single token that we want to insert. After
+% \texttt{x}-expansion, \cs{use:x} does \cs{use:n}, so we have
+% \cs{exp_after:wN} \cs{l_@@_peek_true_tl} \cs{exp:w} \ldots{}
+% \cs{exp_end:}. This is set up such as to obtain
+% \cs{l_@@_peek_true_tl} followed by the replaced tokens (possibly
+% unbalanced) in the input stream.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_replace_end:
+ {
+ \bool_if:NTF \g_@@_success_bool
+ {
+ \@@_extract:
+ \@@_query_set_from_input_tl:
+ \cs_set_eq:NN \@@_replacement_put:n \@@_peek_replacement_put:n
+ \cs_set_eq:NN \@@_replacement_put_submatch_aux:n
+ \@@_peek_replacement_put_submatch_aux:n
+ \cs_set_eq:NN \@@_input_item:n \@@_reinsert_item:n
+ \cs_set_eq:NN \@@_replacement_exp_not:N \@@_peek_replacement_token:n
+ \cs_set_eq:NN \@@_replacement_exp_not:V \@@_peek_replacement_var:N
+ \exp_args:No \@@_replacement:n { \l_@@_replacement_tl }
+ \use:x
+ {
+ \exp_not:n { \exp_after:wN \l_@@_peek_true_tl \exp:w }
+ \@@_replacement_do_one_match:n
+ { \l_@@_zeroth_submatch_int }
+ \@@_query_range:nn
+ {
+ \__kernel_intarray_item:Nn \g_@@_submatch_end_intarray
+ { \l_@@_zeroth_submatch_int }
+ }
+ { \l_@@_max_pos_int }
+ \exp_end:
+ }
+ }
+ { \@@_peek_reinsert:N \l_@@_peek_false_tl }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_query_set_from_input_tl:, \@@_query_set_item:n}
+% The input was stored into \cs{l_@@_input_tl} as successive items
+% \cs{@@_input_item:n} \Arg{tokens}. Store that in successive
+% \tn{toks}. It's not clear whether the empty entries before and
+% after are both useful.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_query_set_from_input_tl:
+ {
+ \tl_build_end:N \l_@@_input_tl
+ \int_zero:N \l_@@_curr_pos_int
+ \cs_set_eq:NN \@@_input_item:n \@@_query_set_item:n
+ \@@_query_set_item:n { }
+ \l_@@_input_tl
+ \@@_query_set_item:n { }
+ \int_set_eq:NN \l_@@_max_pos_int \l_@@_curr_pos_int
+ }
+\cs_new_protected:Npn \@@_query_set_item:n #1
+ {
+ \int_incr:N \l_@@_curr_pos_int
+ \@@_toks_set:Nn \l_@@_curr_pos_int { \@@_input_item:n {#1} }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_peek_replacement_put:n}
+% While building the replacement function
+% \cs{@@_replacement_do_one_match:n}, we often want to put simple
+% material, given as |#1|, whose \texttt{x}-expansion
+% \texttt{o}-expands to a single token. Normally we can just add the
+% token to \cs{l_@@_build_tl}, but for
+% \cs{peek_regex_replace_once:nnTF} we eventually want to do some
+% strange expansion that is basically using \cs{exp_after:wN} to jump
+% through numerous tokens (we cannot use \texttt{x}-expansion like for
+% \cs{regex_replace_once:nnNTF} because it is ok for the result to be
+% unbalanced since we insert it in the input stream rather than
+% storing it. When within a csname we don't do any such shenanigan
+% because \cs{cs:w} \ldots{} \cs{cs_end:} does all the expansion we
+% need.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_replacement_put:n #1
+ {
+ \if_case:w \l_@@_replacement_csnames_int
+ \tl_build_put_right:Nn \l_@@_build_tl
+ { \exp_not:N \@@_reinsert_item:n {#1} }
+ \else:
+ \tl_build_put_right:Nn \l_@@_build_tl {#1}
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_peek_replacement_token:n}
+% When hit with \cs{exp:w}, \cs{@@_peek_replacement_token:n}
+% \Arg{token} stops \cs{exp_end:} and does \cs{exp_after:wN}
+% \meta{token} \cs{exp:w} to continue expansion after it.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_replacement_token:n #1
+ { \exp_after:wN \exp_end: \exp_after:wN #1 \exp:w }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_peek_replacement_put_submatch_aux:n}
+% While analyzing the replacement we also have to insert submatches
+% found in the query. Since query items \cs{@@_input_item:n}
+% \Arg{tokens} expand correctly only when surrounded by \cs{exp:w}
+% \ldots{} \cs{exp_end:}, and since these expansion controls are not
+% there within csnames (because \cs{cs:w} \ldots{} \cs{cs_end:} make
+% them unnecessary in most cases), we have to put \cs{exp:w} and
+% \cs{exp_end:} by hand here.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_replacement_put_submatch_aux:n #1
+ {
+ \if_case:w \l_@@_replacement_csnames_int
+ \tl_build_put_right:Nn \l_@@_build_tl
+ { \@@_query_submatch:n { \int_eval:n { #1 + ##1 } } }
+ \else:
+ \tl_build_put_right:Nn \l_@@_build_tl
+ { \exp:w \@@_query_submatch:n { \int_eval:n { #1 + ##1 } } \exp_end: }
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_peek_replacement_var:N}
+% This is used for |\u| outside csnames. It makes sure to continue
+% expansion with \cs{exp:w} before expanding the variable~|#1| and
+% stopping the \cs{exp:w} that precedes.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_replacement_var:N #1
+ {
+ \exp_after:wN \exp_last_unbraced:NV
+ \exp_after:wN \exp_end:
+ \exp_after:wN #1
+ \exp:w
+ }
+% \end{macrocode}
+% \end{macro}
%
% \subsection{Messages}
%
@@ -6726,8 +7125,6 @@
%^^A NOT IMPLEMENTED
%^^A \p{xx} a character with the xx property
%^^A \P{xx} a character without the xx property
-%^^A [[:xxx:]] positive POSIX named set
-%^^A [[:^xxx:]] negative POSIX named set
%^^A (?=...) positive look ahead
%^^A (?!...) negative look ahead
%^^A (?<=...) positive look behind
diff --git a/macros/latex/contrib/l3kernel/l3seq.dtx b/macros/latex/contrib/l3kernel/l3seq.dtx
index 737a4c776a..28df8592d9 100644
--- a/macros/latex/contrib/l3kernel/l3seq.dtx
+++ b/macros/latex/contrib/l3kernel/l3seq.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3skip.dtx b/macros/latex/contrib/l3kernel/l3skip.dtx
index aeef5ae45c..6336081c2c 100644
--- a/macros/latex/contrib/l3kernel/l3skip.dtx
+++ b/macros/latex/contrib/l3kernel/l3skip.dtx
@@ -44,7 +44,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3sort.dtx b/macros/latex/contrib/l3kernel/l3sort.dtx
index 30d2b9737e..b47a9d73d6 100644
--- a/macros/latex/contrib/l3kernel/l3sort.dtx
+++ b/macros/latex/contrib/l3kernel/l3sort.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3str-convert.dtx b/macros/latex/contrib/l3kernel/l3str-convert.dtx
index 2acfdcf0aa..e3ebadbcdd 100644
--- a/macros/latex/contrib/l3kernel/l3str-convert.dtx
+++ b/macros/latex/contrib/l3kernel/l3str-convert.dtx
@@ -44,7 +44,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3str.dtx b/macros/latex/contrib/l3kernel/l3str.dtx
index bdde861655..02083ae9b5 100644
--- a/macros/latex/contrib/l3kernel/l3str.dtx
+++ b/macros/latex/contrib/l3kernel/l3str.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3styleguide.pdf b/macros/latex/contrib/l3kernel/l3styleguide.pdf
index 6969a153b8..05a31a39b5 100644
--- a/macros/latex/contrib/l3kernel/l3styleguide.pdf
+++ b/macros/latex/contrib/l3kernel/l3styleguide.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3styleguide.tex b/macros/latex/contrib/l3kernel/l3styleguide.tex
index c94f8239f8..74449f2f6d 100644
--- a/macros/latex/contrib/l3kernel/l3styleguide.tex
+++ b/macros/latex/contrib/l3kernel/l3styleguide.tex
@@ -32,7 +32,7 @@ The released version of this bundle is available from CTAN.
{latex-team@latex-project.org}%
}%
}
-\date{Released 2020-10-27}
+\date{Released 2020-12-03}
\begin{document}
diff --git a/macros/latex/contrib/l3kernel/l3syntax-changes.pdf b/macros/latex/contrib/l3kernel/l3syntax-changes.pdf
index f191cd2bdc..3ef526163b 100644
--- a/macros/latex/contrib/l3kernel/l3syntax-changes.pdf
+++ b/macros/latex/contrib/l3kernel/l3syntax-changes.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3syntax-changes.tex b/macros/latex/contrib/l3kernel/l3syntax-changes.tex
index d1ac214651..3d627d053c 100644
--- a/macros/latex/contrib/l3kernel/l3syntax-changes.tex
+++ b/macros/latex/contrib/l3kernel/l3syntax-changes.tex
@@ -32,7 +32,7 @@ The released version of this bundle is available from CTAN.
{latex-team@latex-project.org}%
}%
}
-\date{Released 2020-10-27}
+\date{Released 2020-12-03}
\newcommand{\TF}{\textit{(TF)}}
diff --git a/macros/latex/contrib/l3kernel/l3sys.dtx b/macros/latex/contrib/l3kernel/l3sys.dtx
index 4e5e3816c7..8ba6b2f2e6 100644
--- a/macros/latex/contrib/l3kernel/l3sys.dtx
+++ b/macros/latex/contrib/l3kernel/l3sys.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3term-glossary.pdf b/macros/latex/contrib/l3kernel/l3term-glossary.pdf
index ce59abacbe..3e89b677e0 100644
--- a/macros/latex/contrib/l3kernel/l3term-glossary.pdf
+++ b/macros/latex/contrib/l3kernel/l3term-glossary.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/l3term-glossary.tex b/macros/latex/contrib/l3kernel/l3term-glossary.tex
index 051c23c1bb..2a8aff656d 100644
--- a/macros/latex/contrib/l3kernel/l3term-glossary.tex
+++ b/macros/latex/contrib/l3kernel/l3term-glossary.tex
@@ -32,7 +32,7 @@ The released version of this bundle is available from CTAN.
{latex-team@latex-project.org}%
}%
}
-\date{Released 2020-10-27}
+\date{Released 2020-12-03}
\newcommand{\TF}{\textit{(TF)}}
diff --git a/macros/latex/contrib/l3kernel/l3text-case.dtx b/macros/latex/contrib/l3kernel/l3text-case.dtx
index fd41c6bdd3..b52d348867 100644
--- a/macros/latex/contrib/l3kernel/l3text-case.dtx
+++ b/macros/latex/contrib/l3kernel/l3text-case.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3text-purify.dtx b/macros/latex/contrib/l3kernel/l3text-purify.dtx
index 1bc377ed02..4a4abbf256 100644
--- a/macros/latex/contrib/l3kernel/l3text-purify.dtx
+++ b/macros/latex/contrib/l3kernel/l3text-purify.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
@@ -396,6 +396,8 @@
\tiny
}
{ \text_declare_purify_equivalent:Nn #1 { } }
+\exp_args:Nc \text_declare_purify_equivalent:Nn
+ { @protected@testopt } { \use_none:nnn }
% \end{macrocode}
% Environments have to be handled by pure expansion.
%
diff --git a/macros/latex/contrib/l3kernel/l3text.dtx b/macros/latex/contrib/l3kernel/l3text.dtx
index 893c43da59..9ab4ae6ef5 100644
--- a/macros/latex/contrib/l3kernel/l3text.dtx
+++ b/macros/latex/contrib/l3kernel/l3text.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
@@ -634,6 +634,8 @@
% \begin{macro}[EXP]{\@@_expand_protect:N}
% \begin{macro}[EXP]{\@@_expand_protect:nN}
% \begin{macro}[EXP]{\@@_expand_protect:Nw}
+% \begin{macro}[EXP]{\@@_expand_testopt:N}
+% \begin{macro}[EXP]{\@@_expand_testopt:NNn}
% \begin{macro}[EXP]{\@@_expand_replace:N}
% \begin{macro}[EXP]{\@@_expand_replace:n}
% \begin{macro}[EXP]{\@@_expand_cs_expand:N}
@@ -917,7 +919,8 @@
% \LaTeXe{}'s \cs{protect} makes life interesting. Where possible, we
% simply remove it and replace with the \enquote{parent} command; of course,
% the \cs{protect} might be explicit, in which case we need to leave it alone
-% if it's required.
+% if it's required. There is also the case of a straight \tn{@protected@testopt}
+% to cover.
% \begin{macrocode}
\cs_new:Npx \@@_expand_cs:N #1
{
@@ -927,7 +930,7 @@
\bool_lazy_and:nnTF
{ \cs_if_exist_p:N \fmtname }
{ \str_if_eq_p:Vn \fmtname { LaTeX2e } }
- { \exp_not:N \@@_expand_encoding:N #1 }
+ { \exp_not:N \@@_expand_testopt:N #1 }
{ \exp_not:N \@@_expand_replace:N #1 }
}
}
@@ -949,6 +952,17 @@
{ \@@_expand_store:n { \protect #1 } }
\@@_expand_loop:w
}
+\cs_new:Npn \@@_expand_testopt:N #1
+ {
+ \str_if_eq:nnTF {#1} { \@protected@testopt }
+ { \@@_expand_testopt:NNn }
+ { \@@_expand_encoding:N #1 }
+ }
+\cs_new:Npn \@@_expand_testopt:NNn #1#2#3
+ {
+ \@@_expand_store:n {#1}
+ \@@_expand_loop:w
+ }
% \end{macrocode}
% Deal with encoding-specific commands
% \begin{macrocode}
@@ -1036,6 +1050,8 @@
% \end{macro}
% \end{macro}
% \end{macro}
+% \end{macro}
+% \end{macro}
%
% \begin{macro}
% {
diff --git a/macros/latex/contrib/l3kernel/l3tl-analysis.dtx b/macros/latex/contrib/l3kernel/l3tl-analysis.dtx
index be4bd2406e..cf61a8f702 100644
--- a/macros/latex/contrib/l3kernel/l3tl-analysis.dtx
+++ b/macros/latex/contrib/l3kernel/l3tl-analysis.dtx
@@ -44,7 +44,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
@@ -52,10 +52,30 @@
%
% \section{\pkg{l3tl-analysis} documentation}
%
-% This module mostly provides internal functions for use in the
-% \pkg{l3regex} module. However, it provides as a side-effect a user
-% debugging function, very similar to the \cs{ShowTokens} macro from the
-% \pkg{ted} package.
+% This module provides functions that are particularly useful in the
+% \pkg{l3regex} module for mapping through a token list one \meta{token}
+% at a time (including begin-group/end-group tokens). For
+% \cs{tl_analysis_map_inline:Nn} or \cs{tl_analysis_map_inline:nn}, the
+% token list is given as an argument; the analogous function
+% \cs{peek_analysis_map_inline:n} documented in \pkg{l3token} finds
+% tokens in the input stream instead. In both cases the user provides
+% \meta{inline code} that receives three arguments for each
+% \meta{token}:
+% \begin{itemize}
+% \item \meta{tokens}, which both \texttt{o}-expand and
+% \texttt{x}-expand to the \meta{token}. The detailed form of
+% \meta{tokens} may change in later releases.
+% \item \meta{char code}, a decimal representation of the character
+% code of the \meta{token}, $-1$ if it is a control sequence.
+% \item \meta{catcode}, a capital hexadecimal digit which denotes the
+% category code of the \meta{token} (0:~control sequence,
+% 1:~begin-group, 2:~end-group, 3:~math shift, 4:~alignment tab,
+% 6:~parameter, 7:~superscript, 8:~subscript, A:~space, B:~letter,
+% C:~other, D:~active). This can be converted to an integer by
+% writing |"|\meta{catcode}.
+% \end{itemize}
+% In addition, there is a debugging function \cs{tl_analysis_show:n},
+% very similar to the \cs{ShowTokens} macro from the \pkg{ted} package.
%
% \begin{function}[added = 2018-04-09]{\tl_analysis_show:N, \tl_analysis_show:n}
% \begin{syntax}
@@ -74,23 +94,9 @@
% \end{syntax}
% Applies the \meta{inline function} to each individual \meta{token}
% in the \meta{token list}. The \meta{inline function} receives three
-% arguments:
-% \begin{itemize}
-% \item \meta{tokens}, which both \texttt{o}-expand and
-% \texttt{x}-expand to the \meta{token}. The detailed form of
-% \meta{token} may change in later releases.
-% \item \meta{char code}, a decimal representation of the character
-% code of the token, $-1$ if it is a control sequence (with
-% \meta{catcode} $0$).
-% \item \meta{catcode}, a capital hexadecimal digit which denotes
-% the category code of the \meta{token} (0: control sequence, 1:
-% begin-group, 2: end-group, 3: math shift, 4: alignment tab, 6:
-% parameter, 7: superscript, 8: subscript, A: space, B: letter,
-% C:other, D:active).
-% \end{itemize}
-% As all other mappings the mapping is done at the current group
-% level, \emph{i.e.}~any local assignments made by the \meta{inline
-% function} remain in effect after the loop.
+% arguments as explained above. As all other mappings the mapping is
+% done at the current group level, \emph{i.e.}~any local assignments
+% made by the \meta{inline function} remain in effect after the loop.
% \end{function}
%
% \end{documentation}
@@ -182,18 +188,54 @@
% \end{macrocode}
% \end{variable}
%
-% \begin{variable}{\l_@@_analysis_token}
-% \begin{variable}{\l_@@_analysis_char_token}
+% \begin{variable}
+% {\l_@@_analysis_token, \l_@@_analysis_char_token, \l_@@_analysis_next_token}
% The tokens in the token list are probed with the \TeX{} primitive
% \tn{futurelet}. We use \cs{l_@@_analysis_token} in that
% construction. In some cases, we convert the following token to a
% string before probing it: then the token variable used is
-% \cs{l_@@_analysis_char_token}.
+% \cs{l_@@_analysis_char_token}. When getting tokens from the input
+% stream we may need to look two tokens ahead, for which we use
+% \cs{l_@@_analysis_next_token}.
% \begin{macrocode}
\cs_new_eq:NN \l_@@_analysis_token ?
\cs_new_eq:NN \l_@@_analysis_char_token ?
+\cs_new_eq:NN \l_@@_analysis_next_token ?
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_peek_code_tl}
+% Holds some code to be run once the next token has been fully
+% analysed in \cs{peek_analysis_map_inline:n}.
+% \begin{macrocode}
+\tl_new:N \l_@@_peek_code_tl
% \end{macrocode}
% \end{variable}
+%
+% \begin{variable}{\c_@@_peek_catcodes_tl}
+% A token list containing the character number~$32$ (space) with all
+% possible category codes except $1$ and $2$ (begin-group and
+% end-group). Why $32$? Because some \LuaTeX{} versions only allow
+% creation of catcode~$10$ (space) tokens with this character code,
+% and because even in other engines it is much easier to produce since
+% \cs{char_generate:nn} refuses to produce spaces.
+% \begin{macrocode}
+\group_begin:
+\char_set_active_eq:NN \ \scan_stop:
+\tl_const:Nx \c_@@_peek_catcodes_tl
+ {
+ \char_generate:nn { 32 } { 3 } 3
+ \char_generate:nn { 32 } { 4 } 4
+ # \char_generate:nn { 32 } { 6 } 6
+ \char_generate:nn { 32 } { 7 } 7
+ \char_generate:nn { 32 } { 8 } 8
+ \c_space_tl \token_to_str:N A
+ \char_generate:nn { 32 } { 11 } \token_to_str:N B
+ \char_generate:nn { 32 } { 12 } \token_to_str:N C
+ \char_generate:nn { 32 } { 13 } \token_to_str:N D
+ }
+\group_end:
+% \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_analysis_normal_int}
@@ -1075,6 +1117,358 @@
% \end{macro}
% \end{macro}
%
+% \subsection{Peeking ahead}
+%
+% \begin{macro}[EXP]{\peek_analysis_map_break:, \peek_analysis_map_break:n}
+% The break statements use the general \cs{prg_map_break:Nn}.
+% \begin{macrocode}
+\cs_new:Npn \peek_analysis_map_break:
+ { \prg_map_break:Nn \peek_analysis_map_break: { } }
+\cs_new:Npn \peek_analysis_map_break:n
+ { \prg_map_break:Nn \peek_analysis_map_break: }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}{\l_@@_peek_charcode_int}
+% \begin{macrocode}
+\int_new:N \l_@@_peek_charcode_int
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_analysis_char_arg:Nw, \@@_analysis_char_arg_aux:Nw}
+% After a call to \tn{futurelet} \cs{l_@@_analysis_token} followed by
+% a stringified character token (either explicit space or catcode
+% other character), grab the argument and pass it to |#1|. We only
+% need to do anything in the case of a space.
+% \begin{macrocode}
+\cs_new:Npn \@@_analysis_char_arg:Nw
+ {
+ \if_meaning:w \l_@@_analysis_token \c_space_token
+ \exp_after:wN \@@_analysis_char_arg_aux:Nw
+ \fi:
+ }
+\cs_new:Npn \@@_analysis_char_arg_aux:Nw #1 ~ { #1 { ~ } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}
+% {
+% \peek_analysis_map_inline:n,
+% \@@_peek_analysis_loop:NNn, \@@_peek_analysis_test:,
+% \@@_peek_analysis_normal:N, \@@_peek_analysis_cs:,
+% \@@_peek_analysis_char:N, \@@_peek_analysis_char:nN,
+% \@@_peek_analysis_special:, \@@_peek_analysis_retest:,
+% \@@_peek_analysis_next:, \@@_peek_analysis_str:,
+% \@@_peek_analysis_str:w, \@@_peek_analysis_str:n,
+% \@@_peek_analysis_active_str:n, \@@_peek_analysis_explicit:n,
+% \@@_peek_analysis_escape:, \@@_peek_analysis_collect:w,
+% \@@_peek_analysis_collect:n, \@@_peek_analysis_collect_loop:,
+% \@@_peek_analysis_collect_test:, \@@_peek_analysis_collect_end:NNN
+% }
+% Save the user's code in a control sequence that is suitable for
+% nested maps. We may wish to pass to this function an \tn{outer}
+% control sequence or active character; for this we will undefine
+% potentially-\tn{outer} tokens within a group, closed after the
+% function receives its arguments. This user's code function also
+% calls the loop auxiliary, and includes the trailing
+% \cs{prg_break_point:Nn} for when the user wants to stop the loop.
+% The loop auxiliary must remove that break point because it must look
+% at the input stream.
+% \begin{macrocode}
+\cs_new_protected:Npn \peek_analysis_map_inline:n #1
+ {
+ \int_gincr:N \g__kernel_prg_map_int
+ \cs_set_protected:cpn
+ { @@_analysis_map_ \int_use:N \g__kernel_prg_map_int :nnN }
+ ##1##2##3
+ {
+ \group_end:
+ #1
+ \@@_peek_analysis_loop:NNn
+ \prg_break_point:Nn \peek_analysis_map_break: { }
+ }
+ \@@_peek_analysis_loop:NNn ? ? ?
+ }
+% \end{macrocode}
+% The loop starts a group (closed by the user-code function defined
+% above) with a normalized escape character, and checks if the next
+% token is special or \texttt{N}-type.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_analysis_loop:NNn #1#2#3
+ {
+ \group_begin:
+ \tl_set:Nx \l_@@_peek_code_tl
+ {
+ \exp_not:c
+ { @@_analysis_map_ \int_use:N \g__kernel_prg_map_int :nnN }
+ }
+ \int_set:Nn \tex_escapechar:D { `\\ }
+ \peek_after:Nw \@@_peek_analysis_test:
+ }
+\cs_new_protected:Npn \@@_peek_analysis_test:
+ {
+ \if_int_odd:w
+ \if_catcode:w \exp_not:N \l_peek_token { 1 \exp_stop_f: \fi:
+ \if_catcode:w \exp_not:N \l_peek_token } 1 \exp_stop_f: \fi:
+ \if_meaning:w \l_peek_token \c_space_token 1 \exp_stop_f: \fi:
+ 0 \exp_stop_f:
+ \exp_after:wN \@@_peek_analysis_special:
+ \else:
+ \exp_after:wN \exp_after:wN
+ \exp_after:wN \@@_peek_analysis_normal:N
+ \exp_after:wN \exp_not:N
+ \fi:
+ }
+% \end{macrocode}
+% Normal tokens are not too hard, but can be \tn{outer}, hence the
+% \cs{exp_not:N} in the code above. If the token is expandable then
+% it might be an \tn{outer} or a \TeX{} conditional, so to be safe we
+% set it to \cs{scan_stop:} (the assignment is local and stopped by
+% the \cs{group_end:} upon calling the user's code). Then distinguish
+% characters (including active ones and macro parameter characters)
+% from control sequences (whose string representation is more than one
+% character because the escape character is printable). For a control
+% sequence call the user code with suitable arguments.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_analysis_normal:N #1
+ {
+ \exp_after:wN \reverse_if:N \exp_after:wN \if_meaning:w
+ \exp_not:N #1 #1
+ \tex_let:D #1 \scan_stop:
+ \tl_put_right:Nn \l_@@_peek_code_tl { { \exp_not:N #1 } }
+ \else:
+ \tl_put_right:Nn \l_@@_peek_code_tl { { \exp_not:n {#1} } }
+ \fi:
+ \if_charcode:w
+ \scan_stop:
+ \exp_after:wN \use_none:n \token_to_str:N #1 \prg_do_nothing:
+ \scan_stop:
+ \exp_after:wN \@@_peek_analysis_char:N
+ \exp_after:wN #1
+ \else:
+ \exp_after:wN \@@_peek_analysis_cs:
+ \fi:
+ }
+\cs_new_protected:Npn \@@_peek_analysis_cs:
+ { \l_@@_peek_code_tl { -1 } 0 }
+\cs_new_protected:Npn \@@_peek_analysis_char:N #1
+ {
+ \char_set_lccode:nn { `#1 } { 32 }
+ \tex_lowercase:D { \@@_peek_analysis_char:nN {#1} } #1
+ }
+\cs_new_protected:Npn \@@_peek_analysis_char:nN #1#2
+ {
+ \cs_set_protected:Npn \@@_tmp:w ##1 #1 ##2 ##3 \scan_stop:
+ { \exp_args:No \l_@@_peek_code_tl { \int_value:w `#2 } ##2 }
+ \exp_after:wN \@@_tmp:w \c_@@_peek_catcodes_tl \scan_stop:
+ }
+% \end{macrocode}
+% For special characters the idea is to eventually act with
+% \cs{token_to_str:N}, then pick up one by one the characters of this
+% string representation until hitting the token that follows. First
+% determine the character code of (the meaning of) the \meta{token}
+% (which we know is a special token), make sure the escape character
+% is different from it, normalize the meanings of two active
+% characters and the empty control sequence, and filter out these
+% cases in \cs{@@_peek_analysis_retest:}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_analysis_special:
+ {
+ \tex_let:D \l_@@_analysis_token = ~ \l_peek_token
+ \int_set:Nn \l_@@_peek_charcode_int
+ { \@@_analysis_extract_charcode: }
+ \if_int_compare:w \l_@@_peek_charcode_int = \tex_escapechar:D
+ \int_set:Nn \tex_escapechar:D { `\/ }
+ \fi:
+ \char_set_active_eq:nN { \l_@@_peek_charcode_int } \scan_stop:
+ \char_set_active_eq:nN { \tex_escapechar:D } \scan_stop:
+ \cs_set_eq:cN { } \scan_stop:
+ \tex_futurelet:D \l_@@_analysis_token
+ \@@_peek_analysis_retest:
+ }
+\cs_new_protected:Npn \@@_peek_analysis_retest:
+ {
+ \if_meaning:w \l_@@_analysis_token \scan_stop:
+ \exp_after:wN \@@_peek_analysis_normal:N
+ \else:
+ \exp_after:wN \@@_peek_analysis_next:
+ \fi:
+ }
+% \end{macrocode}
+% At this point we know the meaning of the \meta{token} in the input
+% stream is \cs{l_peek_token}, either a space (32, 10) or a
+% begin-group or end-group token (catcode $1$ or~$2$), and we excluded
+% a few cases that would be difficult later (empty control sequence,
+% active character with the same character code as its meaning or as
+% the escape character). Now look at the \meta{next token} following
+% it using a combination of \tn{afterassignment} and \tn{futurelet}.
+% The syntax of this primitive is \tn{futurelet} \meta{peek token}
+% \meta{first token} \meta{next token}, and it sets \meta{peek token}
+% equal to \meta{next token}. Traditionally, one takes \meta{first
+% token} to be some macro that regains control of the code and, e.g.,
+% analyses \meta{peek token}. Here, both \meta{first token} and
+% \meta{next token} are mostly unknown tokens in the input stream (but
+% we know the \meta{first token} has catcode $1$, $2$ or $10$), where
+% \meta{first token} was already stored as \cs{l_peek_token}, and we
+% regain control using \tn{afterassignment}, which inserts its
+% argument after the assignment, hence after \meta{peek token} but
+% before \meta{first token}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_analysis_next:
+ {
+ \tl_if_empty:oT { \tex_the:D \tex_everyeof:D }
+ { \tex_everyeof:D { \scan_stop: } }
+ \tex_afterassignment:D \@@_peek_analysis_str:
+ \tex_futurelet:D \l_@@_analysis_next_token
+ }
+% \end{macrocode}
+% We then hit the \meta{first token} with \cs{token_to_str:N} and grab
+% characters until finding \cs{l_@@_analysis_next_token}. More
+% precisely, by looking at the first character in the string
+% representation of the \meta{first token} we distinguish three cases:
+% a stringified control sequence starts with the escape character; for
+% an explicit character we find that same character; for an explicit
+% character we find anything else (we made sure to exclude the case of
+% an active character whose string representation coincides with the
+% other two cases).
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_analysis_str:
+ {
+ \exp_after:wN \tex_futurelet:D
+ \exp_after:wN \l_@@_analysis_token
+ \exp_after:wN \@@_peek_analysis_str:w
+ \token_to_str:N
+ }
+\cs_new_protected:Npn \@@_peek_analysis_str:w
+ { \@@_analysis_char_arg:Nw \@@_peek_analysis_str:n }
+\cs_new_protected:Npn \@@_peek_analysis_str:n #1
+ {
+ \int_case:nnF { `#1 }
+ {
+ { \l_@@_peek_charcode_int }
+ { \@@_peek_analysis_explicit:n {#1} }
+ { \tex_escapechar:D } { \@@_peek_analysis_escape: }
+ }
+ { \@@_peek_analysis_active_str:n {#1} }
+ }
+% \end{macrocode}
+% When |#1| is a stringified active character we pass appropriate
+% arguments to the user's code; thankfully \cs{char_generate:nn}
+% can make active characters.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_analysis_active_str:n #1
+ {
+ \tl_put_right:Nx \l_@@_peek_code_tl
+ {
+ { \char_generate:nn { `#1 } { 13 } }
+ { \int_value:w `#1 }
+ \token_to_str:N D
+ }
+ \l_@@_peek_code_tl
+ }
+% \end{macrocode}
+% When |#1| matches the character we had extracted from the meaning of
+% \cs{l_peek_token}, the token was an explicit character, which can be
+% a standard space, or a begin-group or end-group character with some
+% character code. In the latter two cases we call
+% \cs{char_generate:nn} with suitable arguments and put suitable
+% \cs{if_false:} \cs{fi:} constructions to make the result balanced
+% and such that \texttt{o}-expanding or \texttt{x}-expanding gives
+% back a single (unbalanced) begin-group or end-group character.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_analysis_explicit:n #1
+ {
+ \tl_put_right:Nx \l_@@_peek_code_tl
+ {
+ \if_meaning:w \l_peek_token \c_space_token
+ { ~ } { 32 } \token_to_str:N A
+ \else:
+ \if_catcode:w \l_peek_token \c_group_begin_token
+ {
+ \exp_not:N \exp_after:wN
+ \char_generate:nn { `#1 } { 1 }
+ \exp_not:N \if_false:
+ \if_false: { \fi: }
+ \exp_not:N \fi:
+ }
+ { \int_value:w `#1 }
+ 1
+ \else:
+ {
+ \exp_not:N \if_false:
+ { \if_false: } \fi:
+ \exp_not:N \fi:
+ \char_generate:nn { `#1 } { 2 }
+ }
+ { \int_value:w `#1 }
+ 2
+ \fi:
+ \fi:
+ }
+ \l_@@_peek_code_tl
+ }
+% \end{macrocode}
+% Finally there is the case of a special token whose string
+% representation starts with an escape character, namely the token was
+% a control sequence. In that case we could have grabbed the token
+% directly as an \texttt{N}-type argument, but of course we couldn't
+% know that until we had run all the various tests including
+% stringifying the token. We are thus left with the hard work of
+% picking up one by one the characters in the csname (being careful
+% about spaces), until finding a token that matches the \meta{next
+% token} picked up earlier (which was not stringified), such that the
+% control sequence that we found so far indeed has the expected
+% meaning \cs{l_peek_token}. This comparison with \cs{l_peek_token}
+% catches a reasonably common case like \cs{c_group_begin_token} |_|
+% in which the trailing |_| has category code other: without
+% comparison of the constructed csname with \cs{l_peek_token}
+% collection would stop at \cs[no-index]{c}, which is wrong.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_analysis_escape:
+ {
+ \tl_clear:N \l_@@_internal_a_tl
+ \tex_futurelet:D \l_@@_analysis_token
+ \@@_peek_analysis_collect:w
+ }
+\cs_new_protected:Npn \@@_peek_analysis_collect:w
+ { \@@_analysis_char_arg:Nw \@@_peek_analysis_collect:n }
+\cs_new_protected:Npn \@@_peek_analysis_collect:n #1
+ {
+ \tl_put_right:Nn \l_@@_internal_a_tl {#1}
+ \@@_peek_analysis_collect_loop:
+ }
+\cs_new_protected:Npn \@@_peek_analysis_collect_loop:
+ {
+ \tex_futurelet:D \l_@@_analysis_token
+ \@@_peek_analysis_collect_test:
+ }
+\cs_new_protected:Npn \@@_peek_analysis_collect_test:
+ {
+ \if_meaning:w \l_@@_analysis_token \l_@@_analysis_next_token
+ \exp_after:wN \if_meaning:w \cs:w \l_@@_internal_a_tl \cs_end: \l_peek_token
+ \@@_peek_analysis_collect_end:NNN
+ \fi:
+ \fi:
+ \@@_peek_analysis_collect:w
+ }
+% \end{macrocode}
+% End by calling the user code with suitable arguments (here |#1|,
+% |#2| are \cs{fi:}), which closes the group begun early on.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_peek_analysis_collect_end:NNN #1#2#3
+ {
+ #1 #2
+ \tl_put_right:Nx \l_@@_peek_code_tl
+ {
+ { \exp_not:N \exp_not:n { \exp_not:c { \l_@@_internal_a_tl } } }
+ { -1 }
+ 0
+ }
+ \l_@@_peek_code_tl
+ }
+% \end{macrocode}
+% \end{macro}
+%
% \subsection{Messages}
%
% \begin{variable}{\c_@@_analysis_show_etc_str}
diff --git a/macros/latex/contrib/l3kernel/l3tl.dtx b/macros/latex/contrib/l3kernel/l3tl.dtx
index b942e97703..f88abe05c7 100644
--- a/macros/latex/contrib/l3kernel/l3tl.dtx
+++ b/macros/latex/contrib/l3kernel/l3tl.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/l3token.dtx b/macros/latex/contrib/l3kernel/l3token.dtx
index 676c55747a..6105f3aab0 100644
--- a/macros/latex/contrib/l3kernel/l3token.dtx
+++ b/macros/latex/contrib/l3kernel/l3token.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
@@ -754,14 +754,42 @@
% this includes primitive-like commands defined using |{token.set_lua}|.
% \end{function}
%
+% \begin{function}[added = 2020-12-03, EXP, noTF]
+% {\token_case_catcode:Nn, \token_case_charcode:Nn, \token_case_meaning:Nn}
+% \begin{syntax}
+% \cs{token_case_meaning:NnTF} \meta{test token} \\
+% ~~"{" \\
+% ~~~~\meta{token case_1} \Arg{code case_1} \\
+% ~~~~\meta{token case_2} \Arg{code case_2} \\
+% ~~~~\ldots \\
+% ~~~~\meta{token case_n} \Arg{code case_n} \\
+% ~~"}" \\
+% ~~\Arg{true code}
+% ~~\Arg{false code}
+% \end{syntax}
+% This function compares the \meta{test token} in turn with each of
+% the \meta{token cases}. If the two are equal (as described for
+% \cs{token_if_eq_catcode:NNTF}, \cs{token_if_eq_charcode:NNTF} and
+% \cs{token_if_eq_meaning:NNTF}, respectively) then the associated
+% \meta{code} is left in the input stream and other cases are
+% discarded. If any of the cases are matched, the \meta{true code} is
+% also inserted into the input stream (after the code for the
+% appropriate case), while if none match then the \meta{false code} is
+% inserted. The functions \cs{token_case_catcode:Nn},
+% \cs{token_case_charcode:Nn}, and \cs{token_case_meaning:Nn}, which
+% do nothing if there is no match, are also available.
+% \end{function}
+%
% \section{Peeking ahead at the next token}
%
% There is often a need to look ahead at the next token in the input
% stream while leaving it in place. This is handled using the
-% \enquote{peek} functions. The generic \cs{peek_after:Nw} is
-% provided along with a family of predefined tests for common cases.
-% As peeking ahead does \emph{not} skip spaces the predefined tests
-% include both a space-respecting and space-skipping version.
+% \enquote{peek} functions. The generic \cs{peek_after:Nw} is provided
+% along with a family of predefined tests for common cases. As peeking
+% ahead does \emph{not} skip spaces the predefined tests include both a
+% space-respecting and space-skipping version. In addition, using
+% \cs{peek_analysis_map_inline:n}, one can map through the following
+% tokens in the input stream and repeatedly perform some tests.
%
% \begin{function}{\peek_after:Nw}
% \begin{syntax}
@@ -980,6 +1008,128 @@
% (as appropriate to the result of the test).
% \end{function}
%
+% \begin{function}[added = 2020-12-03]{\peek_analysis_map_inline:n}
+% \begin{syntax}
+% \cs{peek_analysis_map_inline:n} \Arg{inline function}
+% \end{syntax}
+% Repeatedly removes one \meta{token} from the input stream and
+% applies the \meta{inline function} to it, until
+% \cs{peek_analysis_map_break:} is called. The \meta{inline function}
+% receives three arguments for each \meta{token} in the input stream:
+% \begin{itemize}
+% \item \meta{tokens}, which both \texttt{o}-expand and
+% \texttt{x}-expand to the \meta{token}. The detailed form of
+% \meta{tokens} may change in later releases.
+% \item \meta{char code}, a decimal representation of the character
+% code of the \meta{token}, $-1$ if it is a control sequence.
+% \item \meta{catcode}, a capital hexadecimal digit which denotes the
+% category code of the \meta{token} (0:~control sequence,
+% 1:~begin-group, 2:~end-group, 3:~math shift, 4:~alignment tab,
+% 6:~parameter, 7:~superscript, 8:~subscript, A:~space, B:~letter,
+% C:~other, D:~active). This can be converted to an integer by
+% writing |"|\meta{catcode}.
+% \end{itemize}
+% These arguments are the same as for \cs{tl_analysis_map_inline:nn}
+% defined in \pkg{l3tl-analysis}. The \meta{char code} and
+% \meta{catcode} do not take the meaning of a control sequence or
+% active character into account: for instance, upon encountering the
+% token \cs{c_group_begin_token} in the input stream,
+% \cs{peek_analysis_map_inline:n} calls the \meta{inline function}
+% with |#1| being \cs{exp_not:n} |{| \cs{c_group_begin_token} |}|
+% (with the current implementation),
+% |#2|~being~$-1$, and
+% |#3|~being~$0$, as for any other control sequence. In contrast,
+% upon encountering an explicit begin-group token~|{|, % ^^A |}|
+% the \meta{inline function} is called with arguments
+% \cs{exp_after:wN} |{| \cs{if_false:} |}| \cs{fi:}, $123$ and~$1$.
+%
+% The mapping is done at the current group level, \emph{i.e.}~any
+% local assignments made by the \meta{inline function} remain in
+% effect after the loop. Within the code, \cs{l_peek_token} is set
+% equal (as a token, not a token list) to the token under
+% consideration.
+% \end{function}
+%
+% \begin{function}[added = 2020-12-03]
+% {\peek_analysis_map_break:, \peek_analysis_map_break:n}
+% \begin{syntax}
+% \cs{peek_analysis_map_inline:n}
+% |{| \dots{} \cs{peek_analysis_map_break:n} \Arg{code} |}|
+% \end{syntax}
+% Stops the \cs{peek_analysis_map_inline:n} loop from seeking more
+% tokens, and inserts \meta{code} in the input stream (empty for
+% \cs{peek_analysis_map_break:}).
+% \end{function}
+%
+% \begin{function}[added = 2020-12-03, TF]{\peek_regex:n, \peek_regex:N}
+% \begin{syntax}
+% \cs{peek_regex:nTF} \Arg{regex} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% Tests if the \meta{tokens} that follow in the input stream match the
+% \meta{regular expression}. Any \meta{tokens} that have been read
+% are left in the input stream after the \meta{true code} or
+% \meta{false code} (as appropriate to the result of the test). See
+% \pkg{l3regex} for documentation of the syntax of regular
+% expressions. The \meta{regular expression} is implicitly anchored
+% at the start, so for instance \cs{peek_regex:nTF}~|{|~|a|~|}| is
+% essentially equivalent to \cs{peek_charcode:NTF}~|a|.
+% \begin{texnote}
+% Implicit character tokens are correctly considered by
+% \cs{peek_regex:nTF} as control sequences, while functions that
+% inspect individual tokens (for instance \cs{peek_charcode:NTF})
+% only take into account their meaning.
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}[added = 2020-12-03, TF]
+% {\peek_regex_remove_once:n, \peek_regex_remove_once:N}
+% \begin{syntax}
+% \cs{peek_regex_remove_once:nTF} \Arg{regex} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% Tests if the \meta{tokens} that follow in the input stream match the
+% \meta{regex}. If the test is true, the \meta{tokens} are removed
+% from the input stream and the \meta{true code} is inserted, while if
+% the test is false, the \meta{false code} is inserted followed by the
+% \meta{tokens} that were originally in the input stream.
+% See \pkg{l3regex} for documentation of the syntax of
+% regular expressions. The \meta{regular expression} is implicitly
+% anchored at the start, so for instance
+% \cs{peek_regex_remove_once:nTF}~|{|~|a|~|}| is essentially equivalent to
+% \cs{peek_charcode_remove:NTF}~|a|.
+% \begin{texnote}
+% Implicit character tokens are correctly considered by
+% \cs{peek_regex_remove_once:nTF} as control sequences, while functions
+% that inspect individual tokens (for instance
+% \cs{peek_charcode:NTF}) only take into account their meaning.
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}[added = 2020-12-03, noTF]
+% {\peek_regex_replace_once:nn, \peek_regex_replace_once:Nn}
+% \begin{syntax}
+% \cs{peek_regex_replace_once:nnTF} \Arg{regex} \Arg{replacement} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% If the \meta{tokens} that follow in the input stream match the
+% \meta{regex}, replaces them according to the \meta{replacement} as
+% for \cs{regex_replace_once:nnN}, and leaves the result in the input
+% stream, after the \meta{true code}. Otherwise, leaves \meta{false
+% code} followed by the \meta{tokens} that were originally in the
+% input stream, with no modifications. See \pkg{l3regex} for
+% documentation of the syntax of regular expressions and of the
+% \meta{replacement}: for instance |\0| in the \meta{replacement} is
+% replaced by the tokens that were matched in the input stream. The
+% \meta{regular expression} is implicitly anchored at the start. In
+% contrast to \cs{regex_replace_once:nnN}, no error arises if the
+% \meta{replacement} leads to an unbalanced token list: the tokens are
+% inserted into the input stream without issue.
+% \begin{texnote}
+% Implicit character tokens are correctly considered by
+% \cs{peek_regex_replace_once:nnTF} as control sequences, while
+% functions that inspect individual tokens (for instance
+% \cs{peek_charcode:NTF}) only take into account their meaning.
+% \end{texnote}
+% \end{function}
+%
% \section{Description of all possible tokens}
% \label{sec:l3token:all-tokens}
%
@@ -1897,9 +2047,10 @@
%<@@=token>
% \end{macrocode}
%
-% \begin{variable}{\s_@@_stop}
+% \begin{variable}{\s_@@_mark, \s_@@_stop}
% Internal scan marks.
% \begin{macrocode}
+\scan_new:N \s_@@_mark
\scan_new:N \s_@@_stop
% \end{macrocode}
% \end{variable}
@@ -2566,6 +2717,72 @@ end
% \end{macro}
% \end{macro}
%
+% \begin{macro}[EXP, noTF]
+% {\token_case_catcode:Nn, \token_case_charcode:Nn, \token_case_meaning:Nn}
+% \begin{macro}[EXP]{\@@_case:NNnTF, \@@_case:NNw, \@@_case_end:nw}
+% The aim here is to allow the case statement to be evaluated
+% using a known number of expansion steps (two), and without
+% needing to use an explicit \enquote{end of recursion} marker.
+% That is achieved by using the test input as the final case,
+% as this is always true. The trick is then to tidy up
+% the output such that the appropriate case code plus either
+% the \texttt{true} or \texttt{false} branch code is inserted.
+% \begin{macrocode}
+\cs_new:Npn \token_case_catcode:Nn #1#2
+ { \exp:w \@@_case:NNnTF \token_if_eq_catcode:NNTF #1 {#2} { } { } }
+\cs_new:Npn \token_case_catcode:NnT #1#2#3
+ { \exp:w \@@_case:NNnTF \token_if_eq_catcode:NNTF #1 {#2} {#3} { } }
+\cs_new:Npn \token_case_catcode:NnF #1#2
+ { \exp:w \@@_case:NNnTF \token_if_eq_catcode:NNTF #1 {#2} { } }
+\cs_new:Npn \token_case_catcode:NnTF
+ { \exp:w \@@_case:NNnTF \token_if_eq_catcode:NNTF }
+\cs_new:Npn \token_case_charcode:Nn #1#2
+ { \exp:w \@@_case:NNnTF \token_if_eq_charcode:NNTF #1 {#2} { } { } }
+\cs_new:Npn \token_case_charcode:NnT #1#2#3
+ { \exp:w \@@_case:NNnTF \token_if_eq_charcode:NNTF #1 {#2} {#3} { } }
+\cs_new:Npn \token_case_charcode:NnF #1#2
+ { \exp:w \@@_case:NNnTF \token_if_eq_charcode:NNTF #1 {#2} { } }
+\cs_new:Npn \token_case_charcode:NnTF
+ { \exp:w \@@_case:NNnTF \token_if_eq_charcode:NNTF }
+\cs_new:Npn \token_case_meaning:Nn #1#2
+ { \exp:w \@@_case:NNnTF \token_if_eq_meaning:NNTF #1 {#2} { } { } }
+\cs_new:Npn \token_case_meaning:NnT #1#2#3
+ { \exp:w \@@_case:NNnTF \token_if_eq_meaning:NNTF #1 {#2} {#3} { } }
+\cs_new:Npn \token_case_meaning:NnF #1#2
+ { \exp:w \@@_case:NNnTF \token_if_eq_meaning:NNTF #1 {#2} { } }
+\cs_new:Npn \token_case_meaning:NnTF
+ { \exp:w \@@_case:NNnTF \token_if_eq_meaning:NNTF }
+\cs_new:Npn \@@_case:NNnTF #1#2#3#4#5
+ {
+ \@@_case:NNw #1 #2 #3 #2 { }
+ \s_@@_mark {#4}
+ \s_@@_mark {#5}
+ \s_@@_stop
+ }
+\cs_new:Npn \@@_case:NNw #1#2#3#4
+ {
+ #1 #2 #3
+ { \@@_case_end:nw {#4} }
+ { \@@_case:NNw #1 #2 }
+ }
+% \end{macrocode}
+% To tidy up the recursion, there are two outcomes. If there was a hit to
+% one of the cases searched for, then |#1| is the code to insert,
+% |#2| is the \emph{next} case to check on and |#3| is all of
+% the rest of the cases code. That means that |#4| is the \texttt{true}
+% branch code, and |#5| tidies up the spare \cs{s_@@_mark} and the
+% \texttt{false} branch. On the other hand, if none of the cases matched
+% then we arrive here using the \enquote{termination} case of comparing
+% the search with itself. That means that |#1| is empty, |#2| is
+% the first \cs{s_@@_mark} and so |#4| is the \texttt{false} code (the
+% \texttt{true} code is mopped up by |#3|).
+% \begin{macrocode}
+\cs_new:Npn \@@_case_end:nw #1#2#3 \s_@@_mark #4#5 \s_@@_stop
+ { \exp_end: #1 #4 }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
% \subsection{Peeking ahead at the next token}
%
% \begin{macrocode}
diff --git a/macros/latex/contrib/l3kernel/l3unicode.dtx b/macros/latex/contrib/l3kernel/l3unicode.dtx
index 14467d9617..c69fa555ab 100644
--- a/macros/latex/contrib/l3kernel/l3unicode.dtx
+++ b/macros/latex/contrib/l3kernel/l3unicode.dtx
@@ -43,7 +43,7 @@
% }^^A
% }
%
-% \date{Released 2020-10-27}
+% \date{Released 2020-12-03}
%
% \maketitle
%
diff --git a/macros/latex/contrib/l3kernel/source3.pdf b/macros/latex/contrib/l3kernel/source3.pdf
index 43048251a4..400ff72f7b 100644
--- a/macros/latex/contrib/l3kernel/source3.pdf
+++ b/macros/latex/contrib/l3kernel/source3.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3kernel/source3.tex b/macros/latex/contrib/l3kernel/source3.tex
index f47fbf0233..07617c24cb 100644
--- a/macros/latex/contrib/l3kernel/source3.tex
+++ b/macros/latex/contrib/l3kernel/source3.tex
@@ -53,7 +53,7 @@ for those people who are interested.
{latex-team@latex-project.org}%
}%
}
-\date{Released 2020-10-27}
+\date{Released 2020-12-03}
\pagenumbering{roman}
\maketitle
diff --git a/macros/latex/contrib/l3kernel/source3body.tex b/macros/latex/contrib/l3kernel/source3body.tex
index 5586d73799..483054416e 100644
--- a/macros/latex/contrib/l3kernel/source3body.tex
+++ b/macros/latex/contrib/l3kernel/source3body.tex
@@ -192,10 +192,22 @@ following argument specifiers:
\emph{weird} arguments. This covers everything else, but mainly
applies to delimited values (where the argument must be terminated
by some specified string).
- \item[\texttt{D}] The \texttt{D} specifier means \emph{do not use}.
+ \item[\texttt{D}] The \texttt{D} stands for \textbf{Do not use}.
All of the \TeX{} primitives are initially \cs{let} to a \texttt{D}
- name, and some are then given a second name. Only the kernel
- team should use anything with a \texttt{D} specifier!
+ name, and some are then given a second name.
+ These functions have no standardized syntax, they are engine
+ dependent and their name can change without warning, thus their
+ use is \emph{strongly discouraged} in package code: programmers
+ should instead use the interfaces documented in
+ \href{interface3.pdf}{interface3.pdf}%^^A
+ \footnote{If a primitive offers a functionality not yet in the
+ kernel, programmers and users are encouraged to write to the
+ \texttt{LaTeX-L} mailing list
+ (\url{mailto:LATEX-L@listserv.uni-heidelberg.de}) describing
+ their use-case and intended behaviour, so that a possible
+ interface can be discussed. Temporarily, while an interface is
+ not provided, programmers may use the procedure described in the
+ \href{l3styleguide.pdf}{l3styleguide.pdf}.}.
\end{description}
Notice that the argument specifier describes how the argument is
processed prior to being passed to the underlying function. For example,
diff --git a/macros/latex/contrib/pbalance/LICENSE b/macros/latex/contrib/pbalance/LICENSE
new file mode 100644
index 0000000000..dea471c70c
--- /dev/null
+++ b/macros/latex/contrib/pbalance/LICENSE
@@ -0,0 +1,11 @@
+Copyright 2020 Nelson Lago <lago@ime.usp.br>
+
+This work may be distributed and/or modified under the conditions of the
+LaTeX Project Public License, either version 1.3 of this license or (at
+your option) any later version. The latest version of this license can be
+found at http://www.latex-project.org/lppl.txt and version 1.3 or later
+is part of all distributions of LaTeX version 2005/12/01 or later.
+
+This work has the LPPL maintenance status `maintained'.
+
+The Current Maintainer of this work is Nelson Lago <lago@ime.usp.br>.
diff --git a/macros/latex/contrib/pbalance/README.md b/macros/latex/contrib/pbalance/README.md
new file mode 100644
index 0000000000..6ac43ea0c8
--- /dev/null
+++ b/macros/latex/contrib/pbalance/README.md
@@ -0,0 +1,14 @@
+# pbalance - poor man's balance package for LaTeX
+
+This is a LaTeX package that tries to *safely* make the columns on the last
+page of a two-column document have approximately the same height. It should
+"just work" without user intervention, which is particularly useful for
+class authors, but also offers the user a command to control the result.
+There are, however, three caveats:
+
+1. Results are adequate, but often not optimal;
+2. In some cases, the package may give up (the document is
+ generated correctly but the last page is not balanced);
+3. The package demands additional LaTeX passes.
+
+Code etc: <https://gitlab.com/lago/pbalance>
diff --git a/macros/latex/contrib/pbalance/pbalance.dtx b/macros/latex/contrib/pbalance/pbalance.dtx
new file mode 100644
index 0000000000..e63bee62a9
--- /dev/null
+++ b/macros/latex/contrib/pbalance/pbalance.dtx
@@ -0,0 +1,1069 @@
+% \iffalse meta-comment
+% Copyright 2020 Nelson Lago <lago@ime.usp.br>
+%
+% This work may be distributed and/or modified under the conditions of the
+% LaTeX Project Public License, either version 1.3 of this license or (at
+% your option) any later version. The latest version of this license can be
+% found at http://www.latex-project.org/lppl.txt and version 1.3 or later
+% is part of all distributions of LaTeX version 2005/12/01 or later.
+%
+% This work has the LPPL maintenance status `maintained'.
+%
+% The Current Maintainer of this work is Nelson Lago <lago@ime.usp.br>.
+%
+% \fi
+% \iffalse
+%
+%<package>\NeedsTeXFormat{LaTeX2e}[2015/01/01]
+%<package>\ProvidesPackage{pbalance}[2020/12/03 v1.0 Poor man's balance]
+%
+%<*driver>
+
+\documentclass{ltxdoc}
+
+\usepackage[hyperref,svgnames,x11names,table]{xcolor}
+\usepackage{url}
+\urlstyle{sf}
+\usepackage{hyperref}
+\hypersetup{
+ pdfborder={0 0 .6},
+ pdfborderstyle={/S/U/W .6},
+ urlbordercolor=DodgerBlue,
+ citebordercolor=White,
+ linkbordercolor=White,
+ filebordercolor=White,
+}
+
+\usepackage[draft]{pbalance}
+\makeatletter
+\dimgdef\@PBtmpHeight{0pt}
+\dimgdef\@PBtmpLeftHeight{0pt}
+\dimgdef\@PBtmpLeftFloatsHeight{0pt}
+\dimgdef\@PBtmpRightHeight{0pt}
+\dimgdef\@PBtmpRightFloatsHeight{0pt}
+\dimgdef\@PBtmpUsedLeft{0pt}
+\dimgdef\@PBtmpUsedRight{0pt}
+\makeatother
+
+\usepackage{libertinus}
+\usepackage[scale=.85]{sourcecodepro}
+
+%%\EnableCrossrefs
+%%\CodelineIndex
+%%\RecordChanges
+
+%%\OnlyDescription
+
+\begin{document}
+\DocInput{pbalance.dtx}
+\end{document}
+
+%</driver>
+%
+% \fi
+%
+% \CheckSum{0}
+%
+% \changes{v1.0}{2020/09/14}{Initial version}
+%
+% \GetFileInfo{pbalance.sty}
+%
+% \title{The \textsf{pbalance} (poor man's balance) package\thanks{This
+% document corresponds to \textsf{pbalance}~\fileversion, dated~\filedate.}}
+%
+% \author{
+% Nelson Lago\\
+% \texttt{lago@ime.usp.br}\\
+% ~\\
+% \url{https://gitlab.com/lago/pbalance}
+%}
+%
+% \maketitle
+%
+% \begin{abstract}
+%
+% This package tries to \emph{safely} make the columns on the last page
+% of a two-column document have approximately the same height. It should
+% ``just work'' without user intervention, which is particularly useful
+% for class authors, but also offers the user a command to control the
+% result. There are, however, three caveats:
+%
+% \begin{enumerate}
+% \item Results are adequate, but often not optimal;
+% \item In some cases, the package may give up (the document is
+% generated correctly but the last page is not balanced);
+% \item The package demands additional \LaTeX{} passes.
+% \end{enumerate}
+%
+% \end{abstract}
+%
+% \section{Introduction and usage}
+%
+% First things first: this package is a \textbf{hack}. It is also
+% \textbf{beta} quality. However, I believe it is \emph{safe},
+% i.e., it should not generate incorrect output.
+%
+% In a two-column document, it is desirable for the columns in the last
+% page to have the same height (the columns should be ``balanced''). Two
+% packages (\texttt{balance} and \texttt{flushend}) try to provide this
+% in \LaTeX, but both may generate defective output in some circumstances.
+% This package tries to solve this problem.
+%
+% Balancing is often not possible: imagine a document with an odd number
+% of lines on the last page, or with ten lines of text together with an
+% image that takes the height of fifteen lines of text, or a document where
+% making columns the same height would leave a sectioning comand at the
+% bottom of the first column etc. Since such cases happen often, we consider
+% it enough for the columns to be ``not too unequal'', even when equal
+% heights might be possible (the algorithm cannot deliver better results
+% and, in fact, striving to make columns exactly the same height with it
+% may sometimes yield poor results, so we do not even try).
+%
+% To use the package, add \verb|\usepackage{pbalance}| to the preamble
+% and things should ``just work''. However, the document will take more
+% \LaTeX{} passes to compile, and minute changes to it that would normally
+% need one additional \LaTeX{} pass will likely need three.
+%
+% If the last page does not have floats, footnotes, or marginpars, the
+% package simply uses \verb|\balance|, from the \texttt{balance} package;
+% otherwise it uses its own algorithm. You may instead use
+% \verb|\shrinkLastPage{some-measurement}| to manually define how much
+% shorter the first column of the last page should be. This, however, will
+% not eliminate the need for extra passes (the package needs to find out
+% which page is the last).
+%
+% \section{How does it work}
+%
+% This package works on two fronts:
+%
+% \begin{enumerate}
+%
+% \item It uses a \LaTeX{} pass to gather information: which is the last page?
+% Does it have floats? Footnotes? How much free space in each column?
+%
+% \item It uses this information in a subsequent pass to balance the last
+% page. If there are no floats or footnotes, it uses the balance
+% package; otherwise it uses the measurements collected to shrink
+% the first column (using \verb|\enlargethispage|) by a
+% ``reasonable'' amount.
+%
+% \end{enumerate}
+%
+% If measurements change between passes, this means the document was
+% edited, so we forget the previously gathered data and start over.
+% However, the package cannot detect changes that happen between the
+% ``collect data'' pass and the ``balance for the first time'' pass:
+% it has to assume that any change at this point is caused by the fact
+% that we started balancing (it can, however, detect if the last text
+% page changed; if this happens, we give up balancing). If the change is
+% small, that is acceptable: we are not aiming at balancing perfectly
+% anyway. If it is a big change (for example, the addition of the
+% bibliography block), results will suffer.
+%
+% To prevent this, it would be ideal to only start the process after all
+% passes needed to stabilize the document have run. In practice, however,
+% this would be too hard to detect. What we do instead is simply wait for
+% two consecutive passes to result in columns of the same size in the
+% last page. When this happens, we proceed to balance in the next pass
+% and assume (quite reasonably) that, as long as the document is not
+% modified, the effect of further passes in the last page is small, so
+% ignoring it disturbs the balancing only slightly\footnote{Like I said
+% on the introduction: this package is a hack.}.
+%
+% \section{TODO / wish list}
+%
+% Some useful stuff we should consider implementing:
+%
+% \begin{itemize}
+%
+% \item add \verb|\if@twocolumn| tests to prevent crashes and
+% other problems;
+%
+% \item add options to (1) allow using the balance package on pages with
+% floats and (2) completely disable the balance package;
+%
+% \item make the mechanism able to work with any page, not just the last,
+% and with more than one page at once. This is useful for book
+% chapters, for example;
+%
+% \item on top of that:
+% \begin{enumerate}
+% \item create command to manually indicate the page numbers to balance
+% \item create command similar to \verb|\balance| from the
+% \texttt{balance} package
+% \item allow user to set manual balancing for each page independently
+% \end{enumerate}
+%
+% \end{itemize}
+%
+% \StopEventually{}
+%
+% \section{The implementation}
+%
+% We define \verb|\@PBlastPage| using \texttt{zref-abspage}; It is the page
+% number for the last page of text. This might \emph{not} be the last page
+% of the document: there may be one or more float pages after it. Still,
+% it \emph{is} the page we want to balance. We do not act on it, however;
+% instead, we save its value to the aux file as \verb|\@PBprevLastPage| and
+% proceed to balance on the next \LaTeX{} pass.
+%
+% We also define two toggles:
+%
+% \begin{description}
+% \item[\texttt{@PBstabilized}] There have been a few passes
+% already and the document has apparently stabilized.
+% \item[\texttt{@PBimpossible}] We tried to balance on a
+% previous pass and failed.
+% \end{description}
+%
+% \subsection{Initialization, required packages etc.}
+%
+% Besides some other required packages, here we load the \texttt{balance}
+% package; if there are no floats or footnotes involved, we will simply use
+% it. We need to load it after we modify \verb|\@outputdblcol|, so we use
+% \verb|\AtEndOfPackage|.
+%
+% We also define the \texttt{draft} option, which disables processing.
+%
+% \begin{macrocode}
+
+\RequirePackage{etoolbox}
+\RequirePackage{expl3}
+\RequirePackage{atbegshi} % Manipulate the page output routine
+\RequirePackage{atveryend} % Write to the aux file after processing ends
+\RequirePackage{zref-abspage} % Figure out the current page
+
+\AtEndOfPackage{\RequirePackage{balance}}
+
+% See https://github.com/latex3/latex2e/issues/399#issuecomment-703081793
+\gdef \@reinserts{%
+ \ifvbox\@kludgeins\insert\@kludgeins
+ {\unvbox\@kludgeins}\fi
+ \ifvoid\footins\else\insert\footins{\unvbox\footins}\fi
+}
+
+\providetoggle{@PBstabilized}
+\providetoggle{@PBimpossible}
+
+\newtoggle{@PBdraft}
+\DeclareOption{draft}{\toggletrue{@PBdraft}}
+\ProcessOptions\relax
+
+% \end{macrocode}
+
+% \subsection{The balancing front}
+%
+% The basic balancing process is reasonably simple: right before each
+% new page, check whether it should be balanced. If so, add the adequate
+% code to the top of the page (either call \verb|\balance|, from the
+% \texttt{balance} package, or add \verb|\enlargethispage|).
+%
+% \begin{macrocode}
+
+% Right before the first page
+\AtBeginDocument{
+ \@PBifShouldBalanceNascentPage
+ {\@PBStartBalancing}
+ {}
+}
+
+% Right before all other pages
+\AtBeginShipout{
+ \@PBifShouldBalanceNascentPage
+ {\@PBStartBalancing}
+ {}
+}
+
+\newcommand\@PBifShouldBalanceNascentPage[2]{
+ \ifboolexpr
+ {
+ togl {@PBdraft}
+ or not togl {@PBstabilized}
+ or test {\ifdefvoid{\@PBprevLastPage}}
+ }
+ {#2}
+ {
+ % abspage refers to the finished page, not the nascent page
+ \ifnumcomp{\@PBprevLastPage - 1}{=}{\value{abspage}}
+ {#1}
+ {#2}
+ }
+}
+
+% Let's give the user a chance to manually define what to do
+\newcommand\shrinkLastPage[1]{\dimgdef\@PBslack{#1}}
+
+\newcommand\@PBStartBalancing{
+ \ifdefvoid{\@PBslack}
+ {\@PBautomaticBalance}
+ {\@PBshrinkPage}
+}
+
+\newcommand\@PBautomaticBalance{
+ \@PBifBalancePossible
+ {
+ \@PBifBalancePkgPossible
+ {\balance} % easy peasy, use the balance package
+ {
+ \@PBifPoorBalancePossible
+ {
+ % use the poor man's balance algorithm
+ \@PBcalculateShrinkage
+ \@PBshrinkPage
+ }
+ {}
+ }
+ }
+ {}
+}
+
+% Calculate how much we should shrink the left column of the last
+% page based on the measurements we took on previous LaTeX passes.
+\newcommand\@PBcalculateShrinkage{
+
+ % Let's use some shorter names, please
+ \dimdef\@PBtmpH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}Height}}
+ \dimdef\@PBtmpUL{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}UsedLeft}}
+ \dimdef\@PBtmpUR{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}UsedRight}}
+
+ \dimdef\@PBtmpLH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}LeftHeight}}
+ \dimdef\@PBtmpLFH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}LeftFloatsHeight}}
+ \dimdef\@PBtmpRH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}RightHeight}}
+ \dimdef\@PBtmpRFH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}RightFloatsHeight}}
+
+ % Figure out the amount of unused space on the last page (both
+ % cols); we will reduce the first column height by half that amount
+ \dimgdef\@PBslack{2\dimexpr\@PBtmpH\relax - \@PBtmpUL - \@PBtmpUR}
+ \dimgdef\@PBslack{.5\dimexpr\@PBslack\relax}
+
+ % Actually, I lied. While half, as said above, sometimes yields
+ % perfectly balanced columns, that is often not the case (the page
+ % may have an odd number of lines, or textheight may not be a
+ % multiple of baselineskip, or there may be glues etc.). Sometimes
+ % the difference is very small, less than a line, which does not
+ % look good at all, and sometimes the right column is slightly
+ % higher than the left, which is also not ideal. Here, we force
+ % the left column to always be somewhat taller than the left; the
+ % result is often ``better''.
+ \dimgdef\@PBslack{\@PBslack - 1.5\baselineskip}
+
+ % If necessary, reduce @PBslack to prevent LaTeX from adding a page
+ \@PBsafetyCheck
+}
+
+\def\@PBshrinkPage{
+ % Zero obviously means ``do nothing''. We could
+ % ``enlarge'' by 0, but IDK, maybe that would
+ % trigger another LaTeX pass or something.
+ \ifdimcomp{\@PBslack}{=}{0pt}
+ {}
+ {
+ % Shrink the first column
+ \enlargethispage{-\@PBslack}
+
+ % Modify the behavior of \@outputdblcol
+ % to raise footnotes in the second column.
+ \global\let\@PBbalanceSecondColumn\@PBrealBalanceSecondColumn
+ }
+}
+
+% If there are footnotes on the right column, it is better to
+% ``push'' them up to be aligned with the end of the left column,
+% especially if there are footnotes on the left column too. We
+% do this by manipulating \@textbottom in \@outputdblcol.
+\pretocmd{\@outputdblcol}{\@PBbalanceSecondColumn}{}{}
+
+\let\@PBbalanceSecondColumn\relax
+
+\def\@PBrealBalanceSecondColumn{
+ \if@firstcolumn
+ % Affects the next column
+ \global\let\@PBorigtextbottom\@textbottom
+ \gdef\@textbottom{\vskip \@PBslack plus .0001fil minus 10000fill\relax \@PBorigtextbottom}
+ \else
+ % Back to normal before starting the next page
+ \global\let\@textbottom\@PBorigtextbottom
+ \global\let\@PBbalanceSecondColumn\relax
+ \fi
+}
+
+% \end{macrocode}
+
+% \subsection{The measuring front}
+% For the mechanism above to work, we need to know:
+%
+% \begin{enumerate}
+% \item The page number of the last page;
+% \item Whether the last page has floats, footnotes etc.;
+% \item The available height in the last page;
+% \item The height of the columns in the last page.
+% \end{enumerate}
+%
+% Let's collect this info for every page.
+%
+% \subsubsection{Are there floats, footnotes, or marginpars in the last page?}
+%
+% \begin{macrocode}
+
+% To check whether there are floats etc. in the last page,
+% we will copy the fancyhdr package:
+% https://tex.stackexchange.com/questions/56673/is-there-a-way-to-determine-if-there-is-a-float-on-a-page
+% But we add the tests to a different place.
+\newcommand\@PBifmidfloat[2]{\ifx\@midlist\empty #2\else #1\fi}
+\newcommand\@PBiftopfloat[2]{\ifx\@toplist\empty #2\else #1\fi}
+\newcommand\@PBifbotfloat[2]{\ifx\@botlist\empty #2\else #1\fi}
+\newcommand\@PBiffootnote[2]{\ifvoid\footins #2\else #1\fi}
+\newcommand\@PBiffloatcol[2]{\if@fcolmade #1\else #2\fi}
+% dbltop floats are apparently ok with the balance package
+%%%\newcommand\@PBifdbltopfloat[2]{\ifx\@dbltoplist\empty #2\else #1\fi}
+% dblbotlist only exists with package stfloats
+\newcommand\@PBifdblbotfloat[2]{%
+ \ifdefvoid{\@dblbotlist}
+ {#2}
+ {\ifx\@dblbotlist\empty #2\else #1\fi}%
+}
+
+% If there are footnotes, floats or marginpars in
+% the page, we should not use the balance package
+\newtoggle{@PBtmpHasFootnotes}
+\newtoggle{@PBtmpHasFloats}
+\newtoggle{@PBtmpHasMarginpars}
+
+\def\@PBcollectPageInfo{
+
+ \ifboolexpr
+ {
+ test {\@PBifmidfloat} or test {\@PBiftopfloat} or
+ test {\@PBifbotfloat} or test {\@PBifdblbotfloat}
+ }
+ {\global\toggletrue{@PBtmpHasFloats}}
+ {}
+
+ \@PBiffootnote
+ {\global\toggletrue{@PBtmpHasFootnotes}}
+ {}
+}
+
+\pretocmd{\@makecol}{\@PBcollectPageInfo}{}{}
+
+% This only gets called if there is a marginpar, so that's easy
+\pretocmd{\@addmarginpar}{\global\toggletrue{@PBtmpHasMarginpars}}{}{}
+
+% \end{macrocode}
+%
+% \subsubsection{What are the sizes of the floats in the left column?}
+%
+% \begin{macrocode}
+
+\ExplSyntaxOn
+\seq_gclear_new:N \@PBtmpLeftFloatHeights
+\seq_gclear_new:N \@PBtmpLeftFloatSpacesBelow
+\def\@PBcollectFloatHeights{
+ \if@firstcolumn
+ \dimdef\@PBfloatHeight{\@textfloatsheight - \@PBtmpHeightBefore}
+ \ifdimcomp{\@PBfloatHeight}{>}{0pt}
+ {
+ \global\seq_put_right:NV \@PBtmpLeftFloatHeights \@PBfloatHeight
+
+ \dimdef\@PBspaceBelow{\@colroom - \@pageht - \@PBfloatHeight}
+ % within \@addtocurcol, \@pageht corresponds to all
+ % currently used space in the column, including footnotes
+ % (check \@specialoutput in the LaTeX kernel). Let's
+ % exclude the footnotes, we only want the space below
+ % the float.
+ \ifvoid\footins\else
+ \dimdef\@PBspaceBelow{\@PBspaceBelow + \ht\footins + \skip\footins + \dp\footins}
+ \fi
+
+ % No idea why, but this can happen; maybe because of the page depth?
+ \ifdimcomp{\@PBspaceBelow}{<}{0pt}
+ {\dimdef\@PBspaceBelow{0pt}}
+ {}
+
+ \global\seq_put_right:NV \@PBtmpLeftFloatSpacesBelow \@PBspaceBelow
+ }
+ {}
+ \fi
+}
+\ExplSyntaxOff
+
+\pretocmd{\@addtocurcol}{\dimgdef{\@PBtmpHeightBefore}{\@textfloatsheight}}{}{}
+\apptocmd{\@addtocurcol}{\@PBcollectFloatHeights}{}{}
+
+% \end{macrocode}
+%
+% \subsubsection{What is the size of each column? Is there a float column / floatpage?}
+%
+% \begin{macrocode}
+
+% If there is a float column in the page, it makes no sense to try to
+% balance; if both columns are float columns, this page cannot be the
+% final text page, i.e., we want to balance some other page.
+\newtoggle{@PBtmpHasFloatcol}
+\newtoggle{@PBtmpIsFloatPage}
+
+\newcommand\@PBcollectColumnInfo{
+
+ \if@firstcolumn
+
+ \@PBiffloatcol
+ {\global\toggletrue{@PBtmpHasFloatcol}}
+ {}
+
+ % Available vertical space excluding
+ % dblfloats; the same for both columns
+ \dimgdef\@PBtmpHeight{\@colht}
+
+ % Available vertical space excluding top/bottom floats
+ \dimgdef\@PBtmpLeftHeight{\@colroom}
+
+ % Space used by \texttt{here} floats
+ \dimgdef\@PBtmpLeftFloatsHeight{\@textfloatsheight}
+
+ \else
+
+ \@PBiffloatcol
+ {
+ \iftoggle{@PBtmpHasFloatcol}
+ {\global\toggletrue{@PBtmpIsFloatPage}}
+ {\global\toggletrue{@PBtmpHasFloatcol}}
+ }
+ {}
+
+ \dimgdef\@PBtmpRightHeight{\@colroom}
+ \dimgdef\@PBtmpRightFloatsHeight{\@textfloatsheight}
+
+ \setbox\@tempboxa=\copy\@leftcolumn
+ \setbox\@tempboxa=\vbox{\unvbox\@tempboxa\unskip\unskip}
+ \dimgdef\@PBtmpUsedLeft{\ht\@tempboxa\relax}
+
+ \setbox\@tempboxa=\copy\@outputbox
+ \setbox\@tempboxa=\vbox{\unvbox\@tempboxa\unskip\unskip}
+ \dimgdef\@PBtmpUsedRight{\ht\@tempboxa\relax}
+ \fi
+}
+
+% The default LaTeX macro
+\pretocmd{\@outputdblcol}{\@PBcollectColumnInfo}{}{}
+
+% The version from the balance package
+\AtEndOfPackage{
+ \pretocmd{\@BAdblcol}{\@PBcollectColumnInfo}{}{}
+ % Revert \balance before next page if it exists (it might be a float page)
+ \patchcmd{\@BAdblcol}{\endgroup}{\endgroup\nobalance}{}{}
+}
+
+% \end{macrocode}
+%
+% \subsubsection{Analyze the obtained information}
+%
+% We have inserted the hooks we need to collect data during
+% the processing of each page and column. Now we inspect the
+% collected data after each page is finalized.
+%
+% \begin{macrocode}
+
+\ExplSyntaxOn
+\AtBeginShipout{
+ \ifboolexpr
+ {
+ % A float page is not the last text page, ignore
+ togl {@PBtmpIsFloatPage}
+ or
+ % A page with a float column on the left and an empty
+ % column on the right is not the last text page, ignore
+ (
+ togl {@PBtmpHasFloatcol}
+ and
+ test {\ifdimcomp{\@PBtmpUsedRight}{=}{\topskip}}
+ )
+ }
+ {}
+ {
+ % This page is a candidate to be the last page
+ \xdef\@PBlastPage{\the\value{abspage}}
+ \@PBcopyPageData{tmp}{candidate}
+ }
+
+ % Reset before processing next page
+ \global\togglefalse{@PBtmpHasFloats}
+ \global\togglefalse{@PBtmpHasFloatcol}
+ \global\togglefalse{@PBtmpHasFootnotes}
+ \global\togglefalse{@PBtmpHasMarginpars}
+ \global\togglefalse{@PBtmpIsFloatPage}
+
+ \seq_gclear_new:N \@PBtmpLeftFloatHeights
+ \seq_gclear_new:N \@PBtmpLeftFloatSpacesBelow
+}
+\ExplSyntaxOff
+
+% \end{macrocode}
+%
+% All pages processed, the last candidate page is actually the last page.
+% Instead of continuing to use the \texttt{@PBcandidate*} macros we already
+% have, we will keep the information we just gathered associated with the
+% page number. We do this because, in the future, we may want to add suport
+% for balancing multiple pages (such as the last page of each chapter) and
+% not only the last one. This also makes it easier to handle the situation
+% where the document was modified and the last page has changed.
+%
+% \begin{macrocode}
+
+\AfterLastShipout{\@PBcopyPageData{candidate}{pg\@Roman{\@PBlastPage}}}
+
+% \end{macrocode}
+%
+% \subsubsection{Saving measurements}
+% The whole document has already been processed and data about the last
+% page has been collected in macros with names \texttt{@PBpgNUM*}. We now
+% process this collected data and save whatever is relevant in the aux
+% file for the next \LaTeX{} pass.
+%
+% In the first \LaTeX{} pass, we will save data to the aux file in macros
+% with names \texttt{@PBunbalpgNUM*}. That is all we need for balancing.
+% However, to detect whether the document has changed and, therefore,
+% whether we should re-balance, we will also record the same data after
+% balancing (during the second pass) in macros with names
+% \texttt{@PBbalpgNUM*}. If, in the third or later \LaTeX{} passes,
+% \texttt{@PBbalpgNUM*} differ from \texttt{@PBpgNUM*}, the document
+% has been changed and we need to re-balance.
+%
+% So, we have:
+%
+% \begin{description}
+% \item[\texttt{@PBpgNUM*}] data collected during the current pass;
+% \item[\texttt{@PBunbalpgNUM*}] data collected in the first pass;
+% \item[\texttt{@PBbalpgNUM*}] data collected in the second pass (after
+% balancing); in subsequent passes, these should always be equal
+% to \texttt{@PBpgNUM*}.
+% \end{description}
+%
+% The last two are saved to / read from the aux file.
+%
+% \begin{macrocode}
+
+% Make sure the document has stabilized before using \@PBmanageBalancingPasses
+\AfterLastShipout{
+ % Reset \@PBimpossible if we are in draft mode,
+ % so the user can start over if they want to.
+ \iftoggle{@PBdraft}{\togglefalse{@PBimpossible}}{}
+
+ \ifboolexpr{togl {@PBdraft} or togl {@PBimpossible}}
+ {}
+ {
+ \ifdefvoid{\@PBprevLastPage}
+ {\@PBsaveUnbalancedInfo\@PBnotifyRerun} % First pass
+ {
+ \iftoggle{@PBstabilized}
+ {\@PBmanageBalancingPasses}
+ {
+ \@PBifSomethingChanged[unbal]
+ % Document still changing; discard data from previous pass
+ {\@PBsaveUnbalancedInfo[update]\@PBnotifyRerun}
+ % No changes, so let's balance from now on (once @PBstabilized
+ % becomes true, it never reverts back to false, unless the aux
+ % file is deleted).
+ {\toggletrue{@PBstabilized}\@PBsaveUnbalancedInfo\@PBnotifyRerun}
+ }
+ }
+ \@PBsaveToggle{@PBstabilized}
+ }
+
+ \@PBsaveToggle{@PBimpossible}
+ \immediate\write\@mainaux{\gdef\string\@PBprevLastPhysPage{\the\value{abspage}}}
+}
+
+\newcommand\@PBmanageBalancingPasses{
+ % This is the second pass or later after we started balancing
+
+ \providetoggle{@PBpg\@Roman{\@PBlastPage}alreadyBalanced}
+
+ \iftoggle{@PBpg\@Roman{\@PBlastPage}alreadyBalanced}
+ {
+ % Third pass or later; document was already
+ % balanced, so there should be no more changes
+ \@PBifSomethingChanged
+ % The document has changed, so we need to measure
+ % stuff again; clear aux file and start over.
+ {\@PBnotifyRerun}
+ % Nothing changed, just write the same info down
+ {\@PBsaveUnbalancedInfo\@PBsaveBalancedInfo}
+ }
+ % Second pass, which means something did change: we have just
+ % balanced the columns for the first time.
+ {
+ \xdef\@tempa{\the\value{abspage}}
+ \ifboolexpr
+ {
+ test {\ifdefstrequal{\@PBprevLastPage}{\@PBlastPage}}
+ and
+ test {\ifdefstrequal{\@PBprevLastPhysPage}{\@tempa}}
+ }
+ % All is well; save the new info
+ {\@PBsaveUnbalancedInfo\@PBsaveBalancedInfo}
+ % Oh, no! As we attempted to balance, we actually changed
+ % the number of pages (maybe we added a float page, maybe
+ % we turned the last float page into a text page etc.). If
+ % we continue, the document might never converge (endless
+ % ``please rerun LaTeX'' messages). Let's give up balancing.
+ {\toggletrue{@PBimpossible}}
+ }
+}
+
+% \end{macrocode}
+%
+% \subsection{Auxiliary macros}
+%
+% \begin{macrocode}
+
+\newcommand\@PBifBalancePossible[2]{
+ \iftoggle{@PBunbalpg\@Roman{\@PBprevLastPage}HasFloatcol}
+ {#2}
+ {#1}
+}
+
+% The balance package may fail badly in the presence of footnotes:
+% they may end up in the middle of the text. Marginpars may end up
+% on the wrong side of the page. Floats are usually ok, but in some
+% cases the columns may become very badly balanced. For ordinary
+% text, it works perfectly.
+\newcommand\@PBifBalancePkgPossible[2]{
+ \ifboolexpr
+ {
+ togl {@PBunbalpg\@Roman{\@PBprevLastPage}HasFloats}
+ or
+ togl {@PBunbalpg\@Roman{\@PBprevLastPage}HasFootnotes}
+ or
+ togl {@PBunbalpg\@Roman{\@PBprevLastPage}HasMarginpars}
+ }
+ {#2}
+ {#1}
+}
+
+% \end{macrocode}
+%
+% We only want to balance if:
+%
+% \begin{enumerate}
+%
+% \item The left column is higher than the right one. If that is not the
+% case, either things are already balanced or the right column is
+% higher, which means \LaTeX{} was forced into a bad solution already.
+%
+% \item This difference is ``large enough'' (6 lines), which means two things:
+% \begin{enumerate}
+%
+% \item There will be an actual improvement in the result.
+%
+% \item There is plenty of free space on the right column, so even if
+% our changes happen to take more space than before, \LaTeX{} will
+% hopefully not add a new page (but it might make the right
+% column higher than the left, which is not so great).
+% \end{enumerate}
+%
+% \end{enumerate}
+%
+% If there is material only on the left column, we want to balance even
+% if the difference in height is not ``large enough'', as long as there
+% are more than 4 lines to split among columns. This is actually quite
+% unlikely: if there is so little material, the page probably has only
+% normal text and we will use the balance package.
+%
+% \begin{macrocode}
+
+\newcommand\@PBifPoorBalancePossible[2]{
+
+ % Let's use some shorter names, please
+ \dimdef\@PBtmpH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}Height}}
+ \dimdef\@PBtmpUL{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}UsedLeft}}
+ \dimdef\@PBtmpUR{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}UsedRight}}
+
+ \dimdef\@PBtmpLH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}LeftHeight}}
+ \dimdef\@PBtmpLFH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}LeftFloatsHeight}}
+ \dimdef\@PBtmpRH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}RightHeight}}
+ \dimdef\@PBtmpRFH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}RightFloatsHeight}}
+
+ \ifboolexpr{
+ % Plenty of space on right column, difference is worth it
+ test {\ifdimcomp{\@PBtmpUL}{>}{\@PBtmpUR + 6\baselineskip}}
+ or
+ (
+ test {\ifdimcomp{\@PBtmpUL}{>}{4\baselineskip}}
+ and
+ test {\ifdimcomp{\@PBtmpUR}{=}{\topskip}} % column is empty
+ )
+ }{#1}{#2}
+}
+
+% \end{macrocode}
+%
+% If there are floats, things can get messy: imagine that we want to
+% shrink by 50pt, but there is a 250pt float near the end of the left
+% column that won't fit after we shrink, so it will move to the right.
+% What if there is no room for such a large float there? Here, we try
+% to prevent this situation and shrink by a smaller amount if there
+% is a float that won't fit. Note that, if the float goes to the
+% right column, there will be more room for text in the left column.
+% Therefore, a float will not fit only if the floats on the right
+% column take too much space. So, what we do here is, starting from
+% the bottom, check whether the accumulated height of the floats
+% from the left column fit in the right. If we find a float that
+% will not fit, we know that we should limit shrinking to the space
+% below that float.
+%
+% In the same vein, we do not want top/bottom floats from the left
+% column that were deferred from a previous page to be pushed to
+% the right column. To that end, we limit the shrinking amount to
+% the space on the left column not taken by top/bottom floats.
+
+% \textbf{TODO:} Top and bottom floats may appear in two cases: (1) they
+% were generated on a previous page and deferred or (2) the user
+% chose placement options \texttt{t} or \texttt{b}, without
+% \texttt{h}. Deferred floats are not a problem: we do not reduce
+% the space available for top/bottom floats, so they should
+% continue to fit. Floats on the left column with placement
+% options \texttt{t} and \texttt{b} without \texttt{h} can be
+% a problem: \LaTeX{} only considers putting a float on the
+% top/bottom of the current column if, at the point of its
+% definition, there is enough space for it. Since we are shrinking
+% the column, it may not fit and be deferred - something the
+% algorithm does not take into consideration. However, not all is
+% lost: most likely, the user will only use such placement
+% options when trying to manually adjust the page, which
+% means they will be able to detect and work around eventual
+% problems.
+%
+% \begin{macrocode}
+
+\ExplSyntaxOn
+\def\@PBsafetyCheck{
+
+ % Let's use some shorter names, please
+ \dimdef\@PBtmpH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}Height}}
+ \dimdef\@PBtmpUL{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}UsedLeft}}
+ \dimdef\@PBtmpUR{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}UsedRight}}
+
+ \dimdef\@PBtmpLH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}LeftHeight}}
+ \dimdef\@PBtmpLFH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}LeftFloatsHeight}}
+ \dimdef\@PBtmpRH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}RightHeight}}
+ \dimdef\@PBtmpRFH{\csuse{@PBunbalpg\@Roman{\@PBprevLastPage}RightFloatsHeight}}
+
+
+ % Make sure we do not push top/bottom floats out of the left col
+ \dimdef\@PBmaxSlack{\@PBtmpLH}
+
+ \dimdef\@PBtmpdim{0pt}
+
+ % Start from the bottom
+ \numdef{\@PBtmpcnt}{\seq_count:c {@PBunbalpg\@Roman{\@PBprevLastPage}LeftFloatHeights}}
+ \int_while_do:nNnn {\@PBtmpcnt} > {0}
+ {
+
+ \dimdef\@PBcurrentFloatHeight
+ {\seq_item:cn {@PBunbalpg\@Roman{\@PBprevLastPage}LeftFloatHeights} {\@PBtmpcnt}}
+
+ \dimdef\@PBcurrentFloatSpaceBelow
+ {\seq_item:cn {@PBunbalpg\@Roman{\@PBprevLastPage}LeftFloatSpacesBelow} {\@PBtmpcnt}}
+
+ \dimdef{\@PBtmpdim}{\@PBtmpdim + \@PBcurrentFloatHeight}
+
+ \ifboolexpr
+ {
+ % Does the float fit in the second column?
+ test {\ifdimcomp{\@PBtmpdim}{<}{\@PBtmpRH - \@PBtmpRFH}}
+ and
+ % Is there something above this float, i.e., if we move
+ % this float to the next column, will anything remain?
+ test
+ {
+ \ifdimcomp
+ {\@PBcurrentFloatSpaceBelow + \@PBcurrentFloatHeight}
+ {<}
+ {\@PBtmpLH - 6\baselineskip}
+ }
+ }
+ {\numdef{\@PBtmpcnt}{\@PBtmpcnt - 1}} % this one is ok; check next
+ {
+ % this one should not be moved, we found the limit
+ \ifdimcomp{\@PBmaxSlack}{>}{\@PBcurrentFloatSpaceBelow}
+ {\dimdef{\@PBmaxSlack}{\@PBcurrentFloatSpaceBelow}}
+ {}
+ \numdef{\@PBtmpcnt}{0} % exit the loop
+ }
+ }
+
+ \ifdimcomp{\@PBmaxSlack}{<}{\@PBslack}
+ {\dimgdef{\@PBslack}{\@PBmaxSlack}}
+ {}
+}
+\ExplSyntaxOff
+
+% Compare current measurements with previous ``bal'' or ``unbal'' measurements
+\newcommand\@PBifSomethingChanged[3][bal]{
+ \ifdefstrequal{\@PBprevLastPage}{\@PBlastPage}
+ {
+ \@PBifPageDataMatches
+ {pg\@Roman{\@PBlastPage}}
+ {#1pg\@Roman{\@PBlastPage}}
+ {#3}
+ {#2}
+ }
+ {#2}
+}
+
+\newcommand\@PBifPageDataMatches[4]{
+ \ifboolexpr{
+ test {\@PBifTogglesMatch{@PB#1HasFloats}{@PB#2HasFloats}}
+ and test {\@PBifTogglesMatch{@PB#1HasFloatcol}{@PB#2HasFloatcol}}
+ and test {\@PBifTogglesMatch{@PB#1HasFootnotes}{@PB#2HasFootnotes}}
+ and test {\@PBifTogglesMatch{@PB#1HasMarginpars}{@PB#2HasMarginpars}}
+ and test {\@PBifDimensionsMatch{@PB#1Height}{@PB#2Height}}
+ and test {\@PBifDimensionsMatch{@PB#1LeftHeight}{@PB#2LeftHeight}}
+ and test {\@PBifDimensionsMatch{@PB#1RightHeight}{@PB#2RightHeight}}
+ and test {\@PBifDimensionsMatch{@PB#1UsedLeft}{@PB#2UsedLeft}}
+ and test {\@PBifDimensionsMatch{@PB#1UsedRight}{@PB#2UsedRight}}
+ }
+ {#3}
+ {#4}
+}
+
+% https://en.wikipedia.org/wiki/XNOR_gate
+\newcommand\@PBifTogglesMatch[4]{
+ \ifboolexpr{(togl{#1} or not togl{#2}) and (togl{#2} or not togl{#1})}
+ {#3}
+ {#4}
+}
+
+\newcommand\@PBifDimensionsMatch[4]{
+ \ifdimcomp{\csuse{#1}}{=}{\csuse{#2}}
+ {#3}
+ {#4}
+}
+
+\newcommand\@PBsaveUnbalancedInfo[1][]{
+
+ \immediate\write\@mainaux{\gdef\string\@PBprevLastPage{\@PBlastPage}}
+
+ \ifboolexpr{test {\ifdefvoid{\@PBprevLastPage}} or test {\ifstrequal{#1}{update}}}
+ % Columns are currently unbalanced, either because this is
+ % the first pass (we do not have any previous information)
+ % or because the document has not stabilized yet, so any
+ % previous information is unreliable. Save the information
+ % we just collected as ``unbalanced''.
+ {\@PBsavePageDataAs{pg\@Roman{\@PBlastPage}}{unbalpg\@Roman{\@PBlastPage}}}
+ % Not the first pass, so columns are possibly balanced;
+ % to save the unbalanced information, repeat what was
+ % gathered during the first pass.
+ {\@PBsavePageData{unbalpg\@Roman{\@PBprevLastPage}}}
+}
+
+\def\@PBsaveBalancedInfo{
+ \global\toggletrue{@PBpg\@Roman{\@PBlastPage}alreadyBalanced}
+ \@PBsaveToggle{@PBpg\@Roman{\@PBlastPage}alreadyBalanced}
+
+ \@PBsavePageDataAs{pg\@Roman{\@PBlastPage}}{balpg\@Roman{\@PBlastPage}}
+}
+
+\def\@PBnotifyRerun{
+ \AtVeryEndDocument{
+ \PackageWarningNoLine{pbalance}
+ {Last page columns not balanced. Rerun LaTeX}
+ }
+}
+
+\ExplSyntaxOn
+\newcommand\@PBcopyPageData[2]{
+
+ \global\providetoggle{@PB#2HasFloats}
+ \global\providetoggle{@PB#2HasFloatcol}
+ \global\providetoggle{@PB#2HasFootnotes}
+ \global\providetoggle{@PB#2HasMarginpars}
+
+ \iftoggle{@PB#1HasFloats}
+ {\global\toggletrue{@PB#2HasFloats}}
+ {\global\togglefalse{@PB#2HasFloats}}
+
+ \iftoggle{@PB#1HasFloatcol}
+ {\global\toggletrue{@PB#2HasFloatcol}}
+ {\global\togglefalse{@PB#2HasFloatcol}}
+
+ \iftoggle{@PB#1HasFootnotes}
+ {\global\toggletrue{@PB#2HasFootnotes}}
+ {\global\togglefalse{@PB#2HasFootnotes}}
+
+ \iftoggle{@PB#1HasMarginpars}
+ {\global\toggletrue{@PB#2HasMarginpars}}
+ {\global\togglefalse{@PB#2HasMarginpars}}
+
+ \csdimgdef{@PB#2Height}{\csuse{@PB#1Height}}
+ \csdimgdef{@PB#2LeftHeight}{\csuse{@PB#1LeftHeight}}
+ \csdimgdef{@PB#2LeftFloatsHeight}{\csuse{@PB#1LeftFloatsHeight}}
+ \csdimgdef{@PB#2RightHeight}{\csuse{@PB#1RightHeight}}
+ \csdimgdef{@PB#2RightFloatsHeight}{\csuse{@PB#1RightFloatsHeight}}
+ \csdimgdef{@PB#2UsedLeft}{\csuse{@PB#1UsedLeft}}
+ \csdimgdef{@PB#2UsedRight}{\csuse{@PB#1UsedRight}}
+
+ \seq_gclear_new:c {@PB#2LeftFloatHeights}
+ \seq_gclear_new:c {@PB#2LeftFloatSpacesBelow}
+ \seq_gset_eq:cc {@PB#2LeftFloatHeights} {@PB#1LeftFloatHeights}
+ \seq_gset_eq:cc {@PB#2LeftFloatSpacesBelow} {@PB#1LeftFloatSpacesBelow}
+}
+\ExplSyntaxOff
+
+\newcommand\@PBsavePageDataAs[2]{
+ \@PBsaveDimAs{@PB#1Height}{@PB#2Height}
+ \@PBsaveDimAs{@PB#1LeftHeight}{@PB#2LeftHeight}
+ \@PBsaveDimAs{@PB#1LeftFloatsHeight}{@PB#2LeftFloatsHeight}
+ \@PBsaveDimAs{@PB#1RightHeight}{@PB#2RightHeight}
+ \@PBsaveDimAs{@PB#1RightFloatsHeight}{@PB#2RightFloatsHeight}
+ \@PBsaveDimAs{@PB#1UsedLeft}{@PB#2UsedLeft}
+ \@PBsaveDimAs{@PB#1UsedRight}{@PB#2UsedRight}
+
+ \@PBsaveToggleAs{@PB#1HasFloats}{@PB#2HasFloats}
+ \@PBsaveToggleAs{@PB#1HasFloatcol}{@PB#2HasFloatcol}
+ \@PBsaveToggleAs{@PB#1HasFootnotes}{@PB#2HasFootnotes}
+ \@PBsaveToggleAs{@PB#1HasMarginpars}{@PB#2HasMarginpars}
+
+ \@PBsaveSeqAs{@PB#1LeftFloatHeights}{@PB#2LeftFloatHeights}
+ \@PBsaveSeqAs{@PB#1LeftFloatSpacesBelow}{@PB#2LeftFloatSpacesBelow}
+}
+
+\newcommand\@PBsavePageData[1]{
+ \@PBsavePageDataAs{#1}{#1}
+}
+
+\newcommand\@PBsaveToggleAs[2]{
+ \immediate\write\@mainaux{\providetoggle{#2}}
+ \iftoggle{#1}
+ {\immediate\write\@mainaux{\global\toggletrue{#2}}}
+ {\immediate\write\@mainaux{\global\togglefalse{#2}}}
+}
+
+\newcommand\@PBsaveToggle[1]{
+ \@PBsaveToggleAs{#1}{#1}
+}
+
+\newcommand\@PBsaveDimAs[2]{
+ \immediate\write\@mainaux{\csdimgdef{#2}{\csuse{#1}}}
+}
+
+\newcommand\@PBsaveDim[1]{
+ \@PBsaveDimAs{#1}{#1}
+}
+
+\ExplSyntaxOn
+\newcommand\@PBsaveSeqAs[2]{
+ \immediate\write\@mainaux{\string\ExplSyntaxOn}
+ \immediate\write\@mainaux{\string\seq_gclear_new:c {#2}}
+ \seq_map_inline:cn {#1}
+ {\immediate\write\@mainaux{\string\global\string\seq_put_right:cn {#2}{##1}}}
+ \immediate\write\@mainaux{\string\ExplSyntaxOff}
+}
+\ExplSyntaxOff
+
+\newcommand\@PBsaveSeq[1]{
+ \@PBsaveSeqAs{#1}{#1}
+}
+
+% \end{macrocode}
+% \Finale
diff --git a/macros/latex/contrib/pbalance/pbalance.ins b/macros/latex/contrib/pbalance/pbalance.ins
new file mode 100644
index 0000000000..2e7d2a2db8
--- /dev/null
+++ b/macros/latex/contrib/pbalance/pbalance.ins
@@ -0,0 +1,49 @@
+%% Copyright 2020 Nelson Lago <lago@ime.usp.br>
+%%
+%% This work may be distributed and/or modified under the conditions of the
+%% LaTeX Project Public License, either version 1.3 of this license or (at
+%% your option) any later version. The latest version of this license can be
+%% found at http://www.latex-project.org/lppl.txt and version 1.3 or later
+%% is part of all distributions of LaTeX version 2005/12/01 or later.
+%%
+%% This work has the LPPL maintenance status `maintained'.
+%%
+%% The Current Maintainer of this work is Nelson Lago <lago@ime.usp.br>.
+
+\input docstrip.tex
+\keepsilent
+\usedir{tex/latex/pbalance}
+
+\preamble
+Copyright 2020 Nelson Lago <lago@ime.usp.br>
+
+This work may be distributed and/or modified under the conditions of the
+LaTeX Project Public License, either version 1.3 of this license or (at
+your option) any later version. The latest version of this license can be
+found at http://www.latex-project.org/lppl.txt and version 1.3 or later
+is part of all distributions of LaTeX version 2005/12/01 or later.
+
+This work has the LPPL maintenance status `maintained'.
+
+The Current Maintainer of this work is Nelson Lago <lago@ime.usp.br>.
+
+\endpreamble
+
+\generate{\file{pbalance.sty}{\from{pbalance.dtx}{package}}}
+
+\obeyspaces
+\Msg{****************************************************}
+\Msg{* *}
+\Msg{* To finish the installation you have to move the *}
+\Msg{* following file into a directory searched by TeX: *}
+\Msg{* *}
+\Msg{* pbalance.sty *}
+\Msg{* *}
+\Msg{* To produce the documentation run the file *}
+\Msg{* pbalance.dtx through LaTeX. *}
+\Msg{* *}
+\Msg{* Happy TeXing! *}
+\Msg{* *}
+\Msg{****************************************************}
+
+\endbatchfile
diff --git a/macros/latex/contrib/pbalance/pbalance.pdf b/macros/latex/contrib/pbalance/pbalance.pdf
new file mode 100644
index 0000000000..319570a8dd
--- /dev/null
+++ b/macros/latex/contrib/pbalance/pbalance.pdf
Binary files differ
diff --git a/macros/latex/contrib/rest-api/README.md b/macros/latex/contrib/rest-api/README.md
index a092c4b8dd..92a058bdd6 100644
--- a/macros/latex/contrib/rest-api/README.md
+++ b/macros/latex/contrib/rest-api/README.md
@@ -3,4 +3,4 @@ LaTeX package for describing rest apis
Developed by Dennis Klein <dennis94.kl@gmail.com>
-This material is subject to the LATEX Project Public License 1.3c. \ No newline at end of file
+This material is subject to the LATEX Project Public License 1.3c.
diff --git a/macros/latex/contrib/rest-api/rest-api.dtx b/macros/latex/contrib/rest-api/rest-api.dtx
index db450b183b..955f744c7e 100644
--- a/macros/latex/contrib/rest-api/rest-api.dtx
+++ b/macros/latex/contrib/rest-api/rest-api.dtx
@@ -22,7 +22,7 @@
%<package>\NeedsTeXFormat{LaTeX2e}[1999/12/01]
%<package>\ProvidesPackage{rest-api}
%<*package>
- [2020/06/23 v1.3 rest-api package]
+ [2020/12/04 v1.4 rest-api package]
%</package>
%
%<*driver>
@@ -40,7 +40,7 @@
%</driver>
% \fi
%
-% \CheckSum{424}
+% \CheckSum{514}
%
% \CharacterTable
% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
@@ -59,6 +59,7 @@
% Right brace \} Tilde \~}
%
%
+% \changes{v1.4}{2020/12/04}{Add xml syntax highlighting support for request and response body}
% \changes{v1.3}{2020/06/23}{Fix typo and table coloring after apiRoute block}
% \changes{v1.2}{2020/03/22}{Corrections for CTAN, rename to rest-api}
% \changes{v1.1}{2020/03/21}{Add documentation and usage}
@@ -270,6 +271,34 @@
%</example>
% \fi
+% \paragraph{DELETE Request xml} \
+% \iffalse
+%<*example>
+% \fi
+\begin{lstlisting}[frame=single,breaklines=true]
+ \begin{apiRoute}{delete}{/api/storage/\{id\}}{remove a storage with id}
+ \begin{routeParameter}
+ \routeParamItem{id}{id of storage}
+ \end{routeParameter}
+ \begin{routeResponse}{application/xml}
+ \begin{routeResponseItem}{200}{ok}
+
+ \end{routeResponseItem}
+ \begin{routeResponseItem}{404}{error: storage not found}
+ \begin{routeResponseItemBody}
+ <message>
+ storage with id '11' not found!
+ </message>
+ \end{routeResponseItemBody}
+ \end{routeResponseItem}
+ \end{routeResponse}
+ \end{apiRoute}
+\end{lstlisting}
+% \iffalse
+%</example>
+% \fi
+
+
%
%
%
@@ -363,7 +392,10 @@
% Return the border color of the current route.
%
% \DescribeMacro{\methodJson}
-% Set the with \\lstset color scheme for the listings of the current route.
+% Set the color scheme and language with \\lstset for the listings of the current route to json.
+%
+% \DescribeMacro{\methodXml}
+% Set the color scheme and language with \\lstset for the listings of the current route to xml.
%
%
@@ -504,6 +536,7 @@
% \end{macrocode}
% \end{macro}
%
+% \begin{macro}{color definitions for json}
% \begin{macrocode}
\colorlet{punct}{black}
\definecolor{background}{HTML}{EEEEEE}
@@ -515,6 +548,11 @@
\colorlet{numbPut}{delimPut}
\colorlet{delimDelete}{\getDeleteColor}
\colorlet{numbDelete}{delimDelete}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{json language defintions}
+% \begin{macrocode}
\lstdefinelanguage{jsonGet}{
basicstyle=\small\ttfamily\color{black},
numberstyle=\small\color{black},
@@ -655,6 +693,105 @@
{[}{{{\color{delimDelete}{[}}}}{1}
{]}{{{\color{delimDelete}{]}}}}{1},
}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{xml language defintions}
+% \begin{macrocode}
+\lstdefinelanguage{xmlGet}{
+ basicstyle=\small\ttfamily\color{black},
+ numberstyle=\small\color{numbGet},
+ keywordstyle=\color{black},
+ commentstyle=\color{black},
+ stringstyle=\color{black},
+ frame=L,
+ framesep=8pt,
+ framerule=2pt,
+ xleftmargin=50pt,
+ xrightmargin=0pt,
+ rulesep=0pt,
+ backgroundcolor=\color{white},
+ numbersep=8pt,
+ tabsize=4,
+ showstringspaces=false,
+ breaklines=false,
+ morestring=[b]",
+ morestring=[s]{>}{<},
+ morecomment=[s]{<?}{?>},
+ identifierstyle=\color{numbGet},
+}
+\lstdefinelanguage{xmlPost}{
+ basicstyle=\small\ttfamily\color{black},
+ numberstyle=\small\color{numbPost},
+ keywordstyle=\color{black},
+ commentstyle=\color{black},
+ stringstyle=\color{black},
+ frame=L,
+ framesep=8pt,
+ framerule=2pt,
+ xleftmargin=50pt,
+ xrightmargin=0pt,
+ rulesep=0pt,
+ backgroundcolor=\color{white},
+ numbersep=8pt,
+ tabsize=4,
+ showstringspaces=false,
+ breaklines=false,
+ morestring=[b]",
+ morestring=[s]{>}{<},
+ morecomment=[s]{<?}{?>},
+ identifierstyle=\color{numbPost},
+}
+\lstdefinelanguage{xmlPut}{
+ basicstyle=\small\ttfamily\color{black},
+ numberstyle=\small\color{numbPut},
+ keywordstyle=\color{black},
+ commentstyle=\color{black},
+ stringstyle=\color{black},
+ frame=L,
+ framesep=8pt,
+ framerule=2pt,
+ xleftmargin=50pt,
+ xrightmargin=0pt,
+ rulesep=0pt,
+ backgroundcolor=\color{white},
+ numbersep=8pt,
+ tabsize=4,
+ showstringspaces=false,
+ breaklines=false,
+ morestring=[b]",
+ morestring=[s]{>}{<},
+ morecomment=[s]{<?}{?>},
+ stringstyle=\color{black},
+ identifierstyle=\color{numbPut},
+}
+\lstdefinelanguage{xmlDelete}{
+ basicstyle=\small\ttfamily\color{black},
+ numberstyle=\small\color{numbDelete},
+ keywordstyle=\color{black},
+ commentstyle=\color{black},
+ stringstyle=\color{black},
+ frame=L,
+ framesep=8pt,
+ framerule=2pt,
+ xleftmargin=50pt,
+ xrightmargin=0pt,
+ rulesep=0pt,
+ backgroundcolor=\color{white},
+ numbersep=8pt,
+ tabsize=4,
+ showstringspaces=false,
+ breaklines=false,
+ morestring=[b]",
+ morestring=[s]{>}{<},
+ morecomment=[s]{<?}{?>},
+ identifierstyle=\color{numbDelete},
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{apiRoute frame style defintion}
+% \begin{macrocode}
\mdfdefinestyle{mystyle}{
linecolor=\methodBorderColor,
backgroundcolor=\methodLightColor,
@@ -670,6 +807,15 @@
nobreak=true
}
% \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}{\bodyFormat}
+% \begin{macrocode}
+\newcommand{\bodyFormat}{json}
+% \end{macrocode}
+% \end{macro}
+%
%
% \begin{macro}{\routeBreakValue}
% \begin{macrocode}
@@ -733,21 +879,21 @@
{
\lstset{language=jsonGet,rulecolor=\color{\methodColor}}
}
-\begin{comment}
- \newcommand{\apiCorner}{notround}
-
- \newcommand{\apiCornerRound}
- {
- \renewcommand{\apiCorner}{round}
- }
-
- \newcommand{\apiCornerNotRound}
- {
- \renewcommand{\apiCorner}{notround}
- }
-\end{comment}
% \end{macrocode}
%
+% \begin{macro}{\methodXml}
+% \begin{macrocode}
+\newcommand{\methodXml}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macrocode}
+{
+ \lstset{language=xmlGet,rulecolor=\color{\methodColor}}
+}
+% \end{macrocode}
+%
+%
% \begin{environment}{apiRoute}
% \begin{macrocode}
\newenvironment{apiRoute}[3]
@@ -768,6 +914,12 @@
\colorlet{numbGet}{delimGet}
\lstset{language=jsonGet,rulecolor=\color{\methodColor}}
}
+ \renewcommand{\methodXml}
+ {
+ \colorlet{delimGet}{\methodColor}
+ \colorlet{numbGet}{delimGet}
+ \lstset{language=xmlGet,rulecolor=\color{\methodColor}}
+ }
}
{}
@@ -782,6 +934,12 @@
\colorlet{numbPost}{delimPost}
\lstset{language=jsonPost,rulecolor=\color{\methodColor}}
}
+ \renewcommand{\methodXml}
+ {
+ \colorlet{delimPost}{\methodColor}
+ \colorlet{numbPost}{delimPost}
+ \lstset{language=xmlPost,rulecolor=\color{\methodColor}}
+ }
}
{}
@@ -796,6 +954,12 @@
\colorlet{numbPut}{delimPut}
\lstset{language=jsonPut,rulecolor=\color{\methodColor}}
}
+ \renewcommand{\methodXml}
+ {
+ \colorlet{delimPut}{\methodColor}
+ \colorlet{numbPut}{delimPut}
+ \lstset{language=xmlPut,rulecolor=\color{\methodColor}}
+ }
}
{}
\ifthenelse{\equal{\method}{delete}}
@@ -809,6 +973,12 @@
\colorlet{numbDelete}{delimDelete}
\lstset{language=jsonDelete,rulecolor=\color{\methodColor}}
}
+ \renewcommand{\methodXml}
+ {
+ \colorlet{delimDelete}{\methodColor}
+ \colorlet{numbDelete}{delimDelete}
+ \lstset{language=xmlDelete,rulecolor=\color{\methodColor}}
+ }
}
{}
@@ -857,6 +1027,8 @@
% \begin{macrocode}
\newenvironment{routeRequest}[1]
{
+ \renewcommand{\bodyFormat}{#1}
+
\arrayrulecolor{\methodColor}
\begin{tabularx}{\textwidth}{X l}
@@ -874,7 +1046,7 @@
}
\lstnewenvironment{routeRequestBody}
{
- \methodJson
+ \IfSubStr{\bodyFormat}{xml}{\methodXml}{\methodJson}
}
{}
% \end{macrocode}
@@ -915,6 +1087,8 @@
% \begin{macrocode}
\newenvironment{routeResponse}[1]
{
+ \renewcommand{\bodyFormat}{#1}
+
\newcommand{\noRouteResponse}[1]
{
\begin{tabularx}{\textwidth}{X}
@@ -954,7 +1128,7 @@
\lstnewenvironment{routeResponseItemBody}
{
\vspace{-1.4em}
- \methodJson
+ \IfSubStr{\bodyFormat}{xml}{\methodXml}{\methodJson}
}
{}
\endinput
diff --git a/macros/latex/contrib/rest-api/rest-api.ins b/macros/latex/contrib/rest-api/rest-api.ins
index eba18ecd93..92070c1572 100644
--- a/macros/latex/contrib/rest-api/rest-api.ins
+++ b/macros/latex/contrib/rest-api/rest-api.ins
@@ -16,7 +16,7 @@
\Msg{************************************************************************}
\Msg{* Installation *}
-\Msg{* Package: rest-api 2020/03/22 v1.2 *}
+\Msg{* Package: rest-api 2020/12/04 v1.4 *}
\Msg{************************************************************************}
\keepsilent
@@ -58,4 +58,4 @@ version 2008/05/04 or later.
\Msg{* *}
\Msg{*************************************************************}
-\endbatchfile \ No newline at end of file
+\endbatchfile
diff --git a/macros/latex/contrib/rest-api/rest-api.pdf b/macros/latex/contrib/rest-api/rest-api.pdf
index 90ddffa791..0d3fb391f7 100644
--- a/macros/latex/contrib/rest-api/rest-api.pdf
+++ b/macros/latex/contrib/rest-api/rest-api.pdf
Binary files differ