def test_rg_to_nfa_conversion(self) -> None: grammar = RegularGrammar("S", {"S": {"&"}}) nfa = NFA.from_regular_grammar(grammar) self.assertTrue(nfa.accept("")) test_nfa = TestNFA() grammar = RegularGrammar( "S", { "S": {"0S", "1A", "0"}, "A": {"0B", "1C"}, "B": {"0D", "1S", "1"}, "C": {"0A", "1B"}, "D": {"0C", "1D"} }) nfa = NFA.from_regular_grammar(grammar) true_cases = {"0", "101", "1000101011"} false_cases = {"", "1", "101010111", "11101010"} test_nfa.nfa_test(nfa, true_cases, false_cases) grammar = RegularGrammar( "S'", { "S'": {"aA", "cC", "bA", "bC", "&"}, "S": {"aA", "cC", "bA", "bC"}, "A": {"bS", "cD", "b", "c"}, "C": {"bS", "aE", "b", "a"}, "D": {"aA", "bA", "bC"}, "E": {"cC", "bC", "bA"}, }) nfa = NFA.from_regular_grammar(grammar) true_cases = {"", "abab", "caca", "abcabbbacb"} false_cases = {"aa", "cc", "bbb", "babcb"} test_nfa.nfa_test(nfa, true_cases, false_cases)
def test_union(self) -> None: first_nfa = NFA.load("examples/aa.json") second_nfa = NFA.load("examples/endsWbb.json") first_nfa.union(second_nfa) self.nfa_test(first_nfa, {"aa", "abb", "abbaabb"}, {"abbaab", "ab", "aaa"})
def test_minimization(self) -> None: nfa = NFA.load("examples/bdiv3.json") nfa.minimize() self.assertEqual(len(nfa.states), 3) true_cases = {"", "aa", "bbb", "abababa", "aaaaabbaababaaabb"} false_cases = {"ba", "bbaaa", "ababababa", "bbaaabb", "babbaaabba"} self.nfa_test(nfa, true_cases, false_cases) nfa = NFA.load("examples/one1.json") nfa.minimize() self.assertEqual(len(nfa.states), 2) true_cases = {"1", "01", "10", "000000000100000"} false_cases = {"", "0", "00", "11", "111", "0100001", "1000001"} self.nfa_test(nfa, true_cases, false_cases) nfa = NFA.load("examples/div5.json") nfa.determinize() nfa.minimize() self.assertEqual(len(nfa.states), 6) true_cases = {"0", "101", "1000101011"} false_cases = {"", "1", "101010111", "11101010"} self.nfa_test(nfa, true_cases, false_cases) nfa = regex_to_dfa("aaa*|a*") nfa.minimize() self.assertEqual(len(nfa.states), 1) nfa = NFA.load("examples/endsWbb.json") with self.assertRaises(RuntimeError): nfa.minimize() with self.assertRaises(RuntimeError): nfa.merge_equivalent()
def test_equivalence(self) -> None: first_nfa = NFA.load("examples/aaORbb.json") second_nfa = NFA.load("examples/one1.json") self.assertFalse(first_nfa.is_equal(second_nfa)) self.assertFalse(second_nfa.is_equal(first_nfa)) self.assertTrue(first_nfa.is_equal(first_nfa)) self.assertTrue(second_nfa.is_equal(second_nfa))
def test_accept(self) -> None: nfa = NFA.load("examples/div3.json") true_cases = {"110100111", "111111000", "1110000001"} false_cases = {"1000000110", "110001", "1010001010", "211"} self.nfa_test(nfa, true_cases, false_cases) nfa = NFA.load("examples/aaORbb.json") true_cases = {"baabaabab", "bbababbbab", "babaaaaba"} false_cases = {"", "aabb", "bbaa", "ababababaababababb"} self.nfa_test(nfa, true_cases, false_cases)
def _grammar_to_nfa(self) -> None: try: self._nfa = NFA.from_regular_grammar( parse_grammar_text(self.grammarText.toPlainText())) self._update_table() except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0])
def __init__(self) -> None: QMainWindow.__init__(self) self.setupUi(self) self.resize(600, 400) self.regexToDFAButton.clicked.connect(self._regex_to_dfa) self.addSymbolButton.clicked.connect(self._add_symbols) self.addStateButton.clicked.connect(self._add_states) self.removeSymbolButton.clicked.connect(self._remove_symbols) self.removeStateButton.clicked.connect(self._remove_states) self.finalStateButton.clicked.connect(self._toggle_final_states) self.fromNFAbutton.clicked.connect(self._nfa_to_grammar) self.toNFAbutton.clicked.connect(self._grammar_to_nfa) self.testButton.clicked.connect(self._test_string) self.actionNew.triggered.connect(self._new) self.actionOpen.triggered.connect(self._open) self.actionSave.triggered.connect(self._save) self.actionDeterminize.triggered.connect(self._determinize) self.actionRemove_unreachable_states.triggered.connect( self._remove_unreachable) self.actionRemove_dead_states.triggered.connect(self._remove_dead) self.actionMerge_equivalent_states.triggered.connect( self._merge_equivalent) self.actionFull_minimization.triggered.connect(self._minimize) self.action_to_abc.triggered.connect(self._beautify_abc) self.action_to_qn.triggered.connect(self._beautify_qn) self.actionUnion.triggered.connect(self._union) self.actionComplement.triggered.connect(self._complement) self.actionIntersection.triggered.connect(self._intersection) self.actionContains.triggered.connect(self._contains) self.actionEquivalent.triggered.connect(self._is_equal) self.transitionTable.cellChanged.connect(self._update_nfa) self._grammar = RegularGrammar() self._nfa = NFA() self._update_table()
def test_nfa_to_rg_conversion(self) -> None: grammar = RegularGrammar.from_nfa(NFA.load("examples/div3.json")) self.assertTrue(grammar.initial_symbol() == "S'") self.assertTrue( grammar.productions() == { "S'": {"0S", "1A", "0", "&"}, "S": {"0S", "1A", "0"}, "A": {"0B", "1S", "1"}, "B": {"0A", "1B"} }) grammar = RegularGrammar.from_nfa(NFA.load("examples/endsWbb.json")) self.assertTrue(grammar.initial_symbol() == "S") self.assertTrue(grammar.productions() == { "S": {"aS", "bS", "bA"}, "A": {"b", "bB"} })
def test_intersection(self) -> None: first_nfa = NFA.load("examples/aaORbb.json") second_nfa = NFA.load("examples/aa.json") first_nfa.intersection(second_nfa) self.nfa_test(first_nfa, {"aa"}, {"", "bb", "bbaa"}) first_nfa = NFA.load("examples/bb.json") second_nfa = NFA.load("examples/aa.json") first_nfa.intersection(second_nfa) self.assertTrue(first_nfa.is_empty()) first_nfa = regex_to_dfa("a") second_nfa = regex_to_dfa("b") first_nfa.intersection(second_nfa) self.assertTrue(first_nfa.is_empty())
def _intersection(self) -> None: try: path, _ = QFileDialog.getOpenFileName(self) if path: second_nfa = NFA.load(path) self._nfa.intersection(second_nfa) self._update_table() except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0])
def regex_to_dfa(regex: str) -> NFA: """ Transforms a RegExp into a DFA using the De Simone/Aho method. """ root = RegExpParser(regex).parse() thread_tree(root) alphabet: Set[str] = set() transitions: Dict[Tuple[str, str], Set[str]] = {} initial_state = "q0" final_states: Set[str] = set() states = {initial_state} initial_nodes = frozenset(root.down()) compositions = {initial_nodes: initial_state} if END_NODE in initial_nodes: final_states.add(initial_state) new_compositions = {initial_nodes} while new_compositions: symbols: Dict[str, Set[Node]] = defaultdict(set) composition = new_compositions.pop() # composition of the new state # separate nodes of the same symbol for node in composition: if node.symbol != END: symbols[node.symbol].add(node) # build the new state transitions for symbol, nodes in symbols.items(): # create composition of the new state, that is, the nodes of the # tree you're in, when you're in that state new_state_composition: Set[Node] = set() for node in nodes: new_state_composition.update(node.right.up()) frozen_new_composition = frozenset(new_state_composition) # if there's a state with the same composition, they're equivalent, # no need to create another one if frozen_new_composition in compositions: new_state = compositions[frozen_new_composition] else: # else, create the new state new_state = "q" + str(len(compositions)) compositions[frozen_new_composition] = new_state new_compositions.add(frozen_new_composition) if END_NODE in frozen_new_composition: final_states.add(new_state) transitions[compositions[composition], symbol] = {new_state} for (state, symbol), next_state in transitions.items(): states.update({state} | next_state) alphabet.add(symbol) Node.up.cache_clear() Node.down.cache_clear() return NFA(states, alphabet, transitions, initial_state, final_states)
def test_determinization(self) -> None: nfa = NFA.load("examples/endsWbb.json") self.assertFalse(nfa.is_deterministic()) true_cases = {"bb", "abaabbabaabb", "babb", "abbabbabb"} false_cases = {"", "abba", "bbbbbba", "bbbaaabba", "absbb"} self.nfa_test(nfa, true_cases, false_cases) nfa.determinize() self.assertTrue(nfa.is_deterministic()) self.nfa_test(nfa, true_cases, false_cases) nfa = NFA.load("examples/bad_case.json") self.assertFalse(nfa.is_deterministic()) true_cases = { "babb", "abbabbabb", "baaa", "bbbb", "aababa", "bbbbbbbb" } false_cases = {"", "abbb", "aaaaaa"} self.nfa_test(nfa, true_cases, false_cases) nfa.determinize() self.assertTrue(nfa.is_deterministic()) self.nfa_test(nfa, true_cases, false_cases)
def _is_equal(self) -> None: try: path, _ = QFileDialog.getOpenFileName(self) if path: second_nfa = NFA.load(path) if self._nfa.is_equal(second_nfa): QMessageBox.information(self, "Equivalent", "The automata are equivalent.") else: QMessageBox.information( self, "Equivalent", "The automata are not equivalent.") except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0])
def test_emptiness(self) -> None: nfa = NFA.load("examples/one1.json") self.assertFalse(nfa.is_empty()) self.assertFalse(nfa.is_finite()) nfa = NFA.load("examples/bad_case.json") self.assertFalse(nfa.is_empty()) self.assertFalse(nfa.is_finite()) nfa = NFA.load("examples/aa.json") self.assertFalse(nfa.is_empty()) self.assertTrue(nfa.is_finite()) nfa = NFA.load("examples/empty.json") self.assertTrue(nfa.is_empty()) nfa = NFA.load("examples/useless_loop.json") self.assertFalse(nfa.is_empty()) self.assertTrue(nfa.is_finite()) nfa = regex_to_dfa("aa|bbb|cccc") self.assertFalse(nfa.is_empty()) self.assertTrue(nfa.is_finite())
def _contains(self) -> None: try: path, _ = QFileDialog.getOpenFileName(self) if path: second_nfa = NFA.load(path) if self._nfa.contains(second_nfa): QMessageBox.information( self, "Contains", "The automaton contains the second one.") else: QMessageBox.information( self, "Contains", "The automaton does not contain the second one.") except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0])
def test_complement(self) -> None: nfa = NFA.load("examples/endsWbb.json") nfa.complement() self.nfa_test(nfa, {"ab", "babbaab"}, {"abb", "aaabb"})
def test_containment(self) -> None: first_nfa = NFA.load("examples/aaORbb.json") second_nfa = NFA.load("examples/aa.json") self.assertTrue(first_nfa.contains(second_nfa)) self.assertFalse(second_nfa.contains(first_nfa))
def _new(self) -> None: self._nfa = NFA() self._grammar = RegularGrammar() self._update_table()
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self) -> None: QMainWindow.__init__(self) self.setupUi(self) self.resize(600, 400) self.regexToDFAButton.clicked.connect(self._regex_to_dfa) self.addSymbolButton.clicked.connect(self._add_symbols) self.addStateButton.clicked.connect(self._add_states) self.removeSymbolButton.clicked.connect(self._remove_symbols) self.removeStateButton.clicked.connect(self._remove_states) self.finalStateButton.clicked.connect(self._toggle_final_states) self.fromNFAbutton.clicked.connect(self._nfa_to_grammar) self.toNFAbutton.clicked.connect(self._grammar_to_nfa) self.testButton.clicked.connect(self._test_string) self.actionNew.triggered.connect(self._new) self.actionOpen.triggered.connect(self._open) self.actionSave.triggered.connect(self._save) self.actionDeterminize.triggered.connect(self._determinize) self.actionRemove_unreachable_states.triggered.connect( self._remove_unreachable) self.actionRemove_dead_states.triggered.connect(self._remove_dead) self.actionMerge_equivalent_states.triggered.connect( self._merge_equivalent) self.actionFull_minimization.triggered.connect(self._minimize) self.action_to_abc.triggered.connect(self._beautify_abc) self.action_to_qn.triggered.connect(self._beautify_qn) self.actionUnion.triggered.connect(self._union) self.actionComplement.triggered.connect(self._complement) self.actionIntersection.triggered.connect(self._intersection) self.actionContains.triggered.connect(self._contains) self.actionEquivalent.triggered.connect(self._is_equal) self.transitionTable.cellChanged.connect(self._update_nfa) self._grammar = RegularGrammar() self._nfa = NFA() self._update_table() def _regex_to_dfa(self) -> None: try: self._nfa = regex_to_dfa(self.regexInput.text()) self._update_table() except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0]) def _add_symbols(self) -> None: text, ok = QInputDialog.getText(self, "Add symbols", "Symbols (a,b,c,...):") if ok: for symbol in text.replace(" ", "").split(","): self._nfa.add_symbol(symbol) self._update_table() def _add_states(self) -> None: text, ok = QInputDialog.getText(self, "Add states", "States (q0,q1,...):") if ok: for state in text.replace(" ", "").split(","): self._nfa.add_state(state) self._update_table() def _remove_symbols(self) -> None: text, ok = QInputDialog.getText(self, "Remove symbols", "Symbols (a,b,c,...):") if ok: for symbol in text.replace(" ", "").split(","): self._nfa.remove_symbol(symbol) self._update_table() def _remove_states(self) -> None: text, ok = QInputDialog.getText(self, "Remove states", "States (q0,q1,...):") if ok: for state in text.replace(" ", "").split(","): self._nfa.remove_state(state) self._update_table() def _toggle_final_states(self) -> None: text, ok = QInputDialog.getText(self, "Final states", "States (q0,q1,...):") if ok: for state in text.replace(" ", "").split(","): self._nfa.toggle_final_state(state) self._update_table() def _test_emptiness(self) -> None: if self._nfa.is_empty(): self.languageLabel.setText("The language is empty.") elif self._nfa.is_finite(): self.languageLabel.setText("The language is finite.") else: self.languageLabel.setText("The language is infinite.") def _remove_unreachable(self) -> None: self._nfa.remove_unreachable() self._update_table() def _remove_dead(self) -> None: self._nfa.remove_dead() self._update_table() def _merge_equivalent(self) -> None: try: self._nfa.merge_equivalent() self._update_table() except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0]) def _minimize(self) -> None: try: self._nfa.minimize() self._update_table() except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0]) def _test_string(self) -> None: try: self.statusbar.showMessage("String accepted" if self._nfa.accept( self.inputString.text()) else "String rejected") except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0]) def _determinize(self) -> None: self._nfa.determinize() self._update_table() def _beautify_qn(self) -> None: self._nfa.beautify_qn() self._update_table() def _beautify_abc(self) -> None: try: self._nfa.beautify_abc() self._update_table() except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0]) def _nfa_to_grammar(self) -> None: self._grammar = RegularGrammar.from_nfa(self._nfa) self._update_grammar_text() def _grammar_to_nfa(self) -> None: try: self._nfa = NFA.from_regular_grammar( parse_grammar_text(self.grammarText.toPlainText())) self._update_table() except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0]) def _union(self) -> None: try: path, _ = QFileDialog.getOpenFileName(self) if path: second_nfa = NFA.load(path) self._nfa.union(second_nfa) self._update_table() except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0]) def _complement(self) -> None: self._nfa.complement() self._update_table() def _intersection(self) -> None: try: path, _ = QFileDialog.getOpenFileName(self) if path: second_nfa = NFA.load(path) self._nfa.intersection(second_nfa) self._update_table() except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0]) def _contains(self) -> None: try: path, _ = QFileDialog.getOpenFileName(self) if path: second_nfa = NFA.load(path) if self._nfa.contains(second_nfa): QMessageBox.information( self, "Contains", "The automaton contains the second one.") else: QMessageBox.information( self, "Contains", "The automaton does not contain the second one.") except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0]) def _is_equal(self) -> None: try: path, _ = QFileDialog.getOpenFileName(self) if path: second_nfa = NFA.load(path) if self._nfa.is_equal(second_nfa): QMessageBox.information(self, "Equivalent", "The automata are equivalent.") else: QMessageBox.information( self, "Equivalent", "The automata are not equivalent.") except RuntimeError as error: QMessageBox.information(self, "Error", error.args[0]) def _update_nfa(self, row: int, col: int) -> None: states = self._nfa.states alphabet = self._nfa.alphabet next_states = \ set(self.transitionTable.item(row, col).text().replace( " ", "").split(",")) try: self._nfa.set_transition( states[row], alphabet[col], next_states if next_states != {""} else set()) except KeyError as error: QMessageBox.information(self, "Error", error.args[0]) self.transitionTable.item(row, col).setText("") self._test_emptiness() def _update_table(self) -> None: # avoid useless calls to _update_nfa() self.transitionTable.cellChanged.disconnect(self._update_nfa) states = [] for state in self._nfa.states: preffix = "" if state in self._nfa.final_states: preffix += "*" if state == self._nfa.initial_state: preffix += "->" states.append(preffix + state) alphabet = self._nfa.alphabet self.transitionTable.setRowCount(len(states)) self.transitionTable.setVerticalHeaderLabels(states) self.transitionTable.setColumnCount(len(alphabet)) self.transitionTable.setHorizontalHeaderLabels(alphabet) table = self._nfa.transition_table for i, state in enumerate(self._nfa.states): for j, symbol in enumerate(alphabet): transition = ",".join(sorted(table[state, symbol])) \ if (state, symbol) in table else "" self.transitionTable.setItem(i, j, QTableWidgetItem(transition)) self._test_emptiness() # reconnect self.transitionTable.cellChanged.connect(self._update_nfa) def _update_grammar_text(self) -> None: """ "B", {"aB", "bC", "a"} turns into "B -> aB | bC | a" """ def transform_production(non_terminal: str, productions: Set[str]): return "{} -> {}".format(non_terminal, " | ".join(sorted(productions))) initial_symbol = self._grammar.initial_symbol() productions = self._grammar.productions() text = "" if initial_symbol in productions: text = transform_production(initial_symbol, productions[initial_symbol]) + "\n" for non_terminal in sorted(set(productions.keys()) - {initial_symbol}): text += transform_production(non_terminal, productions[non_terminal]) + "\n" self.grammarText.setPlainText(text) def _new(self) -> None: self._nfa = NFA() self._grammar = RegularGrammar() self._update_table() def _open(self) -> None: path, _ = QFileDialog.getOpenFileName(self) if path: self._nfa = NFA.load(path) self._update_table() def _save(self) -> None: path, _ = QFileDialog.getSaveFileName(self) if path: self._nfa.save(path)
def test_dead_removal(self) -> None: nfa = NFA.load("examples/one1.json") self.assertEqual(nfa.states, ['A', 'B', 'C', 'D', 'E', 'F']) nfa.remove_dead() self.assertEqual(nfa.states, ['A', 'B', 'C', 'D', 'E'])
def _open(self) -> None: path, _ = QFileDialog.getOpenFileName(self) if path: self._nfa = NFA.load(path) self._update_table()
def nfa_test(self, nfa: NFA, true_cases: Set[str], false_cases: Set[str]) \ -> None: self.assertTrue(all(nfa.accept(s) for s in true_cases)) self.assertFalse(any(nfa.accept(s) for s in false_cases))