Important:Pyodide takes time to initialize.
Initialization completion is indicated by a red border around Run all button.
In the previous post, we
discussed how to produce a grammar out of regular expressions. This is
useful to make the regular expression a generator of matching inputs. However,
one detail is unsatisfying. The grammar produced is a context-free grammar.
Regular expressions actually correspond to regular grammars (which
are strictly less powerful than context-free grammars).
For reference, a context-free grammar is a grammar where the rules are of
the form where is a single nonterminal symbol
and is any sequence of terminal or nonterminal symbols
including (empty).
A regular grammar on the other hand, is a grammar where the rules can take one
of the following forms:
where and are nonterminal symbols, is a terminal
symbol, and is the empty string.
So, why is producing a context-free grammar instead of regular grammar
unsatisfying? Because such regular grammars have more interesting properties
such as being closed under intersection and complement. By using a
context-free grammar, we miss out on such properties.
Hence, it would be really good if we could
translate the regular expression directly into a regular grammar. This is what
we will do in this post.
We start with importing the prerequisites
System Imports
These are available from Pyodide, but you may wish to make sure that they are
installed if you are attempting to run the program directly on the machine.
sympy
Available Packages
These are packages that refer either to my previous posts or to pure python
packages that I have compiled, and is available in the below locations. As
before, install them if you need to run the program directly on the machine.
To install, simply download the wheel file (`pkg.whl`) and install using
`pip install pkg.whl`.
We want to produce regular grammars directly from regular expressions.
Union of Regular Grammars
Given two regular grammars such that their nonterminals do not overlap,
we need to produce a union grammar.
The idea is that you only need to modify the start symbol such that
the definition of the new start symbol is a combination of starts from both
grammars.
Using it
Concatenation of Regular Grammars
Next, given two regular grammars and such that
their nonterminals do not overlap, producing a concatenation grammar is as
follows: We collect all terminating rules from which looks like
where
is a terminal symbol. We then transform them to
where is the start symbol of . If was present in
one of the rules of , then we simply produce .
We start with catenation of nonterminals.
Next, we define what happens when we catenate a nontrminal to a rule.
It returns any new keys created, along with the new rule
Finally, we define our regular catenation of two grammars.
Using it
Kleene Plus of Regular Grammars
Given a nonterminal symbol and the grammar in which it is defined, the
Kleene plus is simply a regular concatenation of the nontrerminal with
itself (recursive), with a regular union of its nonterminalās rules. The small
difference here from regular concatenation is that, when we concatenate the
nonterminal with itself, we do not need to check for disjointness of
nonterminals, because the definitions of other nonterminals are exactly the
same. Further, is never used in the algorithm except in the final
grammar.
Using it
Kleene Star of Regular Grammars
For Kleene Star, add to the language of Kleene Plus.
Using it
At this point, we have all operations necessary to convert a regular
expression to a regular grammar directly. We first define the class
<cex> ::= <exp>
| <exp> <cex>
<regex> ::= <cex>
| <cex> `|` <regex>
<regexplus> ::= <unitexp> `+`
Using it
At this point, the grammar may still contain degenerate rules
of the form . We need to clean that up so
that rules follow one of or
or .