I've created a Regex to DFA parser, using Thompson's construction algorithm and epsilon-reduction. Here is the code:
builtins.dart
:
bool setEquals(Set a, Set b) {
List temp_a = a.toList();
List temp_b = b.toList();
return listEquals(temp_a, temp_b);
}
bool listEquals(List a, List b) {
a.sort();
b.sort();
for (int c = 0; c < a.length; c++) {
if (a[c] != b[c])
return false;
}
return true;
}
Node.dart
:
/// A node - required for the NFA and DFA.
class Node {
int ident;
List paths;
Node(int ident) {
this.ident = ident;
this.paths = [];
}
void addPath(String char, Node node) {
this.paths.add([char, node.ident]);
}
void removePath(int node_ident) {
for (int a = 0; a < this.paths.length; a++) {
int destination = this.paths[a][1];
if (node_ident == destination) {
this.paths.removeAt(a);
return;
}
}
}
}
NondeterministicFA.dart
:
import "Node.dart";
String EPSILON = "";
/// The main class for the non-determinstic automaton
/// generated by Thompson's algorithm.
class NondeterministicFA {
String regex;
List<Node> nodes;
int node_index;
/// Checks if a string has balanced parentheses.
bool _isBalanced(String str) {
int index = 0;
for (int a = 0; a < str.length; a++) {
if (str[a] == "(")
index++;
else if (str[a] == ")")
index--;
}
return index == 0;
}
/// Turns the regex into something easier to parse
/// for the NFA.
List _regexToGroups(String regex) {
List regex_groups = [];
Map quantifiers = {
"*": "STAR",
"+": "PLUS",
"?": "QMARK"
};
while (regex.length > 0) {
if (regex[0] == "(") {
int paren_count = 1;
int paren_index = 1;
while (paren_count != 0) {
if (regex[paren_index] == "(")
paren_count += 1;
else if (regex[paren_index] == ")")
paren_count -= 1;
paren_index += 1;
}
List<String> inside_parens = regex.substring(1, paren_index - 1).split("|");
List temp = [];
if (this._isBalanced(inside_parens[0])) {
temp.add("OR");
} else {
inside_parens = [inside_parens.join("|")];
}
List other = inside_parens.map((str) => ["GROUP", this._regexToGroups(str)]).toList();
if (other.length == 1) {
temp.addAll(other[0]);
} else {
temp.addAll(other);
}
if (quantifiers.containsKey(regex[paren_index])) {
regex_groups.add([quantifiers[regex[paren_index]], temp]);
regex = regex.substring(paren_index + 1);
} else {
regex_groups.add(temp);
regex = regex.substring(paren_index);
}
} else {
if (regex.length > 1 && quantifiers.containsKey(regex[1])) {
regex_groups.add([quantifiers[regex[1]], ["CHAR", regex[0]]]);
regex = regex.substring(2);
} else {
regex_groups.add(["CHAR", regex[0]]);
regex = regex.substring(1);
}
}
}
return regex_groups;
}
/// Connects two nodes together, according to the rules of
/// Thompson's Construction Algorithm.
void _connectNode(List group, Node start, Node end) {
int temp_index = this.node_index;
if (group[0] == "STAR") {
this.node_index += 2;
Node left = new Node(temp_index);
Node right = new Node(temp_index + 1);
start.addPath(EPSILON, left);
start.addPath(EPSILON, end);
right.addPath(EPSILON, left);
right.addPath(EPSILON, end);
this._connectNode(group[1], left, right);
this.nodes.addAll([left, right]);
} else if (group[0] == "PLUS") {
this.node_index += 2;
Node left = new Node(temp_index);
Node right = new Node(temp_index + 1);
start.addPath(EPSILON, left);
right.addPath(EPSILON, left);
right.addPath(EPSILON, end);
this._connectNode(group[1], left, right);
this.nodes.addAll([left, right]);
} else if (group[0] == "QMARK") {
start.addPath(EPSILON, end);
this._connectNode(group[1], start, end);
} else if (group[0] == "OR") {
List connected_groups = group.sublist(1);
this.node_index += connected_groups.length * 2;
for (int a = 0; a < connected_groups.length; a++) {
Node group_start = new Node(temp_index + (2 * a));
Node group_end = new Node(temp_index + (2 * a) + 1);
start.addPath(EPSILON, group_start);
group_end.addPath(EPSILON, end);
this._connectNode(connected_groups[a], group_start, group_end);
this.nodes.addAll([group_start, group_end]);
}
} else if (group[0] == "GROUP") {
List items = group[1];
List<Node> group_nodes = [start];
this.node_index += items.length - 1;
for (int a = 0; a < items.length - 1; a++) {
Node new_node = new Node(temp_index + a);
this._connectNode(items[a], group_nodes[a], new_node);
group_nodes.add(new_node);
}
this._connectNode(items[items.length - 1], group_nodes[group_nodes.length - 1], end);
this.nodes.addAll(group_nodes.sublist(1));
} else if (group[0] == "CHAR") {
start.addPath(group[1], end);
}
}
/// Parses the final regex.
void _parseRegex() {
List regex_groups = this._regexToGroups(this.regex);
int starting_index = 0;
for (int a = 0; a < regex_groups.length; a++) {
List current_group = regex_groups[a];
Node temp_node = new Node(this.node_index);
this.node_index += 1;
this._connectNode(current_group, this.nodes[starting_index], temp_node);
this.nodes.add(temp_node);
starting_index = this.nodes.length - 1;
}
this.nodes.sort((first, second) => first.ident - second.ident);
}
NondeterministicFA(String regex) {
this.regex = regex;
this.nodes = [new Node(0)];
this.node_index = 1;
this._parseRegex();
}
}
DeterminsticFA.dart
:
import "Node.dart";
import "NondeterministicFA.dart";
String EPSILON = "";
class DeterministicFA {
List<Node> nodes;
List test;
List _movements(List<Node> nodes) {
List moves = [];
for (int a = 0; a < nodes.length; a++) {
List move_paths = nodes[a].paths.where((path) => path[0] != EPSILON);
moves.addAll(move_paths);
}
return moves;
}
List<int> _epsilonClosure(Node starting_node) {
Set<int> accessible = new Set();
for (List path in starting_node.paths) {
String connect_item = path[0];
int connect_node = path[1];
if (connect_item == EPSILON) {
accessible.add(connect_node);
}
}
Set<int> new_elements = new Set();
for (int a in accessible) {
new_elements.addAll(this._epsilonClosure(this.nodes[a]));
new_elements = new_elements.where((n) => !accessible.contains(n)).toSet();
}
if (new_elements.length == 0) {
List<int> accessible_ls = accessible.toList();
accessible_ls.insert(0, starting_node.ident);
return accessible_ls;
}
accessible.addAll(new_elements);
List<int> accessible_ls = accessible.toList();
accessible_ls.insert(0, starting_node.ident);
return accessible_ls;
}
List<Node> _toDeterministic() {
List<Node> nfa_nodes = this.nodes;
List<List<int>> new_nodes = [[0]];
List<List> node_paths = [];
Set<int> used_nodes = new Set();
int ending_index;
for (int a = 0; a < nfa_nodes.length; a++) {
if (nfa_nodes[a].paths.length == 0)
ending_index = a;
break;
}
while (used_nodes.length < nfa_nodes.length || new_nodes.length > node_paths.length) {
Set<int> connected_nodes = new Set();
for (int a = new_nodes.length - node_paths.length; a > 0; a--) {
List<int> starting_node = this._epsilonClosure(this.nodes[new_nodes[new_nodes.length - a][0]]);
List connections = this._movements(starting_node.map((n) => this.nodes[n]).toList());
print(starting_node);
new_nodes[new_nodes.length - a] = starting_node;
node_paths.add(connections);
used_nodes.addAll(starting_node);
connected_nodes.addAll(connections.map((n) => n[1]));
}
List<int> first_items = new_nodes.map((ls) => ls[0]);
connected_nodes = connected_nodes.where((n) => !first_items.contains(n));
new_nodes.addAll(connected_nodes.map((n) => [n]));
}
List<Node> actual_nodes = [];
for (int a = 0; a < new_nodes.length; a++) {
List paths = node_paths[a];
Node temp_node = new Node(new_nodes[a][0]);
for (int b = 0; b < paths.length; b++) {
temp_node.addPath(paths[b][0], nodes[paths[b][1]]);
}
actual_nodes.add(temp_node);
}
this.nodes = actual_nodes;
}
DeterministicFA(NondeterministicFA nfa) {
this.nodes = nfa.nodes;
this._toDeterministic();
}
}
(I apologise in advance for the lack of comments.)
Here are my main concerns with the code:
- There is definitely a lot of fluff in the code. A lot of the code felt like it was hacked on, and some unnecessary elements can definitely get removed.
- Readability is also a problem - I can hardly read the code myself. Is there any way to separate a larger function into a bunch of smaller functions?
- Speed may be an issue as well, since I'm designing this to rival Dart's native regex library, which uses JavaScript's regex flavour (which is awful).