Skip to content
OCaml code to construct an NFA from a regular expression
OCaml Makefile
Branch: master
Clone or download
yallop Merge pull request #12 from yallop/brackets
Add support for bracket expressions
Latest commit 5a5ccef Jan 10, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
images
lib Add parsing/unparsing support for bracket expressions. Jan 10, 2019
lib_test Add tests for bracket expressions. Jan 10, 2019
src
.gitignore Initial commit Nov 7, 2018
.travis.yml
LICENSE Initial commit Nov 7, 2018
META
Makefile Add tests for bracket expressions. Jan 10, 2019
README.md Add a badge. Dec 10, 2018
_tags Add tests for bracket expressions. Jan 10, 2019
opam
re-nfa.install Initial commit Nov 7, 2018

README.md

re-nfa: convert regular expressions to NFAs

Travis build Status

This repository provides a library and executable for converting regular expressions into nondeterministic finite automata (NFAs) using Glushkov's construction, for converting NFAs into DFAs using the powerset construction, for minimizing DFAs using Brzozowski's algorithm and for formatting the NFAs using DOT so that they can be displayed using graphviz.

Online demo

The easiest way to try the code is to use the web UI written by Joel Jakobsson.

The re-nfa executable

The re-nfa executable accepts a single regular expression argument and prints a DOT graph for the corresponding NFA or DFA to standard output. For example, the following command

re-nfa "a*b"

produces the following output.

digraph {
  "rankdir" = "LR";
  node [ "shape" = "none";] ""
  node [ "shape" = "circle";] "1"
  node [ "shape" = "doublecircle";] "2"
  node [ "shape" = "circle";] "0"
  "" -> "0" 
  "1" -> "2" [ "label" = "b";]
  "0" -> "2" [ "label" = "b";]
  "1" -> "1" [ "label" = "a";]
  "0" -> "1" [ "label" = "a";]
}

To display the corresponding DFA or minimized DFA, pass the -type argument:

re-nfa -type dfa "a*b"
re-nfa -type dfa-minimized "a*b"

On a Unix system you might pipe the output directly to dot, and then on to display, like this:

re-nfa "a*b" | dot -Tpng | display

to display the following graph:

a*b

Here is the minimized version:

a*b

Here is a more complex graph constructed from the regex a?a?a?aaa that causes pathological backtracking behaviour in some engines, as described in Russ Cox's article Regular Expression Matching Can Be Simple And Fast:

a?a?a?aaa

and here is the corresponding DFA:

a?a?a?aaa

Library

This repository also provides a library interface. The Regex module provides an ocaml-re-style combinator interface for constructing regular expressions

seq (star (chr 'a')) (chr 'b')     (* a*b *)

as well as functions parse and compile for building a regular expression from a string and for turning a regular expression into an NFA.

val parse : string -> t
val compile : t -> Nfa.nfa

The Nfa module provides a function for testing whether an NFA accepts a string (represented as a list of characters):

val accept : Nfa.t -> char list -> bool

The Nfa_dot module provides functions for converting NFAs to DOT directed graphs and for pretty-printing the graphs:

val digraph_of_nfa : Nfa.nfa -> digraph
val format_digraph : Format.formatter -> digraph -> unit

The Dfa module provides functions for converting between NFAs and DFAs, a DFA minimization function, and and an accept function for DFAs

val determinize : Nfa.nfa -> dfa
val inject : dfa -> Nfa.nfa
val minimize : dfa -> dfa
val accept : dfa -> char list -> bool

Rationale

The code in this repository is an extracted and extended portion of an exercise from a 2018 advanced functional programming course. If you're interested in learning MetaOCaml then you may enjoy completing the original exercise, perhaps after reading the course notes.

Knowing the provenance of the code helps in understanding some of the choices made.

For example, there are several algorithms for constructing NFAs, but Glushkov's has a property that turns out to be convenient for the exercise: it constructs an automaton without ε-transitions.

Additionally, the code here is not especially efficient; the remainder of the original exercise involves using multi-stage programming to turn the rather inefficient NFA interpreter into a compiler that produces rather efficient code --- typically more efficient than production regex engines. (Similar transformations using Scala LMS are also described in the literature: see Optimizing data structures in high-level programs: New directions for extensible compilers based on staging (Rompf et al. 2013))

Installation

The re-nfa library and executable can be installed via OPAM by pinning this repository:

opam pin add re-nfa https://github.com/yallop/ocaml-re-nfa.git

ReasonML port

A ReasonML port of this project is available.

You can’t perform that action at this time.