Skip to content

Commit

Permalink
Merge 351d331 into 89fa965
Browse files Browse the repository at this point in the history
  • Loading branch information
caleb531 committed Apr 14, 2023
2 parents 89fa965 + 351d331 commit e5267fe
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 57 deletions.
13 changes: 5 additions & 8 deletions automata/pda/configuration.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
#!/usr/bin/env python3
"""Classes and methods for working with PDA configurations."""

from typing import NamedTuple
from dataclasses import dataclass

from automata.base.automaton import AutomatonStateT
from automata.pda.stack import PDAStack


class PDAConfiguration(NamedTuple):
@dataclass(frozen=True)
class PDAConfiguration:
"""
A configuration is a triple of current state, remaining input and stack.
It represents the complete runtime state of a PDA.
It is hashable and immutable.
"""

__slots__ = ("state", "remaining_input", "stack")

state: AutomatonStateT
remaining_input: str
stack: PDAStack

def __repr__(self) -> str:
"""Return a string representation of the configuration."""
return "{}('{}', '{}', {})".format(
self.__class__.__name__, self.state, self.remaining_input, self.stack
)
4 changes: 3 additions & 1 deletion automata/pda/dpda.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ def _check_for_input_rejection(
if not self._has_accepted(current_configuration):
raise exceptions.RejectionException(
"the DPDA stopped in a non-accepting configuration "
"({state}, {stack})".format(**current_configuration._asdict())
"({}, {})".format(
current_configuration.state, current_configuration.stack
)
)

def _get_next_configuration(self, old_config: PDAConfiguration) -> PDAConfiguration:
Expand Down
15 changes: 8 additions & 7 deletions automata/pda/stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@
"""Classes and methods for working with PDA stacks."""
from __future__ import annotations

import collections
from dataclasses import dataclass
from typing import Iterator, Sequence, Tuple


class PDAStack(collections.namedtuple("PDAStack", ["stack"])):
@dataclass(frozen=True)
class PDAStack:
"""A PDA stack."""

__slots__ = ("stack",)

stack: Tuple[str, ...]

# TODO can we get rid of this? Kinda weird inheritance here
def __new__(cls, elements: Sequence[str]):
"""Create the new PDA stack."""
return super(PDAStack, cls).__new__(cls, tuple(elements))
def __init__(self, stack: Sequence[str]):
object.__setattr__(self, "stack", tuple(stack))

def top(self) -> str:
"""Return the symbol at the top of the stack."""
Expand Down Expand Up @@ -50,4 +51,4 @@ def __iter__(self) -> Iterator[str]:

def __repr__(self) -> str:
"""Return a string representation of the stack."""
return "{}{}".format(self.__class__.__name__, self.stack)
return "{}({})".format(self.__class__.__name__, self.stack)
13 changes: 10 additions & 3 deletions automata/tm/configuration.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
#!/usr/bin/env python3
"""Classes and methods for working with Turing machine configurations."""

from typing import List, NamedTuple
from dataclasses import dataclass
from typing import List

from automata.tm.tape import TMTape
from automata.tm.tm import TMStateT


class TMConfiguration(NamedTuple):
@dataclass(frozen=True)
class TMConfiguration:
"""A Turing machine configuration."""

__slots__ = ("state", "tape")

state: TMStateT
tape: TMTape

Expand All @@ -30,9 +34,12 @@ def print(self) -> None:
)


class MTMConfiguration(NamedTuple):
@dataclass(frozen=True)
class MTMConfiguration:
"""A Multitape Turing machine configuration."""

__slots__ = ("state", "tapes")

state: TMStateT
tapes: List[TMTape]

Expand Down
18 changes: 10 additions & 8 deletions automata/tm/tape.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
#!/usr/bin/env python3
"""Classes and methods for working with Turing machine tapes."""

import collections
from dataclasses import dataclass
from typing import Iterator, Sequence, Tuple

from typing_extensions import Self

from automata.tm.tm import TMDirectionT


class TMTape(
collections.namedtuple("TMTape", ["tape", "blank_symbol", "current_position"])
):
@dataclass(frozen=True)
class TMTape:
"""A Turing machine tape."""

__slots__ = ("tape", "blank_symbol", "current_position")

tape: Tuple[str]
blank_symbol: str
current_position: int

def __new__(
cls, tape: Sequence[str], *, blank_symbol: str, current_position: int = 0
def __init__(
self, tape: Sequence[str], *, blank_symbol: str, current_position: int = 0
):
"""Initialize a new Turing machine tape."""
tape = list(tape)
# Make sure that there's something under the cursor.
while len(tape) <= current_position:
tape.append(blank_symbol)
tape = tuple(tape)
return super(TMTape, cls).__new__(cls, tape, blank_symbol, current_position)
object.__setattr__(self, "tape", tuple(tape))
object.__setattr__(self, "blank_symbol", blank_symbol)
object.__setattr__(self, "current_position", current_position)

def read_symbol(self) -> str:
"""Read the symbol at the current position in the tape."""
Expand Down
16 changes: 8 additions & 8 deletions tests/test_dtm.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,24 +265,24 @@ def test_validate_blank_symbol(self):
def test_read_input_accepted(self):
"""Should return correct state if acceptable TM input is given."""
final_config = self.dtm1.read_input("00001111")
self.assertEqual(final_config[0], "q4")
self.assertEqual(str(final_config[1]), "TMTape('xxxxyyyy..', 9)")
self.assertEqual(final_config.state, "q4")
self.assertEqual(str(final_config.tape), "TMTape('xxxxyyyy..', 9)")

def test_read_input_step(self):
"""Should return validation generator if step flag is supplied."""
validation_generator = self.dtm1.read_input_stepwise("00001111")
self.assertIsInstance(validation_generator, types.GeneratorType)
configs = list(validation_generator)
self.assertEqual(configs[0][0], "q0")
self.assertEqual(str(configs[0][1]), "TMTape('00001111', 0)")
self.assertEqual(configs[-1][0], "q4")
self.assertEqual(str(configs[-1][1]), "TMTape('xxxxyyyy..', 9)")
self.assertEqual(configs[0].state, "q0")
self.assertEqual(str(configs[0].tape), "TMTape('00001111', 0)")
self.assertEqual(configs[-1].state, "q4")
self.assertEqual(str(configs[-1].tape), "TMTape('xxxxyyyy..', 9)")

def test_read_input_offset(self):
"""Should valdiate input when tape is offset."""
final_config = self.dtm2.read_input("01010101")
self.assertEqual(final_config[0], "q4")
self.assertEqual(str(final_config[1]), "TMTape('yyx1010101', 3)")
self.assertEqual(final_config.state, "q4")
self.assertEqual(str(final_config.tape), "TMTape('yyx1010101', 3)")

def test_read_input_rejection(self):
"""Should raise error if the machine halts."""
Expand Down
28 changes: 15 additions & 13 deletions tests/test_mntm.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,11 +385,13 @@ def test_read_input_as_ntm(self):
validation_generator = self.mntm2.read_input_as_ntm("#0000")
configs = list(validation_generator)
first_config = configs[0].pop()
self.assertEqual(first_config[0], "q-1")
self.assertEqual(str(first_config[1]), "TMTape('#^0000_#^_#^_', 0)")
self.assertEqual(first_config.state, "q-1")
self.assertEqual(str(first_config.tape), "TMTape('#^0000_#^_#^_', 0)")
last_config = configs[-1].pop()
self.assertEqual(last_config[0], "qf")
self.assertEqual(str(last_config[1]), "TMTape('#0000#^_#0000#^_#XYYY#^_', 23)")
self.assertEqual(last_config.state, "qf")
self.assertEqual(
str(last_config.tape), "TMTape('#0000#^_#0000#^_#XYYY#^_', 23)"
)

with self.assertRaises(exceptions.RejectionException):
for _ in self.mntm2.read_input_as_ntm("#00"):
Expand All @@ -398,23 +400,23 @@ def test_read_input_as_ntm(self):
def test_read_input_accepted(self):
"""Should return correct state if acceptable TM input is given."""
final_config = self.mntm1.read_input("0101101011").pop()
self.assertEqual(final_config[0], "q1")
self.assertEqual(str(final_config[1][0]), "TMTape('0101101011#', 10)")
self.assertEqual(str(final_config[1][1]), "TMTape('111111#', 6)")
self.assertEqual(final_config.state, "q1")
self.assertEqual(str(final_config.tapes[0]), "TMTape('0101101011#', 10)")
self.assertEqual(str(final_config.tapes[1]), "TMTape('111111#', 6)")

def test_read_input_step(self):
"""Should return validation generator if step flag is supplied."""
validation_generator = self.mntm1.read_input_stepwise("0010101111")
self.assertIsInstance(validation_generator, types.GeneratorType)
configs = list(validation_generator)
first_config = configs[0].pop()
self.assertEqual(first_config[0], "q0")
self.assertEqual(str(first_config[1][0]), "TMTape('0010101111', 0)")
self.assertEqual(str(first_config[1][1]), "TMTape('#', 0)")
self.assertEqual(first_config.state, "q0")
self.assertEqual(str(first_config.tapes[0]), "TMTape('0010101111', 0)")
self.assertEqual(str(first_config.tapes[1]), "TMTape('#', 0)")
last_config = configs[-1].pop()
self.assertEqual(last_config[0], "q1")
self.assertEqual(str(last_config[1][0]), "TMTape('0010101111#', 10)")
self.assertEqual(str(last_config[1][1]), "TMTape('111111#', 6)")
self.assertEqual(last_config.state, "q1")
self.assertEqual(str(last_config.tapes[0]), "TMTape('0010101111#', 10)")
self.assertEqual(str(last_config.tapes[1]), "TMTape('111111#', 6)")

def test_read_input_rejection(self):
"""Should raise error if the machine halts."""
Expand Down
12 changes: 6 additions & 6 deletions tests/test_ntm.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,20 +250,20 @@ def test_validate_final_state_transitions(self):
def test_read_input_accepted(self):
"""Should return correct state if acceptable TM input is given."""
final_config = self.ntm1.read_input("00120001111").pop()
self.assertEqual(final_config[0], "q3")
self.assertEqual(str(final_config[1]), "TMTape('00120001111.', 11)")
self.assertEqual(final_config.state, "q3")
self.assertEqual(str(final_config.tape), "TMTape('00120001111.', 11)")

def test_read_input_step(self):
"""Should return validation generator if step flag is supplied."""
validation_generator = self.ntm1.read_input_stepwise("00120001111")
self.assertIsInstance(validation_generator, types.GeneratorType)
configs = list(validation_generator)
first_config = configs[0].pop()
self.assertEqual(first_config[0], "q0")
self.assertEqual(str(first_config[1]), "TMTape('00120001111', 0)")
self.assertEqual(first_config.state, "q0")
self.assertEqual(str(first_config.tape), "TMTape('00120001111', 0)")
last_config = configs[-1].pop()
self.assertEqual(last_config[0], "q3")
self.assertEqual(str(last_config[1]), "TMTape('00120001111.', 11)")
self.assertEqual(last_config.state, "q3")
self.assertEqual(str(last_config.tape), "TMTape('00120001111.', 11)")

def test_read_input_rejection(self):
"""Should raise error if the machine halts."""
Expand Down
5 changes: 3 additions & 2 deletions tests/test_pdaconfiguration.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ class TestPDAConfiguration(test_pda.TestPDA):
"""A test class for testing configurations of pushdown automata."""

def test_config_repr(self):
"""Should create proper string representation of PDA stack."""
"""Should create proper string representation of PDA configuration."""
config = PDAConfiguration("q0", "ab", PDAStack(["a", "b"]))
self.assertEqual(
repr(config), "PDAConfiguration('q0', 'ab', PDAStack('a', 'b'))"
repr(config),
"PDAConfiguration(state='q0', remaining_input='ab', stack=PDAStack(('a', 'b')))", # noqa
)
2 changes: 1 addition & 1 deletion tests/test_pdastack.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ def test_stack_iter(self):
def test_stack_repr(self):
"""Should create proper string representation of PDA stack."""
stack = PDAStack(["a", "b"])
self.assertEqual(repr(stack), "PDAStack('a', 'b')")
self.assertEqual(repr(stack), "PDAStack(('a', 'b'))")

0 comments on commit e5267fe

Please sign in to comment.