summaryrefslogtreecommitdiff
path: root/Master
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2025-02-16 21:02:41 +0000
committerKarl Berry <karl@freefriends.org>2025-02-16 21:02:41 +0000
commit1740363df2e9b8d8179bbc7927475e4fc69f8de8 (patch)
treeb857e54c479ee6679a94845f959a0794587239a1 /Master
parente55e3066798455d996a40b25e100d60d109283a5 (diff)
pegmatch (16feb25)
git-svn-id: svn://tug.org/texlive/trunk@74035 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master')
-rw-r--r--Master/texmf-dist/doc/latex/pegmatch/README.md16
-rw-r--r--Master/texmf-dist/doc/latex/pegmatch/pegmatch.pdfbin0 -> 163940 bytes
-rw-r--r--Master/texmf-dist/doc/latex/pegmatch/pegmatch.tex274
-rw-r--r--Master/texmf-dist/tex/latex/pegmatch/pegmatch.sty928
-rwxr-xr-xMaster/tlpkg/bin/tlpkg-ctan-check3
-rw-r--r--Master/tlpkg/tlpsrc/collection-latexextra.tlpsrc1
-rw-r--r--Master/tlpkg/tlpsrc/pegmatch.tlpsrc0
7 files changed, 1221 insertions, 1 deletions
diff --git a/Master/texmf-dist/doc/latex/pegmatch/README.md b/Master/texmf-dist/doc/latex/pegmatch/README.md
new file mode 100644
index 00000000000..1efd6141b8d
--- /dev/null
+++ b/Master/texmf-dist/doc/latex/pegmatch/README.md
@@ -0,0 +1,16 @@
+# The LaTeX3 package `pegmatch`
+
+```
+Caption : Parsing Expression Grammars for TeX
+Author : Jianrui Lyu <tolvjr@163.com>
+CTAN Page : https://ctan.org/pkg/pegmatch
+Repository : https://github.com/lvjr/pegmatch
+License : The LaTeX Project Public License 1.3c
+```
+
+The `pegmatch` package ports PEG ([Parsing Expression Grammars](https://bford.info/packrat/)) to TeX.
+Following the design in LPEG ([Parsing Expression Grammars for Lua](https://www.inf.puc-rio.br/~roberto/lpeg/)),
+it defines patterns as LaTeX3 variables, and offers several operators to compose patterns.
+In general, PEG matching is much more powerful than RE (Regular Expressions) matching.
+At this time, `pegmatch` package only supports TeX strings.
+Also it is still in experimental status, hence some interfaces may change in future releases.
diff --git a/Master/texmf-dist/doc/latex/pegmatch/pegmatch.pdf b/Master/texmf-dist/doc/latex/pegmatch/pegmatch.pdf
new file mode 100644
index 00000000000..82539f2bd78
--- /dev/null
+++ b/Master/texmf-dist/doc/latex/pegmatch/pegmatch.pdf
Binary files differ
diff --git a/Master/texmf-dist/doc/latex/pegmatch/pegmatch.tex b/Master/texmf-dist/doc/latex/pegmatch/pegmatch.tex
new file mode 100644
index 00000000000..2263b4ac475
--- /dev/null
+++ b/Master/texmf-dist/doc/latex/pegmatch/pegmatch.tex
@@ -0,0 +1,274 @@
+
+\documentclass[oneside]{book}
+\usepackage[a4paper,margin=2cm]{geometry}
+
+\newcommand*{\myversion}{2025B}
+\newcommand*{\mydate}{Version \myversion\ (\the\year-\mylpad\month-\mylpad\day)}
+\newcommand*{\mylpad}[1]{\ifnum#1<10 0\the#1\else\the#1\fi}
+
+\setlength{\parindent}{0pt}
+\setlength{\parskip}{4pt plus 1pt minus 1pt}
+
+\usepackage{hyperref}
+\hypersetup{
+ colorlinks=true,
+ urlcolor=blue3,
+ linkcolor=green3,
+}
+
+\usepackage{tabularray}
+
+\NewTblrEnviron{spectblr}
+\SetTblrOuter[spectblr]{long}
+\SetTblrInner[spectblr]{
+ hlines = {gray3}, column{Z} = {co=1}, colsep = 5pt,
+ row{2-Z} = {brown8},
+ row{1} = {fg=white, bg=purple2, font=\bfseries\sffamily},
+ rowhead = 1,
+}
+
+\usepackage{codehigh}
+\colorlet{highback}{azure9}
+\CodeHigh{language=latex/latex2,style/main=gray9,style/code=gray9}
+
+\NewDocumentCommand\PP{m}{\texttt{\fakeverb{#1}}} % package
+\NewDocumentCommand\CC{m}{\texttt{\fakeverb{#1}}} % command
+\NewDocumentCommand\VV{m}{\texttt{\fakeverb{#1}}} % variable
+\NewDocumentCommand\VT{m}{\texttt{\fakeverb{#1}}} % variable type
+\NewDocumentCommand\TT{m}{\texttt{\fakeverb{#1}}} % text
+
+\usepackage{pegmatch}
+
+\begin{document}
+
+\title{\textsf{\color{green3}PEGMATCH: Parsing Expression Grammars for TeX}}
+\author{Jianrui Lyu (tolvjr@163.com)\\ \url{https://github.com/lvjr/pegmatch}}
+\date{\mydate}
+\maketitle
+
+\tableofcontents
+
+\chapter{Package Interfaces}
+
+\section{Introduction}
+
+The \PP{pegmatch} package ports PEG (Parsing Expression Grammars)%
+\footnote{See Parsing Expression Grammars page: \url{https://bford.info/packrat/}.} to TeX.
+Following the design in LPEG (Parsing Expression Grammars for Lua),%
+\footnote{See Parsing Expression Grammars for Lua page: \url{https://www.inf.puc-rio.br/~roberto/lpeg/}.}
+it defines patterns as LaTeX3 variables, and offers several operators to compose patterns.
+
+In general, PEG matching is much more powerful than RE (Regular Expressions) matching.
+At this time, \PP{pegmatch} package only supports TeX strings.%
+\footnote{I started to write it for my \PP{codehigh} package to get rid of \PP{l3regex} dependency.}
+Also it is still in experimental status, hence some interfaces may change in future releases.
+
+\section{The first example}
+
+The following is the first example:
+\begin{demohigh}
+\NewSpeg\lMyTestSpeg
+\SetSpeg\lMyTestSpeg{\SpegP{abc}}
+\IfSpegMatchTF\lMyTestSpeg{a}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{ab}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{abc}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{abcd}{T}{F}
+\end{demohigh}
+In this example, we use \CC{\NewSpeg} to create a new \VT{speg} variable \VV{\lMyTestSpeg},
+and use \CC{\SetSpeg} to set the variable with a pattern expression,
+then use \CC{\IfSpegMatchTF} to match it against different subject strings.
+The pattern \TT{\SpegP{abc}} matches the string \TT{abc} literally.
+
+\section{Basic commands}
+
+This package provides the following commands for creating and matching \VT{speg} patterns:\nopagebreak
+\begin{spectblr}[
+ caption = Basic commands
+]{}
+ Command & Description \\
+ \CC{\NewSpeg#1} & create \VT{speg} variable \TT{#1}\\
+ \CC{\SetSpeg#1{#2}} & set \VT{speg} variable \TT{#1} with \VT{speg} expresssion \VT{#2}\\
+ \CC{\IfSpegMatchT#1{#2}{#3}}
+ & match \TT{#1} against string \TT{#2}, then run code \TT{#3} if the match succeeds\\
+ \CC{\IfSpegMatchF#1{#2}{#3}}
+ & match \TT{#1} against string \TT{#2}, then run code \TT{#3} if the match fails\\
+ \CC{\IfSpegMatchTF#1{#2}{#3}{#4}}
+ & match \TT{#1} against string \TT{#2}, then run code \TT{#3} if the match succeeds,
+ othewise run code \TT{#4}\\
+ \CC{\IfSpegExtractT#1{#2}#3{#4}}
+ & match \TT{#1} against string \TT{#2},
+ then store captures in \TT{#3} and run code \TT{#4} if the match succeeds\\
+ \CC{\IfSpegExtractF#1{#2}#3{#4}}
+ & match \TT{#1} against string \TT{#2},
+ then clear \TT{#3} and run code \TT{#4} if the match fails\\
+ \CC{\IfSpegExtractTF#1{#2}#3{#4}{#5}}
+ & match \TT{#1} against string \TT{#2},
+ then store captures in \TT{#3} and run code \TT{#4} if the match succeeds,
+ othewise clear \TT{#3} and run code \TT{#5}
+\end{spectblr}
+
+\section{Scratch variables}
+
+There are two predefined scratch \VT{speg} variables for setting \VT{speg} patterns:
+\VV{\lTmpaSpeg} and \VV{\lTmpbSpeg}.
+Also there are two predefined scratch \VT{seq} variables for storing captures
+(see Section~\ref{sect:capture}): \VV{\lSpegTmpaSeq} and \VV{\lSpegTmpbSeq}.%
+
+\section{Primitive patterns}
+
+This package provides the following commands for making primitive patterns:%\nopagebreak
+\begin{spectblr}[
+ caption = Primitive patterns
+]{}
+ Pattern & Description \\
+ \TT{\SpegP{<string>}} & match \TT{<string>} literally \\
+ \TT{\SpegQ{<n>}} & match exactly \TT{<n>} characters \\
+ \TT{\SpegR{<X><Y><x><y>}}
+ & match any character between \TT{<X>} and \TT{<Y>} or between \TT{<x>} and \TT{<y>} \\
+ \TT{\SpegS{<string>}} & match any character in \TT{<string>}
+\end{spectblr}
+
+The following examples demonstrate pattern matching with other primitive patterns:
+\begin{demohigh}
+\SetSpeg\lMyTestSpeg{\SpegQ{2}}
+\IfSpegMatchTF\lMyTestSpeg{u}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{vw}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{xyz}{T}{F}
+\end{demohigh}
+\begin{demohigh}
+\SetSpeg\lMyTestSpeg{\SpegR{AZ}}
+\IfSpegMatchTF\lMyTestSpeg{Qq}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{q1}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{1Q}{T}{F}
+\SetSpeg\lMyTestSpeg{\SpegR{AZaz}}
+\IfSpegMatchTF\lMyTestSpeg{Qq}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{q1}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{1Q}{T}{F}
+\end{demohigh}
+
+\begin{demohigh}
+\SetSpeg\lMyTestSpeg{\SpegS{world}}
+\IfSpegMatchTF\lMyTestSpeg{one}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{two}{T}{F}
+\end{demohigh}
+
+By default, PEG always starts at the first character.
+Since both \CC{\SpegR} and \CC{\SpegS} match only one letter,
+both last commands in previous two examples give \TT{F}.
+
+\section{Pattern operators}
+
+This package provides the following pattern operators for composing patterns:
+\begin{spectblr}[
+ caption = Pattern operators
+]{}
+ Operator & Precedence & Description \\
+ \TT{patt1/patt2} & 1 (choice) & match \TT{patt1} or \TT{patt2} (ordered choice) \\
+ \TT{patt1*patt2} & 2 (concat) & match \TT{patt1} followed by \TT{patt2} \\
+ \TT{!patt} & 3 (not predicate) & match only if \TT{patt} does not match, and consume no input \\
+ \TT{&patt} & 3 (and predicate) & match \TT{patt} but consume no input \\
+ \TT{patt^{<n>}} & 4 (repeat) & match at least \TT{<n>} ($n\ge0)$ repetitions of \TT{patt} \\
+ \TT{patt^{-<n>}} & 4 (repeat) & match at most \TT{<n>} ($n>0$) repetitions of \TT{patt} \\
+ \TT{{patt expr}} & 5 (group) & match \TT{patt expr} (pattern expression)
+\end{spectblr}
+
+With \TT{!} and \TT{*} operators, we can create negative character sets:
+\begin{demohigh}
+\SetSpeg\lMyTestSpeg{!\SpegR{09} * \SpegQ{1}}
+\IfSpegMatchTF\lMyTestSpeg{A}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{5}{T}{F}
+\SetSpeg\lMyTestSpeg{!\SpegS{abc} * \SpegQ{1}}
+\IfSpegMatchTF\lMyTestSpeg{B}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{b}{T}{F}
+\end{demohigh}
+
+With \TT{^} operator, we can match words:
+\begin{demohigh}
+\SetSpeg\lMyTestSpeg{\SpegR{AZaz} ^ {1}}
+\IfSpegMatchTF\lMyTestSpeg{HELLO}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{world}{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{ text }{T}{F}
+\IfSpegMatchTF\lMyTestSpeg{(text)}{T}{F}
+\end{demohigh}
+
+In fact, \TT{patt^{-1}} is similar to \TT{expr?}, \TT{patt^0} is similar to \TT{expr*},
+and \TT{patt^1} is similar to \TT{expr+} in regular expression matching.
+
+\section{Pattern variables}
+
+In using \CC{\SetSpeg} command to set a \VT{speg} variable with a pattern expression,
+you can use other \VT{speg} variables. For example:
+\begin{demohigh}
+\SetSpeg\lTmpaSpeg{\SpegR{AZ} / \SpegR{az}}
+\SetSpeg\lTmpbSpeg{\SpegS{135} * \lTmpaSpeg}
+\IfSpegMatchTF\lTmpbSpeg{2ab}{[T]}{[F]}
+\IfSpegMatchTF\lTmpbSpeg{3ab}{[T]}{[F]}
+\SetSpeg\lTmpbSpeg{\SpegS{135} * \lTmpaSpeg^{3}}
+\IfSpegMatchTF\lTmpbSpeg{3ab}{[T]}{[F]}
+\IfSpegMatchTF\lTmpbSpeg{3abcd}{[T]}{[F]}
+\end{demohigh}
+
+By using another recursive pattern, we can make \PP{speg} find a pattern anywhere in a string.
+The following example demonstrates how to match a word with at least three letters
+inside a string:\nopagebreak
+\begin{demohigh}
+\NewSpeg\lMyWordSpeg
+\NewSpeg\lMyAnywhereSpeg
+\SetSpeg\lMyWordSpeg{\SpegR{AZaz}^{3}}
+\SetSpeg\lMyAnywhereSpeg{\lMyWordSpeg / \SpegQ{1} * \lMyAnywhereSpeg}
+\IfSpegMatchTF\lMyAnywhereSpeg{foo bar}{[T]}{[F]}
+\IfSpegMatchTF\lMyAnywhereSpeg{fo bar}{[T]}{[F]}
+\IfSpegMatchTF\lMyAnywhereSpeg{123 ba}{[T]}{[F]}
+\IfSpegMatchTF\lMyAnywhereSpeg{123 bar}{[T]}{[F]}
+\end{demohigh}
+In this example, \VV{\lMyAnywhereSpeg} tries to match \VV{\lMyWordSpeg},
+skipping one letter and tries again if it fails.
+
+\section{Capture patterns}%
+\label{sect:capture}
+
+This package provides the following commands for making capture patterns:
+\begin{spectblr}[
+ caption = Primitive patterns
+]{}
+ Pattern & Name & Description \\
+ \CC{\SpegC{<patt>}} & simple capture & capture the match for \TT{<patt>}\\
+ \CC{\SpegCp} & position capture & capture current position
+\end{spectblr}
+
+Position capture \CC{\SpegCp} must be concatenated with other patterns (by using \TT{*} operator):
+\begin{demohigh}
+\SetSpeg\lTmpaSpeg{\SpegCp * \SpegR{az}^{1} * \SpegCp * \SpegR{09}^{1} * \SpegCp}
+\IfSpegExtractTF\lTmpaSpeg{12ab}\lSpegTmpaSeq{%
+ \MapSpegSeqInline\lSpegTmpaSeq{[#1]}%
+}{Failed}
+\IfSpegExtractTF\lTmpaSpeg{ab12}\lSpegTmpaSeq{%
+ \MapSpegSeqInline\lSpegTmpaSeq{[#1]}%
+}{Failed}
+\IfSpegExtractTF\lTmpaSpeg{abcd12345}\lSpegTmpaSeq{%
+ \MapSpegSeqInline\lSpegTmpaSeq{[#1]}%
+}{Failed}
+\end{demohigh}
+In this example, we use \CC{\IfSpegExtractTF} command to extract all captures,
+which are stored in the \VT{seq} variable (\CC{\lSpegTmpaSeq}) specified by the third argument.
+Then we use \CC{\MapSpegSeqInline} command to print each capture.
+
+If you want to capture the substrings, you can modified the above example as follows:\nopagebreak
+\begin{demohigh}
+\SetSpeg\lTmpaSpeg{\SpegC{\SpegR{az}^{1}} * \SpegC{\SpegR{09}^{1}}}
+\IfSpegExtractTF\lTmpaSpeg{12ab}\lSpegTmpaSeq{%
+ \MapSpegSeqInline\lSpegTmpaSeq{[#1]}%
+}{Failed}
+\IfSpegExtractTF\lTmpaSpeg{ab12}\lSpegTmpaSeq{%
+ \MapSpegSeqInline\lSpegTmpaSeq{[#1]}%
+}{Failed}
+\IfSpegExtractTF\lTmpaSpeg{abcd12345}\lSpegTmpaSeq{%
+ \MapSpegSeqInline\lSpegTmpaSeq{[#1]}%
+}{Failed}
+\end{demohigh}
+
+\chapter{The Source Code}
+
+\dochighinput[language=latex/latex3]{pegmatch.sty}
+
+\end{document}
diff --git a/Master/texmf-dist/tex/latex/pegmatch/pegmatch.sty b/Master/texmf-dist/tex/latex/pegmatch/pegmatch.sty
new file mode 100644
index 00000000000..ea746ec2d84
--- /dev/null
+++ b/Master/texmf-dist/tex/latex/pegmatch/pegmatch.sty
@@ -0,0 +1,928 @@
+%%% ----------------------------------------------------------------------------
+%%% Caption : Parsing Expression Grammars for TeX
+%%% Author : Jianrui Lyu <tolvjr@163.com>
+%%% CTAN Page : https://ctan.org/pkg/pegmatch
+%%% Repository : https://github.com/lvjr/pegmatch
+%%% License : The LaTeX Project Public License 1.3c
+%%% ----------------------------------------------------------------------------
+
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesExplPackage{pegmatch}{2025-02-16}{v2025B}
+ {Parsing Expression Grammars for TeX}
+
+\cs_generate_variant:Nn \iow_log:n {V}
+\cs_generate_variant:Nn \str_range:nnn {nne}
+\cs_generate_variant:Nn \tl_analysis_map_inline:nn {e}
+
+\prg_generate_conditional_variant:Nnn \int_compare:nNn {eN} {p,TF}
+\prg_generate_conditional_variant:Nnn \str_if_eq:nn {en} {TF}
+\prg_generate_conditional_variant:Nnn \str_if_in:nn {nV} {TF}
+\prg_generate_conditional_variant:Nnn \tl_if_head_is_group:n {V} {TF}
+\prg_generate_conditional_variant:Nnn \tl_if_head_is_space:n {v} {TF}
+
+%% Every speg variable starts with scan mark \s__speg.
+\scan_new:N \s__speg
+
+\cs_new_protected:Npn \speg_new:N #1
+ {
+ \tl_new:N #1
+ \tl_set:Nn #1 { \s__speg }
+ }
+\cs_set_eq:NN \NewSpeg \speg_new:N
+
+\speg_new:N \lTmpaSpeg
+\speg_new:N \lTmpbSpeg
+\speg_new:N \gTmpaSpeg
+\speg_new:N \gTmpbSpeg
+
+\seq_new:N \lSpegTmpaSeq
+\seq_new:N \lSpegTmpbSeq
+\cs_set_eq:NN \MapSpegSeqInline \seq_map_inline:Nn
+
+%%% -------------------------------------------------------
+%%> \section{Set speg variables}
+%%% -------------------------------------------------------
+
+\int_new:N \g__speg_prg_map_int
+
+%% Split tl #1 into items separated by tl #2, and pass each item to code #3.
+%% Braces around each item are kept but spaces around each item are removed.
+\cs_new_protected:Npn \__speg_tl_split_map_inline:nnn #1 #2 #3
+ {
+ \int_gincr:N \g__speg_prg_map_int
+ \cs_gset_protected:cpn
+ {__speg_map_ \int_use:N \g__speg_prg_map_int :w} ##1 {#3}
+ \__speg_tl_split_map_function:nnc
+ {#1} {#2} {__speg_map_ \int_use:N \g__speg_prg_map_int :w}
+ \int_gdecr:N \g__speg_prg_map_int
+ }
+\cs_generate_variant:Nn \__speg_tl_split_map_inline:nnn {V}
+
+%% Split tl #1 into items separated by tl #2, and pass each item to function #3.
+%% Braces around each item are kept but spaces around each item are removed.
+%% We insert \prg_do_nothing: before each item to avoid losing outermost braces.
+\cs_new_protected:Npn \__speg_tl_split_map_function:nnN #1 #2 #3
+ {
+ \cs_set_protected:cpn
+ { __speg_tl_split_map_ \int_use:N \g__speg_prg_map_int _aux:Nw } ##1 ##2 #2
+ {
+ \tl_if_eq:nnF {\prg_do_nothing: \c_novalue_tl} {##2}
+ {
+ \exp_args:Ne ##1 {\tl_trim_spaces:o {##2}}
+ \use:c {__speg_tl_split_map_\int_use:N \g__speg_prg_map_int _aux:Nw}
+ ##1 \prg_do_nothing:
+ }
+ }
+ \use:c {__speg_tl_split_map_ \int_use:N \g__speg_prg_map_int _aux:Nw}
+ #3 \prg_do_nothing: #1 #2 \c_novalue_tl #2
+ }
+\cs_generate_variant:Nn \__speg_tl_split_map_function:nnN {V, nnc}
+
+\tl_new:N \l__speg_result_tl
+
+\cs_new_protected:Npn \speg_set:Nn #1 #2
+ {
+ \__speg_tracing:nn {set} { \tl_log:n {Input=#2} }
+ \tl_clear:N \l__speg_result_tl
+ \__speg_parse_expr:n {#2}
+ \tl_set:Ne #1 { \s__speg \exp_not:V \l__speg_result_tl }
+ \__speg_tracing:nn {set} { \speg_log:N #1 }
+ }
+\cs_set_eq:NN \SetSpeg \speg_set:Nn
+
+\cs_new_protected:Npn \__speg_parse_expr:n #1
+ {
+ \__speg_parse_choice:n {#1}
+ %\tl_log:N \l__speg_result_tl
+ \tl_set:Ne \l__speg_result_tl { \l__speg_result_tl }
+ }
+
+\tl_const:Nn \c__speg_left_brace_tl { \exp_after:wN {\if_false:} \fi: }
+\tl_const:Nn \c__speg_right_brace_tl { \if_false: {\fi:} }
+
+%% parse choice operator
+\cs_new_protected:Npn \__speg_parse_choice:n #1
+ {
+ \tl_if_in:nnTF {#1} {/}
+ {
+ \tl_put_right:Nn \l__speg_result_tl {\SpegChoice \c__speg_left_brace_tl}
+ \__speg_tl_split_map_inline:nnn {#1} {/}
+ {
+ \tl_put_right:Nn \l__speg_result_tl {\c__speg_left_brace_tl}
+ \__speg_parse_concat:n {##1}
+ \tl_put_right:Nn \l__speg_result_tl {\c__speg_right_brace_tl}
+ }
+ \tl_put_right:Nn \l__speg_result_tl {\c__speg_right_brace_tl}
+ }
+ { \__speg_parse_concat:n {#1} }
+ }
+\cs_generate_variant:Nn \__speg_parse_choice:n {e}
+
+%% parse concat operator
+\cs_new_protected:Npn \__speg_parse_concat:n #1
+ {
+ \tl_if_in:nnTF {#1} {*}
+ {
+ \tl_put_right:Nn \l__speg_result_tl {\SpegConcat \c__speg_left_brace_tl}
+ \__speg_tl_split_map_inline:nnn {#1} {*}
+ {
+ \tl_put_right:Nn \l__speg_result_tl {\c__speg_left_brace_tl}
+ \__speg_parse_predicate:n {##1}
+ \tl_put_right:Nn \l__speg_result_tl {\c__speg_right_brace_tl}
+ }
+ \tl_put_right:Nn \l__speg_result_tl {\c__speg_right_brace_tl}
+ }
+ { \__speg_parse_predicate:n {#1} }
+ }
+
+\tl_new:N \l__speg_predicate_head_tl
+\tl_new:N \l__speg_predicate_tail_tl
+\tl_const:Nn \c__speg_not_tl {!}
+\tl_const:Nn \c__speg_and_tl {&}
+
+%% parse "not predicate" and "and predicate" operators
+\cs_new_protected:Npn \__speg_parse_predicate:n #1
+ {
+ \tl_set:Ne \l__speg_predicate_head_tl {\tl_head:n {#1}}
+ \tl_set:Ne \l__speg_predicate_tail_tl {\tl_trim_spaces:e {\tl_tail:n {#1}}}
+ \tl_case:NnTF \l__speg_predicate_head_tl
+ {
+ \c__speg_not_tl
+ {
+ \tl_put_right:Nn \l__speg_result_tl {\SpegNot}
+ }
+ \c__speg_and_tl
+ {
+ \tl_put_right:Nn \l__speg_result_tl {\SpegAnd}
+ }
+ }
+ {
+ \tl_put_right:Nn \l__speg_result_tl {\c__speg_left_brace_tl}
+ \__speg_parse_repeat:V \l__speg_predicate_tail_tl
+ \tl_put_right:Nn \l__speg_result_tl {\c__speg_right_brace_tl}
+ }
+ { \__speg_parse_repeat:n {#1} }
+ }
+
+\quark_new:N \q__speg_stop
+
+%% parse repeat operator
+\cs_new_protected:Npn \__speg_parse_repeat:n #1
+ {
+ \tl_if_in:nnTF {#1} {^}
+ {
+ %% we prepend \prg_do_nothing: to avoid losing outermost braces
+ \__speg_parse_repeat_aux:wnw \prg_do_nothing: #1 \q__speg_stop
+ }
+ { \__speg_parse_or_store_atom:e { \tl_trim_spaces:n {#1} } }
+ }
+\cs_generate_variant:Nn \__speg_parse_repeat:n {V}
+
+\msg_new:nnn {speg} {invalid-repeat-arguments}
+ {Invalid ~ arguments ~ '#1' ~ for ~ \token_to_str:N \SpegRepeat!}
+
+\cs_new_protected:Npn \__speg_parse_repeat_aux:wnw #1 ^ #2 #3 \q__speg_stop
+ {
+ %% remove \prg_do_nothing: at the beginning of #1
+ \__speg_parse_repeat_real:eee { \tl_trim_spaces:e { \tl_tail:n {#1} } }
+ { \tl_trim_spaces:n {#2} } { \tl_trim_spaces:n {#3} }
+ }
+
+\cs_new_protected:Npn \__speg_parse_repeat_real:nnn #1 #2 #3
+ {
+ \tl_if_empty:eF {#3} {\msg_error:nne {speg} {invalid-repeat-arguments} {#3}}
+ \tl_put_right:Nn \l__speg_result_tl {\SpegRepeat \c__speg_left_brace_tl}
+ \__speg_parse_or_store_atom:n {#1}
+ \tl_put_right:Nn \l__speg_result_tl {\c__speg_right_brace_tl {#2}}
+ }
+\cs_generate_variant:Nn \__speg_parse_repeat_real:nnn {eee}
+
+%% We need to put speg atom (especially speg variable) inside \exp_not:n,
+%% to protect it from e-expansion in \__speg_parse_expr:n function.
+\cs_new_protected:Npn \__speg_parse_or_store_atom:n #1
+ {
+ \tl_if_head_is_group:nTF {#1}
+ { \__speg_parse_choice:n #1 }
+ {
+ \tl_if_head_eq_meaning:nNTF {#1} \SpegC
+ {
+ \tl_put_right:Nn \l__speg_result_tl {\SpegC \c__speg_left_brace_tl}
+ \__speg_parse_choice:e { \tl_tail:n {#1} }
+ \tl_put_right:Nn \l__speg_result_tl {\c__speg_right_brace_tl}
+ }
+ { \tl_put_right:Nn \l__speg_result_tl { \exp_not:n {#1} } }
+ }
+ }
+\cs_generate_variant:Nn \__speg_parse_or_store_atom:n {e}
+
+%%% -------------------------------------------------------
+%%> \section{Log speg variables}
+%%% -------------------------------------------------------
+
+\int_new:N \l__speg_log_indent_int
+\tl_new:N \l__speg_log_line_tl
+\seq_new:N \l__speg_log_seq
+
+\cs_new_protected:Npn \speg_log:N #1
+ {
+ \int_zero:N \l__speg_log_indent_int
+ \seq_clear:N \l__speg_log_seq
+ \tl_clear:N \l__speg_log_line_tl
+ %% Remove leading \s__speg in speg variable #1
+ \tl_analysis_map_inline:en { \tl_tail:N #1 }
+ {
+ \int_case:nnF {"##3} % convert hexadecimal digit to integer
+ {
+ {1} % begin-group
+ {
+ \tl_if_empty:NF \l__speg_log_line_tl {\__speg_log_line:}
+ \tl_set_eq:NN \l__speg_log_line_tl \c_left_brace_str
+ \__speg_log_line:
+ \int_incr:N \l__speg_log_indent_int
+ }
+ {2} % end-group
+ {
+ \tl_if_empty:NF \l__speg_log_line_tl {\__speg_log_line:}
+ \int_decr:N \l__speg_log_indent_int
+ \tl_set_eq:NN \l__speg_log_line_tl \c_right_brace_str
+ \__speg_log_line:
+ }
+ }
+ {\tl_put_right:Ne \l__speg_log_line_tl {##1}}
+ }
+ \iow_log:e
+ {
+ >~Compiled~speg~variable~\token_to_str:N #1:^^J
+ \seq_use:Nn \l__speg_log_seq {^^J}
+ }
+ }
+\cs_set_eq:NN \LogSpeg \speg_log:N
+
+\cs_new_protected:Npn \__speg_log_line:
+ {
+ \seq_put_right:Ne \l__speg_log_seq
+ {
+ \prg_replicate:nn {\l__speg_log_indent_int * 2} {~}
+ \exp_not:V \l__speg_log_line_tl
+ }
+ \tl_clear:N \l__speg_log_line_tl
+ }
+
+%%% -------------------------------------------------------
+%%> \section{Match speg variables}
+%%% -------------------------------------------------------
+
+\int_new:N \g__speg_match_level_int
+\int_new:N \g__speg_match_level_max_int
+\prop_new_linked:N \g__speg_match_index_prop
+
+\cs_new_protected:Npn \__speg_match_gzero_level:
+ {
+ \int_gzero:N \g__speg_match_level_int
+ }
+
+\cs_new_protected:Npn \__speg_match_gincr_level:
+ {
+ \int_gincr:N \g__speg_match_level_int
+ \int_compare:nNnT
+ { \g__speg_match_level_int } > { \g__speg_match_level_max_int }
+ {
+ \int_gset_eq:NN \g__speg_match_level_max_int \g__speg_match_level_int
+ \bool_new:c
+ { g__speg_match_ \int_use:N \g__speg_match_level_int _bool }
+ \str_new:c { g__speg_match_ \int_use:N \g__speg_match_level_int _str }
+ }
+ }
+
+\cs_new_protected:Npn \__speg_match_gdecr_level:
+ {
+ \int_gdecr:N \g__speg_match_level_int
+ }
+
+\cs_new_protected:Npn \__speg_match_gset_true:
+ {
+ \bool_gset_true:c
+ { g__speg_match_ \int_use:N \g__speg_match_level_int _bool }
+ }
+
+\cs_new_protected:Npn \__speg_match_gset_false:
+ {
+ \bool_gset_false:c
+ { g__speg_match_ \int_use:N \g__speg_match_level_int _bool }
+ }
+
+\cs_new:Npn \__speg_match_use_status:
+ {
+ \bool_if:cTF
+ { g__speg_match_ \int_use:N \g__speg_match_level_int _bool }
+ {true} {false}
+ }
+
+\prg_new_conditional:Npnn \__speg_match_if_success: { p, T, F, TF }
+ {
+ \bool_if:cTF
+ { g__speg_match_ \int_use:N \g__speg_match_level_int _bool }
+ { \prg_return_true: } { \prg_return_false: }
+ }
+
+\prg_new_conditional:Npnn \__speg_match_if_sub_success: { p, T, F, TF }
+ {
+ \bool_if:cTF
+ { g__speg_match_ \int_eval:n { \g__speg_match_level_int + 1 } _bool }
+ { \prg_return_true: } { \prg_return_false: }
+ }
+
+\cs_new_protected:Npn \__speg_match_gincr_index:
+ {
+ \prop_gput:Nee \g__speg_match_index_prop
+ { \int_use:N \g__speg_match_level_int }
+ {
+ \int_eval:n
+ {
+ \prop_item:Ne \g__speg_match_index_prop
+ { \int_use:N \g__speg_match_level_int }
+ + 1
+ }
+ }
+ }
+
+\cs_new_protected:Npn \__speg_match_gdecr_index:
+ {
+ \prop_gput:Nee \g__speg_match_index_prop
+ { \int_use:N \g__speg_match_level_int }
+ {
+ \int_eval:n
+ {
+ \prop_item:Ne \g__speg_match_index_prop
+ { \int_use:N \g__speg_match_level_int }
+ - 1
+ }
+ }
+ }
+
+\cs_new_protected:Npn \__speg_match_gset_index:n #1
+ {
+ \prop_gput:Nen \g__speg_match_index_prop
+ { \int_use:N \g__speg_match_level_int } {#1}
+ }
+\cs_generate_variant:Nn \__speg_match_gset_index:n {e}
+
+\cs_new_protected:Npn \__speg_match_gset_eq_index:
+ {
+ \prop_gput:Nee \g__speg_match_index_prop
+ { \int_use:N \g__speg_match_level_int }
+ {
+ \prop_item:Ne \g__speg_match_index_prop
+ { \int_eval:n { \g__speg_match_level_int + 1 } }
+ }
+ }
+
+\cs_new_protected:Npn \__speg_match_update_index:
+ {
+ \prop_gput:Nee \g__speg_match_index_prop
+ { \int_use:N \g__speg_match_level_int }
+ {
+ \int_eval:n
+ {
+ \prop_item:Ne \g__speg_match_index_prop
+ { \int_use:N \g__speg_match_level_int }
+ +
+ \prop_item:Ne \g__speg_match_index_prop
+ { \int_eval:n { \g__speg_match_level_int + 1 } }
+ - 1
+ }
+ }
+ }
+
+\cs_new:Npn \__speg_match_use_index:
+ {
+ \prop_item:Ne \g__speg_match_index_prop
+ { \int_use:N \g__speg_match_level_int }
+ }
+
+\cs_new:Npn \__speg_match_use_sub_index:
+ {
+ \prop_item:Ne \g__speg_match_index_prop
+ { \int_eval:n { \g__speg_match_level_int + 1 } }
+ }
+
+\cs_new:Npn \__speg_match_use_index:n #1
+ {
+ \prop_item:Nn \g__speg_match_index_prop {#1}
+ }
+
+\cs_new_protected:Npn \__speg_match_get_position:N #1
+ {
+ \int_set:Nn #1 {1}
+ \int_step_inline:nnn {2} { \g__speg_match_level_int }
+ {
+ \int_add:Nn #1
+ { \prop_item:Nn \g__speg_match_index_prop {##1} - 1 }
+ }
+ %\int_log:N #1
+ }
+
+\cs_new_protected:Npn \__speg_match_gclear_subject:
+ {
+ \str_gclear:c
+ { g__speg_match_ \int_use:N \g__speg_match_level_int _str }
+ }
+
+\cs_new_protected:Npn \__speg_match_gset_subject:n #1
+ {
+ \str_gset:cn
+ { g__speg_match_ \int_use:N \g__speg_match_level_int _str } {#1}
+ }
+\cs_generate_variant:Nn \__speg_match_gset_subject:n {e}
+
+\cs_new_protected:Npn \__speg_match_gset_eq_subject:
+ {
+ \str_gset_eq:cc
+ { g__speg_match_ \int_use:N \g__speg_match_level_int _str }
+ { g__speg_match_ \int_eval:n { \g__speg_match_level_int + 1 } _str }
+ }
+
+\cs_new_protected:Npn \__speg_match_get_capture:nN #1 #2
+ {
+ \str_set:Ne #2
+ {
+ \str_range:nne {#1} {1}
+ { \int_eval:n { \__speg_match_use_sub_index: - 1 } }
+ }
+ }
+
+\cs_new:Npn \__speg_match_use_subject:
+ {
+ \str_use:c { g__speg_match_ \int_use:N \g__speg_match_level_int _str }
+ }
+
+\cs_new_protected:Npn \__speg_match_apply_subject:n #1
+ {
+ \__speg_match_apply_subject_aux:vn
+ { g__speg_match_ \int_use:N \g__speg_match_level_int _str } {#1}
+ }
+
+\cs_new_protected:Npn \__speg_match_apply_subject_aux:nn #1 #2
+ {
+ #2 {#1}
+ }
+\cs_generate_variant:Nn \__speg_match_apply_subject_aux:nn {vn}
+
+\prg_new_conditional:Npnn \__speg_match_if_head_is_space: {TF}
+ {
+ \tl_if_head_is_space:vTF
+ { g__speg_match_ \int_use:N \g__speg_match_level_int _str }
+ { \prg_return_true: } { \prg_return_false: }
+ }
+
+\cs_new:Npn \__speg_match_head_subject:
+ {
+ \tl_head:v { g__speg_match_ \int_use:N \g__speg_match_level_int _str }
+ }
+
+\cs_new:Npn \__speg_match_tail_subject:
+ {
+ \tl_tail:v { g__speg_match_ \int_use:N \g__speg_match_level_int _str }
+ }
+
+\exp_last_unbraced:NNo \cs_new:Npn \__speg_gobble_space:w \c_space_tl { }
+
+\cs_new:Npn \__speg_match_head_subject_keep_space:
+ {
+ \__speg_match_if_head_is_space:TF { ~ } { \__speg_match_head_subject: }
+ }
+
+\cs_new:Npn \__speg_match_tail_subject_keep_space:
+ {
+ \__speg_match_if_head_is_space:TF
+ {
+ \exp_last_unbraced:Nv \__speg_gobble_space:w
+ { g__speg_match_ \int_use:N \g__speg_match_level_int _str }
+ }
+ { \__speg_match_tail_subject: }
+ }
+
+\cs_new_protected:Npn \__speg_match_gpop_subject:N #1
+ {
+ \tl_set:Ne #1 { \__speg_match_head_subject_keep_space: }
+ \__speg_match_gset_subject:e
+ { \__speg_match_tail_subject_keep_space: }
+ }
+
+%% #1: pattern variable; #2: subject string.
+\cs_new_protected:Npn \__speg_match:Nn #1 #2
+ {
+ %\iow_log:e { Matching ~ \exp_not:V #1 : }
+ \__speg_match_gincr_level:
+ \__speg_tracing_match_start:n { Match {#2} }
+ \__speg_match_gset_index:n {1}
+ \__speg_match_gset_false:
+ #1 {#2}
+ \__speg_match_if_sub_success:T
+ {
+ \__speg_match_gset_true:
+ \__speg_match_update_index:
+ }
+ \__speg_tracing_match_stop:n { Match }
+ \__speg_match_gdecr_level:
+ }
+\cs_set_eq:NN \MatchSpeg \__speg_match:Nn
+
+\prg_new_protected_conditional:Npnn \speg_match:Nn #1 #2 { T, F, TF }
+ {
+ \__speg_match:Nn #1 {#2}
+ \bool_if:cTF { g__speg_match_1_bool }
+ { \prg_return_true: } { \prg_return_false: }
+ }
+\cs_set_eq:NN \IfSpegMatchT \speg_match:NnT
+\cs_set_eq:NN \IfSpegMatchF \speg_match:NnF
+\cs_set_eq:NN \IfSpegMatchTF \speg_match:NnTF
+
+\seq_new:N \g__speg_capture_seq
+
+%% #1: pattern variable; #2: subject string; #3: seq variable for captures.
+\cs_new_protected:Npn \__speg_extract:NnN #1 #2 #3
+ {
+ \seq_gclear:N \g__speg_capture_seq
+ \__speg_match:Nn #1 {#2}
+ \seq_set_eq:NN #3 \g__speg_capture_seq
+ }
+\cs_set_eq:NN \ExtractSpeg \__speg_extract:NnN
+
+\prg_new_protected_conditional:Npnn \speg_extract:NnN #1 #2 #3 { T, F, TF }
+ {
+ \__speg_extract:NnN #1 {#2} #3
+ \bool_if:cTF { g__speg_match_1_bool }
+ { \prg_return_true: }
+ {
+ \seq_clear:N #3
+ \prg_return_false:
+ }
+ }
+\cs_set_eq:NN \IfSpegExtractT \speg_extract:NnNT
+\cs_set_eq:NN \IfSpegExtractF \speg_extract:NnNF
+\cs_set_eq:NN \IfSpegExtractTF \speg_extract:NnNTF
+
+%% #1: pattern from "patt1 / patt2 / patt3"; #2: subject string.
+\cs_new_protected:Npn \speg_choice:nn #1 #2
+ {
+ \__speg_match_gincr_level:
+ \__speg_tracing_match_start:n { Choice {#2} }
+ \__speg_match_gset_index:n {1}
+ \__speg_match_gset_false:
+ \tl_map_inline:nn {#1}
+ {
+ ##1 {#2}
+ \__speg_match_if_sub_success:T
+ {
+ \__speg_match_gset_true:
+ \tl_map_break:
+ }
+ }
+ \__speg_match_if_success:T
+ {
+ \__speg_match_gset_eq_subject:
+ \__speg_match_update_index:
+ }
+ \__speg_tracing_match_stop:n { Choice }
+ \__speg_match_gdecr_level:
+ }
+\cs_generate_variant:Nn \speg_choice:nn {nV}
+\cs_set_eq:NN \SpegChoice \speg_choice:nn
+
+\tl_new:N \l__speg_pattern_head_tl
+
+%% #1: pattern from "patt1 * patt2 * patt3"; #2: subject string.
+\cs_new_protected:Npn \speg_concat:nn #1 #2
+ {
+ \__speg_match_gincr_level:
+ \__speg_tracing_match_start:n { Concat {#2} }
+ \__speg_match_gset_subject:n {#2}
+ \__speg_match_gset_index:n {1}
+ \__speg_match_gset_true:
+ \tl_map_inline:nn {#1}
+ {
+ \tl_set:Ne \l__speg_pattern_head_tl { \tl_head:n {##1} }
+ \exp_after:wN \tl_if_eq:NNTF \l__speg_pattern_head_tl \SpegCp
+ { ##1 }
+ {
+ \__speg_match_apply_subject:n {##1}
+ \__speg_match_if_sub_success:TF
+ {
+ \__speg_match_gset_eq_subject:
+ \__speg_match_update_index:
+ }
+ {
+ \__speg_match_gset_false:
+ \tl_map_break:
+ }
+ }
+ }
+ \__speg_tracing_match_stop:n { Concat }
+ \__speg_match_gdecr_level:
+ }
+\cs_generate_variant:Nn \speg_concat:nn {nV}
+\cs_set_eq:NN \SpegConcat \speg_concat:nn
+
+%% #1: pattern from "! patt"; #2: subject string.
+\cs_new_protected:Npn \speg_not:nn #1 #2
+ {
+ \__speg_match_gincr_level:
+ \__speg_tracing_match_start:n { Not {#2} }
+ \__speg_match_gset_subject:n {#2}
+ \__speg_match_gset_index:n {1}
+ #1 {#2}
+ \__speg_match_if_sub_success:TF
+ { \__speg_match_gset_false: } { \__speg_match_gset_true: }
+ \__speg_tracing_match_stop:n { Not }
+ \__speg_match_gdecr_level:
+ }
+\cs_generate_variant:Nn \speg_not:nn {nV}
+\cs_set_eq:NN \SpegNot \speg_not:nn
+
+%% #1: pattern from "& patt"; #2: subject string.
+\cs_new_protected:Npn \speg_and:nn #1 #2
+ {
+ \__speg_match_gincr_level:
+ \__speg_tracing_match_start:n { And {#2} }
+ \__speg_match_gset_subject:n {#2}
+ \__speg_match_gset_index:n {1}
+ #1 {#2}
+ \__speg_match_if_sub_success:TF
+ { \__speg_match_gset_true: } { \__speg_match_gset_false: }
+ \__speg_tracing_match_stop:n { And }
+ \__speg_match_gdecr_level:
+ }
+\cs_generate_variant:Nn \speg_and:nn {nV}
+\cs_set_eq:NN \SpegAnd \speg_and:nn
+
+%% #1 and #2: pattern and repeat number from "patt ^ {n}"; #3: subject string.
+\cs_new_protected:Npn \speg_repeat:nnn #1 #2 #3
+ {
+ \__speg_match_gincr_level:
+ \__speg_tracing_match_start:n { Repeat {#2} {#3} }
+ \__speg_match_gset_index:n {1}
+ \__speg_match_gset_subject:n {#3}
+ \int_compare:nNnTF {#2} < {0}
+ %% at most -#2 occurrences
+ {
+ \__speg_match_gset_true:
+ \int_step_inline:nn {-#2}
+ {
+ \__speg_match_apply_subject:n {#1}
+ \__speg_match_if_sub_success:TF
+ {
+ \__speg_match_gset_eq_subject:
+ \__speg_match_update_index:
+ }
+ { \prg_break: }
+ }
+ }
+ %% at least #2 occurrences
+ {
+ \__speg_match_gset_true:
+ \int_step_inline:nn {#2}
+ {
+ \__speg_match_apply_subject:n {#1}
+ \__speg_match_if_sub_success:TF
+ {
+ \__speg_match_gset_eq_subject:
+ \__speg_match_update_index:
+ }
+ {
+ \__speg_match_gset_false:
+ \prg_break:
+ }
+ }
+ \__speg_match_if_success:T
+ {
+ \bool_do_while:nn { \__speg_match_if_sub_success_p: }
+ {
+ \__speg_match_apply_subject:n {#1}
+ \__speg_match_if_sub_success:T
+ {
+ \__speg_match_gset_eq_subject:
+ \__speg_match_update_index:
+ }
+ }
+ }
+ }
+ \__speg_tracing_match_stop:n { Repeat }
+ \__speg_match_gdecr_level:
+ }
+\cs_generate_variant:Nn \speg_repeat:nnn {nnV}
+\cs_set_eq:NN \SpegRepeat \speg_repeat:nnn
+
+%% #1: string to match; #2: subject string.
+\cs_new_protected:Npn \speg_p:nn #1 #2
+ {
+ \__speg_match_gincr_level:
+ \__speg_tracing_match_start:n { P {#1} {#2} }
+ \__speg_match_gset_true:
+ \__speg_match_gset_index:n {1}
+ \__speg_match_gset_subject:n {#2}
+ \str_map_inline:nn {#1}
+ {
+ \__speg_match_gpop_subject:N \l_tmpa_str
+ \str_if_eq:VnTF \l_tmpa_str {##1}
+ {
+ \__speg_match_gincr_index:
+ }
+ {
+ \__speg_match_gset_false:
+ \str_map_break:
+ }
+ }
+ \__speg_tracing_match_stop:n { P }
+ \__speg_match_gdecr_level:
+ }
+\cs_generate_variant:Nn \speg_p:nn {nV}
+\cs_set_eq:NN \SpegP \speg_p:nn
+
+%% #1: number of characters; #2: subject string.
+\cs_new_protected:Npn \speg_q:nn #1 #2
+ {
+ \__speg_match_gincr_level:
+ \__speg_tracing_match_start:n { Q {#1} {#2} }
+ \int_compare:nNnTF { \str_count:n {#2} } < {#1}
+ {
+ \__speg_match_gset_false:
+ \__speg_match_gset_index:n {1}
+ }
+ {
+ \__speg_match_gset_true:
+ \__speg_match_gset_subject:e { \str_range:nnn {#2} {1 + #1} {-1} }
+ \__speg_match_gset_index:e { \int_eval:n {1 + #1} }
+ }
+ \__speg_tracing_match_stop:n { Q }
+ \__speg_match_gdecr_level:
+ }
+\cs_generate_variant:Nn \speg_q:nn {nV}
+\cs_set_eq:NN \SpegQ \speg_q:nn
+
+%% #1: character ranges; #2: subject string.
+\cs_new_protected:Npn \speg_r:nn #1 #2
+ {
+ \__speg_match_gincr_level:
+ \__speg_tracing_match_start:n { R {#1} {#2} }
+ \__speg_match_gset_false:
+ \__speg_match_gset_index:n {1}
+ \str_if_empty:nF {#2}
+ {
+ %% \l_tmpa_str is empty when #2 is empty
+ \__speg_match_gset_subject:n {#2}
+ \__speg_match_gpop_subject:N \l_tmpa_str
+ \__speg_r_aux:nn #1 {} {} \prg_break_point:
+ }
+ \__speg_tracing_match_stop:n { R }
+ \__speg_match_gdecr_level:
+ }
+\cs_generate_variant:Nn \speg_r:nn {nV}
+\cs_set_eq:NN \SpegR \speg_r:nn
+
+\cs_new_protected:Npn \__speg_r_aux:nn #1 #2
+ {
+ \tl_if_empty:nTF {#2}
+ { \prg_break: }
+ {
+ \bool_lazy_or:nnTF
+ { \int_compare_p:eNn { `\l_tmpa_str } < { `#1 } }
+ { \int_compare_p:eNn { `\l_tmpa_str } > { `#2 } }
+ { \__speg_r_aux:nn }
+ {
+ \__speg_match_gset_true:
+ \__speg_match_gset_index:n {2}
+ \prg_break:
+ }
+ }
+ }
+
+%% #1: character set; #2: subject string.
+\cs_new_protected:Npn \speg_s:nn #1 #2
+ {
+ \__speg_match_gincr_level:
+ \__speg_tracing_match_start:n { S {#1} {#2} }
+ \tl_if_empty:nTF {#2}
+ {
+ \__speg_match_gset_false:
+ \__speg_match_gset_index:n {1}
+ }
+ {
+ \__speg_match_gset_subject:n {#2}
+ \__speg_match_gpop_subject:N \l_tmpa_str
+ \str_if_in:nVTF {#1} \l_tmpa_str
+ {
+ \__speg_match_gset_true:
+ \__speg_match_gset_index:n {2}
+ }
+ {
+ \__speg_match_gset_false:
+ \__speg_match_gset_index:n {1}
+ }
+ }
+ \__speg_tracing_match_stop:n { S }
+ \__speg_match_gdecr_level:
+ }
+\cs_generate_variant:Nn \speg_s:nn {nV}
+\cs_set_eq:NN \SpegS \speg_s:nn
+
+\int_new:N \l__speg_pos_int
+
+%% \SpegC: simple capture. #1: pattern; #2: subject string.
+\cs_new_protected:Npn \speg_c:nn #1 #2
+ {
+ \__speg_match_gincr_level:
+ \__speg_match_gset_subject:n {#2}
+ \__speg_tracing_match_start:n { C {#2} }
+ #1 {#2}
+ \__speg_match_if_sub_success:TF
+ {
+ \__speg_match_gset_true:
+ \__speg_match_gset_eq_index:
+ \__speg_match_gset_eq_subject:
+ \__speg_match_get_capture:nN {#2} \l_tmpa_str
+ \seq_gput_right:NV \g__speg_capture_seq \l_tmpa_str
+ %\seq_log:N \g__speg_capture_seq
+ }
+ {
+ \__speg_match_gset_false:
+ \__speg_match_gset_index:n {1}
+ }
+ \__speg_tracing_match_stop:n { C }
+ \__speg_match_gdecr_level:
+ }
+\cs_set_eq:NN \SpegC \speg_c:nn
+
+%% \SpegCp: position capture.
+\cs_new_protected:Npn \speg_cp:
+ {
+ \__speg_tracing_match_start:n { Cp }
+ \__speg_match_get_position:N \l__speg_pos_int
+ \seq_gput_right:Ne \g__speg_capture_seq { \int_use:N \l__speg_pos_int }
+ \__speg_tracing_match_stop:n { Cp }
+ }
+\cs_set_eq:NN \SpegCp \speg_cp:
+
+%%% -------------------------------------------------------
+%%> \section{Trace speg functions}
+%%% -------------------------------------------------------
+
+\NewDocumentCommand \SetSpegTracing { m }
+ {
+ \keys_set:nn { speg/tracing/main } {#1}
+ }
+
+\bool_new:N \g__speg_tracing_set_bool
+\bool_new:N \g__speg_tracing_match_bool
+
+\keys_define:nn { speg/tracing/main }
+ {
+ +set .code:n = \bool_gset_true:N \g__speg_tracing_set_bool,
+ -set .code:n = \bool_gset_false:N \g__speg_tracing_set_bool,
+ +match .code:n = \bool_gset_true:N \g__speg_tracing_match_bool,
+ -match .code:n = \bool_gset_false:N \g__speg_tracing_match_bool,
+ all .code:n = \__speg_enable_all_tracings:,
+ none .code:n = \__speg_disable_all_tracings:,
+ }
+
+\cs_new_protected_nopar:Npn \__speg_enable_all_tracings:
+ {
+ \bool_gset_true:N \g__speg_tracing_set_bool
+ \bool_gset_true:N \g__speg_tracing_match_bool
+ }
+
+\cs_new_protected_nopar:Npn \__speg_disable_all_tracings:
+ {
+ \bool_gset_false:N \g__speg_tracing_set_bool
+ \bool_gset_false:N \g__speg_tracing_match_bool
+ }
+
+\cs_new_protected:Npn \__speg_tracing:nn #1 #2
+ {
+ \bool_if:cT { g__speg_tracing_ #1 _bool } {#2}
+ }
+
+\cs_new_protected:Npn \__speg_tracing_match_start:n #1
+ {
+ \__speg_tracing:nn {match}
+ {
+ \iow_log:e
+ {
+ \prg_replicate:nn { (\g__speg_match_level_int - 1) * 2 } {~}
+ Start#1
+ }
+ }
+ }
+
+\cs_new_protected:Npn \__speg_tracing_match_stop:n #1
+ {
+ \__speg_tracing:nn {match}
+ {
+ \iow_log:e
+ {
+ \prg_replicate:nn { (\g__speg_match_level_int - 1) * 2 } {~}
+ Stop#1: \__speg_match_use_status:, \__speg_match_use_index:
+ %, {\__speg_match_use_subject:}
+ }
+ }
+ }
diff --git a/Master/tlpkg/bin/tlpkg-ctan-check b/Master/tlpkg/bin/tlpkg-ctan-check
index dc764cc5c2d..1863a05ef58 100755
--- a/Master/tlpkg/bin/tlpkg-ctan-check
+++ b/Master/tlpkg/bin/tlpkg-ctan-check
@@ -670,7 +670,8 @@ my @TLP_working = qw(
pdfpagediff pdfpages pdfpc pdfpc-movie pdfprivacy pdfrender pdfreview
pdfscreen pdfslide pdfsync
pdftex-quiet pdftexcmds pdftricks pdftricks2 pdfx pdfxup
- pecha pedigree-perl penlight penlightplus perception perfectcut perltex
+ pecha pedigree-perl pegmatch
+ penlight penlightplus perception perfectcut perltex
permute persian-bib
petiteannonce petri-nets pfarrei pfdicons
pgf pgf-blur pgf-interference pgf-periodictable pgf-pie
diff --git a/Master/tlpkg/tlpsrc/collection-latexextra.tlpsrc b/Master/tlpkg/tlpsrc/collection-latexextra.tlpsrc
index fd4a4cafa53..aa9ef435eca 100644
--- a/Master/tlpkg/tlpsrc/collection-latexextra.tlpsrc
+++ b/Master/tlpkg/tlpsrc/collection-latexextra.tlpsrc
@@ -1109,6 +1109,7 @@ depend pdfsync
depend pdfwin
depend pdfx
depend pecha
+depend pegmatch
depend perltex
depend permute
depend petiteannonce
diff --git a/Master/tlpkg/tlpsrc/pegmatch.tlpsrc b/Master/tlpkg/tlpsrc/pegmatch.tlpsrc
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/Master/tlpkg/tlpsrc/pegmatch.tlpsrc