Skip to content

Commit e4bf660

Browse files
committed
correct pda intersection, add more tests for union, concatenation and kleene star
1 parent fafd489 commit e4bf660

File tree

2 files changed

+90
-28
lines changed

2 files changed

+90
-28
lines changed

pyformlang/finite_automaton/tests/test_epsilon_nfa.py

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def test_deterministic(self):
8787
assert not dfa.accepts([point])
8888
assert not dfa.accepts([plus])
8989

90-
def test_union(self):
90+
def test_union0(self):
9191
""" Tests the union of two epsilon NFA """
9292
enfa0 = get_enfa_example0()
9393
enfa1 = get_enfa_example1()
@@ -101,7 +101,25 @@ def test_union(self):
101101
assert not enfa.accepts([symb_a])
102102
assert not enfa.accepts([])
103103

104-
def test_concatenate(self):
104+
def test_union1(self):
105+
"""
106+
Tests the union of three ENFAs.
107+
Union is (a*b)|(ab+)|c
108+
"""
109+
enfa0 = get_enfa_example0()
110+
enfa1 = get_enfa_example1()
111+
enfa2 = get_enfa_example2()
112+
enfa = enfa0 | enfa2
113+
enfa |= enfa1
114+
accepted_words = list(enfa.get_accepted_words(3))
115+
assert ["b"] in accepted_words
116+
assert ["a", "b"] in accepted_words
117+
assert ["a", "a", "b"] in accepted_words
118+
assert ["a", "b", "b"] in accepted_words
119+
assert ["c"] in accepted_words
120+
assert len(accepted_words) == 5
121+
122+
def test_concatenate0(self):
105123
""" Tests the concatenation of two epsilon NFA """
106124
enfa0 = get_enfa_example0()
107125
enfa1 = get_enfa_example1()
@@ -116,7 +134,23 @@ def test_concatenate(self):
116134
assert not enfa.accepts([symb_b])
117135
assert not enfa.accepts([])
118136

119-
def test_kleene(self):
137+
def test_concatenate1(self):
138+
"""
139+
Tests the concatenation of three ENFAs.
140+
Concatenation is a*bc((ab+)|c)
141+
"""
142+
enfa0 = get_enfa_example0()
143+
enfa1 = get_enfa_example1()
144+
enfa2 = get_enfa_example2()
145+
enfa = enfa0 + enfa1
146+
enfa += enfa2
147+
accepted_words = list(enfa.get_accepted_words(4))
148+
assert ["b", "c", "c"] in accepted_words
149+
assert ["a", "b", "c", "c"] in accepted_words
150+
assert ["b", "c", "a", "b"] in accepted_words
151+
assert len(accepted_words) == 3
152+
153+
def test_kleene0(self):
120154
""" Tests the kleene star of an epsilon NFA """
121155
enfa0 = get_enfa_example0()
122156
symb_a = Symbol("a")
@@ -130,6 +164,23 @@ def test_kleene(self):
130164
assert not enfa.accepts([symb_a])
131165
assert not enfa.accepts([symb_a, symb_b, symb_a])
132166

167+
def test_kleene1(self):
168+
"""
169+
Tests the kleene star of an ENFA.
170+
Expression is ((ab+)|c)*
171+
"""
172+
enfa = get_enfa_example2()
173+
enfa = enfa.kleene_star()
174+
accepted_words = list(enfa.get_accepted_words(3))
175+
assert [] in accepted_words
176+
assert ["a", "b"] in accepted_words
177+
assert ["a", "b", "b"] in accepted_words
178+
assert ["a", "b", "c"] in accepted_words
179+
assert ["c", "a", "b"] in accepted_words
180+
for i in range(3):
181+
assert ["c"] * (i + 1) in accepted_words
182+
assert len(accepted_words) == 8
183+
133184
def test_complement(self):
134185
""" Tests the complement operation """
135186
enfa = EpsilonNFA()
@@ -544,7 +595,7 @@ def get_enfa_example0():
544595

545596

546597
def get_enfa_example1():
547-
""" Gives and example ENFA
598+
""" Gives an example ENFA
548599
Accepts c
549600
"""
550601
enfa1 = EpsilonNFA()
@@ -557,6 +608,21 @@ def get_enfa_example1():
557608
return enfa1
558609

559610

611+
def get_enfa_example2():
612+
""" Gives an example ENFA
613+
Accepts (ab+)|c
614+
"""
615+
enfa = EpsilonNFA(start_states={0, 3},
616+
final_states={2, 4})
617+
enfa.add_transitions([
618+
(0, "a", 1),
619+
(1, "b", 2),
620+
(2, "b", 2),
621+
(3, "c", 4),
622+
])
623+
return enfa
624+
625+
560626
def get_enfa_example0_bis():
561627
""" A non minimal NFA, equivalent to example0 """
562628
enfa0 = EpsilonNFA()

pyformlang/pda/pda.py

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -467,12 +467,11 @@ def intersection(self, other: DeterministicFiniteAutomaton) -> "PDA":
467467
When intersecting with something else than a regex or a finite
468468
automaton
469469
"""
470-
start_state_other = other.start_states
471-
if len(start_state_other) == 0:
470+
start_state_other = other.start_state
471+
if not start_state_other:
472472
return PDA()
473473
pda_state_converter = _PDAStateConverter(self._states, other.states)
474-
start_state_other = list(start_state_other)[0]
475-
final_state_other = other.final_states
474+
final_states_other = other.final_states
476475
start = pda_state_converter.to_pda_combined_state(self._start_state,
477476
start_state_other)
478477
pda = PDA(start_state=start,
@@ -484,40 +483,37 @@ def intersection(self, other: DeterministicFiniteAutomaton) -> "PDA":
484483
while to_process:
485484
state_in, state_dfa = to_process.pop()
486485
if (state_in in self._final_states and state_dfa in
487-
final_state_other):
486+
final_states_other):
488487
pda.add_final_state(
489488
pda_state_converter.to_pda_combined_state(state_in,
490489
state_dfa))
491490
for symbol in symbols:
492491
if symbol == Epsilon():
493492
symbol_dfa = finite_automaton.Epsilon()
493+
next_state_dfa = state_dfa
494494
else:
495495
symbol_dfa = finite_automaton.Symbol(symbol.value)
496-
if symbol == Epsilon():
497-
next_states_dfa = [state_dfa]
498-
else:
499-
next_states_dfa = other(state_dfa, symbol_dfa)
500-
if len(next_states_dfa) == 0:
496+
next_state_dfa = other.get_next_state(state_dfa, symbol_dfa)
497+
if not next_state_dfa:
501498
continue
502499
for stack_symbol in self._stack_alphabet:
503500
next_states_self = self._transition_function(state_in,
504501
symbol,
505502
stack_symbol)
506503
for next_state, next_stack in next_states_self:
507-
for next_state_dfa in next_states_dfa:
508-
pda.add_transition(
509-
pda_state_converter.to_pda_combined_state(
510-
state_in,
511-
state_dfa),
512-
symbol,
513-
stack_symbol,
514-
pda_state_converter.to_pda_combined_state(
515-
next_state,
516-
next_state_dfa),
517-
next_stack)
518-
if (next_state, next_state_dfa) not in processed:
519-
to_process.append((next_state, next_state_dfa))
520-
processed.add((next_state, next_state_dfa))
504+
pda.add_transition(
505+
pda_state_converter.to_pda_combined_state(
506+
state_in,
507+
state_dfa),
508+
symbol,
509+
stack_symbol,
510+
pda_state_converter.to_pda_combined_state(
511+
next_state,
512+
next_state_dfa),
513+
next_stack)
514+
if (next_state, next_state_dfa) not in processed:
515+
to_process.append((next_state, next_state_dfa))
516+
processed.add((next_state, next_state_dfa))
521517
return pda
522518

523519
def __and__(self, other: DeterministicFiniteAutomaton) -> "PDA":

0 commit comments

Comments
 (0)