From 6f42d8fa3ffb99e29c14066e0dea47eb68537053 Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Thu, 16 Nov 2023 15:42:52 -0500 Subject: [PATCH] Separate HeadTape from Tape --- test/test_coverage.py | 2 - test/test_tape.py | 67 ++++++++++++++++++++++++++++------ tm/lin_rec.py | 85 +++++++++++++++++++++++++++++++++++-------- tm/machine.py | 2 +- 4 files changed, 127 insertions(+), 29 deletions(-) diff --git a/test/test_coverage.py b/test/test_coverage.py index 11beda06..a97192d1 100644 --- a/test/test_coverage.py +++ b/test/test_coverage.py @@ -51,8 +51,6 @@ def test_display(self): print(Machine("1RB ... ... ...").run(watch_tape = True)) - print(StrictLinRecMachine("1RB 0LB 1LA 0RB").run(check_rec = 0)) - class TestFloss(TestCase): def test_macro(self): diff --git a/test/test_tape.py b/test/test_tape.py index 4f01f6f8..af79652c 100644 --- a/test/test_tape.py +++ b/test/test_tape.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from tm.tape import Tape, TagTape, EnumTape, Block -from tm.lin_rec import HeadTape +from tm.lin_rec import HeadTape, Block as HeadBlock if TYPE_CHECKING: from tm.tape import Color, Signature @@ -26,7 +26,7 @@ def stringify_sig(sig: Signature) -> str: class TestTape(TestCase): - tape: HeadTape + tape: Tape | HeadTape def set_tape( self, @@ -35,24 +35,60 @@ def set_tape( rspan: BlockSpan, head: int | None = None, ) -> None: - self.tape = HeadTape( - [Block(color, count) for color, count in lspan], - scan, - [Block(color, count) for color, count in rspan], - head = head or 0) + block = Block if head is None else HeadBlock + + lspan = [ + block(color, count) # type: ignore[misc] + for color, count in lspan + ] + rspan = [ + block(color, count) # type: ignore[misc] + for color, count in rspan + ] + + self.tape = ( + Tape(lspan, scan, rspan) # type: ignore[arg-type] + if head is None else + HeadTape(lspan, scan, rspan, head) # type: ignore[arg-type] + ) def assert_tape(self, tape: str): - self.assertEqual(tape, str(self.tape)) + self.assertEqual( + tape, + str(tape) + if isinstance( + tape := self.tape, # type: ignore[assignment] + Tape + ) else + str(Tape( + [Block(block.color, block.count) + for block in tape.lspan], # type: ignore[attr-defined] + tape.scan, # type: ignore[attr-defined] + [Block(block.color, block.count) + for block in tape.rspan] # type: ignore[attr-defined] + )) + ) def assert_signature( self, expected: str, - tape: Tape | None = None, + tape: Tape | HeadTape | None = None, ) -> None: + if tape is None: + tape = self.tape + + assert isinstance(tape, HeadTape) + self.assertEqual( expected, stringify_sig( - (tape or self.tape).signature)) + Tape( + [Block(block.color, block.count) + for block in tape.lspan], + tape.scan, + [Block(block.color, block.count) + for block in tape.rspan] + ).signature)) def test_marks(self): self.assertFalse( @@ -62,7 +98,8 @@ def test_copy(self): self.set_tape( [[1, 1], [0, 1], [1, 1]], 2, - [[2, 1], [1, 2]] + [[2, 1], [1, 2]], + 0, ) self.assert_tape( @@ -71,6 +108,8 @@ def test_copy(self): self.assert_signature( '1|0|1[2]2|1') + assert isinstance(self.tape, HeadTape) + copy_1 = self.tape.copy() copy_2 = self.tape.copy() @@ -97,6 +136,8 @@ def test_rule_1(self): self.assert_tape( "2^3 1^12 [3] 4^15 5^2 6^2") + assert isinstance(self.tape, Tape) + self.tape.apply_rule({ (0, 1): 3, (1, 0): -2, @@ -119,6 +160,8 @@ def test_rule_2(self): 4, [[5, 60], [2, 1], [4, 1], [5, 7], [1, 1]]) + assert isinstance(self.tape, Tape) + self.assert_tape( "4^2 [4] 5^60 2^1 4^1 5^7 1^1") @@ -133,6 +176,8 @@ def test_rule_2(self): def test_rule_3(self): self.set_tape([[1, 152], [2, 655345], [3, 1]], 0, []) + assert isinstance(self.tape, Tape) + self.assert_tape( "3^1 2^655345 1^152 [0]") diff --git a/tm/lin_rec.py b/tm/lin_rec.py index 8f6120ff..5fb457e6 100644 --- a/tm/lin_rec.py +++ b/tm/lin_rec.py @@ -5,14 +5,12 @@ from collections import defaultdict from dataclasses import dataclass, field -from tm.tape import Tape, Block from tm.machine import BasicMachine if TYPE_CHECKING: from typing import Self - from tm.tape import Shift - from tm.parse import Color, State, Slot + from tm.parse import Color, Shift, State, Slot, GetInstr RecRes = tuple[int, int] LinRec = tuple[int | None, int] @@ -21,19 +19,22 @@ @dataclass -class HeadTape(Tape): - head: int = 0 +class Block: + color: Color + count: int - def __init__( - self, - lspan: list[Block], - scan: Color, - rspan: list[Block], - head: int = 0, - ): - self.head = head - super().__init__(lspan, scan, rspan) +@dataclass +class HeadTape: + lspan: list[Block] + scan: Color + rspan: list[Block] + + head: int = 0 + + @classmethod + def init(cls, scan: Color = 0) -> Self: + return cls([], scan, []) @classmethod def init_stepped(cls) -> Self: # no-cover @@ -72,8 +73,62 @@ def unroll(self) -> list[Color]: for _ in range(int(block.count)) ] + @property + def blank(self) -> bool: + return self.scan == 0 and not self.lspan and not self.rspan + + def at_edge(self, edge: Shift) -> bool: + return ( + self.scan == 0 + and not (self.rspan if edge else self.lspan) + ) + def step(self, shift: Shift, color: Color, skip: bool) -> int: - stepped = int(super().step(shift, color, skip)) + pull, push = ( + (self.rspan, self.lspan) + if shift else + (self.lspan, self.rspan) + ) + + push_block = ( + pull.pop(0) + if skip and pull and pull[0].color == self.scan else + None + ) + + stepped = 1 if push_block is None else 1 + push_block.count + + next_scan: Color + + if not pull: + next_scan = 0 + else: + next_pull = pull[0] + + if next_pull.count != 1: + next_pull.count -= 1 + else: + popped = pull.pop(0) + + if push_block is None: + push_block = popped + push_block.count = 0 + + next_scan = next_pull.color + + if push and (top_block := push[0]).color == color: + top_block.count += stepped + else: + if push_block is None: + push_block = Block(color, 1) + else: + push_block.color = color + push_block.count += 1 + + if push or color != 0: + push.insert(0, push_block) + + self.scan = next_scan self.head += stepped if shift else -stepped diff --git a/tm/machine.py b/tm/machine.py index 63e1786e..95547b0e 100644 --- a/tm/machine.py +++ b/tm/machine.py @@ -66,7 +66,7 @@ def term_results(self) -> list[tuple[str, Result]]: try: # pylint: disable = bad-builtin data = getattr(self, cat) - except AttributeError: + except AttributeError: # no-cover continue if data is not None: