\documentclass[AMA,STIX1COL]{WileyNJD-v2}

\articletype{Research Article}

\received{24 July 2019}
\revised{22 June 2020}
\accepted{30 June 2020}

\doi{10.1002/spe.2881}

\raggedbottom

\let\procedure\relax
\let\endprocedure\relax

\usepackage[utf8]{inputenc}
\usepackage{amsmath, amssymb, amsthm, amsfonts}
\usepackage{accents}
\usepackage{mathtools}
\usepackage{graphicx}
\usepackage{enumitem}
\usepackage[justification=centering]{caption}
\usepackage{url}
\usepackage[section]{placeins}
\usepackage{proof-at-the-end}
\usepackage{setspace}
\usepackage{tikz, pgfplots, pgfplotstable}
\usetikzlibrary{patterns}

\usepackage[noline, noend, nofillcomment, linesnumbered]{algorithm2e}
\SetArgSty{textnormal}
\newcommand\Xcommentfont[1]{\selectfont\textnormal{#1}}
\SetCommentSty{Xcommentfont}
\SetNoFillComment
\SetNlSty{textnormal}{}{}
\renewcommand{\algorithmcfname}{ALGORITHM}
\def\Centerline#1{\begin{centering}{#1}\end{centering}}\SetAlgoCaptionLayout{Centerline}


\let\oldnl\nl
\newcommand{\nonl}{\renewcommand{\nl}{\let\nl\oldnl}} % Remove line number for one line

\newcommand{\Xl}{\langle}
\newcommand{\Xr}{\rangle}
\newcommand{\Xm}{\langle\!\rangle}
\newcommand{\Xset}{\!\leftarrow\!}
\newcommand{\Xund}{\rule{.4em}{.4pt}}
\newcommand{\Xlb}{[\![}
\newcommand{\Xrb}{]\!]}
\newcommand{\Xmap}{\!\mapsto\!}
\newcommand{\XB}{\mathcal{B}}
\newcommand{\XD}{\mathcal{D}}
\newcommand{\XE}{\mathcal{E}}
\newcommand{\XF}{\mathcal{F}}
\newcommand{\XI}{\mathcal{I}}
\newcommand{\XPT}{\XP\!\XT}
\newcommand{\XIT}{\XI\!\XT}
\newcommand{\XIR}{\XI\!\XR}
\newcommand{\XL}{\mathcal{L}}
\newcommand{\XN}{\mathcal{N}}
\newcommand{\XM}{\mathcal{M}}
\newcommand{\XO}{\mathcal{O}}
\newcommand{\XP}{\mathcal{P}}
\newcommand{\XR}{\mathcal{R}}
\newcommand{\XS}{\mathcal{S}}
\newcommand{\XT}{\mathcal{T}}
\newcommand{\XX}{\mathcal{X}}
\newcommand{\YB}{\mathbb{B}}
\newcommand{\YC}{\mathbb{C}}
\newcommand{\YK}{\mathbb{K}}
\newcommand{\YF}{\mathbb{F}}
\newcommand{\YN}{\mathbb{N}}
\newcommand{\YT}{\mathbb{T}}
\newcommand{\YQ}{\mathbb{Q}}
\newcommand{\YP}{\mathbb{P}}
\newcommand{\YZ}{\mathbb{Z}}
\newcommand{\PT}{PT}
\newcommand{\PE}{P\!E}
\newcommand{\PR}{P\!R}
\newcommand{\IPT}{I\!PT}
\newcommand{\IRE}{I\!RE}
\newcommand{\Eps}{E\!ps}
\newcommand{\Sym}{S\!ym}
\newcommand{\Alt}{Alt}
\newcommand{\Cat}{C\!at}
\newcommand{\Rep}{Rep}
\newcommand{\Xop}{X\!op}
\newcommand{\fix}{f\!ix}
\newcommand{\isub}{\mathit{isub}}
\newcommand{\esub}{\mathit{esub}}

\newcommand{\Xstirling}[2]{\genfrac{\{}{\}}{0pt}{}{#1}{#2}}
\newcommand*{\Xbar}[1]{\overline{#1}}
\newcommand{\pnorm}[2]{\|{#1}\|^{pos}_{#2}}
\newcommand{\snorm}[2]{\|{#1}\|^{sub}_{#2}}

\DeclarePairedDelimiter\ceil{\lceil}{\rceil}
\DeclarePairedDelimiter\floor{\lfloor}{\rfloor}

\setlist{nosep}

\newenvironment{Xfig}
    {\par\medskip\noindent\minipage{\linewidth}\begin{center}}
    {\end{center}\endminipage\par\medskip}
\newenvironment{Xtab}
    {\par\medskip\noindent\minipage{\linewidth}\begin{center}}
    {\end{center}\endminipage\par\medskip}

\setlength{\parindent}{0pt}
\setlength{\belowcaptionskip}{-1em}


\begin{document}

\title{Efficient POSIX Submatch Extraction on NFA}

\author[1]{Angelo Borsotti}
\author[2]{Ulya Trofimovich}

\address[1]{\orgdiv{Department of Electronics, Information, and Bioengineering},
    \orgname{Polytechnic University of Milan},
    \orgaddress{\city{Milan}, \country{Italy}}. \\
}

\address[2]{\orgdiv{Department of Discrete Mathematics and Algorithmics},
    \orgname{Belarusian State University},
    \orgaddress{\city{Minsk}, \country{Belarus}}. \\
}

\corres{
    Angelo Borsotti \\
    \email{angelo.borsotti@mail.polimi.it} \\
\\
    Ulya Trofimovich \\
    \email{skvadrik@gmail.com} \\
}

\abstract[Summary]{
In this paper we study the performance of NFA-based POSIX submatch extraction algorithms.
We propose an algorithm that combines Laurikari tagged NFA and extended Okui-Suzuki disambiguation.
%
The algorithm works in worst-case $O(n \, m^2 \, t)$ time and $O(m^2)$ space (including preprocessing),
where $n$ is the length of input, $m$ is the size of the regular expression with bounded repetition expanded
and $t$ is the number of capturing groups and subexpressions that contain them.
%
On real-world benchmarks our algorithm performs close to the $O(n \, m \, t)$ complexity of leftmost-greedy matching,
although on artificial benchmarks it can be significantly slower.
%
We propose a lazy version of the algorithm that runs much faster, but requires $O(n \, m^2)$ space.
%
We show that the Kuklewicz algorithm is slower in practice,
and the backward matching algorithm proposed by Cox is incorrect.
}

\keywords{Regular Expressions, Parsing, Submatch Extraction, Finite-State Automata, POSIX}

\maketitle
\footnotetext{Preprint, the original is \textit{Softw Pract Exper. 2020;1-36. https://doi.org/10.1002/spe.2881}}

\section{Introduction}

Regular expressions (RE) are a convenient notation for describing regular languages.
RE syntax varies among different specifications and standards.
Most of the variations allow a more succinct representation, but do not increase the expressive power of RE.
In this paper we do not consider such trivial extensions, with the exception of bounded repetition,
which requires non-trivial changes in the algorithms and proofs.
We also do not consider extensions that bring RE beyond regular languages, such as backreferences.
%
The recognition problem for RE can be solved in $O(n \, m)$ time and $O(m)$ space,
where $n$ is the size of input and $m$ is the size of RE
(e.g. by simulation of a Thompson's NFA \cite{Tho68} \cite{ALSU07}).
The parsing problem is more difficult because it has to deal with ambiguity.
Ambiguity means that there are multiple ways to parse the input.
The preferred way is usually defined by a disambiguation policy.
There are two widely used policies: the Perl leftmost-greedy policy and the POSIX longest-match policy.
The Perl policy is defined in terms of RE structure; it admits a simple and efficient implementation.
The POSIX policy, on the other hand, is defined in terms of the structure of parse results;
it is difficult to implement compared to the Perl policy.
In this paper we focus on the POSIX policy.
%
The problem of submatch extraction is a special case of parsing: it has the same worst-case complexity,
but in practice specialized submatch extraction algorithms are faster than generic full parsing algorithms.
\\

We study NFA-based approaches to the problem.
Namely, we consider the algorithms proposed by Okui and Suzuki\cite{OS13}, Kuklewicz\cite{Kuk07} and Cox\cite{Cox09}.
Our experiments show that in general Okui-Suzuki approach is the most efficient one.
We combine it with the insights and useful techniques from other approaches
and suggest a few algorithmic and practical improvements of our own.
The proposed algorithm is thoroughly formalized.
%
We are aware of an alternative approach based on Brzozowski derivatives proposed by Sulzmann and Lu\cite{SL14},
but in our experience this approach is slower in practice and has worse algorithmic complexity (discussed in more detail below).
%
Both NFA-based and derivative-based approach can be used to construct DFA with POSIX semantics \cite{SL13} \cite{Bor15} \cite{Tro17}.
The resulting DFA are very fast, because the disambiguation is done at determinization time and there is no run-time overhead.
However, determinization is not always viable due to its exponential worst-case complexity.
The algorithm we propose in this paper is based on NFA, but it can also be used as a basis for DFA construction.
\\

We give an overview of existing algorithms, including some that are incorrect, but interesting from historical perspective.
The list is by no means exhaustive, but in our experience other approaches
produce incorrect results or require memory proportional to the length of input (e.g. the Glibc implementation\cite{Glibc}).

\subparagraph{Laurikari, 2001 (incorrect).}

Laurikari described an algorithm based on TNFA --- $\epsilon$-NFA with tagged transitions \cite{Lau01}.
He represented each submatch group with a pair of \emph{tags} (opening and closing).
Disambiguation is based on minimizing the value of opening tags and maximizing the value of closing tags, where
different tags have priority according to the POSIX subexpression hierarchy.
The algorithm gives incorrect results for REs with iteration subexpressions, such as \texttt{(a|aa)*} on a string \texttt{aa}
(it minimizes the value of the opening tag on the last iteration,
and fails to take into account the preceding iterations).
The reported time complexity is $O(n \, m \, c \, t \log(t))$, where
$n$ is the length of input,
$m$ is TNFA size,
$c$ is the time for comparing tag values
and $t$ is the number of tags.
Space complexity is $O(m \, t)$.
Notably, Laurikari used the idea of topological order to avoid worst-case exponential time of $\epsilon$-closure construction,
although his closure algorithm is not optimal, as shown in \cite{Tro17}.

\subparagraph{Kuklewicz, 2007.}

Kuklewicz fixed Laurikari algorithm by introducing \emph{orbit} tags for iteration subexpressions.
He gave only an informal description \cite{Kuk07}, but the algorithm was later formalized in \cite{Tro17}.
It works in the same way as the Laurikari algorithm,
except that the comparison of orbit tags takes into account the history of all iterations, not just the most recent one.
The key idea that allows the algorithm to execute in bounded memory
is to compress orbit tag histories in a matrix of size $t \times m$, where $m$ is the size of TNFA and $t$ is the number of tags.
$t$-Th row of the matrix represents the ordering of closure states with respect to $t$-th tag.
%(with possible ties --- different states may have the same order).
The matrix is updated at each step using the continuations of tag histories.
The algorithm requires $O(m \, t)$ space and $O(n \, m \, t \, (m + t \log(m)))$ time, where $n$ is the length of input
(we assume a worst-case optimal $O(m^2 \, t)$ algorithm for $\epsilon$-closure construction,
and an $O(m \log(m) \, t^2)$ matrix update, because for $t$ tags $m$ states are sorted with an $O(t)$ comparison function).
Kuklewicz disambiguation is combined with Laurikari determinization in construction of TDFA\cite{Tro17}.

\subparagraph{Cox, 2009 (incorrect).}

Cox came up with the idea of backward POSIX matching \cite{Cox09},
which is based on the observation that
reversing the longest-match rule simplifies the handling of iteration subexpressions:
instead of maximizing submatch from the first to the last iteration, one needs to maximize the iterations in reverse order.
This means that the disambiguation is always based on the most recent iteration,
removing the need to remember all previous iterations
(except for the backwards-first, i.e. the last one, which contains submatch result).
The algorithm tracks two pairs of offsets per each submatch group:
the \emph{active} pair (used for disambiguation)
and the \emph{result} pair.
It gives incorrect results under two conditions:
(1) ambiguous matches have equal offsets on some iteration,
and (2) disambiguation happens too late, when the active offsets have already been updated and the difference between ambiguous matches is erased.
We found that such situations may occur for two reasons.
First, the $\epsilon$-closure algorithm may compare ambiguous paths \emph{after} their join point,
when both paths have a common suffix with tagged transitions.
This is the case with the Cox prototype implementation \cite{Cox09}; for example, it gives incorrect results for \texttt{(aa|a)*} and string \texttt{aaaaa}.
Most of such failures can be repaired by exploring states in topological order,
but a topological order does not exist in the presence of $\epsilon$-loops.
The second reason is bounded repetition: ambiguous paths may not have an intermediate join point at all.
For example, in the case of \texttt{(aaaa|aaa|a)\{3,4\}} and string \texttt{aaaaaaaaaa}
we have matches \texttt{(aaaa)(aaaa)(a)(a)} and \texttt{(aaaa)(aaa)(aaa)}
with a different number of iterations.
Assuming that the bounded repetition is unrolled by chaining three sub-automata for \texttt{(aaaa|aaa|a)} and an optional fourth one,
by the time ambiguous paths meet both have active offsets \texttt{(0,4)}.
Despite the flaw, Cox algorithm is interesting: if somehow the delayed comparison problem was fixed, it would work.
The algorithm requires $O(m \, t)$ memory and $O(n \, m^2 \, t)$ time
(assuming a worst-case optimal closure algorithm),
where $n$ is the length of input,
$m$ it the size of RE
and $t$ is the number of submatch groups and subexpressions that contain them.

\subparagraph{Okui and Suzuki, 2013.}

Okui and Suzuki view the disambiguation problem from the point of comparison of parse trees \cite{OS13}.
Ambiguous trees have the same frontier of leaf symbols, but their branching structure is different.
Each subtree corresponds to a subexpression.
The \emph{norm} of a subtree is the number of alphabet symbols in it (a.k.a. submatch length).
The longest match corresponds to a tree in which the norm of each subtree in leftmost in-order traversal is maximized.
The clever idea of Okui and Suzuki is to relate the norm of subtrees to their \emph{height} (the distance from the root).
Namely, if we walk through the leaves of two ambiguous trees, tracking the height of each complete subtree,
then at some step the heights will diverge:
subtree with a smaller norm will already be complete, but the one with a greater norm will not.
The height of subtrees is easy to track by attributing it to parentheses and encoding in the automaton transitions.
Okui and Suzuki use PAT --- $\epsilon$-free position automaton with transitions labeled by sequences of parentheses.
Disambiguation is based on comparing parentheses along ambiguous PAT paths.
Similar to Kuklewicz, Okui and Suzuki avoid recording full-length paths
by pre-comparing them at each step and storing the comparison results in a pair of matrices indexed by PAT states.
The authors report complexity $O(n(m^2 + c))$, where
$n$ is the input length,
$m$ is the number of occurrences of the most frequent symbol in RE
and $c$ is the number of submatch groups and repetition operators.
Memory requirement is $O(m^2)$.
However, these estimates leave out precomputation of the precedence relation and the construction of PAT,
which may grow exponential in the size of RE:
the transformation from RE to PAT is ambiguity-preserving,
which means that there may be multiple transitions with different labels between a pair of PAT states.
For example, for RE \texttt{((a*)|(a*))\{k\}} there are $2^k$ different ways to match the empty string,
and consequently $2^k$ transitions between the initial and the final states of PAT.
Although such RE are unlikely in practice, RE engines should be able to handle them in reasonable time.
Okui-Suzuki disambiguation is combined with Berry-Sethi construction in parsing DFA\cite{Bor15}.

\subparagraph{Sulzmann and Lu, 2013.}

Sulzmann and Lu based their algorithm on Brzozowski derivatives \cite{SL14}
(correctness proof is given by Ausaf, Dyckhoff and Urban\cite{ADU16}).
The algorithm unfolds a RE into a sequence of derivatives
and then folds it back into a parse tree.
Each derivative is obtained from the previous one by consuming an input symbol in left-to-right order,
and each tree is built from the next tree by injecting a symbol in reversed right-to-left order.
In practice, Sulzmann and Lu fuse backward and forward passes,
which allows to avoid potentially unbounded memory usage on keeping all intermediate derivatives.
The algorithm is elegant in that it does not require explicit disambiguation:
by the definition of derivative, parse trees are ordered by the longest-match criterion.
Time and space complexity is not entirely clear.
In \cite{SL14} Sulzmann and Lu consider the size of RE as a constant.
In \cite{SL13} they give more precise estimates: $O(2^m \, t)$ space and $O(n \log(2^m) \, 2^m \, t^2)$ time,
where $m$ is the size of RE,
$n$ is the length of input
and $t$ the number of submatch groups (the authors do not differentiate between $m$ and $t$).
However, this estimate assumes worst-case $O(2^m)$ derivative size and on-the-fly DFA construction.
The authors also mention a better $O(m^2)$ theoretical bound for derivative size.
If we adopt this bound and exclude DFA construction, we get $O(m^2 \, t)$ memory requirement and $O(n \, m^2 \, t^2)$ time,
which seems reasonably close to (but worse than) NFA-based approaches.
\\

Our contributions are the following:
\begin{itemize}[itemsep=0.2em, topsep=0.5em]

    \item We extend Okui-Suzuki algorithm on partially ordered parse trees,
        which greatly reduces the disambiguation overhead.

    \item We extend Okui-Suzuki algorithm on the case of bounded repetition.

    \item We use Laurikari TNFA instead of Okui-Suzuki PAT, which avoids the potential blowout of PAT size.

    \item We introduce \emph{negative tags} that allow us to handle no-match and match cases uniformly.

    \item We consider $\epsilon$-closure construction as a shortest-path problem and use the Goldberg-Radzik algorithm.

    \item We give a fast $O(m^2)$ algorithm for updating the precedence matrix (where $m$ is the size of the $\epsilon$-closure).

    \item We provide a way to output either parse trees or POSIX-style offsets.

    \item We give a lazy version of the algorithm that is faster, but uses memory proportional to the size of input.

    \item We provide a C++ implementation of the described algorithms.
    \\[-0.5em]
\end{itemize}

The rest of this paper is arranged as follows.
In section \ref{section_main} we present the main idea and the skeleton of our algorithm.
In section \ref{section_formalization} we provide theoretical foundations for the rest of the paper.
After that, we go into specific details:
section \ref{section_closure} is concerned with $\epsilon$-closure construction,
section \ref{section_pathtree} discusses data structures used to represent TNFA paths,
section \ref{section_results} discusses possible output formats (parse trees or POSIX-style offsets),
section \ref{section_comparison} gives the core disambiguation algorithms,
section \ref{section_lazy} presents a lazy version of the algorithm,
and section \ref{section_tnfa} gives specific TNFA construction.
The remaining sections \ref{section_complexity}, \ref{section_benchmarks} and \ref{section_conclusions}
contain complexity analysis, benchmarks, conclusions and directions for future work.


\section{Skeleton of the algorithm}\label{section_main}

Our algorithm is based on four cornerstone concepts:
regular expressions, parse trees, parenthesized expressions and tagged NFA.
%
Following Okui and Suzuki \cite{OS13}, we give the interpretation of regular expressions as sets of parse trees
and define POSIX disambiguation semantics in terms of order on parse trees.
This definition reflects the POSIX standard, but it is too high-level to be used in practice.
%
From parse trees we go to their linearized representation --- parenthesized expressions.
We define an order on parenthesized expressions and show its equivalence to the order on parse trees.
The latter definition of order is more low-level and can be converted to an efficient disambiguation algorithm.
%
Finally, we construct TNFA and map parenthesized expressions to its paths,
which allows us to compare ambiguous paths using the algorithm for parenthesized expressions.
%
In this section we give the four basic definitions and the skeleton of the algorithm.
In the following sections we formalize the relation between different representations and fill in all the details.

    \begin{definition}
    \emph{Regular expressions (RE)} over finite alphabet $\Sigma$, denoted $\XR_\Sigma$:
    \begin{enumerate}
        \item
          Empty RE $\epsilon$ and
          unit RE $a$ (where $a \in \Sigma$) are in $\XR_\Sigma$.
        \item If $e_1, e_2 \in \XR_\Sigma$, then
          alternative $e_1 | e_2$,
          concatenation $e_1 \cdot e_2$,
          repetition $e_1^{n, m}$ (where $0 \leq n \leq m \leq \infty$), and
          submatch group $(e_1)$
          are in $\XR_\Sigma$.
          (Convention: $e_1 \cdot e_2$ may be shortened as $e_1 e_2$, and $e^{n,n}$ may be shortened as $e^n$.)
    \end{enumerate}
    \end{definition}


    \begin{definition}
    \emph{Parse trees (PT)} over finite alphabet $\Sigma$, denoted $\XT_\Sigma$:
    \begin{enumerate}
        \item
          Nil tree ${\bot}^i$,
          empty tree ${\epsilon}^i$ and
          unit tree $a^i$
          are in $\XT_\Sigma$,
          where $a \in \Sigma$ and $i \in \YZ$.
        \item If $t_1, \dots, t_n \in \XT_\Sigma$ (where $n \geq 1$) and $i \in \YZ$, then
          ${T}^i(t_1, \dots, t_n)$
          is in $\XT_\Sigma$.
    \end{enumerate}
    \end{definition}


    \begin{definition}
    \emph{Parenthesized expressions (PE)} over finite alphabet $\Sigma$, denoted $\XP_\Sigma$:
    \begin{enumerate}
        \item
            Nil expression $\Xm$,
            empty expression $\epsilon$ and
            unit expression $a$ (where $a \in \Sigma$)
            are in $\XP_\Sigma$.
        \item If $\alpha, \beta \in \XP_\Sigma$, then
            $\alpha \beta$ and
            $\Xl \alpha \Xr$
            are in $\XP_\Sigma$.
    \end{enumerate}
    \end{definition}


    \begin{definition} \label{def_tnfa}
    \emph{Tagged Nondeterministic Finite Automaton (TNFA)}
    is a structure $(\Sigma, Q, M, \Delta, q_0, q_f)$, where:
    \begin{itemize}
        \item[] $\Sigma$ is a finite set of symbols (\emph{alphabet})
        \item[] $Q$ is a finite set of \emph{states}
        \item[] $M$ is a function that maps \emph{tags} $m \in \YZ$ to tuples $(S, N)$,
        \begin{itemize}
            \item[] where $S \subset \YZ$ is a set of submatch groups,
                and $N \subset \YZ$ is a set of nested tags
        \end{itemize}
        \item[] $\Delta = \Delta^\Sigma \sqcup \Delta^\epsilon$ is the \emph{transition} relation,
            consisting of two parts:
        \begin{itemize}
            \item[] $\Delta^\Sigma$ contains untagged transitions on symbols of the form $(q_1, a, \epsilon, q_2)$
            \item[] $\Delta^\epsilon$ contains optionally tagged $\epsilon$-transitions with priority of the form $(q_1, n, m, q_2)$
            \begin{itemize}
                \item[] where $q_1, q_2 \in Q$,
                $a \in \Sigma$,
                $n \in \YZ$ is \emph{priority},
                $m \in \YZ \cup \{\epsilon\}$ is an optional tag
            \end{itemize}
        \end{itemize}
        \item[] $q_0 \in Q$ is the \emph{initial} state
        \item[] $q_f \in Q$ is the \emph{final} state
    \end{itemize}
    \end{definition}

Our definition of RE is extended with submatch operator and generalized repetition.
This is not just syntactic sugar: in POSIX \texttt{(a)(a)} is semantically different from \texttt{(a)\{2\}},
and \texttt{(a)} in not the same as \texttt{a}.
Parse trees have a special \emph{nil-tree} constructor
and an upper index, which allows us to distinguish between submatch and non-submatch subtrees.
Mirroring parse trees, parenthesized expressions have a \emph{nil-parenthesis}.
TNFA is in essence a nondeterministic finite-state transducer
which reads symbolic strings and transduces them to sequences of
\emph{tags} --- integer numbers that denote opening and closing parentheses of submatch groups.
%For $i$-th group, the opening tag is $2i - 1$ and the closing tag is $2i$ (where $i \in \YN$).
Tags on transitions can be negative, which represents the absence of match and corresponds to the nil-parenthesis $\Xm$ and the nil-tree $\bot$.
Transition priorities are used to impose specific order of TNFA traversal.
\\

We use the following notation in the algorithms throughout the paper
(with possible subscripts and diacritics):

\begin{itemize}[itemsep=0.2em, topsep=0.5em]
    \item Integer numbers, indexes and tags are denoted with $i$, $j$, $k$, $l$, $m$, $n$.

    \item Strings over $\Sigma$ are denoted with $w$ and $a_1 \hdots a_n$.

    \item REs and their variations are denoted with $e$.

    \item PTs are denoted with $t$, $s$ and $r$.

    \item PEs and PE fragments are denoted with $\alpha$, $\beta$, $\gamma$, $\delta$.

    \item TNFA is denoted with $F$, and its states are denoted with $q$.

    \item $U$ is the \emph{tag path tree} --- a data structure that stores tag sequences along TNFA paths.
        Individual paths are represented with \emph{tree indices} --- integer numbers, denoted with $u$.
        Index zero is the empty path.

    \item $P$ is the \emph{precedence matrix} --- a square matrix indexed by TNFA states,
        with elements in the $\{-1, 0, 1\}$ set, denoted with $p$.
        It is used for disambiguation and corresponds to the Okui-Suzuki $D$-matrix.
        The value of a matrix cell $P[q_1][q_2]$ equals $-1$ if the path to $q_1$
        precedes the path to $q_2$, $0$ if they are equal, or $1$ otherwise.

    \item $H$ is the \emph{height matrix} --- a square integer matrix indexed by TNFA states,
        with elements denoted with $h$.
        It contains auxiliary data used in the computation of $P$-matrix,
        and corresponds to the Okui-Suzuki $B$-matrix.

    \item Partial match results (parse trees or POSIX-style offsets) are denoted with $d$.

    \item $C$ is a set of \emph{configurations} $(q_2, q_1, u, d)$,
        where $q_2$ is a unique \emph{target} state,
        $q_1$ is the \emph{origin} state (an index in the $H$ and $P$ matrices),
        $u$ is the tagged path from $q_1$ to $q_2$ (an index in the $U$-tree),
        and $d$ is the partial match result.

    \item We use either subscript (as in $a_i$) or square brackets (as in $P[q_1][q_2]$) to denote the elements of sequences or matrices.

    \item We use the dot notation (as in $U[n].pred$) to access the elements of tuples and structures.

    \item We avoid global state and explicitly pass parameters to functions.
    \\[-0.5em]
\end{itemize}

Below is the skeleton of the matching algorithm:

\begin{algorithm}[H] \DontPrintSemicolon \SetKwProg{Fn}{}{}{} \SetAlgoInsideSkip{medskip}
\setstretch{0.8}
\Fn {$\underline{match \big( F = (\Sigma, Q, M, \Delta, q_0, q_f), \; a_1 \hdots a_n \big)} \smallskip$} {

    $H, P : \text{square integer matrices of size } |Q|$ \;
    $U = empty \Xund path \Xund tree()$ \;
    $C = \big\{ (q_0, \bot, 0, initial \Xund result(M)) \big\}$ \;

    \BlankLine
    \For {$i = \overline{1, n}$} {
        $C = closure(F, C, U, H, P)$ \;
        $C = update \Xund result(T, C, U, i, a_i)$ \;
        $(H, P) = update \Xund precedence(F, C, U, H, P)$ \;
        $C = \big\{ (q_2, q_1, 0, d) \mid (q_1, \Xund, \Xund, d) \in C \wedge (q_1, a_i, \epsilon, q_2) \in \Delta \big\}$ \;
        \If {$C = \emptyset$} {
            \Return $\bot$
        }
    }

    \BlankLine
    $C = closure(F, C, U, H, P)$ \;
    \If {$\exists (q, \Xund, u, d) \in C \mid q = q_f$} {
        \Return $f\!inal \Xund result (M, U, u, d, n)$
    }

    \BlankLine
    \Return $\bot$

    \BlankLine
}
\caption{TNFA simulation on a string.}\label{alg_match}
\end{algorithm}
\medskip

The $match$ algorithm takes a TNFA $F$ and a string $a_1 \hdots a_n$ as input.
%
During the initialization step it creates an empty $U$-tree and allocates $H$ and $P$ matrices
(initialization of their elements is not needed, as the initial values are not used).
The initial configuration set $C$ contains a single configuration
that consists of the initial TNFA state $q_0$, undefined origin state $\bot$, empty tagged path and initial match result.
%
The algorithm loops over the input characters $a_i$ until either all characters are matched,
or the configuration set $C$ becomes empty, indicating a match failure.
%
At each step the algorithm constructs $\epsilon$-closure of the current configuration set, extending the tagged paths in the $U$-tree,
updates the partial match result,
re-computes the precedence information in the $H$ and $P$ matrices,
and steps on TNFA transitions labeled with the current input symbol.
%
Finally, if all input symbols have been matched and the final configuration set contains a configuration with the final state $q_f$,
the algorithm terminates successfully and returns the final match result.
Otherwise it returns a failure.
\\

We intentionally leave some parts of the algorithm undefined in this section.
They will be addressed in detail in subsequent sections,
after we present the formal foundations of our algorithm in section \ref{section_formalization}:

\begin{itemize}[itemsep=0.2em, topsep=0.5em]
    \item Function $closure$ constructs $\epsilon$-closure of a configuration set $C$ (section \ref{section_closure}).
        Essentially, it builds shortest paths from the states in the current set,
        following $\epsilon$-transitions in TNFA
        and comparing paths by the POSIX criterion.

    \item Functions $empty \Xund path \Xund tree$, $extend \Xund path$ and $unroll \Xund path$
        are used with the $U$-tree
        (section \ref{section_pathtree}).

    \item Functions $initial \Xund result$, $update \Xund result$ and $f\!inal \Xund result$
        update match results (section \ref{section_results}).

    \item Function $update \Xund precedence$ computes the $H$ and $P$ matrices
        by performing a pairwise comparison of all configurations in $C$ (section \ref{section_comparison}).
        It is the key part of the algorithm that allows to compress precedence information about arbitrary long tagged paths in constant space.
        If the paths originating from the current configurations join at some future step,
        the $closure$ function will compare them using the information in $H$ and $P$.
        If, on the other hand, the paths do not join, then the comparison performed by $update \Xund precedence$ is redundant.
        Unfortunately, we cannot avoid such redundant comparisons,
        as we do not know in advance which configurations will spawn ambiguous paths.
        It is possible to use on-demand comparison instead of $update \Xund precedence$ (section \ref{section_lazy}),
        but that requires keeping arbitrary long tagged paths in memory.
    \\[-0.5em]
\end{itemize}


\section{Formalization}\label{section_formalization}

In this section we establish the relation between all intermediate representations.
For brevity all proofs have been moved to the appendix.
%
First of all, we rewrite REs in a form that separates submatch information from the RE structure:
instead of using parentheses to denote submatch groups,
it stores submatch information for each subexpression in the form of an \emph{implicit submatch index} and a set of \emph{explicit submatch indices}.
Explicit indices enumerate submatch groups in RE:
an empty set $\emptyset$ means that the subexpression is not a submatch group,
and a set with multiple indices means that the subexpression is enclosed in multiple submatch groups, as in $((e))$.
Implicit indices enumerate all subexpressions that affect disambiguation, in top-down and left-to-right order
(this includes submatch groups and subexpressions that contain nested or sibling submatch groups).
The above representation reflects the POSIX standard, which states that
submatch extraction applies only to parenthesized subexpressions,
but the disambiguation rules apply to all subexpressions regardless of parentheses.

    \begin{definition}\label{defire}
    \emph{Indexed regular expressions (IRE)} over finite alphabet $\Sigma$, denoted $\XIR_\Sigma$:
    \begin{enumerate}
        \item
          $\Eps(i, J)$ and $\Sym(i, J, \alpha)$ are in $\XIR_\Sigma$,
          where $\alpha \in \Sigma$,
          $i \in \YZ$
          and $J \subseteq \YZ$.

        \item If $e_1, e_2 \in \XIR_\Sigma$ then
          alternative $\Alt(i, J, e_1, e_2)$,
          concatenation $\Cat(i, J, e_1, e_2)$ and
          repetition $\Rep(i, J, e_1, n, m)$
          are in $\XIR_\Sigma$, where
          $i \in \YZ$,
          $J \subseteq \YZ$
          and $n, m \in \YZ: 0 \leq n \leq m \leq \infty$.
    \end{enumerate}
    \end{definition}

Here $i$ is the implicit submatch index
and $J$ is the set of explicit submatch indices, denoted $\isub(e)$ and $esub(e)$ respectively.
Function $\IRE: \XR_\Sigma \rightarrow \XIR_\Sigma$ transforms RE into IRE.
It is defined via a composition of two functions.
Function $mark: \YZ \times \XR_\Sigma \longrightarrow \YZ \times \XIR_\Sigma$
transforms RE into IRE with implicit indices in the boolean range ($1$ if a subexpression affects disambiguation, otherwise $0$)
and computes explicit index sets. It tracks the maximal used value of the explicit index.
Function $enum: \YZ \times \XIR_\Sigma \longrightarrow \YZ \times \XIR_\Sigma$
substitutes boolean implicit indices with consecutive numbers. It tracks the maximal used value of the implicit index.
Auxiliary function $\fix: \YZ \times 2^{\YZ} \times \XIR_\Sigma \longrightarrow \XIR_\Sigma$
applies its input submatch indices to the top-level indices of IRE.

\begin{align*}
&\begin{aligned}
    mark &(j, \epsilon) = (j, \Eps(0, \emptyset)) \\[-0.2em]
    mark &(j, \alpha)   = (j, \Sym(0, \emptyset, \alpha)) \\[-0.2em]
    %
    mark &(j, e_1 \mid e_2) = (j'', \Alt(i, \emptyset, \fix(i, \emptyset, e'_1), \fix(i, \emptyset, e'_2) )) \\[-0.2em]
        &\text{where }            (j',  e'_1) = mark(j,  e_1) \\[-0.2em]
        &\space{\hphantom{where }}(j'', e'_2) = mark(j', e_2) \\[-0.2em]
        &\space{\hphantom{where }}i = \isub(e'_1) \vee \isub(e'_2) \\[-0.2em]
    %
    mark &(j, e_1 \cdot e_2) = (j'', \Cat(i, \emptyset, \fix(i, \emptyset, e'_1), \fix(i, \emptyset, e'_2) )) \\[-0.2em]
        &\text{where }            (j',  e'_1) = mark(j,  e_1) \\[-0.2em]
        &\space{\hphantom{where }}(j'', e'_2) = mark(j', e_2) \\[-0.2em]
        &\space{\hphantom{where }}i = \isub(e'_1) \vee \isub(e'_2) \\[-0.2em]
    %
    mark &(j, e^{n, m}) = (j', \Rep(\isub(e'), \emptyset, e', n, m)) \\[-0.2em]
        &\text{where } (j', e') = mark(j, e) \\[-0.2em]
    %
    mark &(j, (e)) = (j', \fix(1, \{j\}, e')) \\[-0.2em]
        &\text{where } (j', e') = mark(j + 1, e)
\end{aligned}
%
&&\begin{aligned}
    enum &(k, \Eps(i, J))         = (k + i, \Eps(k \cdot i, J))         \\[-0.2em]
    enum &(k, \Sym(i, J, \alpha)) = (k + i, \Sym(k \cdot i, J, \alpha)) \\[-0.2em]
    %
    enum &(k, \Xop(i, J, e_1, e_2)) \mid_{\Xop \in \{\Alt, \Cat\}} = (k'', \Xop(k \cdot i, J, e'_1, e'_2)) \\[-0.2em]
        &\text{where }            (k', e'_1) = enum(k + i, e_1) \\[-0.2em]
        &\space{\hphantom{where }}(k'', e'_2) = enum(k',   e_2) \\[-0.2em]
    %
    enum &(k, \Rep(i, J, e, n, m)) = (k', \Rep(k \cdot i, J, e', n, m)) \\[-0.2em]
        &\text{where } (k', e') = enum(k + i, e) \\[-0.2em]
    %
    \\[-0.2em]
    \fix (k&, K, \Eps(i, J))         = \Eps(i \!\vee\! k, J \!\cup\! K)         \\[-0.2em]
    \fix (k&, K, \Sym(i, J, \alpha)) = \Sym(i \!\vee\! k, J \!\cup\! K, \alpha) \\[-0.2em]
    \fix (k&, K, \Xop(i, J, e_1, e_2)) \mid_{\Xop \in \{\Alt, \Cat\}} = \Xop(i \!\vee\! k, J \!\cup\! K, e_1, e_2) \\[-0.2em]
    \fix (k&, K, \Rep(i, J, e, n, m)) = \Rep(i \!\vee\! k, J \!\cup\! K, e, n, m) \\[-0.2em]
    %
    \\[-0.2em]
    \IRE&(e) = e'' \text{ where }(\Xund, e') = mark(1, e), \, (\Xund, e'') = enum(1, e')
\end{aligned}
\end{align*}
\medskip

A step-by-step example of constructing an IRE from a RE is given on figure \ref{fig:mark_and_enum}.
\\

The relation between REs and PTs is given by the operator $\PT : \XIR_\Sigma \rightarrow 2^{\XT_\Sigma}$.
Each IRE denotes a set of PTs.
%
We write $str(t)$ to denote the string formed by concatenation of all alphabet symbols in the left-to-right traversal of $t$,
and $\PT(e, w)$ denotes the set $\big\{ t \in \PT(e) \mid str(t) = w \big\}$ of all PTs for IRE $e$ and a string $w$.
%
\begin{align*}
    \PT(\Eps(i, \Xund))         &= \{ {\epsilon}^{i} \} \\[-0.2em]
    \PT(\Sym(i, \Xund, \alpha)) &= \{ {\alpha}^{i} \}   \\[-0.2em]
    \PT(\Alt(i, \Xund, e_1, e_2)) &=
        \{ {T}^{i}(t, \bot^{\isub(e_2)}) \mid t \in \PT(e_1) \} \cup
        \{ {T}^{i}(\bot^{\isub(e_1)}, t) \mid t \in \PT(e_2) \}
    \\[-0.2em]
    \PT(\Cat(i, \Xund, e_1, e_2)) &=
        \{ {T}^{i}(t_1, t_2) \mid
            t_1 \in \PT(e_1),
            t_2 \in \PT(e_2)
        \}
    \\[-0.2em]
    \PT(\Rep(i, \Xund, e, n, m)) \mid_{n = 0} &=
        \{ {T}^{i}(t_1, \dots, t_m) \mid t_k \in \PT(e), 1 \leq k \leq m \}
            \cup \{ {T}^{i}(\bot^{\isub(e)}) \}
    \\[-0.2em]
    \PT(\Rep(i, \Xund, e, n, m)) \mid_{n > 0} &=
        \{ {T}^{i}(t_n, \dots, t_m) \mid t_k \in \PT(e), n \leq k \leq m \}
\end{align*}

Following Okui and Suzuki, we assign \emph{positions} to the nodes of IRE and PT.
The root position is $\Lambda$, and position of the $i$-th subtree of a tree with position $x$ is $x.i$
(we shorten $\|t\|_\Lambda$ as $\|t\|$).
The \emph{length} of position $x$, denoted $|x|$, is defined as $0$ for $\Lambda$ and $|x| + 1$ for $x.i$.
The subtree of a tree $t$ at position $x$ is denoted $t|_x$.
Position $x$ is a \emph{prefix} of position $y$ iff $y = x.x'$ for some $x'$,
and a \emph{proper prefix} if additionally $x \neq y$.
Position $x$ is a \emph{sibling} of position $y$ iff $y = z.i, x = z.j$ for some $z$ and $i,j \in \YN$.
Positions are ordered lexicographically.
The set of all positions of a tree $t$ is denoted $Pos(t)$.
Additionally, we define a set of \emph{submatch positions} as
$Sub(t) = \big\{ x \in Pos(t) \mid \exists i \neq 0, s: t|_x = s^i\big\}$ ---
a subset of $Pos(t)$ that contains positions of subtrees with nonzero implicit submatch index.
Intuitively, this is the set of positions important for disambiguation:
in the case of ambiguity we do not need to consider the full trees,
just the relevant parts of them.
%
PTs have two definitions of norm, one for $Pos$ and one for $Sub$,
which we call \emph{p-norm} and \emph{s-norm} respectively.

\begin{figure}[t!]
\includegraphics[width=\linewidth]{img/mark_and_enum.pdf}
\vspace{-1em}
\caption{
IRE for RE $(((\epsilon|a^{0,\infty}))(a|\epsilon)^{0,3})$
and examples of PTs for string $a$
with S-norm in parentheses.
}\label{fig:mark_and_enum}
\end{figure}

\FloatBarrier

    \begin{definition}\label{tnorm_of_PTs}
    The \emph{p-norm} and \emph{s-norm} of a PT $t$ at position $x$ are:
    \begin{align*}
        \pnorm{t}{x} =
            \begin{cases}
                -1          &\text{if } x \in Pos(t) \text{ and } t|_x = \bot^i  \\[-0.2em]
                |str(t|_x)| &\text{if } x \in Pos(t) \text{ and } t|_x \neq \bot^i \\[-0.2em]
                \infty      &\text{if } x \not\in Pos(t)
            \end{cases}
    \quad\quad\quad
        \snorm{t}{x} =
            \begin{cases}
                -1          &\text{if } x \in Sub(t) \text{ and } t|_x = \bot^i  \\[-0.2em]
                |str(t|_x)| &\text{if } x \in Sub(t) \text{ and } t|_x \neq \bot^i \\[-0.2em]
                \infty      &\text{if } x \not\in Sub(t)
            \end{cases}
    \end{align*}
    \end{definition}

Generally, the norm of a subtree means the number of alphabet symbols in its leaves, with two exceptions.
First, for nil subtrees the norm is $-1$: intuitively, they have the lowest ``ranking'' among all possible subtrees.
Second, for nonexistent subtrees (those with positions not in $Pos(t)$) the norm is infinite.
This may seem counter-intuitive at first, but it makes sense in the presence of REs with empty repetitions.
According to POSIX, optional empty repetitions are not allowed, and our definition reflects this:
if a tree $s$ has a subtree $s|_x$ corresponding to an empty repetition,
and another tree $t$ has no subtree at position $x$,
then the infinite norm $\|t\|_x$ ``outranks'' $\|s\|_x$.
We define two orders on PTs:

    \begin{definition}[P-order on PTs]
    \label{total_order_on_PTs}
    Given parse trees $t, s \in PT(e, w)$ for some IRE $e$ and string $w$, we say that $t <_x s$ w.r.t. \emph{decision position} $x$
    iff $\pnorm{t}{x} > \pnorm{s}{x}$ and $\pnorm{t}{y} = \pnorm{s}{y} \; \forall y < x$.
    We say that $t < s$ iff $t <_x s$ for some $x$.
    \end{definition}

    \begin{definition}[S-order on PTs]
    \label{partial_order_on_PTs}
    Given parse trees $t, s \in PT(e, w)$ for some IRE $e$ and string $w$, we say that $t \prec_x s$ w.r.t. \emph{decision position} $x$ % $x \in Sub(t) \cup Sub(s)$
    iff $\snorm{t}{x} > \snorm{s}{x}$ and $\snorm{t}{y} = \snorm{s}{y} \; \forall y < x$.
    We say that $t \prec s$ iff $t \prec_x s$ for some $x$.
    \end{definition}

    \begin{definition}\label{incomparable_PTs}
    PTs $t$ and $s$ are \emph{incomparable}, denoted $t \sim s$,
    iff neither $t \prec s$, nor $s \prec t$.
    \end{definition}

\begin{theoremEnd}[restate, no link to proof, no link to theorem, category=theorem_porder_on_PTs]{theorem}
    \label{theorem_porder_on_PTs}
    P-order $<$ is a strict total order on $\PT(e, w)$ for any IRE $e$ and string $w$.
\end{theoremEnd}
\begin{proofEnd}
    We need to show that $<$ is transitive and trichotomous.
    \begin{itemize}[itemsep=0.3em, topsep=0.5em]
        \item[(1)]
            Transitivity: we need to show that $\forall t, s, r \in \PT(e, w): (t < s \wedge s < r) \implies t < r$.
            \\[0.5em]
            Let $t <_x s$ and $s <_y r$ for some positions $x$, $y$, and let $z = min (x, y)$.
            \\[0.5em]
            First, we show that $\pnorm{t}{z} > \pnorm{r}{z}$.
            If $x \leq y$, we have $\pnorm{t}{x} > \pnorm{s}{x}$ (implied by $t <_x s$)
            and $\pnorm{s}{x} \geq \pnorm{r}{x}$ (implied by $s <_y r \wedge x \leq y$),
            therefore $\pnorm{t}{x} > \pnorm{r}{x}$.
            Otherwise $x > y$, we have $\pnorm{t}{y} > \pnorm{r}{y}$ (implied by $s <_y r$)
            and $\pnorm{t}{y} = \pnorm{s}{y}$ (implied by $t <_x s \wedge y < x$),
            therefore $\pnorm{t}{y} > \pnorm{r}{y}$.
            \\[0.5em]
            Second, we show that $\forall z' < z : \pnorm{t}{z'} = \pnorm{r}{z'}$.
            We have $\pnorm{t}{z'} = \pnorm{s}{z'}$ (implied by $t <_x s \wedge z' < x$)
            and $\pnorm{s}{z'} = \pnorm{r}{z'}$ (implied by $s <_y r \wedge z' < y$),
            therefore $\pnorm{t}{z'} = \pnorm{r}{z'}$.

        \item[(2)]
            Trichotomy: we need to show that $\forall t, s \in \PT(e, w)$
            exactly one of $t < s$, $s < t$ or $t = s$ holds.
            Consider the set of positions where norms of $t$ and $s$ disagree
            $X = \{x \in Pos(t) \cup Pos(s) : \pnorm{t}{x} \neq \pnorm{s}{x}\}$.
            %
            \begin{itemize}[itemsep=0.5em]
                \item[(2.1)] First case: $X \neq \emptyset$.
                    We show that in this case exactly one of $t < s$ or $s < t$ is true
                    ($t \neq s$ is obvious).
                    \\[0.5em]
                    First, we show that at least one of $t < s$ or $s < t$ is true.
                    %
                    Let $x = min(X)$; it is well-defined since $X$ is non-empty, finite and lexicographically ordered.
                    %
                    For all $y < x$ we have $\pnorm{t}{y} = \pnorm{s}{y}$ (by definition of $x$
                    and because $\pnorm{t}{y} = \infty = \pnorm{s}{y}$ if $y \not\in Pos(t) \cup Pos(s)$).
                    %
                    Since $\pnorm{t}{x} \neq \pnorm{s}{x}$, we have either $t <_x s$ or $t <_x s$.
                    \\[0.5em]
                    Second, we show that at most one of $t < s$ or $s < t$ is true,
                    i.e. $<$ is asymmetric: $\forall t, s \in \PT(e, w) : t < s \implies s \not< t$.
                    %
                    Suppose, on the contrary, that $t <_x s$ and $s <_y t$ for some $x$, $y$.
                    Without loss of generality let $x \leq y$.
                    On one hand $t <_x s$ implies $\pnorm{t}{x} > \pnorm{s}{x}$.
                    But on the other hand $s <_y t \wedge x \leq y$ implies $\pnorm{t}{x} \leq \pnorm{s}{x}$.
                    Contradiction.

                \item[(2.2)] Second case: $X = \emptyset$.
                    We show that in this case $t = s$.
                    \\[0.5em]
                    We have $Pos(t) = Pos(s)$ --- otherwise there is a position with norm $\infty$ in only one of the trees.
                    Therefore $t$ and $s$ have identical node structure.
                    %
                    By lemma \ref{lemma_positions} any position in $t$ and $s$ corresponds to the same position in $e$.
                    %
                    Since any position in $e$ corresponds to a unique explicit submatch index,
                    it must be that submatch indices of all nodes in $t$ and $s$ coincide.
                    %
                    Consider some position $x \in Pos(t)$.
                    If $x$ corresponds to an inner node, then both $t|_x$ and $s|_x$ are of the form $T^i(\hdots)$.
                    Otherwise, $x$ corresponds to a leaf node, which can be either $\bot$ or $\epsilon$ or $\alpha$.
                    Since all three have different norms ($-1$, $0$ and $1$ respectively),
                    and since $\pnorm{t}{x} = \pnorm{s}{x}$, it must be that $t|_x$ and $s|_x$ are identical.
            \end{itemize}
    \end{itemize}
\end{proofEnd}
\vspace{-0.5em}

\begin{theoremEnd}[restate, no link to proof, no link to theorem, category=theorem_sorder_on_PTs]{theorem}
    \label{theorem_sorder_on_PTs}
    S-order $\prec$ is a strict weak order on $\PT(e, w)$ for any IRE $e$ and string $w$.
\end{theoremEnd}
\begin{proofEnd}
    We need to show that $\prec$ is asymmetric and transitive, and incomparability relation $\sim$ is transitive.
    \begin{itemize}[itemsep=0.2em, topsep=0.5em]
        \item[(1)]
            Asymmetry: we need to show that $\forall t, s \in \PT(e, w): t \prec s \implies s \not\prec t$.
            \\[0.5em]
            Suppose, on the contrary, that $t \prec_x s$ and $s \prec_y t$ for some $x$, $y$.
            Without loss of generality let $x \leq y$.
            On one hand $t \prec_x s$ implies $\snorm{t}{x} > \snorm{s}{x}$.
            But on the other hand $s \prec_y t \wedge x \leq y$ implies $\snorm{t}{x} \leq \snorm{s}{x}$.
            Contradiction.

        \item[(2)]
            Transitivity: we need to show that $\forall t, s, r \in \PT(e, w): (t \prec s \wedge s \prec r) \implies t \prec r$.
            \\[0.5em]
            Let $t \prec_x s$ and $s \prec_y r$ for some positions $x$, $y$, and let $z = min (x, y)$.
            \\[0.5em]
            First, we show that $\snorm{t}{z} > \snorm{r}{z}$.
            If $x \leq y$, we have $\snorm{t}{x} > \snorm{s}{x}$ (implied by $t \prec_x s$)
            and $\snorm{s}{x} \geq \snorm{r}{x}$ (implied by $s \prec_y r \wedge x \leq y$),
            therefore $\snorm{t}{x} > \snorm{r}{x}$.
            Otherwise $x > y$, we have $\snorm{t}{y} > \snorm{r}{y}$ (implied by $s \prec_y r$)
            and $\snorm{t}{y} = \snorm{s}{y}$ (implied by $t \prec_x s \wedge y < x$),
            therefore $\snorm{t}{y} > \snorm{r}{y}$.
            \\[0.5em]
            Second, we show that $\forall z' < z : \snorm{t}{z'} = \snorm{r}{z'}$.
            We have $\snorm{t}{z'} = \snorm{s}{z'}$ (implied by $t \prec_x s \wedge z' < x$)
            and $\snorm{s}{z'} = \snorm{r}{z'}$ (implied by $s \prec_y r \wedge z' < y$),
            therefore $\snorm{t}{z'} = \snorm{r}{z'}$.

        \item[(3)]
            Transitivity of incomparability: we need to show that $\forall t, s \in \PT(e, w): (t \sim s \wedge s \sim r) \implies t \sim r$.
            \\[0.5em]
            By forward implication of lemma \ref{lemma_incomparability_equivdef}
            $t \sim s \Rightarrow \forall x : \snorm{t}{x} = \snorm{s}{x}$ and
            $s \sim r \Rightarrow \forall x : \snorm{s}{x} = \snorm{r}{x}$, therefore
            $(t \sim s \wedge s \sim r) \Rightarrow \forall x : \snorm{t}{x} = \snorm{r}{x} \Rightarrow t \sim u$
            by backward implication of lemma \ref{lemma_incomparability_equivdef}.
    \end{itemize}
\end{proofEnd}

The following theorem \ref{theorem_order_compat} establishes an important relation between P-order and S-order.
P-order is total, and there is a unique $<$-minimal tree $t_{min}$.
S-order is partial, it partitions all trees into equivalence classes
and there is a whole class of $\prec$-minimal trees $T_{min}$
(such trees coincide in submatch positions, but differ in some non-submatch positions).
Theorem \ref{theorem_order_compat} shows that $t_{min} \in T_{min}$.
This means that P-order and S-order ``agree'' on the notion of minimal tree:
we can continuously narrow down $T_{min}$ until we are left with $t_{min}$.
%
Note that this doesn't mean that P-order is an extension of S-order:
the two orders may disagree.
For example, consider trees $t$ and $r$ on figure\ref{fig:mark_and_enum}:
on one hand $t \prec_{2.2} r$, because $\snorm{t}{2.2} = \infty > 0 = \snorm{r}{2.2}$ and s-norms at all preceding submatch positions agree;
on the other hand $r <_{1.1} t$, because $\pnorm{t}{1.1} = -1 < 0 = \pnorm{r}{1.1}$
and p-norms at all preceding positions agree.

\begin{theoremEnd}[restate, no link to proof, no link to theorem, category=theorem_order_compat]{theorem}
    \label{theorem_order_compat}
    Let $t_{min}$ be the $<$-minimal tree in $\PT(e, w)$ for some IRE $e$ and string $w$,
    and let $T_{min}$ be the class of $\prec$-minimal trees in $\PT(e, w)$.
    Then $t_{min} \in T_{min}$.
\end{theoremEnd}
\begin{proofEnd}
    Consider any $t \in T_{min}$.
    From $t$ we can construct another tree $t'$ in the following way.
    Consider all positions $x \in Sub(t)$ which are not proper prefixes of another position in $Sub(t)$.
    For each such position, $t|_x$ is itself a PT for some sub-IRE $e'$ and substring $w'$: $t|_x \in \PT(e', w')$.
    Let $t'_{min}$ be the $<$-minimal tree in $\PT(e', w')$ and substitute $t|_x$ with $t'_{min}$.
    Let $t'$ be the tree resulting from all such substitutions
    (note that they are independent of the order in which we consider positions $x$).
    Since substitutions preserve s-norm at submatch positions, we have $t' \in T_{min}$.
    We will show that $t' = t_{min}$.
    \\[0.5em]
    Suppose, on the contrary, that $t' \neq t_{min}$.
    %
    Since $t_{min}$ is $<$-minimal, it must be that $t_{min} <_x t'$ for some decision position $x$.
    %
    By definition it means that $\pnorm{t_{min}}{x} > \pnorm{t'}{x}$ and $\pnorm{t_{min}}{y} = \pnorm{t'}{y} \; \forall y < x$.
    %
    It follows that all positions $y \leq x$ that are in $Pos(t_{min})$ are also in $Pos(t')$, otherwise we get a contradiction $\pnorm{t'}{y} = \infty > \pnorm{t_{min}}{y}$.
    By lemma \ref{lemma_positions} position $y$ corresponds to the same position in the IRE $e$ for both $t_{min}$ and $t'$.
    Consequently, any $y \leq x$ is either a submatch position in both $t_{min}$ and $t'$, or in neither of them.
    %
    Let $x'$ be the longest prefix of $x$ in $Sub(t')$.
    %
    Since $x' \leq x$, by the above reasoning $x'$ is also in $Sub(t_{min})$.
    All prefixes of a submatch position are also submatch positions,
    therefore for $y \leq x'$ s-norms coincide with p-norms: $\snorm{t_{min}}{y} = \pnorm{t_{min}}{y}$ and $\snorm{t'}{y} = \pnorm{t'}{y}$.
    %
    It follows that $x' \neq x$, otherwise $t_{min} <_x t'$ implies $t_{min} \prec_x t'$, which contradicts to $t' \in T_{min}$.
    %
    Therefore $x' < x$,
    and since $\pnorm{t_{min}}{y} = \pnorm{t'}{y} \; \forall y < x$ and s-norms coincide with p-norms,
    we have $\snorm{t_{min}}{y} = \snorm{t'}{y} \; \forall y \leq x'$.
    %
    %It must be that $x \not\in Sub(t') \cup Sub(t_{min})$ ---
    %otherwise $x$ must be a submatch position in both trees,
    %which implies that s-norms at all positions $y \leq x$ coincide with p-norms,
    %which implies $t_{min} \prec_x t'$,
    %which contradicts to $t' \in T_{min}$.
    %%
    %Therefore we can represent $x$ as $x'.x''$, where $x'$ is the longest proper prefix of $x$ in $Sub(t')$.
    %%
    %By the above reasoning, since $x' < x$ and $x' \in Sub(t')$, it must be that $x' \in Sub(t_{min})$,
    %and consequently s-norms at all positions $y \leq x'$ coincide with p-norms,
    %therefore $\snorm{t_{min}}{y} = \snorm{t'}{y} \; \forall y \leq x'$.
    %%
    Then by lemma \ref{lemma_subtrees} subtrees $t'|_{x'}$ and $t_{min}|_{x'}$ are comparable:
    $\exists e', w' : t'|_{x'}, t_{min}|_{x'} \in \PT(e', w')$.
    By construction of $t'$, subtree $t'|_{x'}$ is $<$-minimal in $\PT(e', w')$,
    but at the same time $t_{min} <_{x'.x''} t'$ implies $t_{min}|_{x'} <_{x''} t'|_{x'}$.
    Contradiction.
\end{proofEnd}

The following theorem \ref{theorem_submatch_refinement}
states that submatch refinement is a contiguous process:
adding more parentheses in RE does not cardinally change existing submatch results, it only adds new details.

\begin{theoremEnd}[restate, no link to proof, no link to theorem, category=theorem_order_compat]{theorem}
    \label{theorem_submatch_refinement}
    Let $e$, $e'$ be two REs, such that $e'$ is constructed from $e$ by adding a pair of parentheses around some subexpression,
    and let $\bar{e} = \IRE(e)$ and $\bar{e}' = \IRE(e')$.
    %
    If $T_{min}$, $T'_{min}$ are the classes of $\prec$-minimal trees
    in $\PT(\bar{e}, w)$ and $\PT(\bar{e}', w)$ respectively for some string $w$,
    then $T'_{min} \subseteq T_{min}$.
\end{theoremEnd}
\begin{proofEnd}
    First, note that adding parentheses in RE
    does not affect the set of IRE positions: we have $Pos(\bar{e}) = Pos(\bar{e}')$.
    Consequently, p-order on $\PT(\bar{e}, w)$ and $\PT(\bar{e}', w)$ is identical,
    and the $<$-minimal tree $t_{min}$ is the same in both cases.
    By theorem \ref{theorem_order_compat} we have $t_{min} \in T_{min}$ and $t_{min} \in T'_{min}$,
    therefore the classes of $\prec$-minimal trees have at least one common tree: $t_{min} \in T_{min} \cap T'_{min}$
    \\[0.5em]
    %
    Second, note that adding parentheses in RE
    may only increase the set of IRE submatch positions: we have $Sub(\bar{e}) \subseteq Sub(\bar{e}')$.
    %
    Let $S$, $S'$ denote $Sub(t_{min})$ for $\bar{e}$ and $\bar{e}'$ respectively,
    then we have $S \subseteq S'$.
    \\[0.5em]
    %
    Consider any tree $t \in T'_{min}$.
    It is incomparable with $t_{min}$ in the s-order on $\PT(\bar{e}', w)$,
    therefore by lemma \ref{lemma_incomparability_equivdef} (forward implication)
    all s-norms at positions in $S'$ in $t$ and $t_{min}$ are equal: $\snorm{t}{x} = \snorm{t_{min}}{x} \; \forall x \in S'$.
    Since $S \subseteq S'$, it follows that $\snorm{t}{x} = \snorm{t_{min}}{x} \; \forall x \in S$.
    Therefore by lemma \ref{lemma_incomparability_equivdef} (backward implication)
    $t$ is incomparable with $t_{min}$ in the s-order on $\PT(\bar{e}, w)$,
    and consequently $t \in T_{min}$.
    %
    Since $t$ is an arbitrary tree in $T'_{min}$, it follows that $T'_{min} \subseteq T_{min}$.
\end{proofEnd}

Following the idea of Okui and Suzuki,
we go from comparison of parse trees to comparison of their linearized representation --- parenthesized expressions.
Parenthesis $\Xl$ is opening, and
parenthesis $\Xr$ is closing;
the \emph{nil}-parenthesis $\Xm$ is both opening and closing.
For convenience we sometimes annotate parentheses with \emph{height},
which we define as the number of preceding opening parentheses (including this one)
minus the number of preceding closing parentheses (including this one).
Explicit height annotations allow us to consider PE fragments in isolation
without losing the context of the whole expression.
However, height is not a part of parenthesis itself,
and it is not taken into account when comparing the elements of PEs.
Function $\Phi : \YZ \times \XT_\Sigma \rightarrow \XP_\Sigma$ transforms PT at the given height into a PE:

    \begin{align*}
    \Phi_{h}(t^{i}) &= \begin{cases}
        &\\[-2em]
        str(t^{i})                                            &\text{if } i = 0 \\[-0.2em]
        \Xm_h                                                 &\text{if } i \neq 0 \wedge t = \bot \\[-0.2em]
        \Xl_{h+1} \Xr_h                                       &\text{if } i \neq 0 \wedge t = \epsilon \\[-0.2em]
        \Xl_{h+1} a \Xr_h                                     &\text{if } i \neq 0 \wedge t = a \in \Sigma \\[-0.2em]
        \Xl_{h+1} \Phi_{h+1}(t_1) \dots \Phi_{h+1}(t_n) \Xr_h &\text{if } i \neq 0 \wedge t = T(t_1, \dots, t_n)
    \end{cases}
    \end{align*}

For a given IRE $e$ and string $w$ the set of all PEs $\big\{ \Phi_{0}(t) \mid t \in \PT(e, w) \big\}$ is denoted $\PE(e, w)$.

    \begin{definition}[Frame representation of PEs]
    \label{def_pe_frames}
    A given PE $\beta$ can be represented as $\beta_0 a_1 \beta_1 \dots a_n \beta_n$,
    where $\beta_i$ is the $i$-th \emph{frame} --- a possibly empty sequence of parentheses between
    subsequent alphabet symbols $a_i$ and $a_{i+1}$, or the beginning and end of $\beta$.
    \end{definition}

    \begin{definition}[Comparable PE fragments]
    \label{def_pe_comparability}
    PE fragments $\alpha$ and $\beta$ are \emph{comparable}
    if they are prefixes of strings in $\PE(e, w)$ for some IRE $e$ and string $w$
    and have the same number of frames.
    \end{definition}

    \begin{definition}[Fork]
    The \emph{fork} of comparable PE fragments $\alpha$ and $\beta$ is the index of the first distinct pair of frames.
    \end{definition}

We use the following notation. For PE fragments $\alpha$ and $\beta$,
$\alpha \sqcap \beta$ denotes their longest common prefix,
and $\alpha \backslash \beta$ denotes the suffix of $\alpha$ after removing $\alpha \sqcap \beta$.
For a PE fragment $\alpha$,
$f\!irst(\alpha)$ denotes the first parenthesis in $\alpha$ (or $\bot$ if $\alpha$ is empty or begins with an alphabet symbol),
$lasth(\alpha)$ denotes the height of the last parenthesis in $\alpha$ (or $\infty$ if $\alpha$ is empty or begins with an alphabet symbol),
and $minh(\alpha)$ denotes the minimal height of parenthesis in $\alpha$ (or $\infty$ if $\alpha$ is empty or begins with an alphabet symbol).

    \begin{definition}[Traces]
    \label{def_traces}
    Let $\alpha$, $\beta$ be comparable PE prefixes, such that
    $\alpha = \alpha_0 a_1 \alpha_1 \dots a_n \alpha_n$,
    $\beta = \beta_0 a_1 \beta_1 \dots a_n \beta_n$ and $k$ is the fork.
    We define $trace (\alpha, \beta)$ as the sequence $(h_0, \dots, h_n)$, where:
    %
    \begin{align*}
    h_i = \begin{cases}
        -1 &\text{if } i < k \\[-0.2em]
        min (lasth (\alpha_i \sqcap \beta_i), minh(\alpha_i \backslash \beta_i)) &\text{if } i = k \\[-0.2em]
        min (h_{i-1}, minh(\alpha_i)) &\text{if } i > k
    \end{cases}
    \end{align*}

    We write $traces(\alpha, \beta)$ to denote $\big( trace (\alpha, \beta), trace (\beta, \alpha) \big)$.
    \end{definition}

    \begin{definition}\label{prec1}
    (Longest precedence.)
    Let $\alpha$, $\beta$ be comparable PE prefixes and
    $traces(\alpha, \beta) = \big( (h_0, \dots, h_n), (h'_0, \dots, h'_n) \big)$.
    Then $\alpha \sqsubset \beta \Leftrightarrow \exists i \leq n:
        \big( h_i > h'_i \big) \wedge
        \big( h_j = h'_j \; \forall j > i \big)$.
    If neither $\alpha \sqsubset \beta$, nor $\beta \sqsubset \alpha$,
    then $\alpha$, $\beta$ are \emph{longest-equivalent}: $\alpha \sim \beta$
    (note that in this case $h_i = h'_i$ for $1 \leq i \leq n$).
    \end{definition}

    \begin{definition}\label{prec2}
    (Leftmost precedence.)
    Let $\alpha$, $\beta$ be comparable PE prefixes, and let
    $x = first (\alpha \backslash \beta)$,
    $y = first (\beta \backslash \alpha)$.
    Then $\alpha \subset \beta \Leftrightarrow x < y$, where
    the set of possible values of $x$ and $y$ is ordered as follows:
    $\bot < \Xr < \Xl < \Xm$.
    \end{definition}

    \begin{definition}\label{pe_order}
    (Longest-leftmost precedence.)
    Let $\alpha$, $\beta$ be comparable PE prefixes, then
    $\alpha < \beta \Leftrightarrow
        \big( \alpha \sqsubset \beta \big) \vee
        \big( \alpha \sim \beta \wedge \alpha \subset \beta \big)$.
    \end{definition}

\begin{theoremEnd}[restate, no link to proof, no link to theorem, category=theorem_order_on_pe_same_as_on_pt]{theorem}
    \label{theorem_order_on_pe_same_as_on_pt}
    If $s, t \in \PT(e, w)$ for some IRE $e$ and string $w$, then
    $s \prec t \Leftrightarrow \Phi_{h}(s) < \Phi_{h}(t) \; \forall h$.
\end{theoremEnd}
\begin{proofEnd}
    Forward implication is given by lemma \ref{lemma_pe_less}.
    Backward implication:
    suppose, on the contrary, that $\Phi_{h}(s) < \Phi_{h}(t) \; \forall h$, but $s \not\prec t$.
    Since $\prec$ is a strict weak order (by theorem \ref{theorem_sorder_on_PTs}),
    it must be that either $s \sim t$
    (then $\Phi_{h}(s) = \Phi_{h}(t) \; \forall h$ by lemma \ref{lemma_pe_equiv}),
    or $t \prec s$
    (then $\Phi_{h}(t) < \Phi_{h}(s) \; \forall h$ by lemma \ref{lemma_pe_less}).
    Both cases contradict $\Phi_{h}(s) < \Phi_{h}(t) \; \forall h$,
    therefore assumption $s \not\prec t$ is incorrect.
\end{proofEnd}

Examples of PE comparison are shown on figures \ref{fig:pe2} and \ref{fig:pe}.

\begin{figure}[b!]
\vspace{-2em}
\includegraphics[width=\linewidth]{img/parenthesized_expressions_2.pdf}
\vspace{-2em}
\caption{
Example of pairwise frame-by-frame comparison
of all PEs for RE $(((a)^{1,3})^{1,3})^{1,3}$ and string $aaa$.\\
Table entry $(t_i, t_j)$ contains $traces(\Phi_0(t_i), \Phi_0(t_j))$.
}\label{fig:pe2}
\end{figure}

\begin{figure}%[t!]
\includegraphics[width=\linewidth]{img/parenthesized_expressions.pdf}
\vspace{-2em}
\caption{
Examples: (a) -- (d): four main rules of POSIX comparison.
}\label{fig:pe}
\end{figure}

%\FloatBarrier

Next, we go from comparison of PEs to comparison of TNFA paths.
We extend the notion of order from PEs to paths.

    \begin{definition}[Path]
    A \emph{path} in TNFA $(\Sigma, Q, T, \Delta, q_0, q_f)$
    is a sequence of transitions $\{(q_i, a_i, b_i, q_{i + 1})\}_{i=1}^{n-1} \subseteq \Delta$, where $n \in \YN$.
    Every path induces a string of alphabet symbols
    and a mixed string of alphabet symbols and tags that corresponds to a PE fragment:
    positive opening tags map to $\Xl$,
    positive closing tags map to $\Xr$,
    and negative tags map to $\Xm$.
    We write $q_1 \overset {w|\alpha} {\longrightarrow} q_n$
    to denote that a path from $q_1$ to $q_n$ induces
    an alphabet string $w$
    and a PE fragment $\alpha$.
    \end{definition}

    \begin{definition}[Path comparison]
    A path
    $\pi_1 = q_1 \overset {w|\alpha} {\longrightarrow} q_2$ is less than a path
    $\pi_2 = q_1 \overset {w|\beta} {\longrightarrow} q_3$,
    denoted $\pi_1 < \pi_2$, if $\alpha < \beta$.
    \end{definition}

    \begin{definition}[Minimal path]
    A path in TNFA for IRE $e$ is \emph{minimal} if it induces
    $\alpha = \PE(t)$ for some minimal tree $t \in \PT(e)$.
    \end{definition}

    \begin{definition}[Ambiguous paths]
    Paths
    $q_1 \overset {w|\alpha} {\longrightarrow} q_2$ and
    $q'_1 \overset {w'|\beta} {\longrightarrow} q'_2$
    are \emph{ambiguous}
    if $q_1=q'_1$, $q_2=q'_2$ and $w=w'$.
    \end{definition}

    \begin{definition}[Join point]
    For paths
    $\pi_1 = q_1 \overset {w_1|\alpha} {\longrightarrow} q \overset {w_2|\gamma} {\longrightarrow} q_2$ and
    $\pi_2 = q_1 \overset {w_1|\beta} {\longrightarrow} q \overset {w_3|\delta} {\longrightarrow} q_3$
    that have ambiguous prefixes $q_1 \overset {w_1|\alpha} {\longrightarrow} q$ and $q_1 \overset {w_1|\beta} {\longrightarrow} q$,
    state $q$ is a \emph{join point} (a state where ambiguous prefixes meet).
    \end{definition}

In order to justify our TNFA simulation algorithm,
we need to show that PEs induced by TNFA paths can be compared incrementally
(otherwise we would have to keep full-length PEs, which requires the amount of memory proportional to the length of input).
Justification of incremental comparison consists of two parts:
the following lemma \ref{lemma_incr_cmp_frames} justifies comparison between frames,
and lemmas \ref{lemma_closure_minpaths}, \ref{lemma_closure_noloop}, \ref{lemma_closure_rightdist}
in section \ref{section_closure} justify comparison at join points inside of one frame
(this is necessary as the number of paths in closure may be exponential in the number of states).

\begin{theoremEnd}[restate, no link to proof, no link to theorem, category=lemma_frames]{lemma}
[Frame-by-frame comparison of PEs]
    \label{lemma_incr_cmp_frames}
    If $\alpha$, $\beta$ are comparable PE prefixes,
    $c$ is an alphabet symbol and
    $\gamma$ is a single-frame PE fragment,
    then $\alpha < \beta$ implies $\alpha c \gamma < \beta c \gamma$.
\end{theoremEnd}
\begin{proofEnd}
    Let $\big((h_1, \dots, h_n), (h'_1, \dots, h'_n)\big) = traces(\alpha, \beta)$ where $n \geq 1$.
    Since $\alpha c \gamma$, $\beta c \gamma$ have one more frame than $\alpha$, $\beta$
    and the first $n$ frames are identical to frames of $\alpha$, $\beta$,
    we can represent $traces(\alpha c \gamma, \beta c \gamma)$
    as $\big((h_1, \dots, h_n, h_{n+1}), (h'_1, \dots, h'_n, h'_{n+1})\big)$.
    %
    \begin{itemize}[itemsep=0.2em, topsep=0.5em]
    \item[(1)]
        Case $\alpha \sim \beta \wedge \alpha \subset \beta$.
        In this case $h_i = h'_i \;\forall i \leq n$,
        therefore $h_{n+1} = min(h_n, minh(\gamma)) = min(h'_n, minh(\gamma)) = h'_{n+1}$
        and $\alpha c \gamma \sim \beta c \gamma$.
        Furthermore,
        $first (\alpha c \gamma \backslash \beta c \gamma) = first (\alpha \backslash \beta)$ and
        $first (\beta c \gamma \backslash \alpha c \gamma) = first (\beta \backslash \alpha)$,
        therefore $\alpha \subset \beta \implies \alpha c \gamma \subset \beta c \gamma$.
    \item[(2)]
        Case $\alpha \sqsubset \beta$.
        In this case $\exists j \leq n$ such that $h_j > h'_j$ and $h_i = h'_i \;\forall j < i \leq n$.
        We show that $\exists l \leq n + 1$ such that $h_l > h'_l$ and $h_i = h'_i \;\forall l < i \leq n + 1$,
        which by definition means that $\alpha c \gamma \sqsubset \beta c \gamma$.
        \begin{itemize}
        \item[(2a)]
            Case $j < n$.
            In this case $h_n = h'_n$ and
            $h_{n+1} = min(h_n, minh(\gamma)) = min(h'_n, minh(\gamma)) = h'_{n+1}$,
            therefore $l = j$.
        \item[(2b)]
            Case $j = n$ and $minh(\gamma) > h'_n$.
            In this case $h_n > h'_n$ and we have
            $h_{n+1} = min(h_n, minh(\gamma)) > h'_n$ and
            $h'_{n+1} = min(h'_n, minh(\gamma)) = h'_n$,
            therefore $h_{n+1} > h'_{n+1}$
            and $l = n + 1$.
        \item[(2c)]
            Case $j = n$ and $minh(\gamma) \leq h'_n$.
            In this case $h_n > h'_n$ and we have
            $h_{n+1} = min(h_n, minh(\gamma)) = minh(\gamma)$ and
            $h'_{n+1} = min(h'_n, minh(\gamma)) = minh(\gamma)$,
            therefore $h_{n+1} = h'_{n+1}$
            and $l = n$.
        \end{itemize}
    \end{itemize}
    In both cases $\alpha c \gamma < \beta c \gamma$.
\end{proofEnd}


\section{Closure construction}\label{section_closure}

The problem of constructing $\epsilon$-closure with POSIX disambiguation
can be formulated as a shortest path problem on a directed graph with weighted arcs.
In our case weight is not a number --- it is the PE fragment induced by the path.
%
We give two alternative implementations of $closure()$: $closure \Xund gor1()$ and $closure \Xund gtop()$.
The first algorithm, GOR1, is named after the well-known Goldberg-Radzik algorithm \cite{GR93}.
The second algorithm, GTOP, is an abbreviation of ``global topological order''.
%
Both algorithms have the usual structure of shortest-path finding algorithms.
Each algorithm starts with a set of initial configurations, an empty queue and an empty set of resulting configurations.
Initial configurations are enqueued and the algorithm loops until the queue becomes empty.
At each iteration it dequeues a configuration $(q_2, q_1, u, d)$ and scans $\epsilon$-transitions from state $q$.
For a transition $(q_2, \Xund, \alpha, q_3)$ it constructs a new configuration $(q_3, q_1, v, d)$
that combines $u$ and $\alpha$ in an extended path $v$.
If the resulting set already contains another configuration for state $q_3$,
the algorithm chooses the configuration which has a better path from POSIX perspective.
Otherwise it adds the new configuration to the resulting set.
If the resulting set has been changed, the new configuration is enqueued for further scanning.
Eventually all states in the $\epsilon$-closure are explored, no improvements can be made, and the algorithm terminates.
%
%Lemma \ref{lemma_closure_rightdist} allows us to skip comparison in non-join states (with in-degree 1), because
%any path to such state is formed by concatenation of the unique transition and the shortest known path to the previous state.
\\

The difference between GOR1 and GTOP is in the order they inspect configurations.
%
Both algorithms are based on the idea of topological ordering.
Unlike other shortest-path algorithms, their queuing discipline is based on graph structure, not on the distance estimates.
This is crucial, because we do not have any distance estimates:
paths can be compared, but there is no absolute ``POSIX-ness'' value that we can attribute to each path.
%
GOR1 is described in \cite{GR93}.
It uses two stacks and makes a number of passes;
each pass consists of a depth-first search on the admissible subgraph
followed by a linear scan of states that are topologically ordered by depth-first search.
The algorithm is one of the most efficient shortest-path algorithms \cite{CGR96}.
$n$-Pass structure guarantees worst-case complexity $O(n \, m)$ of the Bellman-Ford algorithm,
where $n$ is the number of states and $m$ is the number of transitions in the $\epsilon$-closure
(both can be approximated by TNFA size) \cite{CGGTW09}.
%
GTOP is a simple algorithm that maintains one global priority queue (e.g. a binary heap)
ordered by the topological index of states (for graphs with cycles, we assume reverse depth-first post-order).
Since GTOP does not have the $n$-pass structure, its worst-case complexity is not clear.
However, it is much simpler to implement
and in practice it performs almost identically to GOR1 on graphs induced by TNFA $\epsilon$-closures.
%
On acyclic graphs, both GOR1 and GTOP have linear $O(n + m)$ complexity.
\\

\begin{algorithm}[] \DontPrintSemicolon \SetKwProg{Fn}{}{}{} \SetAlgoInsideSkip{medskip}
\begin{multicols}{2}

    \newcommand \NOPASS {O\!F\!F}
    \newcommand \TOPSORT {T\!O\!P}
    \newcommand \LINEAR {L\!I\!N}
    \newcommand \INQUEUE {I\!N}
    \newcommand \OFFQUEUE {OUT}
    \newcommand \Xfalse {f\!al\!se}

    \setstretch{0.8}

    \Fn {$\underline{closure \Xund gor1(F\!=\!(\Sigma, Q, M, \Delta, q_0, q_f), C, U, H, P)} \smallskip$} {

        $X = (F, U, H, P$ \;
        $,\, topsort, linear$ \tcp{stacks of states}
        $,\, result $ \tcp{configuration or $\bot$ mapped to a state}
        $,\, status $ \tcp{status of a state (one of $\NOPASS, \TOPSORT, \LINEAR$)}
        $,\, active $ \tcp{a boolean indicating if a state needs rescan}
        $,\, etrans $ \tcp{$\epsilon$-transitions from a state, in priority order}
        $,\, next   $ \tcp{index of the active transition for a state}
        $)$ \tcp{context}

        \BlankLine
        $result(q) \equiv \bot$ \;
        $status(q) \equiv \NOPASS$ \;
        $active(q) \equiv \Xfalse$ \;
        $next(q)   \equiv 1$ \;

        \BlankLine
        \For {$c = (\Xund, q, \Xund, \Xund) \in C$ sorted by inverted $prec()$} {
            $result(q) = c$ \;
            $push(topsort, q)$
        }

        \BlankLine
        \While {$topsort$ is not empty} {

            \BlankLine
            \While {$topsort$ is not empty} {
                $q = pop(topsort)$ \;

                \If {$status(q) \neq \LINEAR$} {

                    $status(q) = \TOPSORT$ \;
                    $push(topsort, q)$ \;

                    \BlankLine
                    \If {$\neg scan(X, q, \Xfalse)$} {
                        $status(q) = \LINEAR$ \;
                        $pop(topsort)$ \;
                        $push(linear, q)$
                    }
                }
            }

            \BlankLine
            \While {$linear$ is not empty} {
                $q = pop(linear)$ \;

                \If {$active(q)$} {
                    $next(q) = 1$ \;
                    $active(q) = \Xfalse$ \;
                    $scan(X, q, true)$ \;
                }

                $status(q) = \NOPASS$ \;
            }
        }

        \BlankLine
        \Return $prune(X)$
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline{scan (X, q, all)} \smallskip$} {
        $any = \Xfalse$ \;

        \While {$next(q) \leq |etrans (q)|$} {
            $(q, \epsilon, \alpha, q') = etrans (q) [next(q)]$ \;
            $next(q) = next(q) + 1$ \;
            $(q'', q, u, d) = result(q)$ \;
            $c_1 = result(q')$ \;
            $c_2 = (q'', q', extend \Xund path (U, u, \alpha), d)$ \;

            \BlankLine
            \If {$c_1 \!=\! \bot \vee indegree(q') \!<\! 2 \vee less(X, c_2, c_1)$} {
                $result(q') = c_2$ \;
                \If {$status(q) = \NOPASS$} {
                    $any = true$ \;
                    $next(q') = 1$ \;
                    $push(topsort, q')$ \;
                    \lIf {$\neg all$} {$break$}
                }
                \lElse {
                    $active(q') = 1$
                }
            }
        }

        \Return $any$ \;
    }
    \BlankLine
    \BlankLine

\columnbreak

    \Fn {$\underline{closure \Xund gtop(F\!=\!(\Sigma, Q, M, \Delta, q_0, q_f), C, U, H, P)} \smallskip$} {

        $X = (F, U, H, P$ \;
        $,\, queue$ \tcp{priority queue of states}
        $,\, result $ \tcp{configuration or $\bot$ mapped to a state}
        $,\, status $ \tcp{status of a state (one of $\INQUEUE, \OFFQUEUE$)}
        $,\, etrans $ \tcp{$\epsilon$-transitions from a state, in priority order}
        $)$ \tcp{context}

        \BlankLine
        $result(q) \equiv \bot$ \;
        $status(q) \equiv \OFFQUEUE$ \;

        \BlankLine
        \For {$c_1 = (\Xund, q, \Xund, \Xund) \in C$} {
            $c_2 = result(q)$ \;
            \If {$c_2 \!=\! \bot \vee less(X, c_1, c_2)$} {
                $result(q) = c_1$ \;
                \If {$status(q) \neq \INQUEUE$} {
                    $k = topological \Xund index(q)$ \;
                    $insert \Xund with \Xund priority(queue, q, k)$ \;
                    $status(q) = \INQUEUE$ \;
                }
            }
        }

        \BlankLine
        \While {$queue$ is not empty} {

            $q = extract \Xund min(queue)$ \;
            $status(q) = \OFFQUEUE$ \;

            \BlankLine
            \For {$(q, \epsilon, \alpha, q') \in etrans (q)$} {
                $(q'', q, u, d) = result(q)$ \;
                $c_1 = result(q')$ \;
                $c_2 = (q'', q', extend \Xund path (U, u, \alpha), d)$ \;

                \BlankLine
                \If {$c_1 \!=\! \bot \vee indegree(q') \!<\! 2 \vee less(X, c_2, c_1)$} {
                    $result(q') = c_2$ \;
                    \If {$status(q') \neq \INQUEUE$} {
                        $k = topological \Xund index(q')$ \;
                        $insert \Xund with \Xund priority(queue, q', k)$ \;
                        $status(q') = \INQUEUE$ \;
                    }
                }
            }
        }

        \BlankLine
        \Return $prune(X)$
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline{prune (X)} \smallskip$} {
        \Return $\{ c \mid c = (\Xund, q, \Xund, \Xund) = result(q), q \in Q$ \;
        \Indp\Indp
        $\wedge \; (q = q_f \vee \exists (q', a, \Xund, \Xund) \in \Delta \mid q = q' \wedge a \in \Sigma) \}$ \;
        \Indm\Indm
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline{less (X, c_1, c_2)} \smallskip$} {
        $(\Xund, \Xund, l) = compare (c_1, c_2, U, H, P)$ \;
        \Return $l < 0$
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline{prec (X, c_1, c_2)} \smallskip$} {
        $(q_1, \Xund, \Xund, \Xund) = c_1$ \;
        $(q_2, \Xund, \Xund, \Xund) = c_2$ \;
        \Return $P[q_1][q_2] < 0$
    }
    \BlankLine
    \BlankLine

\end{multicols}
\vspace{1em}
\caption{
Closure algorithms GOR1 and GTOP.
Context components are addressed without qualification in the subroutines, e.g. $U$ rather than $X.U$.
Functions $compare ()$ and $extend \Xund path ()$ are defined in sections \ref{section_comparison} and \ref{section_pathtree}.
The definition of $push()$, $pop()$, $insert \Xund with \Xund priority()$, $extract \Xund min()$,
$indegree()$ and $topological \Xund index()$ is omitted for brevity.
}
\end{algorithm}

The general proof of correctness of shortest-path algorithms is out of the scope of this paper.
However, we need to justify the application of these algorithms to our problem.
%
In order to do that, we recall the framework for solving shortest-path algorithms based on \emph{closed semirings}
described in \cite{CLR} (section 26.4)
and show that our problem fits into this framework.
%
A \emph{semiring} is a structure $(\YK, \oplus, \otimes, 0, 1)$, where
$\YK$ is a set,
$\oplus \!\!:\!\! \YK \times \YK \rightarrow \YK$ is an associative and commutative operation with identity element $0$,
$\otimes \!\!:\!\! \YK \times \YK \rightarrow \YK$ is an associative operation with identity element $1$,
$\otimes$ distributes over $\oplus$
and $0$ is annihilator for $\otimes$.
%
Additionally, \emph{closed} semiring requires that
$\oplus$ is idempotent,
any countable $\oplus$-sum of $\YK$ elements is in $\YK$,
and associativity, commutativity, distributivity and idempotence apply to countable $\oplus$-sums.
Mohri generalizes this definition and notes that either left or right distributivity is sufficient \cite{Moh02}.
%
In our case $\YK$ is the set of closure paths without tagged $\epsilon$-loops:
the following lemma \ref{lemma_closure_minpaths} and \ref{lemma_closure_noloop}
show that, on one hand, paths with tagged $\epsilon$-loops are not minimal,
and on the other hand such paths are discarded by the algorithm,
so they can be removed from consideration.
%
Consequently $\YK$ is finite.
We have semiring $(\YK, min, \cdot, \bot, \epsilon)$, where
$min$ is POSIX comparison of ambiguous paths,
$\cdot$ is concatenation of paths at the join points
(subject to restriction that paths do not contain tagged $\epsilon$-loops
and remain within TNFA bounds --- concatenation of arbitrary paths is not in $\YK$),
$\bot$ corresponds to artificial infinitely long path,
and $\epsilon$ is the empty path.
%
It is easy to see that
$min$ is commutative and associative,
$\bot$ is identity for $min$ ($min(\pi, \bot) = min(\bot, \pi) = \pi$),
$\cdot$ is associative,
$\epsilon$ is identity for $\cdot$ ($\pi \cdot \epsilon = \epsilon \cdot \pi = \pi$),
and $\bot$ is an annihilator for $\cdot$ ($\pi \cdot \bot = \bot \cdot \pi = \bot$).
Right distributivity of $\cdot$ over $min$ for paths with at most one $\epsilon$-loop is given by lemma \ref{lemma_closure_rightdist}.
%
Idempotence holds because $min(\pi, \pi) = \pi$.
%
Since $\YK$ is finite, the properties for $\oplus$-sums over countable subsets are satisfied.

\begin{theoremEnd}[restate, no link to proof, no link to theorem, category=lemmata_closure]{lemma}
    \label{lemma_closure_minpaths}
    Minimal paths do not contain tagged $\epsilon$-loops.
\end{theoremEnd}
\begin{proofEnd}
    % Proof in terms of REs and correspondence between subexpression and loop
    % is a bit hard because of unrolling of repetition in TNFA construction
    % (there is no direct correspondence between sub-RE and sub-TNFA).
    %
    Suppose, on the contrary, that $\pi$ is a minimal path in some TNFA
    and that $\pi$ contains at least one tagged $\epsilon$-loop.
    We show that it is possible to construct another path $\pi'$ such that $\pi' < \pi$.
    %
    Path $\pi$ can be represented as
    $\pi = \pi_1 \pi_2 \pi_3$, where
    $\pi_1 = q_0 \overset {u | \alpha} {\longrightarrow} q$,
    $\pi_2 = q \overset {\epsilon | \beta} {\longrightarrow} q$ is the last tagged $\epsilon$-loop on $\pi$ and
    $\pi_3 = q \overset {v | \gamma} {\longrightarrow} q_f$.
    Let $\pi' = \pi_1 \pi_3$ be the path that is obtained from $\pi$ by removing the loop $\pi_2$.
    Paths $\pi$ and $\pi'$ consume the same input string $uv$
    and induce comparable PEs $\alpha \beta \gamma$ and $\alpha \gamma$.
    Let $\big( (h_1, \hdots, h_n), (h'_1, \hdots, h'_n) \big) = traces (\alpha \beta \gamma, \alpha \gamma)$
    and let $k$ be the index of the fork frame.
    %
    By construction of TNFA the loop $\pi_2$ must be contained in a sub-TNFA $f$
    for sub-IRE of the form $e = \Rep(\Xund, \Xund, e_1, 1, \infty)$,
    as this is the only looping TNFA construct --- see algorithm \ref{alg_tnfa}.
    Let $f_1$ be the sub-TNFA for $e_1$.
    Path $\pi$ enters $f$ and iterates through $f_1$ at least twice before leaving $f$
    (single iteration is not enough to create a loop by TNFA construction).
    Let $j$ be the total number of iterations through $f_1$,
    and let $i$ be the index of the last $\epsilon$-loop iteration
    (note that not all iterations are necessarily $\epsilon$-loops).
    Consider two possible cases:
    %
    \begin{enumerate}[itemsep=0.2em, topsep=0.5em]
    \item[(1)]
        Case $i = j$.
        In this case fork of $\alpha \beta \gamma$ and $\alpha \gamma$ happens immediately after $(i-1)$-th iteration:
        %
        \begin{alignat*}{10}
            \alpha \beta \gamma &= x_0 \Xl_{h-1} \;&&\; \Xl_h x_1 \Xr_h \hdots \Xl_h x_{i-1} \Xr_h \;&&\big|\; \Xl_h x_{i} \Xr_h \;&&\; \Xr_{h-1} x_{j+1} \\[-0.5em]
            \alpha \gamma       &= x_0 \Xl_{h-1} \;&&\; \Xl_h x_1 \Xr_h \hdots \Xl_h x_{i-1} \Xr_h \;&&\big|\;                   \;&&\; \Xr_{h-1} x_{j+1}
        \end{alignat*}
        %
        Since $x_i$ is an $\epsilon$-loop, it is contained in the fork frame of $\alpha \beta \gamma$.
        We have $minh (\beta) = h$ and $minh (\gamma) \leq h - 1$, therefore $h_k = h'_k \leq h - 1$.
        Subsequent frames $l > k$ (if any) are identical and thus $h_l = h'_l$.
        Furthermore, $first (\gamma) = \Xr < \Xl = first (\beta)$.
        Therefore $\alpha \beta \gamma \sim \alpha \gamma$ and $\alpha \gamma \subset \alpha \beta \gamma$.

    \item[(2)]
        Case $i < j$.
        In this case $(i + 1)$-th iteration cannot be an $\epsilon$-loop
        (because we assumed that $i$-th iteration is the last $\epsilon$-loop),
        therefore the fork of $\alpha \beta \gamma$ and $\alpha \gamma$ happens
        inside of $i$-th iteration of $\alpha \beta \gamma$
        and $(i + 1)$-th iteration of $\alpha \gamma$:
        %
        \begin{alignat*}{10}
            \alpha \beta \gamma &= x_0 \Xl_{h-1} \;&&\; \Xl_h x_1 \Xr_h \hdots \Xl_h x_{i-1} \Xr_h \Xl_h y_1 \;&&\big|\; y_2 \Xr_h \Xl_h x_{i+1} && \Xr_h \Xl_h x_{i+2} \Xr_h \hdots \Xl_h x_j \Xr_h \;&&\; \Xr_{h-1} x_{j+1} \\[-0.5em]
            \alpha \gamma       &= x_0 \Xl_{h-1} \;&&\; \Xl_h x_1 \Xr_h \hdots \Xl_h x_{i-1} \Xr_h \Xl_h y_1 \;&&\big|\; y_3                     && \Xr_h \Xl_h x_{i+2} \Xr_h \hdots \Xl_h x_j \Xr_h \;&&\; \Xr_{h-1} x_{j+1}
        \end{alignat*}
        %
        Here $y_1 y_2 = x_i$ and $y_1 y_3 = x_{i+1}$ ($i$-th iteration is missing from $\alpha \gamma$ by construction of $\pi'$).
        Fragment $y_2$ is part of the $\epsilon$-loop,
        therefore fork frame of $\alpha \beta \gamma$ contains a parenthesis $\Xr_h$ and we have $h_k = h$.
        On the other hand, $y_3$ contains alphabet symbols,
        because $x_{i+1}$ is not an $\epsilon$-loop and $y_1$ is a part of the $\epsilon$-loop.
        Therefore fork frame of $\alpha \gamma$ ends in $y_3$ and we have $h'_k > h$.
        %
        %In this case
        %fork frame of $\alpha \beta \gamma$ contains $y_2 \Xr_h \Xl_h$ fragment, because $y_2$ is part of the $\epsilon$-loop.
        %But the fork frame of $\alpha \gamma$ ends inside of $y_3$, because $(i+1)$-th repetiton is not an $\epsilon$-loop and must contain alphabet symbols.
        %Therefore at the fork frame $k$ we have $h_k = h$ and $h'_k > h$.
        %
        All subsequent frames $l > k$ are identical:
        if they contain parentheses of height less than $h$, then $h_l = h'_l < h$;
        otherwise $h_l \leq h$ and $h'_l > h$.
        Therefore $\alpha \gamma \sqsubset \alpha \beta \gamma$.
    \end{enumerate}
    %
    In both cases $\alpha \gamma < \alpha \beta \gamma$,
    which contradicts the fact that $\pi$ is a minimal path.
\end{proofEnd}
\vspace{-0.5em}

\begin{theoremEnd}[restate, no link to proof, no link to theorem, category=lemmata_closure]{lemma}
    \label{lemma_closure_noloop}
    GOR1 and GTOP discard paths with tagged $\epsilon$-loops.
\end{theoremEnd}
\begin{proofEnd}
    Suppose that GOR1/GTOP finds path $\pi_1 \pi_2$
    where $\pi_1 = q_0 \overset {s | \alpha} {\longrightarrow} q_1$
    and $\pi_2 = q_1 \overset {\epsilon | \gamma} {\longrightarrow} q_1$ is a tagged $\epsilon$-loop.
    Both algorithms construct new paths by exploring transitions from the end state of existing paths,
    so they can only find $\pi_1 \pi_2$ after they find $\pi_1$.
    Therefore when GOR1/GTOP finds $\pi_1 \pi_2$,
    it already has some shortest-path candidate $\pi'_1 = q_0 \overset {s | \alpha'} {\longrightarrow} q_1$
    and must compare ambiguous paths $\pi_1 \pi_2$ and $\pi'_1$.
    There are two possibilities: either $\alpha' = \alpha$
    or $\alpha' < \alpha$ (the latter means that the algorithm has found
    a shorter path to $q_1$ in between finding $\pi_1$ and $\pi_1 \pi_2$).
    Let $\big( (h_1, \hdots, h_k), (h'_1, \hdots, h'_k) \big) = traces (\alpha', \alpha \gamma)$.
    %
    \begin{itemize}[itemsep=0.2em, topsep=0.5em]
    \item[(1)]
        Case $\alpha' = \alpha$.
        Because $\alpha$ is a proper prefix of $\alpha \gamma$,
        fork happens at the last frame and we have
        $h_k = lasth(\alpha)$ and
        $h'_k = min (lasth(\alpha), minh(\gamma))$.
        If $lasth(\alpha) > minh(\gamma)$, then $h_k > h'_k$ and $\alpha \sqsubset \alpha \gamma$.
        Otherwise $h_k = h'_k$ and $\alpha \sim \alpha \gamma$,
        and we have $first(\alpha \backslash \alpha \gamma) = \bot$ and $first(\alpha \gamma \backslash \alpha) \neq \bot$,
        therefore $\alpha \subset \alpha \gamma$.
        In both cases $\alpha < \alpha \gamma$.

    \item[(2)]
        Case $\alpha' < \alpha$.
        Let $\big( (\sigma_1, \hdots, \sigma_k), (\sigma'_1, \hdots, \sigma'_k) \big) = traces (\alpha', \alpha)$.
        We have $h_k = \sigma_k$ and $h'_k = min (\sigma'_k, minh(\gamma)) \leq \sigma_k$.
        If $minh(\gamma) < \sigma'_k$ then $h_k > h'_k$ and $\alpha' \sqsubset \alpha \gamma$.
        Otherwise $h'_k = \sigma'_k$.
        If $\alpha' \sqsubset \alpha$ then $\alpha' \sqsubset \alpha \gamma$.
        Otherwise $\alpha' \sim \alpha$ and $\alpha' \subset \alpha$.
        None of $\alpha$ and $\alpha'$ is a proper prefix of the other
        because otherwise the longer path has an $\epsilon$-loop through $q_1$, which contradicts our assumption about $\pi_1$ and $\pi'_1$.
        Therefore $first (\alpha' \backslash \alpha) = first (\alpha' \backslash \alpha \gamma)$
        and $first (\alpha \backslash \alpha') = first (\alpha \gamma \backslash \alpha')$.
        Consequently $\alpha' \subset \alpha \implies \alpha' \subset \alpha \gamma$.
        Thus $\alpha' < \alpha \gamma$.
    \end{itemize}
    %
    In both cases $\alpha' < \alpha \gamma$, therefore path $\pi_1 \pi_2$ is discarded.
\end{proofEnd}
\vspace{-0.5em}

\begin{theoremEnd}[restate, no link to proof, no link to theorem, category=lemmata_closure]{lemma}
[Right distributivity of comparison over concatenation for paths without tagged $\epsilon$-loops]
    \label{lemma_closure_rightdist}
    Let
    $\pi_\alpha = q_0 \overset {u | \alpha} {\longrightarrow} q_1$ and
    $\pi_\beta  = q_0 \overset {u | \beta}  {\longrightarrow} q_1$
    be ambiguous paths in TNFA $f$ for IRE $e$,
    and let $\pi_\gamma = q_1 \overset {\epsilon | \gamma} {\longrightarrow} q_2$
    be their common $\epsilon$-suffix,
    such that $\pi_\alpha \pi_\gamma$ and $\pi_\beta \pi_\gamma$ do not contain tagged $\epsilon$-loops.
    If $\alpha < \beta$ then $\alpha \gamma < \beta \gamma$.
\end{theoremEnd}
\begin{proofEnd}
    Let
    $\big( (h_1, \hdots, h_k),$ $(h'_1, \hdots, h'_k) \big) = traces (\alpha, \beta)$ and
    $\big( (\sigma_1, \hdots, \sigma_k),$ $(\sigma'_1, \hdots, \sigma'_k) \big) = traces (\alpha \gamma, \beta \gamma)$.
    Appending $\gamma$ to $\alpha$ and $\beta$ changes only the last frame, therefore
    for frames $i < k$ we have $h_i = \sigma_i$ and $h'_i = \sigma'_i$.
    Consider two possible cases.
    %
    \begin{itemize}[itemsep=0.2em, topsep=0.5em]
    \item[(1)]
        Case $\alpha \sim \beta \wedge \alpha \subset \beta$.
        %
        We show that $\alpha \gamma \sim \beta \gamma \wedge \alpha \gamma \subset \beta \gamma$.
        %
        We have $h_i = h'_i \; \forall i$ (implied by $\alpha \sim \beta$), therefore
        $\sigma_i = \sigma'_i \; \forall i$ and consequently $\alpha \gamma \sim \beta \gamma$.
        Let
        $x = first (\alpha \backslash \beta)$,
        $y = first (\beta \backslash \alpha)$,
        $x' = first (\alpha \gamma \backslash \beta \gamma)$ and
        $y' = first (\beta \gamma \backslash \alpha \gamma)$.
        If one of $\pi_\alpha$ and $\pi_\beta$ is a proper prefix of another,
        then the longer path contains tagged $\epsilon$-loop through $q_1$,
        which contradicts lemma conditions
        (the suffix of the longer path must be an $\epsilon$-path,
        because $\alpha$ and $\beta$ have the same number of frames
        and the suffix is contained in the last frame).
        Therefore none of $\pi_\alpha$ and $\pi_\beta$ is a proper prefix of another.
        Consequently $x = x'$ and $y = y'$, and we have
        $\alpha \subset \beta$
        $\implies$
        $x < y$
        $\implies$
        $x' < y'$
        $\implies$
        $\alpha \gamma \subset \beta \gamma$.

    \item[(2)]
        Case $\alpha \sqsubset \beta$:
        by definition this means that $\exists j \leq k$ such that $h_j > h'_j$ and $h_i = h'_i \;\forall i > j$.
        We show that $\alpha \gamma \sqsubset \beta \gamma$.
        %
        \begin{itemize}
        \item[(2a)]
            Case $j < k$. In this case $h_k = h'_k$
            and appending $\gamma$ does not change relation on the last frame:
            $\sigma_k = min (h_k, minh (\gamma)) = min (h'_k, minh (\gamma)) = \sigma'_k$.
            Since $\sigma_i = h_i$ and $\sigma'_i = h'_i$ for all preceding frames $i < k$,
            we have $\alpha \gamma \sqsubset \beta \gamma$.

        \item[(2b)]
            Case $j = k$ and $minh (\gamma) > h'_k$.
            In this case $h_k > h'_k$
            and again appending $\gamma$ does not change relation on the last frame:
            $\sigma_k = min (h_k, minh (\gamma)) > h'_k$ and
            $\sigma'_k = min (h'_k, minh (\gamma)) = h'_k$, therefore
            $\sigma_k > \sigma'_k$.
            Therefore $\alpha \gamma \sqsubset \beta \gamma$.

        \item[(2c)]
            Case $j = k$ and $minh (\gamma) \leq h'_k$
            and $\exists l < k$ such that $h_l > h'_l$ and $h_i = h'_i$ for $l < i < k$.
            In this case $\gamma$ contains parentheses of low height
            and appending it makes height on the last frame equal:
            $\sigma_k = \sigma'_k = minh (\gamma)$.
            However, the relation on the last preceding differing frame is the same:
            $\sigma_l = h_l > h'_l = \sigma'_l$.
            Therefore $\alpha \gamma \sqsubset \beta \gamma$.

        \item[(2d)]
            Case $j = k$ and $minh (\gamma) \leq h'_k$
            and $\nexists l < k$ such that $h_l > h'_l$ and $h_i = h'_i$ for $l < i < k$.
            In this case $\gamma$ contains parentheses of low height,
            appending it makes height on the last frame equal:
            $\sigma_k = \sigma'_k = minh (\gamma)$,
            and this may change comparison result
            as the relation on the last preceding differing frame may be different.
            %
            We show that in this case the extended path $\pi_\beta \pi_\gamma$ contains a tagged $\epsilon$-loop.
            %
            Consider the fragments of paths $\pi_\alpha$ and $\pi_\beta$ from fork to join,
            including (if it exists) the common $\epsilon$-transition to the fork state:
            $\pi_\alpha'$ and $\pi_\beta'$.
            %
            Minimal parenthesis height on $\pi_\alpha'$ is $h_k$.
            By TNFA construction this means that $\pi_\alpha'$ is contained
            in a sub-TNFA $f'$ for $e|_x$ at some position $x$ with length $|x| = h_k$.
            %
            As for $\pi_\beta'$, its start state coincides with $\pi_\alpha'$ and thus is in $f'$.
            The minimal height of all but the last frames of $\pi_\beta'$ is at least $h_k$:
            by conditions of (2d) either $k = 1$ and there are no such frames,
            or $h'_{k-1} \geq h_{k-1}$ which implies $h'_{k-1} \geq h_k$
            (because by definition $h_k = min(h_{k-1}, minh(\alpha_k)) \leq h_{k-1}$).
            On the last frame of $\pi_\beta'$ minimal height is $h'_k < h_k$.
            Therefore all but the last frames of $\pi_\beta'$ are contained in $f'$,
            but the the last frame is not.
            %
            Now consider $\pi_\gamma$: by conditions of (2d) its minimal height is less than $h_k$,
            therefore it is not contained in $f'$,
            %
            but its start state is the join point of $\pi_\alpha'$ and $\pi_\beta'$ and thus in $f'$.
            %
            Taken together, above facts imply that the last frame of $\pi_\beta \pi_\gamma$
            starts in $f'$, then leaves $f'$, then returns to $f'$ and joins with $\pi_\alpha \pi_\gamma$,
            and then leaves $f'$ second time.
            Since the end state of $f'$ is unique (by TNFA construction),
            $\pi_\beta \pi_\gamma$ must contain a tagged $\epsilon$-loop through it,
            which contradicts lemma conditions.
        \end{itemize}
    \end{itemize}
    %
    (Note that in the presence of tagged $\epsilon$-loops right distributivity may not hold:
    we may have paths $\pi_1$, $\pi_2$ and $\pi_3$
    such that $\pi_2$ and $\pi_3$ are two different $\epsilon$-loops through the same subautomaton
    and $\pi_1 \pi_2 < \pi_1 \pi_3$,
    in which case $\pi_1 \pi_2 \pi_3 < \pi_1 \pi_3$,
    but $\pi_1 < \pi_1 \pi_2$ because the first is a proper prefix of the second.)
    %
\end{proofEnd}


\section{Tree representation of paths}\label{section_pathtree}

In this section we specify the representation of path fragments in configurations
and define tag path tree $U$ and the associated functions.
The $U$-tree is a \emph{prefix tree} of tags induced by the shortest path tree that is constructed by $closure()$.
Some care is necessary with TNFA construction in order to ensure the prefix property
(subautomata for alternatives should not start with identical tagged transitions),
but that is easy to accommodate and we give the details in section \ref{section_tnfa}.
The $U$-tree can be stored as an array of nodes $(pred, succ, tag)$ where
$pred$ is the index of a predecessor node,
$succ$ is an ordered set of successor indices,
and $tag$ is a positive or negative tag.
%
Successor indices are only necessary if the advanced algorithm for $update \Xund precedence ()$ is used
(section \ref{section_comparison}), otherwise the $succ$ component can be omitted.
%
Individual paths in the $U$-tree are addressed by integer indices of tree nodes
(zero index corresponds to the empty path).
%
It is important to use numeric indices rather than pointers to nodes
because it allows the use of the ``two-fingers'' algorithm that find the fork of two paths (section \ref{section_comparison}).
%
The prefix tree representation is space efficient (common path prefixes are not duplicated),
and the operation of copying a path (e.g. from one configuration to another) is as cheap as copying an integer index.
This representation was used by multiple researches,
e.g. Laurikari mentions a \emph{functional data structure} \cite{Lau01}
and Karper describes it as the \emph{flyweight pattern} \cite{Kar14}.
\\

\begin{algorithm}[H] \DontPrintSemicolon \SetKwProg{Fn}{}{}{} \SetAlgoInsideSkip{medskip}
\begin{multicols}{2}
    \setstretch{0.8}

    \Fn {$\underline {empty \Xund path \Xund tree ()} \smallskip$} {
        \Return an empty list of tuples $(pred, succ, tag)$ \;
    }
    \BlankLine

    \Fn {$\underline {extend \Xund path (U, n, t)} \smallskip$} {
        \If {$t \neq \epsilon$} {
            $m = length(U) + 1$ \;
            $insert(U[n].succ, m)$ \;
            $append(U, (n, \emptyset, t))$ \;
            \Return $m$ \;
        }
        \lElse {
            \Return $n$
        }
    }
    \BlankLine

\columnbreak

    \Fn {$\underline {unroll \Xund path (U, n)} \smallskip$} {
        $s = \epsilon$ \;
        \While { $n \neq 0$ } {
            $append(s, U[n].tag)$ \;
            $n = U[n].pred$ \;
        }
        \Return $reverse(s)$ \;
    }
    \BlankLine

    \vfill

\end{multicols}
\vspace{1em}
\caption{Operations on tag path tree.
The definition of $insert()$, $append()$ and $reverse()$ is omitted for brevity.}
\end{algorithm}


\section{Representation of match results}\label{section_results}

In this section we show two ways to construct match results: POSIX offsets and a parse tree.
\\

In the case of POSIX offsets, the $d$-component of a configuration is an array of offsets for each tag.
Offsets are updated incrementally at each step by scanning the corresponding path fragment
and setting negative tags to $-1$ and positive tags to the current step number.
For negative tags, it is necessary to also set all nested tags to $-1$.
Only the most recent value of each tag is needed, therefore the offset is updated at most once.
At the end of match, the offset array is converted to the representation specified by the POSIX standard:
an array $pmatch$ of offset pairs $(rm \Xund so, rm \Xund eo)$.
Conversion takes into account the mapping of tags to submatch groups.
Section \ref{section_tnfa} shows the construction of the mapping $M$ from tags to their nested tags and submatch groups.
\\

In the case of a parse tree, the $d$-component of a configuration is a tagged string that is accumulated at each step,
and eventually converted to a parse tree at the end of match.
The resulting parse tree is only partially structured:
leaves that correspond to subexpressions with zero implicit submatch index contain ``flattened'' substring of alphabet symbols.
It is possible to construct parse trees incrementally as well,
but this is more complex and the partial trees may require even more space than tagged strings.
\\

\begin{algorithm}[H] \DontPrintSemicolon \SetKwProg{Fn}{}{}{}
\begin{multicols}{2}
    \setstretch{0.8}

    \Fn {$\underline{initial \Xund result (M)} \smallskip$} {
        $d$: integer array of size $|dom(M)|$ \;
        \Return $d$\;
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline{update \Xund result (M, C, U, k, \Xund)} \smallskip$} {
        \Return $\big\{ (q_2, q_1, u, apply \Xund tags (M, U, u, d, k))$ \;
        \Indp\Indp\Indp $\mid (q_2, q_1, u, d) \in C \big\}$ \; \Indm\Indm\Indm
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline{apply \Xund tags (M, U, u, d, k)} \smallskip$} {
        $done(t) = f\!alse \; \forall t$ \;
        \While {$u \neq 0$} {
            $t = U[u].tag$ \;
            \If {$t > 0$} {
                \If {$\neg done(t)$} {
                    $done(t) = true$ \;
                    $d[t] = k$ \;
                }
            }
            \Else {
                \For {$n \in M(-t).N$} {
                    \If {$\neg done(n)$} {
                        $done(n) = true$ \;
                        $d[n] = -1$ \;
                    }
                }
            }
            $u = U[u].pred$ \;
        }
        \Return $d$ \;
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline{f\!inal \Xund result (M, U, u, d, k)} \smallskip$} {
        $d' = apply \Xund tags (M, U, u, d, k)$ \;

        $pmatch[0].rm \Xund so = 0$ \;
        $pmatch[0].rm \Xund eo = k$ \;

        \For {$i = \overline {1, |dom(M)| / 2} $} {
            \For {$s \in M(2i).S$} {
                $pmatch[s].rm \Xund so = d'[2i - 1]$ \;
                $pmatch[s].rm \Xund eo = d'[2i]$ \;
            }
        }

        \Return $pmatch$ \;
    }
    \BlankLine
    \BlankLine

    \vfill

\columnbreak

    \Fn {$\underline{initial \Xund result (\Xund)} \smallskip$} {
        \Return $\epsilon$ \;
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline{update \Xund result (\Xund, C, U, \Xund, a)} \smallskip$} {
        \Return $\big\{ (q_2, q_1, u, d \cdot unroll \Xund path (U, u) \cdot a)$ \;
        \Indp\Indp\Indp $\mid (q_2, q_1, u, d) \in C \big\}$ \; \Indm\Indm\Indm
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline{parse \Xund tree (u, i)} \smallskip$} {
        \If {$u = (2i \!-\! 1) \cdot (2i)$} {
            \Return $T^i(\epsilon)$
        }
        \If {$u = (1 \!-\! 2i) \cdot \hdots $} {
            \Return $T^i(\bot)$
        }
        \If {$u = (2i \!-\! 1) \cdot a_1 \hdots a_n \cdot (2i) \wedge a_1, \hdots, a_n \in \Sigma $} {
            \Return $T^i(a_1, \hdots, a_n)$
        }
        \If {$u = (2i \!-\! 1) \cdot \beta_1 \hdots \beta_m \cdot (2i) \wedge \beta_1 = 2j \!-\! 1 \in T$} {
            $n = 0, k = 1$ \;
            \While {$k \leq m$} {
                $l = k$ \;
                \lWhile {$|\beta_{k+1}| > 2j$} {
                    $k = k + 1$
                }
                $n = n + 1$ \;
                $t_n = parse \Xund tree (\beta_l \dots \beta_k, j)$
            }
            \Return $T^i(t_1, \dots, t_n)$
        }
        \Return $\bot$ \tcp{ill-formed PE}
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline{f\!inal \Xund result (\Xund, U, u, d, \Xund)} \smallskip$} {
        \Return $parse \Xund tree (d \cdot unroll \Xund path (U, u), 1)$ \;
    }
    \BlankLine

    \vfill

\end{multicols}
\vspace{2em}
\caption{Construction of match results: POSIX offsets (on the left) and parse tree (on the right).}
\end{algorithm}


\section{Disambiguation procedures}\label{section_comparison}

In this section we define disambiguation procedures $compare ()$ and $update \Xund precedence ()$.
The pseudocode follows definition \ref{pe_order} closely
and relies on the prefix tree representation of paths given in section \ref{section_results}.
%
In order to find fork of two paths in $compare ()$ we used the so-called ``two-fingers'' algorithm,
which is based on the observation that parent index is always less than child index.
Given two indices $n_1$ and $n_2$, we continuously set the greater index to its parent until the indices become equal,
at which point we have either found fork or the root of $U$-tree.
We track minimal height of each path along the way
and memorize the pair of indices right after the fork --- they are used to determine the leftmost path in case of equal heights.

\medskip

\begin{algorithm}[H] \DontPrintSemicolon \SetKwProg{Fn}{}{}{} \SetAlgoInsideSkip{medskip}
\begin{multicols}{2}
    \setstretch{0.8}

    \Fn {$\underline {compare (c_1, c_2, U, H, P)} \smallskip$} {
        $(\Xund, q_1, u_1, \Xund) = c_1$ \;
        $(\Xund, q_2, u_2, \Xund) = c_2$ \;

        \lIf { $q_1 = q_2 \wedge u_1 = u_2$ } {
            \Return $(\infty, \infty, 0)$
        }

        \BlankLine
        $f\!ork = (q_1 = q_2)$ \;
        \lIf {$f\!ork$ } {
            $h_1 = h_2 = \infty$
        }
        \lElse {
            $h_1 = H[q_1][q_2], \; h_2 = H[q_2][q_1]$
        }

        \BlankLine
        $u'_1 = u'_2 = \bot$ \;
        \While {$u_1 \neq u_2$} {
            \If {$u_1 > u_2$} {
                $h_1 = min(h_1, height(U[u_1].tag))$ \;
                $u'_1 = u_1, \; u_1 = U[u_1].pred$ \;
            }
            \Else {
                $h_2 = min(h_2, height(U[u_2].tag))$ \;
                $u'_2 = u_2, \; u_2 = U[u_2].pred$ \;
            }
        }
        \If {$u_1 \neq \bot$} {
            $h = height(U[u_1].tag)$ \;
            $h_1 = min(h_1, h), \; h_2 = min(h_2, h)$ \;
        }

        \BlankLine
        \lIf     {$h_1 > h_2$}    {$l = -1$}
        \lElseIf {$h_1 < h_2$}    {$l = 1$ }
        \lElseIf {$\neg f\!ork$}  {$l = P[q_1][q_2]$}
        \lElse                    {$l = le\!f\!tprec(u'_1, u'_2, U)$}

        \BlankLine
        \Return $(h_1, h_2, l)$ \;
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline {le\!f\!tprec (u_1, u_2, U)} \smallskip$} {

        \lIf {$u_1 = u_2$} { \Return $0$ }
        \lIf {$u_1 = \bot$} { \Return $-1$ }
        \lIf {$u_2 = \bot$} { \Return $1$ }

        \BlankLine
        $t_1 = U[u_1].tag, \; t_2 = U[u_2].tag$ \;

        \BlankLine
        \lIf {$t_1 < 0$} { \Return $1$ }
        \lIf {$t_2 < 0$} { \Return $-1$ }

        \BlankLine
        \lIf {$t_1 mod \, 2 \equiv 0$} { \Return $-1$ }
        \lIf {$t_2 mod \, 2 \equiv 0$} { \Return $1$ }

        \BlankLine
        \Return $0$
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline {update \Xund precedence (F, C, U, H, P)} \smallskip$} {
        \For {$c_1 = (q_1, \Xund, \Xund, \Xund) \in C$} {
            \For {$c_2 = (q_2, \Xund, \Xund, \Xund) \in C$} {
                $(h_1, h_2, l) = compare (c_1, c_2, U, H, P)$ \;
                $H' [q_1] [q_2] = h_1, \; P' [q_1] [q_2] = l$ \;
                $H' [q_2] [q_1] = h_2, \; P' [q_2] [q_1] = -l$
            }
        }
        \BlankLine
        \Return $(H', P')$ \;
    }

    \vfill
    \columnbreak

    \Fn {$\underline {update \Xund precedence (F, C, U, H, P)} \smallskip$} {
        $stack : $ empty stack of $U$-tree indices \;
        $level : $ empty array of tuples $(q', q, u, h)$ \;
        $next : $ active successor for a given $U$-tree index
        \BlankLine

        $next(u) \equiv 1$ \;
        $i = 0$ \;
        $push(stack, 0)$ \;
        \BlankLine

        \While {$stack$ is not empty} {
            $u = pop(stack)$ \;

            \BlankLine
            \If {$next(u) < k$} {
                $push(stack, u)$ \;
                $push(stack, U[u].succ[next(u)])$ \;
                $next(u) = next(u) + 1$ \;
                $continue$ \;
            }

            \BlankLine
            $h = height(U[u].tag), \; i_1 = i$ \;

            \BlankLine
            \For {$(q', q, u_1, \Xund) \in C \mid u_1 = u$} {
                $i = i + 1, \; level[i] = (q', q, \bot, h)$ \;
            }
            \For {$j_1 = \overline{i_1 + 1, i}$} {
                \For {$j_2 = \overline{j_1, i}$} {
                    $(q'_1, q_1, \Xund, \Xund) = level[j_1]$ \;
                    $(q'_2, q_2, \Xund, \Xund) = level[j_2]$ \;

                    \BlankLine
                    \If {$u = 0 \wedge q_1 \neq q_2$} {
                        $h_1 = H[q_1][q_2], \; h_2 = H[q_2][q_1]$ \;
                        $l = P[q_1][q_2]$ \;
                    }
                    \lElse {
                        $h_1 = h_2 = h, \; l = 0$
                    }

                    \BlankLine
                    $H'[q'_1][q'_2] = h_1, \; P'[q'_1][q'_2] = l$ \;
                    $H'[q'_2][q'_1] = h_2, \; P'[q'_2][q'_1] = -l$ \;
                }
            }

            \BlankLine
            \For {$u' \in U[u].succ$ in reverse} {
                $i_2 = i_1$ \;
                \lWhile {$i_2 > 0 \wedge level[i_2].u = u'$} {
                    $i_2 = i_2 - 1$
                }

                \BlankLine
                \For {$j_1 = \overline{i_2, i_1}$} {
                    $level[j_1].h = min(level[j_1].h, h)$; \;

                    \BlankLine
                    \For {$j_2 = \overline{i_1, i}$} {
                        $(q'_1, q_1, u_1, h_1) = level[j_1]$ \;
                        $(q'_2, q_2, u_2, h_2) = level[j_2]$ \;

                        \BlankLine
                        \If {$u = 0 \wedge q_1 \neq q_2$} {
                            $h_1 = min(h_1, H[q_1][q_2])$ \;
                            $h_2 = min(h_2, H[q_2][q_1])$ \;
                        }

                        \BlankLine
                        \lIf     {$h_1 > h_2$}    {$l = -1$}
                        \lElseIf {$h_1 < h_2$}    {$l = 1$ }
                        \lElseIf {$q_1 \neq q_2$} {$l = P[q_1][q_2]$}
                        \lElse                    {$l = le\!f\!tprec(u_1, u_2, U)$}
                    }

                    \BlankLine
                    $H'[q'_1][q'_2] = h_1, \; P'[q'_1][q'_2] = l$ \;
                    $H'[q'_2][q'_1] = h_2, \; P'[q'_2][q'_1] = -l$ \;
                }

                $i_1 = i_2$ \;
            }

            \BlankLine
            \lFor {$j = \overline{i_1, i}$} {
                $level[j].u = u$
            }
        }

        \BlankLine
        \Return $(H', P')$ \;
    }

\end{multicols}
\vspace{1em}
\caption{Disambiguation procedures.
Function $height()$ gives the height of a tag; its definition is omitted for brevity
(it can be computed in one top-down pass over IRE when constructing the TNFA, see section \ref{section_tnfa}).
}
\end{algorithm}
\medskip

We give two alternative algorithms for $update \Xund precedence ()$:
a simple one with $O(m^2 \, t)$ complexity (on the left) and a complex one with $O(m^2)$ complexity (on the right),
where $m$ is the number of states in the $\epsilon$-closure and $t$ is the number of tags.
The worst case is demonstrated by RE $((a|\epsilon)^{0,k})^{0,\infty}$ where $k \in \YN$,
for which the simple algorithm takes $O(k^3)$ time and the complex algorithm takes $O(k^2)$ time.
%
The idea of the complex algorithm is to avoid repeated re-scanning of path prefixes in the $U$-tree.
It makes one pass over the tree,
constructing an array $level$ of items $(q', q, u, h)$, where
$q'$ and $q$ are target and origin states (as in configurations),
$u$ is the current tree index, and $h$ is the current minimal height.
One item is added per each closure configuration $(q', q, u, d)$ when traversal reaches the tree node with index $u$.
After a subtree has been traversed,
the algorithm scans $level$ items added during traversal of this subtree (such items are distinguished by their $u$-component),
sets their $h$-component to the minimum of $h$ and the height of tag at the current node,
and computes the new value of $H$ and $P$ matrices for each pair of target states in items from different branches.
After that, $u$-component of all scanned items is downgraded to the tree index of the current node
(erasing the difference between items from different branches).


\section{Lazy disambiguation}\label{section_lazy}

Most of the disambiguation overhead in our algorithm comes from updating the $H$ and $P$ matrices at each step.
It is all the more unfortunate since many comparisons performed by $update \Xund precedence ()$ are useless ---
the compared paths may never meet.
In fact, if the input is unambiguous, all comparisons are useless.
%
A natural idea, therefore, is to compare paths only in case of a real ambiguity (when they meet in closure)
and avoid the computation of precedence matrices altogether.
%
We can do it with a few modifications to our original algorithm.
%
First, we no longer need the $H$ and $P$ matrices and $update \Xund precedence ()$ function.
Instead, we introduce a cache $Z$ that maps a pair of $U$-tree indices $(u_1, u_2)$ to a triple of precedence values $(h_1, h_2, l)$.
The $Z$-cache stores the ``useful'' part of $H$ and $P$ matrices on multiple preceding steps.
It is populated lazily during disambiguation
and allows us to avoid re-computing the same values multiple times.
%
Second, we need to modify the $U$-tree representation of paths in the following way:
successor indices are no longer needed (they are only used in the advanced $update \Xund precedence ()$ algorithm),
and the $U$-tree nodes must be augmented with the current step and the origin state: $(pred, tag, step, orig)$.
%
Third, instead of setting the $u$-component of configurations to zero at each step of the $match()$ algorithm in section $\ref{section_main}$,
we need to set it to the $u$-component of the parent configurations,
so that paths are accumulated rather than reset at each step.
%
Fourth, we no longer need to call $update \Xund result ()$ at each step --- this can be done once at the end of match.
%
The only part of the algorithm that requires non-trivial change is the $compare()$ function, which is defined below
via co-recursive functions $compare1()$ that populates the $Z$-cache and $compare2()$ that recurses one frame back, or stops if the fork has been reached.

\medskip

\begin{algorithm}[H] \DontPrintSemicolon \SetKwProg{Fn}{}{}{} \SetAlgoInsideSkip{medskip}
\begin{multicols}{2}
    \setstretch{0.8}

    \Fn {$\underline {compare (c_1, c_2, U, Z)} \smallskip$} {
        $(\Xund, \Xund, u_1, \Xund) = c_1$ \;
        $(\Xund, \Xund, u_2, \Xund) = c_2$ \;

        \BlankLine
        \Return $compare1 (u_1, u_2, U, Z)$ \;
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline {compare1 (u_1, u_2, U, Z)} \smallskip$} {
        \If {$Z(u_1, u_2) = \bot $} {
            $Z(u_1, u_2) = compare2 (u_1, u_2, U, Z)$ \;
        }

        \BlankLine
        \Return $Z(u_1, u_2)$ \;
    }
    \BlankLine
    \BlankLine

    \vfill
    \columnbreak

    \Fn {$\underline {compare2 (u_1, u_2, U, Z)} \smallskip$} {
        \lIf { $u_1 = u_2$ } {
            \Return $(\infty, \infty, 0)$
        }

        \BlankLine
        $h_1 = h_2 = \infty$ \;
        $q_1 = U[u_1].orig, \; q_2 = U[u_2].orig$ \;
        $k_1 = U[u_1].step, \; k_2 = U[u_2].step, \; k = max (k_1, k_2)$ \;
        $f\!ork = (q_1 = q_2) \wedge (k_1 = k_2)$ \;

        \BlankLine
        $u'_1 = u'_2 = \bot$ \;
        \While {$u_1 \neq u_2 \wedge (k_1 \geq k \vee k_2 \geq k)$} {
            \If {$k_1 \geq k \wedge (u_1 > u_2 \vee k_2 < k)$} {
                $h_1 = min(h_1, height(U[u_1].tag))$ \;
                $u'_1 = u_1, \; u_1 = U[u_1].pred, \; k_1 = U[u_1].step$ \;
            }
            \Else {
                $h_2 = min(h_2, height(U[u_2].tag))$ \;
                $u'_2 = u_2, \; u_2 = U[u_2].pred, \; k_2 = U[u_2].step$ \;
            }
        }

        \BlankLine
        \If {$\neg f\!ork$ } {
            $(h'_1, h'_2, l) = compare1(u_1, u_2, U, Z)$ \;
            $h_1 = min(h_1, h'_1), \; h_2 = min(h_2, h'_2)$ \;
        }
        \ElseIf {$u_1 \neq \bot$} {
            $h = height(U[u_1].tag)$ \;
            $h_1 = min(h_1, h), \; h_2 = min(h_2, h)$ \;
        }

        \BlankLine
        \lIf     {$h_1 > h_2$} {$l = -1$}
        \lElseIf {$h_1 < h_2$} {$l = 1$ }
        \lElseIf {$f\!ork$}    {$l = le\!f\!tprec(u'_1, u'_2, U)$}

        \BlankLine
        \Return $(h_1, h_2, l)$ \;
    }
    \BlankLine
    \BlankLine

\end{multicols}
\vspace{1em}
\caption{Lazy disambiguation procedures (the $Z$-cache is modified in-place).}
\end{algorithm}
\medskip

The problem with this approach is that we need to keep full-length history of each active path:
at the point of ambiguity we may need to look an arbitrary number of steps back
in order to find the fork of ambiguous paths.
%
This may be acceptable for small inputs (and memory footprint may even be smaller due to the reduction of precedence matrices),
but it is infeasible for long or streaming inputs.
%
A possible solution may be a hybrid approach that uses lazy disambiguation,
but every $k$ steps fully calculates the precedence matrices and ``forgets'' the path prefixes.
Another possible solution is to keep both algorithms and switch between them depending on the length of input.


\section{TNFA construction}\label{section_tnfa}

TNFA construction is given by the function $tn\!f\!a()$
that accepts IRE $e$ and state $q_f$ and returns TNFA for $e$ with the final state $q_f$
(algorithm \ref{alg_tnfa}).
%
This precise construction is not necessary for the algorithms to work,
but it has a number of important properties.
\begin{itemize}[itemsep=0.2em, topsep=0.5em]
    \item Non-essential $\epsilon$-transitions are removed, as they make closure algorithms GOR1 and GTOP slower.

    \item Bounded repetition $e^{n,m}$ is unrolled in a way
        that duplicates $e$ exactly $m$ times
        and factors out common path prefixes:
        subautomaton for $(k+1)$-th iteration is only reachable from subautomaton for $k$-th iteration.
        For example, $a^{2,5}$ is unrolled as $aa(\epsilon | a (\epsilon | a (\epsilon | a)))$, not as $aa(\epsilon|a|aa|aaa)$.
        This ensures that the tag tree build by $\epsilon$-closure is a prefix tree.

    \item Priorities are assigned so as to make it more likely
        that depth-first traversal of the $\epsilon$-closure finds short paths before long paths.
        %
        This is an optimization that makes GOR1 much faster in specific cases
        with many ambiguous paths that are longest-equivalent and must be compared by the leftmost criterion.
        An example of such case is $(((\epsilon)^{0,k})^{0,k})^{0,k})$ for some large $k$.
        %
        Because GOR1 has a depth-first component, it is sensitive to the order of transitions in TNFA.
        If it finds the shortest path early, then all other paths are just canceled at the first join point with the shortest path
        (because there is no improvement and further scanning is pointless).
        In the opposite case GOR1 finds long paths before short ones,
        and whenever it finds an improved (shorter) path, it has to schedule configurations for re-scan on the next pass.
        This causes GOR1 to make more passes and scan more configurations on each pass,
        which makes it significantly slower.
        Arguably this bias is a weakness of GOR1 --- GTOP is more robust in this respect.

    \item When adding negative tags, we add a single transition for the topmost closing tag
        (it corresponds to the nil-parenthesis, which has the height of a closing parenthesis).
        Then we map this tag to the full range of its nested tags, including itself and the pair opening tag.
        An alternative approach is to add all nested negative tags as TNFA transitions and get rid of the mapping,
        but this may result in significant increase of TNFA size and major slowdown
        (we observed 2x slowdown on large tests with hundreds of submatch groups).

    \item Although for simplicity we use sets to represent nested tags and submatch groups for each tag (the $M$ component of a TNFA),
        these sets contain consecutive numbers and in practice can be represented more efficiently as ranges.

    \item Passing the final state $q_f$ in $tn\!f\!a()$ function allows to link subautomata in a simple and efficient way.
        It allows to avoid tracking and patching of subautomaton transitions that go to the final state
        (when this final state needs to be changed).
    \\[-0.5em]
\end{itemize}

See figure\ref{fig:tnfa_example} for an example of TNFA.


\section{Complexity analysis}\label{section_complexity}

Our algorithm consists of three steps: conversion of RE to IRE,
construction of TNFA from IRE
and simulation of TNFA on the input string.
We discuss time and space complexity of each step
in terms of the following parameters:
$n$ --- the length of input,
$m$ --- the size of RE with counted repetition subexpressions expanded
(each subexpression duplicated the number of times equal to the repetition counter),
and $t$ --- the number of capturing groups and subexpressions that contain them.
\\

\begin{algorithm}[] \DontPrintSemicolon \SetKwProg{Fn}{}{}{}
\begin{multicols}{2}
\setstretch{0.8}

    \newcommand \retonfa {tn\!f\!a}
    \newcommand \ntag {ntag}

    \Fn {$\underline{\retonfa(e, q_f)} \smallskip$} {
    \Indm
    $i = \isub(e), \; J = \esub(e)$ \tcp{submatch indices}
    \If {$i = 0$} {
        \If {$e = \Eps(0, \emptyset)$} {
            \Return $(\Sigma, \{q_f\}, \emptyset, \emptyset, q_f, q_f)$
        }
        \BlankLine
        \ElseIf {$e = \Sym(0, \emptyset, a) \mid_{a \in \Sigma}$} {
            \Return $(\Sigma, \{q_0,q_f\}, \emptyset, \{(q_0, a, \epsilon, q_f)\}, q_0, q_f)$
        }
        \BlankLine
        \ElseIf {$e = \Cat(0, \emptyset, e_1, e_2)$} {
            $(\Sigma, Q_2, M_2, \Delta_2, q_2, q_f) = \retonfa (e_2, q_f)$ \;
            $(\Sigma, Q_1, M_1, \Delta_1, q_1, q_2) = \retonfa (e_1, q_2)$ \;
            \Return $(\Sigma, Q_1 \cup Q_2, M_1 \sqcup M_2, \Delta_1 \cup \Delta_2, q_1, q_f)$
        }
        \BlankLine
        \ElseIf {$e = \Alt(0, \emptyset, e_1, e_2)$} {
            $(\Sigma, Q_2, M_2, \Delta_2, q_2, q_f) = \retonfa (e_2, q_f)$ \;
            $(\Sigma, Q'_2, M_2, \Delta'_2, q'_2, q_f) = \ntag (M_2, q_f)$ \;
            $(\Sigma, Q_1, M_1, \Delta_1, q_1, q'_2) = \retonfa (e_2, q'_2)$ \;
            $(\Sigma, Q'_1, M_1, \Delta'_1, q'_1, q_2) = \ntag (M_1, q_2)$ \;
            $Q = Q_1 \cup Q'_1 \cup Q_2 \cup Q'_2 \cup \{q_0\}$ \;
            $\Delta = \Delta_1 \cup \Delta'_1 \cup \Delta_2 \cup \Delta'_2 \cup \{ (q_0,1,\epsilon,q_1), (q_0,2,\epsilon,q'_1) \}$ \;
            \Return $(\Sigma, Q, M_1 \sqcup M_2, \Delta, q_0, q_f)$
        }
        \BlankLine
        \ElseIf {$e = \Rep(0, \emptyset, e_1, n, m) \mid_{1 < n \leq m \leq \infty}$} {
            $(\Sigma, Q_1, M_1, \Delta_1, q_2, q_f) = \retonfa(\Rep(0,\! \emptyset,\! e_1,\! n\!-\!1,\! m\!-\!1),\! q_f)$ \;
            $(\Sigma, Q_2, M_2, \Delta_2, q_1, q_2) = \retonfa(e_1, q_2)$ \;
            \Return $(\Sigma, Q_1 \cup Q_2, M_1 \sqcup M_2, \Delta_1 \cup \Delta_2, q_1, q_f)$
        }
        \BlankLine
        \ElseIf {$e = \Rep(0, \emptyset, e_1, 0, m)$} {
            $(\Sigma, Q_1, M_1, \Delta_1, q_1, q_f) = \retonfa (\Rep(0, \emptyset, e_1, 1, m), q_f)$ \;
            $(\Sigma, Q'_1, M_1, \Delta'_1, q'_1, q_f) = \ntag (M_1, q_f)$ \;
            $Q = Q_1 \cup Q'_1 \cup \{q_0\}$ \;
            $\Delta = \Delta_1 \cup \Delta'_1 \cup \{ (q_0, 1, \epsilon, q_1), (q_0, 2, \epsilon, q'_1) \}$ \;
            \Return $(\Sigma, Q, M_1, \Delta, q_0, q_f)$
        }
        \BlankLine
        \ElseIf {$e = \Rep(0, \emptyset, e_1, 1, \infty)$} {
            $(\Sigma, Q_1, M_1, \Delta_1, q_0, q_1) = \retonfa (e_1, \Xund)$ \;
            $Q = Q_1 \cup \{q_f\}$ \;
            $\Delta = \Delta_1 \cup \{ (q_1, 1, \epsilon, q_0), (q_1, 2, \epsilon, q_f) \}$ \;
            \Return $(\Sigma, Q, M_1, \Delta, q_0, q_f)$
        }
        \BlankLine
        \lElseIf {$e = \Rep(0, \emptyset, e_1, 1, 1)$} {
            \Return $\retonfa (e_1, q_f)$
        }
        \BlankLine
        \ElseIf {$e = \Rep(0, \emptyset, e_1, 1, m) \mid_{1 < m < \infty}$} {
            $(\Sigma, Q_1, M_1, \Delta_1, q_1, q_f) = \retonfa (\Rep(0, \emptyset, e_1, 1, m\!-\!1), q_f)$ \;
            $(\Sigma, Q_2, M_2, \Delta_2, q_0, q_2) = \retonfa (e_1, q_1)$ \;
            $\Delta = \Delta_1 \cup \Delta_2 \cup \{ (q_1, 1, \epsilon, q_f), (q_1, 2, \epsilon, q_2) \}$ \;
            \Return $(\Sigma, Q_1 \cup Q_2, M_1 \sqcup M_2, \Delta, q_0, q_f)$
        }
    }
    \Else {
        $e' = \fix(0, \emptyset, e)$ \tcp{reset submatch indices}
        $(\Sigma, Q_1, M_1, \Delta_1, q_1, q_2) = \retonfa(e', q_2)$ \;
        $Q = Q_1 \cup \{q_0, q_f\}$ \;
        $N = dom(M_1) \sqcup \{2i\!-\!1,\! 2i\}$ \tcp{nested tags}
        $M(m) = \begin{cases}
            M_1(m) &\text{if } m \in dom(M_1) \\[-0.2em]
            (J, N) &\text{if } m \in \{2i\!-\!1, 2i\}
        \end{cases}$ \;
        $\Delta = \Delta_1 \cup \{ (q_0, 1, 2i\!-\!1, q_1), (q_2, 1, 2i, q_f) \}$ \;
        \Return $(\Sigma, Q, M, \Delta, q_0, q_f)$
    }
    }
    \BlankLine
    \BlankLine

    \Fn {$\underline{\ntag(M, q_f)} \smallskip$} {
        \tcp{smallest closing tag represents all tags in $M$}
        $n = min \{ m \in dom(M) \mid m \equiv 0 \;mod\; 2\}$ \;
        \Return $(\Sigma, \{q_0, q_f\}, M, \{ (q_0, 1, -n, q_f) \}, q_0, q_f)$ \;
    }

    \vfill

\columnbreak

    \nonl \includegraphics[width=\linewidth]{img/tnfa_construction.pdf}

\end{multicols}
\vspace{1.5em}
\caption{
TNFA construction.
Notation $M = M_1 \sqcup M_2$ for functions with disjoint domains
means that $M(m) = M_i(m) \;\forall m \in dom(M_i)$, $1 \leq i \leq 2$.
Functions $\fix$, $\isub$ and $\esub$ are defined on page \pageref{defire}.
}\label{alg_tnfa}
\end{algorithm}

%\FloatBarrier

\begin{figure}
\includegraphics[width=\linewidth]{img/tnfa_example.pdf}
\vspace{-2em}
\caption{Example of TNFA for RE
$(((\epsilon|a^{0,\infty}))(a|\epsilon)^{0,3})$.}\label{fig:tnfa_example}
\end{figure}

The first step, conversion of RE to IRE, is given by the functions $mark()$ and $enum()$ from section \ref{section_formalization}.
%
For each sub-RE, $mark()$ constructs a corresponding sub-IRE,
and $enum()$ performs a linear visit of the IRE (which doesn't change its structure),
therefore IRE size is $O(m)$.
%
Each subexpression is processed twice (once by $mark()$ and once by $enum()$)
and processing takes $O(1)$ time, therefore total time is $O(m)$.
\\

The second step, TNFA construction, is given by the $tn\!f\!a()$ function (algorithm \ref{alg_tnfa}).
At this step counted repetition is unrolled, so TNFA may be much larger than IRE.
For each subexpressions of the form $\Rep(i, J, e, k, l)$ automaton for $e$ is duplicated exactly $l$ times if $l \neq \infty$, or $max(1, k)$ times otherwise
(at each step of recursion the counter is decremented and one copy of automaton is constructed).
Since repetition counters are constants, unrolling increases the size of sub-TNFA by a constant factor.
Other $tn\!f\!a()$ clauses are in one-to-one correspondence with sub-IRE.
Each clause adds only a constant number of states and transitions (including calls to $ntags()$ and excluding recursive calls).
Therefore TNFA contains $O(m)$ states and transitions.
The size of mapping $M$ is $O(t)$, which is $O(m)$.
Therefore total TNFA size is $O(m)$.
Time complexity is $O(m)$, because each clause takes $O(1)$ time (excluding recursive calls), and total $O(m)$ clauses are executed.
\\

The third step, TNFA simulation, is given by algorithm \ref{alg_match}.
Initialization at lines 2-4 takes $O(1)$ time.
Main loop at lines 5-11 is executed at most $n$ times.
The size of closure is bounded by TNFA size, which is $O(m)$ (typically closure is much smaller than that).
Each iteration of the loop includes the following steps.
%
At line 6 the call to $closure()$ takes $O(m^2 \, t)$ time if GOR1 from section \ref{section_closure} is used,
because GOR1 makes $O(m^2)$ scans (closure contains $O(m)$ states and $O(m)$ transitions),
and at each scan we may need to compare the tag sequences using $compare()$ from section \ref{section_comparison},
which takes $O(t)$ time
(there is $O(t)$ unique tags and we consider paths with at most one $\epsilon$-loop,
so the number of occurrences of each tag is bounded by the repetition counters,
which amounts to a constant factor).
%
At line 7 the call to $update \Xund result()$ takes $O(m \, t)$ time,
because closure size is $O(m)$,
and the length of the tag sequence is $O(t)$ as argued above.
%
At line 8 the call to $update \Xund precedence ()$ takes $O(m^2)$ time
if the advanced algorithm from section \ref{section_comparison} is used.
%
At line 9 scanning the closure for reachable states takes $O(m)$ time,
because closure size is $O(m)$.
%
The sum of the above steps is $O(m^2 \, t)$, so the total time of loop at lines 5-11 is $O(n \, m^2 \, t)$.
The final call to $closure()$ at line 12 takes $O(m^2 \, t)$,
and $f\!inal \Xund result ()$ at line 14 takes $O(m \, t)$.
The total time taken by $match()$ is therefore $O(n \, m^2 \, t)$.
\\

Space complexity of $match()$ is as follows.
%
The size of the closure is $O(m)$.
%
Precedence matrices $H$ and $P$ take $O(m^2)$ space.
%
Match results take $O(m \, t)$ space in case of POSIX-style offsets,
because the number of parallel results is bounded by the closure size,
and each result takes $O(t)$ space.
In case of parse trees, match results take $O(m \, n)$ space, because each result accumulates all loop iterations.
%
The size of the $U$-tree is $O(m^2)$
because GOR1 makes $O(m^2)$ scans and thus adds no more than $O(m^2)$ tags in the tree.
The total space taken by $match()$ is therefore $O(m^2)$
for POSIX-style offsets and $O(m (m + n))$ for parse trees.
\\

For leftmost-greedy submatch extraction,
space complexity is $O(m \, t)$ and time complexity is $O(n \, m \, t)$,
because the $\epsilon$-closure is constructed with a DFS in $O(m)$ time and has $O(m)$ size,
and each closure configuration contains $O(t)$ submatch results that need to be updated.


\section{Benchmarks}\label{section_benchmarks}

In this section $n$, $m$, $t$ have the same meaning as in section \ref{section_complexity}.
%
We used two groups of benchmarks:
\begin{enumerate}[itemsep=0.2em, topsep=0.5em]
\item Real-world benchmarks (A1 -- A12).
    These REs are designed to evaluate the algorithms in a natural setting.
    They describe practical concepts, such as URI or HTTP message headers,
    and range from very large (containing thousands of characters and hundreds of capturing groups)
    to small (containing under a hundred of characters and about five capturing groups).
    The input consists of short strings conforming to the RE grammar.

\item Artificial benchmarks (B1 -- B12 and C1 -- C12).
    These REs are designed to be highly ambiguous and reveal worst-case behavior and pathological cases for different algorithms.
    All REs in this group have an alphabet consisting of a single symbol \texttt{a},
    and the input is a long string of \texttt{a}s (16K).
    There are two subgroups in this group:
    \begin{enumerate}
    \item REs with alternative.
        Benchmarks B1 -- B6 contain REs of the form $(a^{k_1}|a^{k_2}|a^{k_3})^{0,\infty}$, where $k_i$ are prime numbers.
        Benchmarks B7 -- B12 contain their variations with more capturing groups: $(((a)^{k_1})|((a)^{k_2})|((a)^{k_3}))^{0,\infty}$.
        These REs show the difficulty of POSIX longest-match semantics,
        because the choice of alternatives varies indefinitely with the number of \texttt{a}s in the input string.
        %
        For example, consider RE $(a^{2}|a^{3}|a^{5})^{0,\infty}$:
        given an input string \texttt{a...a},
        submatch on the last iteration depends on the length of input:
        it equals \texttt{aaaaa} for a $5n$-character string,
        \texttt{aa} for strings of length $5n - 3$ and $5n - 1$,
        and \texttt{aaa} for strings of length $5n - 2$ and $5n + 1$ ($n \in \YN$).
        Variation continues indefinitely with a period of five characters.
        %
        The variation period is longer for higher counter values.

    \item REs with concatenation.
        These benchmarks contain REs of the form $(e^{0,k})^{0,\infty}$, where $e$ is
        $(a|\epsilon)$ for C1 -- C3,
        $(a^{0,\infty})$ for C4 -- C6,
        $a$ for C7 -- C9 and
        $(a)$ for C10 -- C11.
        These REs have large $\epsilon$-closure sizes.
    \\[-0.5em]
    \end{enumerate}
\end{enumerate}

We benchmarked the following algorithms:
\begin{itemize}[itemsep=0.2em, topsep=0.5em]
\item LG: the basic leftmost-greedy implementation.
    It updates offsets at each step of TNFA simulation
    and runs in $O(n \, m \, t)$ time and $O(m \, t)$ space.
    This algorithm has no overhead on disambiguation
    and serves as a baseline for other algorithms.

\item Lazy-LG: a ``lazy'' version of LG.
    It accumulates tag history of all simulation steps,
    updates offsets at the end of match rather than at each step,
    and runs in $O(n \, m)$ time and $O(n \, m)$ space.
    This algorithm reduces the overhead on incremental offset updates,
    but it requires memory proportional to the size of input.

\item Re2-LG: a leftmost-greedy implementation by the Google RE2 library \cite{RE2}.
    This algorithm is used to relate our implementations to the real world:
    both RE2C and RE2 are written in C++, so they are comparable.
    Internally RE2 uses a bunch of different algorithms:
    simple cases are optimized,
    and the results vary considerably from one algorithm to another.

\item OS: the main Okui-Suzuki implementation described in this paper.
    It uses GOR1 and the advanced $update \Xund precedence()$ algorithm.
    Disambiguation and offset updates are performed eagerly at each step of TNFA simulation
    and the algorithm runs in $O(n \, m^2 \, t)$ time and $O(m^2)$ space.

\item GTOP-OS: the same as OS, but uses GTOP instead of GOR1.

\item Simple-OS: the same as OS, but uses the simple $update \Xund precedence()$ algorithm.

\item Lazy-OS: the ``lazy'' version of OS described in section \ref{section_lazy}.
    It accumulates tag history of all simulation steps and
    disambiguates on demand, memoizing the results in cache.
    Offsets are updated at the end of match.
    The algorithm has the same $O(n \, m^2 \, t)$ time complexity as OS (because the $\epsilon$-closure construction takes $O(m^2 \, t)$ time),
    and its space complexity is $O(n \, m^2)$ due to the need to store $O(m^2)$ tag path tree for each of the $n$ steps.
    This algorithm reduces the overhead on incremental offset updates and precedence matrix computation,
    but it requires memory proportional to the size of input.

\item Kuklewicz: the implementation of Kuklewicz algorithm \cite{Kuk07} as described in \cite{Tro17}.
    The main difference with OS is that the algorithm splits the common tag history into individual tag histories and considers them separately.
    %Tags that correspond to repetition subexpressions (orbit tags) require more complex handling than other (simple) tags.
    The algorithm runs in $O(n \, m \, t \, (m + t \log(m))$ time and $O(m \, t)$ space.

\item Backward: the backward-matching algorithm proposed by Cox \cite{Cox09}.
    We had to modify it substantially in order to support bounded repetition and fix bugs (described in the introduction).
    We also use a fast forward pass to find the matching string prefix before running the main backward pass.
    Naturally, these modifications incur some overhead.
    The resulting algorithm is not fully correct, but the errors are rare compared to the original algorithm (about 2.5\% of our tests),
    and the test cases that trigger errors are more complex.
\\[-0.5em]
\end{itemize}

All algorithms except re2-LG are implemented in the open-source lexer generator RE2C \cite{RE2C}
in the form of a regular expression library \texttt{libre2c},
which provides the usual POSIX API of \texttt{regcomp}, \texttt{regexec} and \texttt{regfree}.
Individual algorithms are selected by passing non-standard flags to \texttt{regcomp}:
for example, there is a flag that enables leftmost-greedy semantics instead of POSIX semantics,
a flag that enables GTOP instead of GOR1,
a flag that enables ``lazy'' mode, etc.
Most of the flags can be combined.
We selected a limited number of combinations for the benchmarks
in order to show the impact of each individual feature.
Correctness of our implementation has been tested on a subset of Glenn Fowler test suite \cite{Fow03}
(we removed tests for backreferences and start/end anchors),
extended by Kuklewicz and further extended by ourselves to some 500 tests.
\\

Benchmark results are shown on figures \ref{fig:bencha}, \ref{fig:benchb} and \ref{fig:benchc}.

\pgfplotstableread[col sep = semicolon, trim cells]{bench/data} \datatableX
\pgfplotstabletranspose{\datatable}{\datatableX}

\def\plotheight{1.5in}
\def\plotshift{\linewidth/5*3}

\pgfplotsset{
    myplotstyle/.style = {
        font=\scriptsize,
        xbar,
        width=3.8in,
        axis y line*=none,
        axis x line=none,
        ytick=data,
        yticklabels={LG,lazy-LG,re2-LG,OS,GTOP-OS,simple-OS,lazy-OS,Kuklewicz,backward},
        nodes near coords,
        nodes near coords style={/pgf/number format/.cd,fixed},
        scaled ticks=false,
        every axis title/.style={below right, at={(-0.6in,-0.12in)}},
        xmin={-\xmax/80},
        xmax=\xmax,
        bar width=0.08in,
        y=0.12in,
        clip=false,
        x filter/.expression={x < \xmax ? x : nan},
        %
        %x axis line style=->,
        %xtick=\empty,
        %xticklabels=\empty,
        %
        %visualization depends on={{\pgfkeysvalueof{/data point/x} < (\xmax - 10)} \as \smallval},
        %every node near coord/.style ={%
        %    anchor={\ifdim\smallval pt=1 pt west\else east\fi},
        %    %at={({\pgfkeysvalueof{/data point/x} > \xmax ? -\pgfkeysvalueof{/data point/x}*10 : 0},\pgfkeysvalueof{/data point/y})},
        %},
    }
}

\begin{figure}
    \def\xmax{70}

    \begin{tikzpicture}
        \begin{axis}[myplotstyle, title={\textbf{A1\quad}RFC-7230 compliant HTTP parser (6204 symbols, 185 captures)}]
        \addplot[fill=lightgray] table[x index={1}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{A2\quad}simple HTTP parser (573 symbols, 33 captures)}, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={2}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{A3\quad}RFC-3986 compliant URI parser (3149 symbols, 93 captures)}, yshift=-\plotheight]
        \addplot[fill=lightgray] table[x index={3}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{A4\quad}simple URI parser (234 symbols, 14 captures)}, yshift=-\plotheight, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={4}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{A5\quad}RFC-7230 compliant IPv6 parser (2343 symbols, 62 captures)}, yshift=-\plotheight*2]
        \addplot[fill=lightgray] table[x index={5}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{A6\quad}simple IPv6 parser (93 symbols, 7 captures)}, yshift=-\plotheight*2, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={6}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{A7\quad}IPv4 parser (235 symbols, 6 captures)}, yshift=-\plotheight*3]
        \addplot[fill=lightgray] table[x index={7}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{A8\quad}simple IPv4 parser (57 symbols, 5 captures)}, yshift=-\plotheight*3, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={8}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{A9\quad}date parser (224 symbols, 14 captures)}, yshift=-\plotheight*4]
        \addplot[fill=lightgray] table[x index={9}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{A10\quad}simple date parser (81 symbols, 7 captures)}, yshift=-\plotheight*4, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={10}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{A11\quad}Gentoo package atom (117 symbols, 14 captures)}, yshift=-\plotheight*5]
        \addplot[fill=lightgray] table[x index={11}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{A12\quad}simple package atom (45 symbols, 5 captures)}, yshift=-\plotheight*5, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={12}, y expr=-\coordindex] from \datatable ;
        \end{axis}
    \end{tikzpicture}

    \caption{
    Real-world benchmarks (A1 -- A12).
    }\label{fig:bencha}
\end{figure}


\begin{figure}
    \def\xmax{270}

    \begin{tikzpicture}
        \begin{axis}[myplotstyle, title={\textbf{B1\quad}$(a^{2}|a^{3}|a^{5})^{0,\infty}$}, yshift=-\plotheight*0]
        \addplot[fill=lightgray] table[x index={13}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{B2\quad}$(a^{7}|a^{13}|a^{19})^{0,\infty}$}, yshift=-\plotheight*1]
        \addplot[fill=lightgray] table[x index={14}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{B3\quad}$(a^{29}|a^{41}|a^{53})^{0,\infty}$}, yshift=-\plotheight*2]
        \addplot[fill=lightgray] table[x index={15}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{B7\quad}$(((a)^{2})|((a)^{3})|((a)^{5}))^{0,\infty}$}, yshift=-\plotheight*0, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={19}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{B8\quad}$(((a)^{7})|((a)^{13})|((a)^{19}))^{0,\infty}$}, yshift=-\plotheight*1, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={20}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{B9\quad}$(((a)^{29})|((a)^{41})|((a)^{53}))^{0,\infty}$}, yshift=-\plotheight*2, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={21}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{B4\quad}$(a^{67}|a^{83}|a^{103})^{0,\infty}$}, yshift=-\plotheight*3]
        \addplot[fill=lightgray] table[x index={16}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{B5\quad}$(a^{127}|a^{151}|a^{179})^{0,\infty}$}, yshift=-\plotheight*4]
        \addplot[fill=lightgray] table[x index={17}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{B6\quad}$(a^{199}|a^{239}|a^{271})^{0,\infty}$}, yshift=-\plotheight*5]
        \addplot[fill=lightgray] table[x index={18}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{B10\quad}$(((a)^{67})|((a)^{83})|((a)^{103}))^{0,\infty}$}, yshift=-\plotheight*3, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={22}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{B11\quad}$(((a)^{127})|((a)^{151})|((a)^{179}))^{0,\infty}$}, yshift=-\plotheight*4, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={23}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{B12\quad}$(((a)^{199})|((a)^{239})|((a)^{271}))^{0,\infty}$}, yshift=-\plotheight*5, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={24}, y expr=-\coordindex] from \datatable ;
        \end{axis}
    \end{tikzpicture}

    \caption{
    Artificial benchmarks (B1 --- B12).\\
    Highly ambiguous REs with alternative on 16K string of \texttt{a}s.
    }\label{fig:benchb}
\end{figure}


\begin{figure}
    \def\xmax{250}

    \begin{tikzpicture}
        \begin{axis}[myplotstyle, title={\textbf{C1\quad}$((a|\epsilon)^{0,1})^{0,\infty}$}, yshift=-\plotheight*0]
        \addplot[fill=lightgray] table[x index={31}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{C2\quad}$((a|\epsilon)^{0,256})^{0,\infty}$}, yshift=-\plotheight*1]
        \addplot[fill=lightgray] table[x index={32}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{C3\quad}$((a|\epsilon)^{0,512})^{0,\infty}$}, yshift=-\plotheight*2]
        \addplot[fill=lightgray] table[x index={33}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{C7\quad}$(a^{0,1})^{0,\infty}$}, yshift=-\plotheight*0, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={25}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{C8\quad}$(a^{0,256})^{0,\infty}$}, yshift=-\plotheight*1, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={26}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{C9\quad}$(a^{0,512})^{0,\infty}$}, yshift=-\plotheight*2, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={27}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{C4\quad}$((a^{0,\infty})^{0,1})^{0,\infty}$}, yshift=-\plotheight*3]
        \addplot[fill=lightgray] table[x index={34}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{C5\quad}$((a^{0,\infty})^{0,256})^{0,\infty}$}, yshift=-\plotheight*4]
        \addplot[fill=lightgray] table[x index={35}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{C6\quad}$((a^{0,\infty})^{0,512})^{0,\infty}$}, yshift=-\plotheight*5]
        \addplot[fill=lightgray] table[x index={36}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{C10\quad}$((a)^{0,1})^{0,\infty}$}, yshift=-\plotheight*3, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={28}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{C11\quad}$((a)^{0,256})^{0,\infty}$}, yshift=-\plotheight*4, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={29}, y expr=-\coordindex] from \datatable ;
        \end{axis}

        \begin{axis}[myplotstyle, title={\textbf{C12\quad}$((a)^{0,512})^{0,\infty}$}, yshift=-\plotheight*5, xshift=\plotshift]
        \addplot[fill=lightgray] table[x index={30}, y expr=-\coordindex] from \datatable ;
        \end{axis}
    \end{tikzpicture}

    \caption{
    Artificial benchmarks (C1 --- C12).\\
    Highly ambiguous REs with concatenation on 16K string of \texttt{a}s.
    }\label{fig:benchc}
\end{figure}

In order to present benchmark results in a meaningful way, we show the time of each algorithm relative to LG.
This allows us to show the net overhead on POSIX disambiguation.
We measured the speed of each algorithm in characters per second and divided it by the speed of LG
(therefore LG time is always equal to 1).
%
We ran benchmarks on Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz machine
with 12 cores (we ran in single-threaded mode),
32 GB of RAM,
32K L1d cache,
32K L1i cache,
256K L2 cache,
9216K L3 cache,
running Gentoo Linux.
We used RE2 version 2019-12-01
and RE2C from Git \cite{RE2C} at commit \texttt{662b45ef468713f59e667200aacd922f29580c4a}.

\subparagraph{Memory consumption.}
We used Massif\cite{Massif} to estimate memory usage.
The algorithms can be partitioned in two groups:
those that execute in bounded memory (depending on the size of RE, but not on the length of input),
and those that require memory proportional to the length of input.
The first group contains LG, re2-LG, OS, GTOP-OS, simple-OS, Kuklewicz and backward:
these algorithms stay under 50M for all benchmarks,
and usually the memory is pre-allocated at the beginning (e.g. for precedence matrices).
The second group contains lazy-LG and lazy-OS:
memory consumption of these algorithms grows with the length of input
and reaches peak values for C6 (over 700M for lazy-LG and over 4G for lazy-OS).
Both algorithms keep closure trees for all steps in memory,
but the size of the trees is linear in the size of RE for lazy-LG (which uses a simple DFS),
and worst-case quadratic for lazy-OS (which uses GOR1).
Lazy-OS also allocates a large disambiguation cache.
Our cache implementation is a red-black tree from the standard C++ library,
which incurs a lot of small heap allocations and deallocations and is not optimal.
However, it suffices to show the general problem.

\subparagraph{Run-time performance.}
%Performance results are very different for real-world RE and for artificial RE.
%
On real-world RE all algorithms except Kuklewicz and backward perform quite well.
Lazy-OS is the fastest among POSIX implementations, and only \textasciitilde2x slower than LG.
Other OS algorithms (OS, GTOP-OS and simple-OS) are 3-4x slower than LG,
with OS being generally a bit slower due to a more complex implementation.
Kuklewicz algorithm is noticeably slower on large RE,
and the backward algorithm is so slow that it timed out on some of the benchmarks.
%
On artificial benchmarks the results are quite different.
Of all POSIX implementations, only the backward algorithm performs reasonably well.
Other algorithms show performance degradation on some of the benchmarks,
and simple-OS is particularly slow.
%Simple-OS is very slow on almost all benchmarks, especially C1 -- C3 and C4 -- C6.
%OS and GTOP-OS slow down considerably on B1 -- B6, B7 -- B12, C7 -- C9 and C10 -- C12.
%Lazy-OS slows down on B7 -- B12.
%Kuklewicz slows down on B7 -- B12, C1 -- C3 and C4 -- C6.
%
In general, we can observe the following tendencies:
\begin{itemize}[itemsep=0.2em, topsep=0.5em]
\item OS degrades with increased closure size,
    as shown by B1 -- B6, B7 -- B12, C7 -- C9 and C10 -- C12.
    This is caused by an $O(m^2)$ precedence matrix update algorithm.

\item Simple-OS degrades with increased closure size,
    and much faster than OS,
    as shown by C1 -- C3 and C4 -- C6. (it is \textasciitilde 50x slower than OS on C3).
    This is caused by an $O(m^2 \, t)$ precedence matrix update algorithm.

\item Kuklewicz algorithm degrades with increased closure size, although not as fast as OS (as shown by B7 -- B12, C1 -- C3 and C4 -- C6)
    and with increased number of tags (compare B1 -- B6 with fewer captures to B7 -- B12 with more captures),
    in particular orbit tags (compare C1 -- C3 with an innermost alternative to C4 -- C6 with an innermost repetition).
    On REs with many capturing groups Kuklewicz algorithm can get very slow (A1, A5).
    This is caused by an $O(m \log(m) \, t^2)$ precedence matrix update algorithm.

\item Backward algorithm degrades with increased number of tags
    (as shown by A1 -- A12, and also observable on B7 -- B12 compared to B1 -- B6).
    This is caused by the necessity to copy offset arrays between closure configurations:
    instead of using a prefix tree to store tag histories during closure construction,
    the algorithm stores an array of offsets (a pair of offsets per tag) in each configuration.
    GOR1 is asymptotically faster than DFS with backtracking used by the original implementation\cite{Cox09},
    but it increases the amount of copying
    (DFS order of graph traversal allows to update and restore a single offset array in-place).
    Even with DFS copying is a bottleneck for REs with many capturing groups.

\item Lazy-OS degrades with increased input length, increased closure size and ambiguity in RE
    (it performs well on B1 -- B6, but much worse on B7 -- B12 which differ only in capturing parentheses).
    This is caused by the need to keep all closure trees in memory and allocate a large disambiguation cache.
    In highly ambiguous cases the lazy algorithm does all the work of a non-lazy algorithm,
    but with the additional overhead on cache lookups/insertions and accumulation of data from the previous steps.

\item GOR1, despite having $O(m^2 \, t)$ worst-case complexity,
    in practice performs closer to $O(m \, t)$
    and adds less overhead than updating the precedence matrix
    (compare B1 -- B6 for OS and lazy-OS, which also uses GOR1, but no precedence matrix).

\item GOR1 and GTOP performance is similar.
    GTOP is usually slightly faster on real-world RE (probably because the closure graphs are simple and GOR1 code is more complex).
    We failed to find a pathological case for GTOP;
    this may mean that we didn't look hard enough,
    or that TNFA closures have some special properties that make GTOP efficient.

\item LG and lazy-LG performance is close to re2-LG, faster on large RE, but slower on small RE.
    This is explained by the fact that RE2 uses an optimized bitmap-based implementation for small RE.
%\\[-0.5em]
\end{itemize}

\FloatBarrier


\section{Conclusions and future work}\label{section_conclusions}

As the benchmarks show, none of the POSIX matching algorithms we considered is perfect,
but for each algorithm there are use cases where it performs better than other algorithms.
On the whole though, we can make the following observations.
Lazy-OS is generally the fastest algorithm, but it cannot be used for long inputs due to its unbounded memory consumption.
OS is generally the most robust algorithm: it has the best worst-case time and space complexity,
it is reasonably fast, and it can be used for long inputs (as it executes in bounded memory).
Therefore a practical implementation would use a use a combination of lazy-OS and OS.
\\

It is still an open question to us
whether it is possible to combine the elegance of the derivative-based approach to POSIX disambiguation
with the efficiency of NFA-based algorithms.
%
In the derivative-based approach, match results are constructed in such order that the longest-leftmost result is always first.
Perhaps a similar idea can be applied to NFA-based algorithms in order to reduce the amount of disambiguation they perform.
We experimented with recursive descent parsers based on the ordering idea
and constructed a prototype implementation (but it did not result in a faster algorithm).
\\

It would be interesting to apply our approach to automata with counters
instead of unrolling bounded repetition.


\section*{Acknowledgments}

I want to thank my husband and fellow programmer Sergei,
my parents Vladimir and Elina, my teachers Tatyana Leonidovna and Demian Vladimirovich, and the Belarusian State University.
And cheers to the RE2C users!
\null\hfill\textit{Ulya Trofimovich}


\begin{thebibliography}{99}

\bibitem{Tho68}
    Ken Thompson,
    \textit{Programming Techniques: Regular expression search algorithm},
    Communications of the ACM,
    vol. 11 (6),
    1968.

\bibitem{ALSU07}
    Alfred V. Aho, Monica S. Lam, Ravi Sethi, Jeffrey D. Ullman,
    \textit{Compilers: Principles, Techniques, \& Tools} (chapter 3.7.3: \textit{Efficiency of NFA Simulation}),
    2nd edition,
    Boston, MA, USA: Pearson Addison-Wesley,
    ISBN 0-321-48681-1,
    pp. 157-159.

\bibitem{OS13}
    Satoshi Okui and Taro Suzuki,
    \textit{Disambiguation in Regular Expression Matching via Position Automata with Augmented Transitions},
    International Conference on Implementation and Application of Automata, pp. 231-240, Springer, Berlin, Heidelberg,
    2013.

\bibitem{Lau01}
    Ville Laurikari,
    \textit{Efficient submatch addressing for regular expressions},
    Helsinki University of Technology,
    2001.

\bibitem{Kuk07}
    Chris Kuklewicz,
    \textit{Regular expressions/bounded space proposal},
    \url{http://wiki.haskell.org/index.php?title=Regular_expressions/Bounded_space_proposal&oldid=11475},
    2007.

\bibitem{Cox09}
    Russ Cox,
    backward POSIX matching algorithm (source code),
    \url{https://swtch.com/~rsc/regexp/nfa-posix.y.txt},
    2009.

\bibitem{SL14}
    Martin Sulzmann, Kenny Zhuo Ming Lu,
    \textit{POSIX Regular Expression Parsing with Derivatives},
    International Symposium on Functional and Logic Programming, pp. 203-220, Springer, Cham,
    2014.

\bibitem{SL13}
    Martin Sulzmann, Kenny Zhuo Ming Lu,
    \textit{Correct and Efficient POSIX Submatch Extraction with Regular Expression Derivatives},
    \url{https://www.home.hs-karlsruhe.de/~suma0002/publications/posix-derivatives.pdf},
    2013.

\bibitem{Bor15}
    Angelo Borsotti1, Luca Breveglieri, Stefano Crespi Reghizzi, Angelo Morzenti,
    \textit{From Ambiguous Regular Expressions to Deterministic Parsing Automata},
    International Conference on Implementation and Application of Automata. Springer, Cham, pp.35-48,
    2015.

\bibitem{ADU16}
    Fahad Ausaf, Roy Dyckhoff, Christian Urban,
    \textit{POSIX Lexing with Derivatives of Regular Expressions},
    International Conference on Interactive Theorem Proving. Springer, Cham, pp. 69-86,
    2016.

\bibitem{Tro17}
    Ulya Trofimovich,
    \textit{Tagged Deterministic Finite Automata with Lookahead},
    arXiv:1907.08837 [cs.FL],
    %(see also \url{http://re2c.org/2017_trofimovich_tagged_deterministic_finite_automata_with_lookahead.pdf}),
    2017.

\bibitem{CLR}
    Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest,
    \textit{Introduction to algorithms},
    1st edition,
    MIT Press and McGraw-Hill,
    ISBN 0-262-03141-8.

\bibitem{Moh02}
    Mehryar Mohri,
    \textit{Semiring frameworks and algorithms for shortest-distance problems},
    Journal of Automata, Languages and Combinatorics 7 (2002) 3, 321–350, Otto-von-Guericke-Universitat, Magdeburg,
    2002.

\bibitem{Kar14}
    Aaron Karper,
    \textit{Efficient regular expressions that produce parse trees},
    Master's thesis,
    University of Bern,
    2014.

\bibitem{GR93}
    Andrew V. Goldberg, Tomasz Radzik,
    \textit{A heuristic improvement of the Bellman-Ford algorithm},
    Elsevier, Applied Mathematics Letters, vol. 6, no. 3, pp. 3-6,
    1993.

\bibitem{CGR96}
    Boris V. Cherkassky, Andrew V. Goldberg, Tomasz Radzik,
    \textit{Shortest paths algorithms: Theory and experimental evaluation},
    Springer, Mathematical programming, vol. 73, no. 2, pp. 129-174,
    1996.

\bibitem{CGGTW09}
    Boris V. Cherkassky, Loukas Georgiadis, Andrew V. Goldberg, Robert E. Tarjan, and Renato F. Werneck.
    \textit{Shortest Path Feasibility Algorithms: An Experimental Evaluation},
    Journal of Experimental Algorithmics (JEA), 14, 7,
    2009.

\bibitem{Fow03}
    Glenn Fowler,
    \textit{An Interpretation of the POSIX Regex Standard},
    \url{https://archive.org/details/glenn_fowler_an_interpretation_of_the_posix_regex_standard},
    2003.

\bibitem{RE2C}
    \textit{RE2C}, lexer generator for C/C++.
    Website: \url{http://re2c.org},
    source code: \url{http://github.com/skvadrik/re2c}.

\bibitem{RE2}
    \textit{RE2}, regular expression library.
    Source code: \url{http://github.com/google/re2}.

\bibitem{Glibc}
    \textit{The GNU C library},
    \url{https://www.gnu.org/software/libc/}.

\bibitem{Massif}
    \textit{Massif, a heap profiler},
    \url{http://www.valgrind.org/docs/manual/ms-manual.html}.

\end{thebibliography}


\vfill\null
\clearpage


\section*{Appendix}

\subsection*{Proof of theorem \ref{theorem_porder_on_PTs}}

\begin{theoremEnd}[normal, no link to proof, no link to theorem]{lemma}
[Unique position mapping from all PTs to IRE]
    \label{lemma_positions}
    If $t, s \in PT(e)$ for some IRE $e$
    and there is a common position $x \in Pos(t) \cap Pos(s)$,
    then $x$ corresponds to the same position $x' \in Pos(e)$ in $e$ for both $t$ and $s$.
\end{theoremEnd}
\begin{proofEnd}
    The proof is by induction on the length of $x$.
    Induction basis: $x = x' = \Lambda$ (the roots of $t$ and $s$ correspond to the root of $e$).
    Induction step: suppose that for any position $x$ of length $|x| < h$ the lemma is true.
    We will show that if exists a $k \in \YN$ such that $x.k \in Pos(t) \cap Pos(s)$,
    then $x.k$ corresponds to the same position $x'.k'$ in $e$ for both $t$ and $s$ (for some $k' \in \YN$).
    If $e|_{x'}$ is an elementary IRE of the form $\Eps(i, j)$ or $\Sym(i, j, a)$,
    or if at least one of $t|_x$ and $s|_x$ is $\bot$,
    then $k$ doesn't exist.
    Otherwise $e|_{x'}$ is a compound IRE and both $t|_x$ and $s|_x$ are not $\bot$.
    If $e|_{x'}$ is an alternative $\Alt(i, j, e_1, e_2)$
    or a concatenation $\Cat(i, j, e_1, e_2)$,
    then both $t|_x$ and $s|_x$ have exactly two subtrees,
    and positions $x.1$ and $x.2$ in $t$ and $s$ correspond to positions $x'.1$ and $x'.2$ in $e$.
    Otherwise, $e|_{x'}$ is a repetition $\Rep(i, j, e_1, n, m)$
    and for any $k \geq 1$ position $x.k$ in $t$ and $s$ corresponds to position $x'.1$ in $e$.
\end{proofEnd}

\printProofs[theorem_porder_on_PTs]


\subsection*{Proof of theorem \ref{theorem_sorder_on_PTs}}

\begin{theoremEnd}[normal, no link to proof, no link to theorem]{lemma}
    \label{lemma_incomparability_equivdef}
    If $t, s \in \PT(e, w)$ for some IRE $e$ and string $w$,
    then $t \sim s \Leftrightarrow \; \forall x : \snorm{t}{x} = \snorm{s}{x}$.
\end{theoremEnd}
\begin{proofEnd}
    Forward implication: let $t \sim s$ and suppose, on the contrary, that $\exists x = min \{ y \mid \snorm{t}{y} \neq \snorm{s}{y} \}$,
    then either $t \prec_x s$ (if $\snorm{t}{x} > \snorm{s}{x}$) or $s \prec_x t$ (if $\snorm{t}{x} < \snorm{s}{x}$),
    both cases contradict $t \sim s$.
    %
    Backward implication: $\forall x : \snorm{t}{x} = \snorm{s}{x}$
    implies $\nexists x : t \prec_x s$ and $\nexists y : s \prec_y t$,
    which implies $t \sim s$.
\end{proofEnd}

\printProofs[theorem_sorder_on_PTs]


\subsection*{Proof of Theorem \ref{theorem_order_compat}}

\begin{theoremEnd}[normal, no link to proof, no link to theorem]{lemma}
[Comparability of subtrees]
    \label{lemma_subtrees}
    For a given IRE $e$, string $w$ and position $x$,
    if $t, s \in \PT(e, w)$, $x \in Sub(t) \cup Sub(s)$ and $\snorm{t}{y} = \snorm{s}{y} \; \forall y \leq x$,
    then $\exists e', w' : t|_x, s|_x \in \PT(e', w')$.
\end{theoremEnd}
\begin{proofEnd}
    By induction on the length of $x$.
    Induction basis: $x = \Lambda$, let $e' = e$ and $w' = w$.
    %
    Induction step: suppose that the lemma is true for any position $x$ of length
    $|x| < h$, we will show that it is true for any position $x.k$ of length $h$
    ($k \in \YN$).
    %
    Assume that $x.k \in Sub(t) \cap Sub(s)$
    (otherwise either $x.k \not\in Sub(t) \cup Sub(s)$,
    or exactly one of $\snorm{t}{x.k}$, $\snorm{s}{x.k}$ is $\infty$ --- in both
    cases lemma conditions are not satisfied).
    Then both $t|_x$ and $s|_x$ have at least one subtree: let
    $t|_{x} = T(t_1, \dots, t_n)$ and
    $s|_{x} = T(s_1, \dots, s_m)$, where $n, m \geq k$.
    %
    By induction hypothesis $\exists e', w' : t|_x, s|_x \in \PT(e', w')$.
    We have $w' = str(t_1) \dots str(t_n) = str(s_1) \dots str(s_m)$.
    %
    We show that $str(t_k) = str(s_k)$.
    Consider positions $x.j$ for $j \leq k$.
    By definition the set of submatch positions contains siblings,
    therefore $x.j \in Sub(t) \cap Sub(s)$.
    By lemma conditions $\snorm{t}{x.j} = \snorm{s}{x.j}$ (because $x.j \leq x.k$),
    therefore $|str(t_1) \dots str(t_{k-1})|$
    $= \sum\nolimits_{j=1}^{k-1}\snorm{t}{j}$
    $= \sum\nolimits_{j=1}^{k-1}\snorm{s}{j}$
    $= |str(s_1) \dots str(s_{k-1})|$ and
    $|str(t_k)| = |str(s_k)|$.
    Consequently, $str(t_k)$ and $str(s_k)$ start and end at the same character in $w'$ and therefore are equal.
    %
    Finally, have $t|_{x.k}, s|_{x.k} \in \PT(r|_{x.k}, str(t_k))$ and induction step is complete.
\end{proofEnd}

\printProofs[theorem_order_compat]


\subsection*{Proof of Theorem \ref{theorem_order_on_pe_same_as_on_pt}}

\begin{theoremEnd}[normal, no link to proof, no link to theorem]{lemma}
    \label{lemma_pe_equiv}
    Let $s, t \in \PT(e, w)$ for some IRE $e$ and string $w$.
    If $s \sim t$, then $\Phi_{h}(s) = \Phi_{h}(t) \; \forall h$.
\end{theoremEnd}
\begin{proofEnd}
    By induction on the height of $e$.
    %
    Induction basis: for height $1$ we have
    $| \PT(e, w) | \leq 1 \; \forall w$,
    therefore $s = t$ and $\Phi_{h}(s) = \Phi_{h}(t)$.
    %
    Induction step:
    height is greater than 1, therefore
    $s = T^{o} (s_1, \dots, s_n)$ and
    $t = T^{o} (t_1, \dots, t_m)$.
    If $o = 0$, then $\Phi_{h}(s) = str(s) = w = str(t) = \Phi_{h}(t)$.
    Otherwise $o \neq 0$.
    By lemma \ref{lemma_incomparability_equivdef} we have $s \sim t \Rightarrow \snorm{s}{x} = \snorm{t}{x} \;\forall x$.
    This implies $n = m$ (otherwise the norm of subtree at position $min(n,m)+1$ is $\infty$ for only one of $s$, $t$).
    Therefore
    $\Phi_{h}(s) = \Xl_{h+1} \Phi_{h+1}(s_1), \dots, \Phi_{h+1}(s_n) \Xr_h$ and
    $\Phi_{h}(t) = \Xl_{h+1} \Phi_{h+1}(t_1), \dots, \Phi_{h+1}(t_n) \Xr_h$.
    It suffices to show that $\forall i \leq n: \Phi_{h+1}(s_i) = \Phi_{h+1}(t_i)$.
    We have $\snorm{s_i}{x} = \snorm{t_i}{x} \;\forall x$ (implied by $\snorm{s}{x} = \snorm{t}{x} \;\forall x$),
    therefore by lemma \ref{lemma_incomparability_equivdef} $s_i \sim t_i$,
    and by lemma \ref{lemma_subtrees} $\exists e', w': s_i, t_i \in \PT(e', w')$,
    where the height of $e'$ is less than the height of $e$.
    By induction hypothesis $\Phi_{h+1}(s_i) = \Phi_{h+1}(t_i)$.
\end{proofEnd}

\begin{theoremEnd}[normal, no link to proof, no link to theorem]{lemma}
    \label{lemma_pe_less_1}
    Let $s, t \in \PT(e, w)$ for some IRE $e$ and string $w$.
    If $s \prec_x t$ and $|x| = 1$, then $\Phi_{h}(s) < \Phi_{h}(t) \; \forall h$.
\end{theoremEnd}
\begin{proofEnd}
    By lemma conditions $|x| = 1$, therefore $x \in \YN$.
    At least one of $s|_x$ and $t|_x$ must exist (otherwise $\snorm{s}{x} = \infty = \snorm{t}{x}$ which contradicts $s \prec_x t$),
    therefore $e$ is a compound IRE and $s$, $t$ can be represented as
    $s = T^{o} (s_1, \dots, s_n)$ and
    $t = T^{o} (t_1, \dots, t_m)$
    where $o \neq 0$ because $\Lambda$ is a prefix of decision position $x$.
    Let $k$ be the number of frames and let $j$ be the fork, then:
    \begin{alignat*}{7}
        \Phi_{h}(s) &\;=\; \Xl_{h+1} &&\Phi_{h+1}(s_1) &&\dots &&\Phi_{h+1}(s_n) \Xr_h
            &&\;=\; \beta_0 a_1 \dots a_j \beta_j &&\;\big|\; && \gamma_j a_{j + 1} \dots a_k \gamma_k \\[-0.5em]
        \Phi_{h}(t) &\;=\; \Xl_{h+1} &&\Phi_{h+1}(t_1) &&\dots &&\Phi_{h+1}(t_m) \Xr_h
            &&\;=\; \beta_0 a_1 \dots a_j \beta_j &&\;\big|\; && \delta_j a_{j + 1} \dots a_k \delta_k
    \end{alignat*}
%
    Consider any $i < x$ ($i \in \YN$).
    By lemma conditions $s \prec_x t$, therefore $\snorm{s}{y} = \snorm{t}{y} \;\forall y < x$.
    In particular $\snorm{s_i}{y} = \snorm{t_i}{y} \;\forall y$, therefore
    by lemma \ref{lemma_incomparability_equivdef} $s_i \sim t_i$,
    therefore by lemma \ref{lemma_pe_equiv} $\Phi_{h+1}(s_i) = \Phi_{h+1}(t_i)$.
    Let $traces (\Phi_{h}(s), \Phi_{h}(t)) = \big( (h_0, \dots, h_k), (h'_0, \dots, h'_k) \big)$.

    \begin{itemize}[itemsep=0.2em, topsep=0.5em]
    \item[(1)]
        Case $\infty = \snorm{s}{x} > \snorm{t}{x}$.
        In this case $s_x$ does not exist
        and fork happens immediately after $\Phi_{h+1}(s_{x-1})$, $\Phi_{h+1}(t_{x-1})$:
        \begin{alignat*}{7}
            \Phi_{h}(s) &\;=\; \Xl_{h+1} &&\Phi_{h+1}(s_1) &&\dots &&\Phi_{h+1}(s_{x-1})
                &&\;\big|\; \Xr_{h}         &&      && \\[-0.5em]
            \Phi_{h}(t) &\;=\; \Xl_{h+1} &&\Phi_{h+1}(t_1) &&\dots &&\Phi_{h+1}(t_{x-1})
                &&\;\big|\; \Phi_{h+1}(t_x) &&\dots &&\Phi_{h+1}(t_m) \Xr_{h}
        \end{alignat*}
        %
        Fork frame is the last one,
        therefore both $\gamma_j$ and $\delta_j$ contain the closing parenthesis $\Xr_{h}$
        and we have $h_j = h'_j = h$.
        For all $i < j$ we have $h_i = h'_i = -1$.
        Therefore $h_i = h'_i \;\forall i$ and $\Phi_{h}(s) \sim \Phi_{h}(t)$.
        Since $first(\gamma_j)$ is $\Xr$ and $first(\delta_j)$ is one of $\Xl$ and $\Xm$,
        we have $\Phi_{h}(s) \subset \Phi_{h}(t)$.
        Therefore $\Phi_{h}(s) < \Phi_{h}(t)$.

    \item[(2)]
        Case $\infty > \snorm{s}{x} > \snorm{t}{x} = -1$.
        In this case both $s_x$ and $t_x$ exist,
        $s_x$ is not $\bot$ and $t_x$ is $\bot$,
        and fork happens immediately after $\Phi_{h+1}(s_{x-1})$, $\Phi_{h+1}(t_{x-1})$:
        \begin{alignat*}{8}
            \Phi_{h}(s) &\;=\; \Xl_{h+1} &&\Phi_{h+1}(s_1) &&\dots &&\Phi_{h+1}(s_{x-1})
                &&\;\big|\; \Xl_{h+2} \; \beta' \; \Xr_{h+1} \; &&\Phi_{h+1}(s_{x+1}) &&\dots &&\Phi_{h+1}(s_n) \Xr_{h} \\[-0.5em]
            \Phi_{h}(t) &\;=\; \Xl_{h+1} &&\Phi_{h+1}(t_1) &&\dots &&\Phi_{h+1}(t_{x-1})
                &&\;\big|\; \Xm_{h+1} \;\;\;\;\;\;              &&\Phi_{h+1}(t_{x+1}) &&\dots &&\Phi_{h+1}(t_m) \Xr_{h}
        \end{alignat*}
        %
        \begin{itemize}
        \item[(2.1)]
            If the fork frame is the last one,
            then both $\gamma_j$ and $\delta_j$ contain the closing parenthesis $\Xr_{h}$
            and we have $h_j = h'_j = h$.

        \item[(2.2)]
            Otherwise the fork frame is not the last one.
            We have $minh(\gamma_j)$, $minh(\delta_j) \geq h + 1$
            and $lasth (\beta_j) = h + 1$
            (the last parenthesis in $\beta_j$ is either $\Xl_{h+1}$ if $x = 1$ and $s_{x-1}$ does not exist,
            or else one of $\Xr_{h+1}$ and $\Xm_{h+1}$),
            therefore $h_j = h'_j = h + 1$.
            %
            For subsequent frames $i$ such that $j < i < k$ we have $h_i = h'_i = h + 1$
            (on one hand $h_i, h'_i \leq h + 1$ because $h_j = h'_j = h + 1$,
            but on the other hand $minh(\gamma_i)$, $minh(\delta_i) \geq h + 1$).
            %
            For the last pair of frames we have $h_k = h'_k = h$ (they both contain the closing parenthesis $\Xr_{h}$).
        \end{itemize}

        In both cases $h_i = h'_i \;\forall i \geq j$.
        Since $h_i = h'_i = -1 \;\forall i < j$,
        we have $h_i = h'_i \;\forall i$ and therefore $\Phi_{h}(s) \sim \Phi_{h}(t)$.
        %
        Since $first (\gamma_j) = \Xl < \Xm = first (\delta_j)$ we have $\Phi_{h}(s) \subset \Phi_{h}(t)$.
        Therefore $\Phi_{h}(s) < \Phi_{h}(t)$.

    \item[(3)]
        Case $\infty > \snorm{s}{x} > \snorm{t}{x} \geq 0$.
        In this case both $s_x$ and $t_x$ exist and none of them is $\bot$,
        and fork happens somewhere after the opening parenthesis $\Xl_{h+2}$
        and before the closing parenthesis $\Xr_{h+1}$ in $\Phi_{h}(s_x)$, $\Phi_{h}(t_x)$:
        \begin{alignat*}{9}
            \Phi_{h}(s) &\;=\; \Xl_{h+1} &&\Phi_{h+1}(s_1) &&\dots &&\Phi_{h+1}(s_{x-1}) &&\; \Xl_{h+2} \; \beta'
                &&\;\big|\; \gamma' \; \Xr_{h+1} \; &&\Phi_{h+1}(s_{x+1}) &&\dots &&\Phi_{h+1}(s_n) \Xr_{h} \\[-0.5em]
            \Phi_{h}(t) &\;=\; \Xl_{h+1} &&\Phi_{h+1}(t_1) &&\dots &&\Phi_{h+1}(t_{x-1}) &&\; \Xl_{h+2} \; \beta'
                &&\;\big|\; \delta' \; \Xr_{h+1} \; &&\Phi_{h+1}(t_{x+1}) &&\dots &&\Phi_{h+1}(t_m) \Xr_{h}
        \end{alignat*}
        %
        From $\snorm{s}{x} > \snorm{t}{x} \geq 0$ it follows that
        $s_x$ contains more alphabet symbols than $t_x$.
        Consequently $\Phi_{h+1}(s_x)$ contains more alphabet symbols, and thus spans more frames than $\Phi_{h+1}(t_x)$.
        %
        Let $l$ be the index of the frame $\delta_l$ that contains the closing parenthesis $\Xr_{h+1}$ of $\Phi_{h+1}(t_x)$.
        By the above reasoning $\Phi_{h+1}(s_x)$ does not end in frame $\gamma_l$,
        therefore $\gamma_l$ does not contain the closing parenthesis $\Xr_{h+1}$
        and we have $minh (\gamma_l) \geq h+2$ and $minh (\delta_l) = h+1$.
        %
        Furthermore, note that $minh(\beta')$, $minh(\gamma')$, $minh(\delta') \geq h + 2$,
        therefore $lasth(\beta_j) \geq h+2$ (including the case when $\beta'$ is empty),
        and for all frames $i$ such that $j \leq i < l$ (if any) we have $h_i, h'_i \geq h+2$.
        %
        Consequently, for $l$-th frame we have $h_l \geq h+2$ and $h'_l = h + 1$, thus $h_l > h'_l$.
        %
        For subsequent frames $i$ such that $l < i < k$ we have $minh(\gamma_i)$, $minh(\delta_i) \geq h + 1$,
        therefore $h_i \geq h+1$ and $h'_i = h + 1$, thus $h_i \geq h'_i$.
        %
        For the last pair of frames we have $h_k = h'_k = h$, as they both contain the closing parenthesis $\Xr_{h}$.
        %
        Therefore $\Phi_{h}(s) \sqsubset \Phi_{h}(t)$,
        which implies $\Phi_{h}(s) < \Phi_{h}(t)$.
    \end{itemize}
\end{proofEnd}

\begin{theoremEnd}[normal, no link to proof, no link to theorem]{lemma}
    \label{lemma_pe_less}
    Let $s, t \in \PT(e, w)$ for some IRE $e$ and string $w$.
    If $s \prec_x t$, then $\Phi_{h}(s) < \Phi_{h}(t) \; \forall h$.
\end{theoremEnd}
\begin{proofEnd}
    The proof is by induction on the length of $x$.
    %
    Induction basis for $|x| = 1$ is given by lemma \ref{lemma_pe_less_1}.
    %
    Induction step: suppose that the lemma is correct for all $x$ of length $|x| < h$ and let $|x| = h$ ($h \geq 2$).
    %
    Let $x = x'.x''$ where $x' \in \YN$.
    %
    At least one of $s|_x$ and $t|_x$ must exist (otherwise $\snorm{s}{x} = \infty = \snorm{t}{x}$ which contradicts $s \prec_x t$),
    therefore both $e$ and $e|_{x'}$ are compound IREs and $s$, $t$ can be represented as
    $s = T^{o} (s_1, \dots, s_n)$ and
    $t = T^{o} (t_1, \dots, t_m)$ where
    $s' = s_{x'} = T^{o'} (s'_1, \dots, s'_{n'})$ and
    $t' = t_{x'} = T^{o'} (t'_1, \dots, t'_{m'})$
    and both $o, o' \neq 0$ (because $\Lambda$ and $x'$ are prefixes of decision position $x$).
    %
    Therefore $\Phi_{h}(s)$, $\Phi_{h}(t)$ can be represented as follows:
    \begin{alignat*}{9}
        \Phi_{h}(s)
            \;&=
                \;&& \Xl_{h+1} \Phi_{h+1}(s_1) \dots \Phi_{h+1}(s_{x'-1})
                \;&& \overbrace {\Xl_{h+2} \Phi_{h+2}(s'_1) \dots \Phi_{h+2}(s'_{n'}) \Xr_{h+1}}^{\Phi_{h+1}(s')}
                \;&& \Phi_{h+1}(s_{x'+1}) \Phi_{h+1}(s_n) \Xr_{h}
                \\
        \Phi_{h}(t)
            \;&=
                \;&& \Xl_{h+1} \Phi_{h+1}(t_1) \dots \Phi_{h+1}(t_{x'-1})
                \;&& \underbrace {\Xl_{h+2} \Phi_{h+2}(t'_1) \dots \Phi_{h+2}(t'_{m'}) \Xr_{h+1}}_{\Phi_{h+1}(t')}
                \;&& \Phi_{h+1}(t_{x'+1}) \Phi_{h+1}(t_m) \Xr_{h}
    \end{alignat*}
    %
    Consider any $i < x'$.
    By lemma conditions $s \prec_x t$, therefore $\snorm{s}{y} = \snorm{t}{y} \;\forall y < x$, and
    in particular $\snorm{s_i}{y} = \snorm{t_i}{y} \;\forall y$, therefore
    by lemma \ref{lemma_incomparability_equivdef} $s_i \sim t_i$,
    therefore by lemma \ref{lemma_pe_equiv} $\Phi_{h+1}(s_i) = \Phi_{h+1}(t_i)$.
    %
    Since $x' < x$ we have $\snorm{s}{y} = \snorm{t}{y} \;\forall y \leq x'$ and
    by lemma \ref{lemma_subtrees} $\exists e', w' : s', t' \in \PT(e', w')$.
    Since $\snorm{s'}{y} = \snorm{s}{x'.y} \;\forall y$
    and $\snorm{t'}{y} = \snorm{t}{x'.y} \;\forall y$,
    we have $s' \prec_{x''} t'$.
    Since $|x''| < |x|$ by induction hypothesis we have $\Phi_{h+1}(s') < \Phi_{h+1}(t')$.
    %
    If $j$ is the fork and $f \leq j \leq k$, then
    $\Phi_{h}(s)$, $\Phi_{h}(t)$ can be represented as:
    \begin{alignat*}{9}
        \Phi_{h}(s)
            \;&=
                \;&& \beta_0 a_1 \dots a_f \beta_f^1
                \;&& \overbrace {\beta_f^2  a_{f+1} \dots a_j \beta_j \;\big|\; \gamma_j a_{j+1} \dots a_k \gamma_k^1}^{\Phi_{h+1}(s')}
                \;&& \gamma_k^2 a_{k+1} \dots a_l \gamma_l
                \\[-0.5em]
        \Phi_{h}(t)
            \;&=
                \;&& \beta_0 a_1 \dots a_f \beta_f^1
                \;&& \underbrace {\beta_f^2  a_{f+1} \dots a_j \beta_j \;\big|\; \delta_j a_{j+1} \dots a_k \delta_k^1}_{\Phi_{h+1}(t')}
                \;&& \delta_k^2 a_{k+1} \dots a_l \delta_l
    \end{alignat*}
    %
    Let $traces (\Phi_{h}(s), \Phi_{h}(t)) = \big( (h_0, \dots, h_l), (h'_0, \dots, h'_l) \big)$
    and $traces (\Phi_{h+1}(s'), \Phi_{h+1}(t')) = \big( (\sigma_h, \dots, \sigma_k), (\sigma'_h, \dots, \sigma'_k) \big)$.
    %
    We show that for frames $i$ such that $j \leq i < k$ we have
    $h_i = \sigma_i \wedge h'_i = \sigma'_i$
    and for subsequent frames $k \leq i \leq l$ we have $h_i = h'_i$.
    %
    \begin{itemize}[itemsep=0.2em, topsep=0.5em]
    \item[(1)]
        Case $i = j < k \leq l$ (the fork frame).
        Since we have shown that $\Phi_{h+1}(s_i) = \Phi_{h+1}(t_i) \;\forall i < x'$,
        and since $\Phi_{h+1}(s')$ and $\Phi_{h+1}(t')$ have nonempty common prefix $\Xl_{h+2}$,
        it follows that $lasth (\Phi_{h}(s) \sqcap \Phi_{h}(t)) = lasth (\Phi_{h+1}(s') \sqcap \Phi_{h+1}(t'))$.
        %
        From $j < k$ it follows that $\gamma_j$ and $\delta_j$ end before $a_k$
        and are not changed by appending $\gamma^2_k$ and $\delta^2_k$.
        %
        Therefore $h_j = \sigma_j \wedge h'_j = \sigma'_j$.

    \item[(2)]
        Case $j < i < k \leq l$.
        The computation of $h_i$, $h'_i$ depends only on
        $h_j$, $h'_j$,
        or which we have shown $h_j = \sigma_j \wedge h'_j = \sigma'_j$ in case (1),
        and on $\Phi_{h+1}(s')$, $\Phi_{h+1}(t')$,
        which are not changed by appending $\gamma^2_k$ and $\delta^2_k$ since $i < k$.
        Therefore $h_i = \sigma_i \wedge h'_i = \sigma'_i$.

    \item[(3)]
        Case $j \leq i = k < l$. We have
        $minh (\gamma^1_k) = minh (\delta^1_k) = h + 1$ and
        $minh (\gamma^2_k) = minh (\delta^2_k) \geq h + 1$.
        None of the preceding frames after the fork contain parentheses with height less than $h + 1$,
        therefore $h_k = h'_k = h + 1$.

    \item[(4)]
        Case $j \leq k < i < l$.
        We have $h_i = h'_i = h + 1$,
        because $h_k = h'_k = h + 1$ and $minh(\gamma_i)$, $minh(\delta_i) \geq h + 1$.

    \item[(5)]
        Case $j \leq k \leq i = l$.
        We have $h_l = h'_l = h$,
        because both $\gamma_l$ and $\delta_l$ contain the closing parenthesis $\Xr_{h}$.
    \end{itemize}
    %
    We have shown that $h_i = \sigma_i \wedge h'_i = \sigma'_i \;\forall i: j \leq i < k$
    and $h_i = h'_i \;\forall i: k \leq i \leq l$.
    It trivially follows that $\Phi_{h+1}(s') \sqsubset \Phi_{h+1}(t')$ $\Rightarrow \Phi_{h}(s) \sqsubset \Phi_{h}(t)$
    and $\Phi_{h+1}(s') \sim \Phi_{h+1}(t')$ $\Rightarrow \Phi_{h}(s) \sim \Phi_{h}(t)$.
    Because none of $\Phi_{h+1}(s')$, $\Phi_{h+1}(t')$ is a proper prefix of another,
    $\Phi_{h+1}(s') \subset \Phi_{h+1}(t')$ $\Rightarrow \Phi_{h}(s) \subset \Phi_{h}(t)$.
    Therefore $\Phi_{h+1}(s') < \Phi_{h+1}(t') \Rightarrow \Phi_{h}(s) < \Phi_{h}(t)$
    (the premise has been shown).
\end{proofEnd}

\printProofs[theorem_order_on_pe_same_as_on_pt]


\subsection*{Correctness of incremental path comparison}

\printProofs[lemma_frames]
\printProofs[lemmata_closure]


\end{document}

