34
+ − 1
\documentclass[a4paper,UKenglish]{lipics}
30
+ − 2
\usepackage{graphic}
+ − 3
\usepackage{data}
+ − 4
\usepackage{tikz-cd}
35
+ − 5
\usepackage{algorithm}
+ − 6
\usepackage{amsmath}
+ − 7
\usepackage[noend]{algpseudocode}
42
+ − 8
\usepackage{enumitem}
30
+ − 9
% \documentclass{article}
+ − 10
%\usepackage[utf8]{inputenc}
+ − 11
%\usepackage[english]{babel}
+ − 12
%\usepackage{listings}
+ − 13
% \usepackage{amsthm}
+ − 14
% \usepackage{hyperref}
+ − 15
% \usepackage[margin=0.5in]{geometry}
+ − 16
%\usepackage{pmboxdraw}
+ − 17
+ − 18
\title{POSIX Regular Expression Matching and Lexing}
+ − 19
\author{Chengsong Tan}
+ − 20
\affil{King's College London\\
+ − 21
London, UK\\
+ − 22
\texttt{chengsong.tan@kcl.ac.uk}}
+ − 23
\authorrunning{Chengsong Tan}
+ − 24
\Copyright{Chengsong Tan}
+ − 25
+ − 26
\newcommand{\dn}{\stackrel{\mbox{\scriptsize def}}{=}}%
+ − 27
\newcommand{\ZERO}{\mbox{\bf 0}}
+ − 28
\newcommand{\ONE}{\mbox{\bf 1}}
+ − 29
\def\lexer{\mathit{lexer}}
+ − 30
\def\mkeps{\mathit{mkeps}}
+ − 31
\def\inj{\mathit{inj}}
+ − 32
\def\Empty{\mathit{Empty}}
+ − 33
\def\Left{\mathit{Left}}
+ − 34
\def\Right{\mathit{Right}}
+ − 35
\def\Stars{\mathit{Stars}}
+ − 36
\def\Char{\mathit{Char}}
+ − 37
\def\Seq{\mathit{Seq}}
+ − 38
\def\Der{\mathit{Der}}
+ − 39
\def\nullable{\mathit{nullable}}
+ − 40
\def\Z{\mathit{Z}}
+ − 41
\def\S{\mathit{S}}
+ − 42
+ − 43
%\theoremstyle{theorem}
+ − 44
%\newtheorem{theorem}{Theorem}
+ − 45
%\theoremstyle{lemma}
+ − 46
%\newtheorem{lemma}{Lemma}
+ − 47
%\newcommand{\lemmaautorefname}{Lemma}
+ − 48
%\theoremstyle{definition}
+ − 49
%\newtheorem{definition}{Definition}
35
+ − 50
\algnewcommand\algorithmicswitch{\textbf{switch}}
+ − 51
\algnewcommand\algorithmiccase{\textbf{case}}
+ − 52
\algnewcommand\algorithmicassert{\texttt{assert}}
+ − 53
\algnewcommand\Assert[1]{\State \algorithmicassert(#1)}%
+ − 54
% New "environments"
+ − 55
\algdef{SE}[SWITCH]{Switch}{EndSwitch}[1]{\algorithmicswitch\ #1\ \algorithmicdo}{\algorithmicend\ \algorithmicswitch}%
+ − 56
\algdef{SE}[CASE]{Case}{EndCase}[1]{\algorithmiccase\ #1}{\algorithmicend\ \algorithmiccase}%
+ − 57
\algtext*{EndSwitch}%
+ − 58
\algtext*{EndCase}%
30
+ − 59
+ − 60
+ − 61
\begin{document}
+ − 62
+ − 63
\maketitle
+ − 64
+ − 65
\begin{abstract}
+ − 66
Brzozowski introduced in 1964 a beautifully simple algorithm for
+ − 67
regular expression matching based on the notion of derivatives of
+ − 68
regular expressions. In 2014, Sulzmann and Lu extended this
40
+ − 69
algorithm to not just give a YES/NO answer for whether or not a
+ − 70
regular expression matches a string, but in case it matches also
+ − 71
\emph{how} it matches the string. This is important for
+ − 72
applications such as lexing (tokenising a string). The problem is to
+ − 73
make the algorithm by Sulzmann and Lu fast on all inputs without
+ − 74
breaking its correctness. We have already developed some
+ − 75
simplification rules for this, but have not proved that they
+ − 76
preserve the correctness of the algorithm. We also have not yet
+ − 77
looked at extended regular expressions, such as bounded repetitions,
+ − 78
negation and back-references.
30
+ − 79
\end{abstract}
+ − 80
+ − 81
\section{Introduction}
+ − 82
+ − 83
This PhD-project is about regular expression matching and
+ − 84
lexing. Given the maturity of this topic, the reader might wonder:
+ − 85
Surely, regular expressions must have already been studied to death?
+ − 86
What could possibly be \emph{not} known in this area? And surely all
+ − 87
implemented algorithms for regular expression matching are blindingly
+ − 88
fast?
+ − 89
+ − 90
+ − 91
+ − 92
Unfortunately these preconceptions are not supported by evidence: Take
+ − 93
for example the regular expression $(a^*)^*\,b$ and ask whether
+ − 94
strings of the form $aa..a$ match this regular
+ − 95
expression. Obviously they do not match---the expected $b$ in the last
+ − 96
position is missing. One would expect that modern regular expression
+ − 97
matching engines can find this out very quickly. Alas, if one tries
+ − 98
this example in JavaScript, Python or Java 8 with strings like 28
+ − 99
$a$'s, one discovers that this decision takes around 30 seconds and
+ − 100
takes considerably longer when adding a few more $a$'s, as the graphs
+ − 101
below show:
+ − 102
+ − 103
\begin{center}
+ − 104
\begin{tabular}{@{}c@{\hspace{0mm}}c@{\hspace{0mm}}c@{}}
+ − 105
\begin{tikzpicture}
+ − 106
\begin{axis}[
+ − 107
xlabel={$n$},
+ − 108
x label style={at={(1.05,-0.05)}},
+ − 109
ylabel={time in secs},
+ − 110
enlargelimits=false,
+ − 111
xtick={0,5,...,30},
+ − 112
xmax=33,
+ − 113
ymax=35,
+ − 114
ytick={0,5,...,30},
+ − 115
scaled ticks=false,
+ − 116
axis lines=left,
+ − 117
width=5cm,
+ − 118
height=4cm,
+ − 119
legend entries={JavaScript},
+ − 120
legend pos=north west,
+ − 121
legend cell align=left]
+ − 122
\addplot[red,mark=*, mark options={fill=white}] table {re-js.data};
+ − 123
\end{axis}
+ − 124
\end{tikzpicture}
+ − 125
&
+ − 126
\begin{tikzpicture}
+ − 127
\begin{axis}[
+ − 128
xlabel={$n$},
+ − 129
x label style={at={(1.05,-0.05)}},
+ − 130
%ylabel={time in secs},
+ − 131
enlargelimits=false,
+ − 132
xtick={0,5,...,30},
+ − 133
xmax=33,
+ − 134
ymax=35,
+ − 135
ytick={0,5,...,30},
+ − 136
scaled ticks=false,
+ − 137
axis lines=left,
+ − 138
width=5cm,
+ − 139
height=4cm,
+ − 140
legend entries={Python},
+ − 141
legend pos=north west,
+ − 142
legend cell align=left]
+ − 143
\addplot[blue,mark=*, mark options={fill=white}] table {re-python2.data};
+ − 144
\end{axis}
+ − 145
\end{tikzpicture}
+ − 146
&
+ − 147
\begin{tikzpicture}
+ − 148
\begin{axis}[
+ − 149
xlabel={$n$},
+ − 150
x label style={at={(1.05,-0.05)}},
+ − 151
%ylabel={time in secs},
+ − 152
enlargelimits=false,
+ − 153
xtick={0,5,...,30},
+ − 154
xmax=33,
+ − 155
ymax=35,
+ − 156
ytick={0,5,...,30},
+ − 157
scaled ticks=false,
+ − 158
axis lines=left,
+ − 159
width=5cm,
+ − 160
height=4cm,
+ − 161
legend entries={Java 8},
+ − 162
legend pos=north west,
+ − 163
legend cell align=left]
+ − 164
\addplot[cyan,mark=*, mark options={fill=white}] table {re-java.data};
+ − 165
\end{axis}
+ − 166
\end{tikzpicture}\\
+ − 167
\multicolumn{3}{c}{Graphs: Runtime for matching $(a^*)^*\,b$ with strings
+ − 168
of the form $\underbrace{aa..a}_{n}$.}
+ − 169
\end{tabular}
+ − 170
\end{center}
+ − 171
+ − 172
\noindent These are clearly abysmal and possibly surprising results.
+ − 173
One would expect these systems doing much better than that---after
+ − 174
all, given a DFA and a string, whether a string is matched by this DFA
+ − 175
should be linear.
+ − 176
+ − 177
Admittedly, the regular expression $(a^*)^*\,b$ is carefully chosen to
+ − 178
exhibit this ``exponential behaviour''. Unfortunately, such regular
+ − 179
expressions are not just a few ``outliers'', but actually they are
+ − 180
frequent enough that a separate name has been created for
+ − 181
them---\emph{evil regular expressions}. In empiric work, Davis et al
+ − 182
report that they have found thousands of such evil regular expressions
+ − 183
in the JavaScript and Python ecosystems \cite{Davis18}.
+ − 184
+ − 185
This exponential blowup sometimes causes real pain in real life:
+ − 186
for example on 20 July 2016 one evil regular expression brought the
+ − 187
webpage \href{http://stackexchange.com}{Stack Exchange} to its knees \footnote{https://stackstatus.net/post/147710624694/outage-postmortem-july-20-2016}.
+ − 188
In this instance, a regular expression intended to just trim white
+ − 189
spaces from the beginning and the end of a line actually consumed
+ − 190
massive amounts of CPU-resources and because of this the web servers
+ − 191
ground to a halt. This happened when a post with 20,000 white spaces
+ − 192
was submitted, but importantly the white spaces were neither at the
+ − 193
beginning nor at the end. As a result, the regular expression matching
+ − 194
engine needed to backtrack over many choices.
+ − 195
+ − 196
The underlying problem is that many ``real life'' regular expression
+ − 197
matching engines do not use DFAs for matching. This is because they
+ − 198
support regular expressions that are not covered by the classical
+ − 199
automata theory, and in this more general setting there are quite a
+ − 200
few research questions still unanswered and fast algorithms still need
+ − 201
to be developed.
+ − 202
+ − 203
There is also another under-researched problem to do with regular
+ − 204
expressions and lexing, i.e.~the process of breaking up strings into
+ − 205
sequences of tokens according to some regular expressions. In this
+ − 206
setting one is not just interested in whether or not a regular
+ − 207
expression matches a string, but if it matches also in \emph{how} it
+ − 208
matches the string. Consider for example a regular expression
+ − 209
$r_{key}$ for recognising keywords such as \textit{if}, \textit{then}
+ − 210
and so on; and a regular expression $r_{id}$ for recognising
+ − 211
identifiers (say, a single character followed by characters or
+ − 212
numbers). One can then form the compound regular expression
+ − 213
$(r_{key} + r_{id})^*$ and use it to tokenise strings. But then how
+ − 214
should the string \textit{iffoo} be tokenised? It could be tokenised
+ − 215
as a keyword followed by an identifier, or the entire string as a
+ − 216
single identifier. Similarly, how should the string \textit{if} be
+ − 217
tokenised? Both regular expressions, $r_{key}$ and $r_{id}$, would
+ − 218
``fire''---so is it an identifier or a keyword? While in applications
+ − 219
there is a well-known strategy to decide these questions, called POSIX
+ − 220
matching, only relatively recently precise definitions of what POSIX
+ − 221
matching actually means have been formalised
+ − 222
\cite{AusafDyckhoffUrban2016,OkuiSuzuki2010,Vansummeren2006}. Roughly,
37
+ − 223
POSIX matching means matching the longest initial substring.
+ − 224
In the case of a tie, the initial submatch is chosen according to some priorities attached to the
30
+ − 225
regular expressions (e.g.~keywords have a higher priority than
+ − 226
identifiers). This sounds rather simple, but according to Grathwohl et
+ − 227
al \cite[Page 36]{CrashCourse2014} this is not the case. They wrote:
+ − 228
+ − 229
\begin{quote}
+ − 230
\it{}``The POSIX strategy is more complicated than the greedy because of
+ − 231
the dependence on information about the length of matched strings in the
+ − 232
various subexpressions.''
+ − 233
\end{quote}
+ − 234
+ − 235
\noindent
+ − 236
This is also supported by evidence collected by Kuklewicz
+ − 237
\cite{Kuklewicz} who noticed that a number of POSIX regular expression
+ − 238
matchers calculate incorrect results.
+ − 239
+ − 240
Our focus is on an algorithm introduced by Sulzmann and Lu in 2014 for
+ − 241
regular expression matching according to the POSIX strategy
+ − 242
\cite{Sulzmann2014}. Their algorithm is based on an older algorithm by
+ − 243
Brzozowski from 1964 where he introduced the notion of derivatives of
+ − 244
regular expressions \cite{Brzozowski1964}. We shall briefly explain
+ − 245
the algorithms next.
+ − 246
+ − 247
\section{The Algorithms by Brzozowski, and Sulzmann and Lu}
+ − 248
40
+ − 249
Suppose (basic) regular expressions are given by the following grammar:
38
+ − 250
\[ r ::= \ZERO \mid \ONE
+ − 251
\mid c
+ − 252
\mid r_1 \cdot r_2
+ − 253
\mid r_1 + r_2
+ − 254
\mid r^*
+ − 255
\]
30
+ − 256
+ − 257
\noindent
40
+ − 258
The intended meaning of the constructors is as usual: $\ZERO$
30
+ − 259
cannot match any string, $\ONE$ can match the empty string, the
+ − 260
character regular expression $c$ can match the character $c$, and so
40
+ − 261
on.
+ − 262
+ − 263
The brilliant contribution by Brzozowski is the notion of
30
+ − 264
\emph{derivatives} of regular expressions. The idea behind this
+ − 265
notion is as follows: suppose a regular expression $r$ can match a
+ − 266
string of the form $c\!::\! s$ (that is a list of characters starting
+ − 267
with $c$), what does the regular expression look like that can match
40
+ − 268
just $s$? Brzozowski gave a neat answer to this question. He started
+ − 269
with the definition of $nullable$:
36
+ − 270
\begin{center}
+ − 271
\begin{tabular}{lcl}
+ − 272
$\nullable(\ZERO)$ & $\dn$ & $\mathit{false}$ \\
+ − 273
$\nullable(\ONE)$ & $\dn$ & $\mathit{true}$ \\
+ − 274
$\nullable(c)$ & $\dn$ & $\mathit{false}$ \\
+ − 275
$\nullable(r_1 + r_2)$ & $\dn$ & $\nullable(r_1) \vee \nullable(r_2)$ \\
+ − 276
$\nullable(r_1\cdot r_2)$ & $\dn$ & $\nullable(r_1) \wedge \nullable(r_2)$ \\
+ − 277
$\nullable(r^*)$ & $\dn$ & $\mathit{true}$ \\
+ − 278
\end{tabular}
+ − 279
\end{center}
38
+ − 280
This function simply tests whether the empty string is in $L(r)$.
36
+ − 281
He then defined
30
+ − 282
the following operation on regular expressions, written
+ − 283
$r\backslash c$ (the derivative of $r$ w.r.t.~the character $c$):
+ − 284
+ − 285
\begin{center}
+ − 286
\begin{tabular}{lcl}
+ − 287
$\ZERO \backslash c$ & $\dn$ & $\ZERO$\\
+ − 288
$\ONE \backslash c$ & $\dn$ & $\ZERO$\\
+ − 289
$d \backslash c$ & $\dn$ &
+ − 290
$\mathit{if} \;c = d\;\mathit{then}\;\ONE\;\mathit{else}\;\ZERO$\\
+ − 291
$(r_1 + r_2)\backslash c$ & $\dn$ & $r_1 \backslash c \,+\, r_2 \backslash c$\\
36
+ − 292
$(r_1 \cdot r_2)\backslash c$ & $\dn$ & $\mathit{if} \, nullable(r_1)$\\
30
+ − 293
& & $\mathit{then}\;(r_1\backslash c) \cdot r_2 \,+\, r_2\backslash c$\\
+ − 294
& & $\mathit{else}\;(r_1\backslash c) \cdot r_2$\\
+ − 295
$(r^*)\backslash c$ & $\dn$ & $(r\backslash c) \cdot r^*$\\
+ − 296
\end{tabular}
+ − 297
\end{center}
+ − 298
+ − 299
\noindent
+ − 300
+ − 301
+ − 302
+ − 303
%Assuming the classic notion of a
+ − 304
%\emph{language} of a regular expression, written $L(\_)$, t
40
+ − 305
\noindent
+ − 306
The main property of the derivative operation is that
30
+ − 307
+ − 308
\begin{center}
+ − 309
$c\!::\!s \in L(r)$ holds
+ − 310
if and only if $s \in L(r\backslash c)$.
+ − 311
\end{center}
+ − 312
+ − 313
\noindent
38
+ − 314
For us the main advantage is that derivatives can be
+ − 315
straightforwardly implemented in any functional programming language,
+ − 316
and are easily definable and reasoned about in theorem provers---the
+ − 317
definitions just consist of inductive datatypes and simple recursive
+ − 318
functions. Moreover, the notion of derivatives can be easily
+ − 319
generalised to cover extended regular expression constructors such as
+ − 320
the not-regular expression, written $\neg\,r$, or bounded repetitions
+ − 321
(for example $r^{\{n\}}$ and $r^{\{n..m\}}$), which cannot be so
+ − 322
straightforwardly realised within the classic automata approach.
+ − 323
For the moment however, we focus only on the usual basic regular expressions.
+ − 324
+ − 325
40
+ − 326
Now if we want to find out whether a string $s$ matches with a regular
+ − 327
expression $r$, build the derivatives of $r$ w.r.t.\ (in succession)
+ − 328
all the characters of the string $s$. Finally, test whether the
+ − 329
resulting regular expression can match the empty string. If yes, then
+ − 330
$r$ matches $s$, and no in the negative case. To implement this idea
+ − 331
we can generalise the derivative operation to strings like this:
30
+ − 332
\begin{center}
+ − 333
\begin{tabular}{lcl}
+ − 334
$r \backslash (c\!::\!s) $ & $\dn$ & $(r \backslash c) \backslash s$ \\
40
+ − 335
$r \backslash [\,] $ & $\dn$ & $r$
30
+ − 336
\end{tabular}
+ − 337
\end{center}
40
+ − 338
37
+ − 339
\noindent
40
+ − 340
Using this definition we obtain a simple and elegant regular
30
+ − 341
expression matching algorithm:
+ − 342
\[
+ − 343
match\;s\;r \;\dn\; nullable(r\backslash s)
+ − 344
\]
40
+ − 345
+ − 346
\noindent
+ − 347
Pictorially this algorithm can be illustrated as follows:
+ − 348
+ − 349
\begin{center}
38
+ − 350
\begin{tikzcd}\label{graph:*}
39
+ − 351
r_0 \arrow[r, "\backslash c_0"] & r_1 \arrow[r, "\backslash c_1"] & r_2 \arrow[r, dashed] & r_n \arrow[r,"nullable?"] & Yes/No
38
+ − 352
\end{tikzcd}
40
+ − 353
\end{center}
+ − 354
+ − 355
\noindent
+ − 356
where we start with a regular expression $r_0$, build successive derivatives
+ − 357
until we exhaust the string and then use \textit{nullable} to test whether the
+ − 358
result can match the empty string. It can be relatively easily shown that this
+ − 359
matcher is correct.
38
+ − 360
30
+ − 361
One limitation, however, of Brzozowski's algorithm is that it only
+ − 362
produces a YES/NO answer for whether a string is being matched by a
+ − 363
regular expression. Sulzmann and Lu~\cite{Sulzmann2014} extended this
+ − 364
algorithm to allow generation of an actual matching, called a
40
+ − 365
\emph{value}.
30
+ − 366
+ − 367
\begin{center}
+ − 368
\begin{tabular}{c@{\hspace{20mm}}c}
+ − 369
\begin{tabular}{@{}rrl@{}}
+ − 370
\multicolumn{3}{@{}l}{\textbf{Regular Expressions}}\medskip\\
+ − 371
$r$ & $::=$ & $\ZERO$\\
+ − 372
& $\mid$ & $\ONE$ \\
+ − 373
& $\mid$ & $c$ \\
+ − 374
& $\mid$ & $r_1 \cdot r_2$\\
+ − 375
& $\mid$ & $r_1 + r_2$ \\
+ − 376
\\
+ − 377
& $\mid$ & $r^*$ \\
+ − 378
\end{tabular}
+ − 379
&
+ − 380
\begin{tabular}{@{\hspace{0mm}}rrl@{}}
+ − 381
\multicolumn{3}{@{}l}{\textbf{Values}}\medskip\\
+ − 382
$v$ & $::=$ & \\
+ − 383
& & $\Empty$ \\
+ − 384
& $\mid$ & $\Char(c)$ \\
+ − 385
& $\mid$ & $\Seq\,v_1\, v_2$\\
+ − 386
& $\mid$ & $\Left(v)$ \\
+ − 387
& $\mid$ & $\Right(v)$ \\
+ − 388
& $\mid$ & $\Stars\,[v_1,\ldots\,v_n]$ \\
+ − 389
\end{tabular}
+ − 390
\end{tabular}
+ − 391
\end{center}
+ − 392
+ − 393
\noindent
39
+ − 394
Values and regular expressions correspond to each other as illustrated by placing corresponding values next to the regular expressions.
+ − 395
The idea of values is to express parse trees.
+ − 396
Suppose by using a flatten operation, written $|v|$, we can extract the underlying string of v.
+ − 397
For example, $|\mathit{Seq} \, \mathit{Char(c)} \, \mathit{Char(d)}|$ = $cd$. We omit this straightforward definition.
+ − 398
Using this flatten notation, we now elaborate how values can express parse trees. $\Seq\,v_1\, v_2$ tells us how the string $|v_1| \cdot |v_2|$ matches the regex $r_1 \cdot r_2$: $r_1$ matches $|v_1|$ and respectively $r_2$ matches $|v_2|$. Exactly how these two are matched are contained in the sub-structure of $v_1$ and $v_2$.
30
+ − 399
+ − 400
To give a concrete example of how value works, consider the string $xy$ and the
+ − 401
regular expression $(x + (y + xy))^*$. We can view this regular
+ − 402
expression as a tree and if the string $xy$ is matched by two Star
+ − 403
``iterations'', then the $x$ is matched by the left-most alternative
+ − 404
in this tree and the $y$ by the right-left alternative. This suggests
+ − 405
to record this matching as
+ − 406
+ − 407
\begin{center}
+ − 408
$\Stars\,[\Left\,(\Char\,x), \Right(\Left(\Char\,y))]$
+ − 409
\end{center}
+ − 410
+ − 411
\noindent
+ − 412
where $\Stars$ records how many
+ − 413
iterations were used; and $\Left$, respectively $\Right$, which
+ − 414
alternative is used. The value for
+ − 415
matching $xy$ in a single ``iteration'', i.e.~the POSIX value,
+ − 416
would look as follows
+ − 417
+ − 418
\begin{center}
+ − 419
$\Stars\,[\Seq\,(\Char\,x)\,(\Char\,y)]$
+ − 420
\end{center}
+ − 421
+ − 422
\noindent
+ − 423
where $\Stars$ has only a single-element list for the single iteration
+ − 424
and $\Seq$ indicates that $xy$ is matched by a sequence regular
+ − 425
expression.
+ − 426
+ − 427
The contribution of Sulzmann and Lu is an extension of Brzozowski's
+ − 428
algorithm by a second phase (the first phase being building successive
+ − 429
derivatives). In this second phase, for every successful match the
39
+ − 430
corresponding POSIX value is computed. Pictorially, the Sulzmann and Lu algorithm looks like the following diagram(the working flow of the simple matching algorithm that just gives a $YES/NO$ answer is given before \ref{graph:*}):\\
30
+ − 431
\begin{tikzcd}
36
+ − 432
r_0 \arrow[r, "\backslash c_0"] \arrow[d] & r_1 \arrow[r, "\backslash c_1"] \arrow[d] & r_2 \arrow[r, dashed] \arrow[d] & r_n \arrow[d, "mkeps" description] \\
30
+ − 433
v_0 & v_1 \arrow[l,"inj_{r_0} c_0"] & v_2 \arrow[l, "inj_{r_1} c_1"] & v_n \arrow[l, dashed]
+ − 434
\end{tikzcd}
35
+ − 435
37
+ − 436
30
+ − 437
We shall briefly explain this interesting process.\\ For the convenience of explanation, we have the following notations: the regular expression $r$ used for matching is also called $r_0$ and the string $s$ is composed of $n$ characters $c_0 c_1 ... c_{n-1}$.
37
+ − 438
First, we do the derivative operation on $r_0$, $r_1$, ..., using characters $c_0$, $c_1$, ... until we get the final derivative $r_n$.We test whether it is $nullable$ or not. If no we know immediately the string does not match the regex. If yes, we start building the parse tree incrementally. We first call $mkeps$(which stands for make epsilon--make the parse tree for how the empty word matched the empty regular expression epsilon) to construct the parse tree $v_n$ for how the final derivative $r_n$ matches the empty string:
30
+ − 439
41
+ − 440
$mkeps $ $1 \,[] $ $= Empty$
+ − 441
......
+ − 442
+ − 443
30
+ − 444
After this, we inject back the characters one by one, in reverse order as they were chopped off, to build the parse tree $v_i$ for how the regex $r_i$ matches the string $s_i$($s_i$ means the string s with the first $i$ characters being chopped off) from the previous parse tree. After $n$ transformations, we get the parse tree for how $r_0$ matches $s$, exactly as we wanted.
+ − 445
An inductive proof can be routinely established.
41
+ − 446
+ − 447
It is instructive to see how it works by a little example. Suppose we have a regular expression $(a+b+ab+c+abc)*$ and we want to match it against the string $abc$. By POSIX rules the lexer should go for the longest matching, i.e. it should match the string $abc$ in one star iteration, using the longest string $abc$ in the sub-expression $a+b+ab+c+abc$(we use $r$ to denote this sub-expression for conciseness). Here is how the lexer achieves a parse tree for this matching.
+ − 448
First, we build successive derivatives until we exhaust the string, as illustrated here( we omitted some parenthesis for better readability):
+ − 449
+ − 450
\[ r^* \xrightarrow{\backslash a} r_1 = (1+0+1 \cdot b + 0 + 1 \cdot b \cdot c) \cdot r* \xrightarrow{\backslash b}\]
+ − 451
\[r_2 = (0+0+1 \cdot 1 + 0 + 1 \cdot 1 \cdot c) \cdot r^* +(0+1+0 + 0 + 0) \cdot r* \xrightarrow{\backslash c}\]
+ − 452
\[r_3 = ((0+0+0 + 0 + 1 \cdot 1 \cdot 1) \cdot r^* + (0+0+0 + 1 + 0) \cdot r*) +((0+1+0 + 0 + 0) \cdot r*+(0+0+0 + 1 + 0) \cdot r* )
+ − 453
\]
+ − 454
+ − 455
Now instead of using $nullable$ to give a $yes$, we call $mkeps$ to construct a parse tree for how $r_3$ matched the string $abc$. $mkeps$ gives the following value $v_3$: \\$Left(Left(Seq(Right(Right(Right(Seq(Empty, Seq(Empty, Empty)))))), Stars []))$\\
+ − 456
This corresponds to the leftmost term $((0+0+0 + 0 + 1 \cdot 1 \cdot 1) \cdot r^* $ in $r_3$. Note that its leftmost location allows $mkeps$ to choose it as the first candidate that meets the requirement of being $nullable$. This location is naturally generated by the splitting clause\\ $(r_1 \cdot r_2)\backslash c (when \, r_1 \, nullable)) \, = (r_1\backslash c) \cdot r_2 \,+\, r_2\backslash c. \\$. By this clause, we put
+ − 457
$r_1 \backslash c \cdot r_2 $ at the front and $r_2 \backslash c$ at the back. This allows $mkeps$ to always pick up among two matches the one with a longer prefix. The value \\
+ − 458
$Left(Left(Seq(Right(Right(Right(Seq(Empty, Seq(Empty, Empty)))))), Stars []))$\\
+ − 459
tells us how about the empty string matches the final regular expression after doing all the derivatives: among the regular expressions $(0+0+0 + 0 + 1 \cdot 1 \cdot 1) \cdot r^* + (0+0+0 + 1 + 0) \cdot r*) +((0+1+0 + 0 + 0) \cdot r*+(0+0+0 + 1 + 0) \cdot r* )$ we choose the left most nullable one, which is composed of a sequence of a nested alternative and a folded star that iterates 0 times. In that nested alternative we take the rightmost alternative.
+ − 460
42
+ − 461
Using the value $v_3$, the character c, and the regular expression $r_2$, we can recover how $r_2$ matched the string $[c]$ : we inject $c$ back to $v_3$, and get \\ $v_2 = Left(Seq(Right(Right(Right(Seq(Empty, Seq(Empty, c)))))), Stars [])$, which tells us how $r_2$ matched $c$. After this we inject back the character $b$, and get\\ $v_1 = Seq(Right(Right(Right(Seq(Empty, Seq(b, c)))))), Stars [])$ for how $r_1= (1+0+1 \cdot b + 0 + 1 \cdot b \cdot c) \cdot r*$ matched the string $bc$ before it split into 2 pieces. Finally, after injecting character a back to $v_1$, we get the parse tree $v_0= Stars [Right(Right(Right(Seq(a, Seq(b, c)))))]$ for how r matched $abc$.
+ − 462
We omit the details of injection function, which is provided by Sulzmann and Lu's paper \cite{Sulzmann2014}.
+ − 463
Readers might have noticed that the parse tree information as actually already available when doing derivatives. For example, immediately after the operation $\backslash a$ we know that if we want to match a string that starts with a, we can either take the initial match to be
+ − 464
\begin{enumerate}
+ − 465
\item[1)] just $a$ or
+ − 466
\item[2)] string $ab$ or
+ − 467
\item[3)] string $abc$.
+ − 468
\end{enumerate}
+ − 469
In order to differentiate between these choices, we just need to remember their positions--$a$ is on the left, $ab$ is in the middle , and $abc$ is on the right. Which one of these alternatives is chosen later does not affect their relative position because our algorithm does not change this order. There is no need to traverse this information twice. This leads to a new approach of lexing-- if we store the information for parse trees in the corresponding regular expression pieces, update this information when we do derivative operation on them, and collect the information when fininshed with derivatives and calling $mkeps$ for deiciding which branch is POSIX, we can generate the parse tree in one pass, instead of doing an n-step backward transformation.This leads to Sulzmann and Lu's novel idea of using bit-codes on derivatives.
+ − 470
+ − 471
In the next section, we shall focus on the bit-coded algorithm and the natural
+ − 472
process of simplification of regular expressions using bit-codes, which is needed in
30
+ − 473
order to obtain \emph{fast} versions of the Brzozowski's, and Sulzmann
+ − 474
and Lu's algorithms. This is where the PhD-project hopes to advance
+ − 475
the state-of-the-art.
+ − 476
+ − 477
+ − 478
\section{Simplification of Regular Expressions}
42
+ − 479
Using bit-codes to guide parsing is not a new idea.
30
+ − 480
+ − 481
The main drawback of building successive derivatives according to
+ − 482
Brzozowski's definition is that they can grow very quickly in size.
+ − 483
This is mainly due to the fact that the derivative operation generates
+ − 484
often ``useless'' $\ZERO$s and $\ONE$s in derivatives. As a result,
+ − 485
if implemented naively both algorithms by Brzozowski and by Sulzmann
+ − 486
and Lu are excruciatingly slow. For example when starting with the
+ − 487
regular expression $(a + aa)^*$ and building 12 successive derivatives
+ − 488
w.r.t.~the character $a$, one obtains a derivative regular expression
+ − 489
with more than 8000 nodes (when viewed as a tree). Operations like
+ − 490
derivative and $\nullable$ need to traverse such trees and
+ − 491
consequently the bigger the size of the derivative the slower the
+ − 492
algorithm. Fortunately, one can simplify regular expressions after
+ − 493
each derivative step. Various simplifications of regular expressions
+ − 494
are possible, such as the simplifications of $\ZERO + r$,
+ − 495
$r + \ZERO$, $\ONE\cdot r$, $r \cdot \ONE$, and $r + r$ to just
+ − 496
$r$. These simplifications do not affect the answer for whether a
+ − 497
regular expression matches a string or not, but fortunately also do
+ − 498
not affect the POSIX strategy of how regular expressions match
+ − 499
strings---although the latter is much harder to establish. Some
+ − 500
initial results in this regard have been obtained in
+ − 501
\cite{AusafDyckhoffUrban2016}. However, what has not been achieved yet
+ − 502
is a very tight bound for the size. Such a tight bound is suggested by
+ − 503
work of Antimirov who proved that (partial) derivatives can be bound
+ − 504
by the number of characters contained in the initial regular
35
+ − 505
expression \cite{Antimirov95}.
+ − 506
+ − 507
Antimirov defined the "partial derivatives" of regular expressions to be this:
+ − 508
%TODO definition of partial derivatives
+ − 509
+ − 510
it is essentially a set of regular expressions that come from the sub-structure of the original regular expression.
+ − 511
Antimirov has proved a nice size bound of the size of partial derivatives. Roughly speaking the size will not exceed the fourth power of the number of nodes in that regular expression. Interestingly, we observed from experiment that after the simplification step, our regular expression has the same size or is smaller than the partial derivatives. This allows us to prove a tight bound on the size of regular expression during the running time of the algorithm if we can establish the connection between our simplification rules and partial derivatives.
+ − 512
+ − 513
%We believe, and have generated test
+ − 514
%data, that a similar bound can be obtained for the derivatives in
+ − 515
%Sulzmann and Lu's algorithm. Let us give some details about this next.
30
+ − 516
+ − 517
We first followed Sulzmann and Lu's idea of introducing
+ − 518
\emph{annotated regular expressions}~\cite{Sulzmann2014}. They are
+ − 519
defined by the following grammar:
+ − 520
+ − 521
\begin{center}
+ − 522
\begin{tabular}{lcl}
+ − 523
$\textit{a}$ & $::=$ & $\textit{ZERO}$\\
+ − 524
& $\mid$ & $\textit{ONE}\;\;bs$\\
+ − 525
& $\mid$ & $\textit{CHAR}\;\;bs\,c$\\
+ − 526
& $\mid$ & $\textit{ALTS}\;\;bs\,as$\\
+ − 527
& $\mid$ & $\textit{SEQ}\;\;bs\,a_1\,a_2$\\
+ − 528
& $\mid$ & $\textit{STAR}\;\;bs\,a$
+ − 529
\end{tabular}
+ − 530
\end{center}
+ − 531
+ − 532
\noindent
+ − 533
where $bs$ stands for bitsequences, and $as$ (in \textit{ALTS}) for a
+ − 534
list of annotated regular expressions. These bitsequences encode
+ − 535
information about the (POSIX) value that should be generated by the
+ − 536
Sulzmann and Lu algorithm. Bitcodes are essentially incomplete values.
+ − 537
This can be straightforwardly seen in the following transformation:
+ − 538
\begin{center}
+ − 539
\begin{tabular}{lcl}
+ − 540
$\textit{code}(\Empty)$ & $\dn$ & $[]$\\
+ − 541
$\textit{code}(\Char\,c)$ & $\dn$ & $[]$\\
+ − 542
$\textit{code}(\Left\,v)$ & $\dn$ & $\Z :: code(v)$\\
+ − 543
$\textit{code}(\Right\,v)$ & $\dn$ & $\S :: code(v)$\\
+ − 544
$\textit{code}(\Seq\,v_1\,v_2)$ & $\dn$ & $code(v_1) \,@\, code(v_2)$\\
+ − 545
$\textit{code}(\Stars\,[])$ & $\dn$ & $[\S]$\\
+ − 546
$\textit{code}(\Stars\,(v\!::\!vs))$ & $\dn$ & $\Z :: code(v) \;@\;
+ − 547
code(\Stars\,vs)$
+ − 548
\end{tabular}
+ − 549
\end{center}
+ − 550
where $\Z$ and $\S$ are arbitrary names for the bits in the
+ − 551
bitsequences.
+ − 552
Here code encodes a value into a bitsequence by converting Left into $\Z$, Right into $\S$, the start point of a non-empty star iteration into $\S$, and the border where a local star terminates into $\Z$. This conversion is apparently lossy, as it throws away the character information, and does not decode the boundary between the two operands of the sequence constructor. Moreover, with only the bitcode we cannot even tell whether the $\S$s and $\Z$s are for $Left/Right$ or $Stars$. The reason for choosing this compact way of storing information is that the relatively small size of bits can be easily moved around during the lexing process. In order to recover the bitcode back into values, we will need the regular expression as the extra information and decode them back into value:\\
37
+ − 553
%\begin{definition}[Bitdecoding of Values]\mbox{}
36
+ − 554
\begin{center}
+ − 555
\begin{tabular}{@{}l@{\hspace{1mm}}c@{\hspace{1mm}}l@{}}
+ − 556
$\textit{decode}'\,bs\,(\ONE)$ & $\dn$ & $(\Empty, bs)$\\
+ − 557
$\textit{decode}'\,bs\,(c)$ & $\dn$ & $(\Char\,c, bs)$\\
+ − 558
$\textit{decode}'\,(\Z\!::\!bs)\;(r_1 + r_2)$ & $\dn$ &
+ − 559
$\textit{let}\,(v, bs_1) = \textit{decode}'\,bs\,r_1\;\textit{in}\;
+ − 560
(\Left\,v, bs_1)$\\
+ − 561
$\textit{decode}'\,(\S\!::\!bs)\;(r_1 + r_2)$ & $\dn$ &
+ − 562
$\textit{let}\,(v, bs_1) = \textit{decode}'\,bs\,r_2\;\textit{in}\;
+ − 563
(\Right\,v, bs_1)$\\
+ − 564
$\textit{decode}'\,bs\;(r_1\cdot r_2)$ & $\dn$ &
+ − 565
$\textit{let}\,(v_1, bs_1) = \textit{decode}'\,bs\,r_1\;\textit{in}$\\
+ − 566
& & $\textit{let}\,(v_2, bs_2) = \textit{decode}'\,bs_1\,r_2$\\
+ − 567
& & \hspace{35mm}$\textit{in}\;(\Seq\,v_1\,v_2, bs_2)$\\
+ − 568
$\textit{decode}'\,(\Z\!::\!bs)\,(r^*)$ & $\dn$ & $(\Stars\,[], bs)$\\
+ − 569
$\textit{decode}'\,(\S\!::\!bs)\,(r^*)$ & $\dn$ &
+ − 570
$\textit{let}\,(v, bs_1) = \textit{decode}'\,bs\,r\;\textit{in}$\\
+ − 571
& & $\textit{let}\,(\Stars\,vs, bs_2) = \textit{decode}'\,bs_1\,r^*$\\
+ − 572
& & \hspace{35mm}$\textit{in}\;(\Stars\,v\!::\!vs, bs_2)$\bigskip\\
+ − 573
+ − 574
$\textit{decode}\,bs\,r$ & $\dn$ &
+ − 575
$\textit{let}\,(v, bs') = \textit{decode}'\,bs\,r\;\textit{in}$\\
+ − 576
& & $\textit{if}\;bs' = []\;\textit{then}\;\textit{Some}\,v\;
+ − 577
\textit{else}\;\textit{None}$
+ − 578
\end{tabular}
+ − 579
\end{center}
37
+ − 580
%\end{definition}
30
+ − 581
+ − 582
To do lexing using annotated regular expressions, we shall first transform the
+ − 583
usual (un-annotated) regular expressions into annotated regular
+ − 584
expressions:\\
37
+ − 585
%\begin{definition}
36
+ − 586
\begin{center}
+ − 587
\begin{tabular}{lcl}
+ − 588
$(\ZERO)^\uparrow$ & $\dn$ & $\textit{ZERO}$\\
+ − 589
$(\ONE)^\uparrow$ & $\dn$ & $\textit{ONE}\,[]$\\
+ − 590
$(c)^\uparrow$ & $\dn$ & $\textit{CHAR}\,[]\,c$\\
+ − 591
$(r_1 + r_2)^\uparrow$ & $\dn$ &
+ − 592
$\textit{ALT}\;[]\,(\textit{fuse}\,[\Z]\,r_1^\uparrow)\,
+ − 593
(\textit{fuse}\,[\S]\,r_2^\uparrow)$\\
+ − 594
$(r_1\cdot r_2)^\uparrow$ & $\dn$ &
+ − 595
$\textit{SEQ}\;[]\,r_1^\uparrow\,r_2^\uparrow$\\
+ − 596
$(r^*)^\uparrow$ & $\dn$ &
+ − 597
$\textit{STAR}\;[]\,r^\uparrow$\\
+ − 598
\end{tabular}
+ − 599
\end{center}
37
+ − 600
%\end{definition}
30
+ − 601
Then we do successive derivative operations on the annotated regular expression. This derivative operation is the same as what we previously have for the simple regular expressions, except that we take special care of the bits to store the parse tree information:\\
37
+ − 602
%\begin{definition}{bder}
36
+ − 603
\begin{center}
+ − 604
\begin{tabular}{@{}lcl@{}}
+ − 605
$(\textit{ZERO})\backslash c$ & $\dn$ & $\textit{ZERO}$\\
+ − 606
$(\textit{ONE}\;bs)\backslash c$ & $\dn$ & $\textit{ZERO}$\\
+ − 607
$(\textit{CHAR}\;bs\,d)\backslash c$ & $\dn$ &
+ − 608
$\textit{if}\;c=d\; \;\textit{then}\;
+ − 609
\textit{ONE}\;bs\;\textit{else}\;\textit{ZERO}$\\
+ − 610
$(\textit{ALT}\;bs\,a_1\,a_2)\backslash c$ & $\dn$ &
+ − 611
$\textit{ALT}\,bs\,(a_1\backslash c)\,(a_2\backslash c)$\\
+ − 612
$(\textit{SEQ}\;bs\,a_1\,a_2)\backslash c$ & $\dn$ &
+ − 613
$\textit{if}\;\textit{bnullable}\,a_1$\\
+ − 614
& &$\textit{then}\;\textit{ALT}\,bs\,(\textit{SEQ}\,[]\,(a_1\backslash c)\,a_2)$\\
+ − 615
& &$\phantom{\textit{then}\;\textit{ALT}\,bs\,}(\textit{fuse}\,(\textit{bmkeps}\,a_1)\,(a_2\backslash c))$\\
+ − 616
& &$\textit{else}\;\textit{SEQ}\,bs\,(a_1\backslash c)\,a_2$\\
+ − 617
$(\textit{STAR}\,bs\,a)\backslash c$ & $\dn$ &
+ − 618
$\textit{SEQ}\;bs\,(\textit{fuse}\, [\Z] (r\backslash c))\,
+ − 619
(\textit{STAR}\,[]\,r)$
+ − 620
\end{tabular}
+ − 621
\end{center}
37
+ − 622
%\end{definition}
30
+ − 623
This way, we do not have to use an injection function and a second phase, but instead only need to collect the bits while running $mkeps$:
37
+ − 624
%\begin{definition}[\textit{bmkeps}]\mbox{}
36
+ − 625
\begin{center}
+ − 626
\begin{tabular}{lcl}
+ − 627
$\textit{bmkeps}\,(\textit{ONE}\,bs)$ & $\dn$ & $bs$\\
+ − 628
$\textit{bmkeps}\,(\textit{ALT}\,bs\,a_1\,a_2)$ & $\dn$ &
+ − 629
$\textit{if}\;\textit{bnullable}\,a_1$\\
+ − 630
& &$\textit{then}\;bs\,@\,\textit{bmkeps}\,a_1$\\
+ − 631
& &$\textit{else}\;bs\,@\,\textit{bmkeps}\,a_2$\\
+ − 632
$\textit{bmkeps}\,(\textit{SEQ}\,bs\,a_1\,a_2)$ & $\dn$ &
+ − 633
$bs \,@\,\textit{bmkeps}\,a_1\,@\, \textit{bmkeps}\,a_2$\\
+ − 634
$\textit{bmkeps}\,(\textit{STAR}\,bs\,a)$ & $\dn$ &
+ − 635
$bs \,@\, [\S]$
+ − 636
\end{tabular}
+ − 637
\end{center}
37
+ − 638
%\end{definition}
+ − 639
and then decode the bits using the regular expression. After putting these pieces together, the whole process looks like this:\\
+ − 640
\begin{center}
+ − 641
\begin{tabular}{lcl}
+ − 642
$\textit{blexer}\;r\,s$ & $\dn$ &
+ − 643
$\textit{let}\;a = (r^\uparrow)\backslash s\;\textit{in}$\\
+ − 644
& & $\;\;\textit{if}\; \textit{bnullable}(a)$\\
+ − 645
& & $\;\;\textit{then}\;\textit{decode}\,(\textit{bmkeps}\,a)\,r$\\
+ − 646
& & $\;\;\textit{else}\;\textit{None}$
+ − 647
\end{tabular}
+ − 648
\end{center}
30
+ − 649
+ − 650
The main point of the bitsequences and annotated regular expressions
+ − 651
is that we can apply rather aggressive (in terms of size)
+ − 652
simplification rules in order to keep derivatives small.
+ − 653
+ − 654
We have
+ − 655
developed such ``aggressive'' simplification rules and generated test
+ − 656
data that show that the expected bound can be achieved. Obviously we
+ − 657
could only partially cover the search space as there are infinitely
+ − 658
many regular expressions and strings. One modification we introduced
+ − 659
is to allow a list of annotated regular expressions in the
+ − 660
\textit{ALTS} constructor. This allows us to not just delete
+ − 661
unnecessary $\ZERO$s and $\ONE$s from regular expressions, but also
+ − 662
unnecessary ``copies'' of regular expressions (very similar to
+ − 663
simplifying $r + r$ to just $r$, but in a more general
35
+ − 664
setting).
+ − 665
A psuedocode version of our algorithm is given below:\\
+ − 666
+ − 667
\begin{algorithm}
+ − 668
\caption{simplification of annotated regular expression}\label{euclid}
+ − 669
\begin{algorithmic}[1]
+ − 670
\Procedure{$Simp$}{$areg$}
+ − 671
\Switch{$areg$}
+ − 672
\Case{$ALTS(bs, rs)$}
+ − 673
\For{\textit{rs[i] in array rs}}
+ − 674
\State $\textit{rs[i]} \gets$ \textit{Simp(rs[i])}
+ − 675
\EndFor
+ − 676
\For{\textit{rs[i] in array rs}}
+ − 677
\If{$rs[i] == ALTS(bs', rs')$}
+ − 678
\State $rs'' \gets$ attach bits $bs'$ to all elements in $rs'$
+ − 679
\State Insert $rs''$ into $rs$ at position $i$ ($rs[i]$ is destroyed, replaced by its list of children regular expressions)
+ − 680
\EndIf
+ − 681
\EndFor
+ − 682
\State Remove all duplicates in $rs$, only keeping the first copy for multiple occurrences of the same regular expression
+ − 683
\State Remove all $0$s in $rs$
+ − 684
\If{$ rs.length == 0$} \Return $ZERO$ \EndIf
+ − 685
\If {$ rs.length == 1$} \Return$ rs[0] $\EndIf
+ − 686
\EndCase
+ − 687
\Case{$SEQ(bs, r_1, r_2)$}
+ − 688
\If{$ r_1$ or $r_2$ is $ZERO$} \Return ZERO \EndIf
+ − 689
\State update $r_1$ and $r_2$ by attaching $bs$ to their front
+ − 690
\If {$r_1$ or $r_2$ is $ONE(bs')$} \Return $r_2$ or $r_1$ \EndIf
+ − 691
\EndCase
+ − 692
\Case{$Others$}
+ − 693
\Return $areg$ as it is
+ − 694
\EndCase
+ − 695
\EndSwitch
+ − 696
\EndProcedure
+ − 697
\end{algorithmic}
+ − 698
\end{algorithm}
36
+ − 699
With this simplification our previous $(a + aa)^*$ example's 8000 nodes will be reduced to only 6.
35
+ − 700
+ − 701
Another modification is that we use simplification rules
30
+ − 702
inspired by Antimirov's work on partial derivatives. They maintain the
+ − 703
idea that only the first ``copy'' of a regular expression in an
+ − 704
alternative contributes to the calculation of a POSIX value. All
+ − 705
subsequent copies can be pruned from the regular expression.
+ − 706
35
+ − 707
30
+ − 708
We are currently engaged with proving that our simplification rules
+ − 709
actually do not affect the POSIX value that should be generated by the
+ − 710
algorithm according to the specification of a POSIX value and
+ − 711
furthermore that our derivatives stay small for all derivatives. For
+ − 712
this proof we use the theorem prover Isabelle. Once completed, this
+ − 713
result will advance the state-of-the-art: Sulzmann and Lu wrote in
+ − 714
their paper \cite{Sulzmann2014} about the bitcoded ``incremental
+ − 715
parsing method'' (that is the matching algorithm outlined in this
+ − 716
section):
+ − 717
+ − 718
\begin{quote}\it
+ − 719
``Correctness Claim: We further claim that the incremental parsing
+ − 720
method in Figure~5 in combination with the simplification steps in
+ − 721
Figure 6 yields POSIX parse trees. We have tested this claim
+ − 722
extensively by using the method in Figure~3 as a reference but yet
+ − 723
have to work out all proof details.''
+ − 724
\end{quote}
+ − 725
+ − 726
\noindent
+ − 727
We would settle the correctness claim and furthermore obtain a much
+ − 728
tighter bound on the sizes of derivatives. The result is that our
+ − 729
algorithm should be correct and faster on all inputs. The original
+ − 730
blow-up, as observed in JavaScript, Python and Java, would be excluded
+ − 731
from happening in our algorithm.
+ − 732
+ − 733
\section{Conclusion}
+ − 734
+ − 735
In this PhD-project we are interested in fast algorithms for regular
+ − 736
expression matching. While this seems to be a ``settled'' area, in
+ − 737
fact interesting research questions are popping up as soon as one steps
+ − 738
outside the classic automata theory (for example in terms of what kind
+ − 739
of regular expressions are supported). The reason why it is
+ − 740
interesting for us to look at the derivative approach introduced by
+ − 741
Brzozowski for regular expression matching, and then much further
+ − 742
developed by Sulzmann and Lu, is that derivatives can elegantly deal
+ − 743
with some of the regular expressions that are of interest in ``real
+ − 744
life''. This includes the not-regular expression, written $\neg\,r$
+ − 745
(that is all strings that are not recognised by $r$), but also bounded
+ − 746
regular expressions such as $r^{\{n\}}$ and $r^{\{n..m\}}$). There is
+ − 747
also hope that the derivatives can provide another angle for how to
+ − 748
deal more efficiently with back-references, which are one of the
+ − 749
reasons why regular expression engines in JavaScript, Python and Java
+ − 750
choose to not implement the classic automata approach of transforming
+ − 751
regular expressions into NFAs and then DFAs---because we simply do not
+ − 752
know how such back-references can be represented by DFAs.
+ − 753
+ − 754
+ − 755
\bibliographystyle{plain}
+ − 756
\bibliography{root}
+ − 757
+ − 758
+ − 759
\end{document}