Research source · Plain text
TapeZipper.lean
site/public/formal/persistent-stacks/lean/FPRDTransfer/TapeZipper.lean
173 lines. Source is displayed for inspection; it is not executed by this page.
File fingerprint
SHA-256: 29f3aa81175570cabad03d728a5fb8159e2c9dceedf1d898af4472d1ca75e3c4
/-
The two-stack zipper for an ordinary single-head Turing machine tape.
A tape is held as `left` (the cells strictly left of the head, nearest first),
`cur` (the scanned cell), and `right` (the cells strictly right of the head,
nearest first). `left` and `right` are *finite* lists; a position beyond
either list reads as the blank symbol. This is the distinction the packet
requires: an empty stack is not a blank cell, it is the absence of any
recorded cell, and reading there yields blank.
`get` gives the cell at an offset from the head, so the four shift equations
for each move say exactly that a move slides the window by one and writes the
outgoing symbol into the cell the head is leaving.
-/
import FPRDTransfer.StackMachine
namespace FPRD
/-- Head motion for one tape in one transition. -/
inductive Move where
| left : Move
| right : Move
| stay : Move
/-- A tape as a zipper: two finite lists and the scanned symbol. -/
structure Tape (T : Type) where
left : List T
cur : T
right : List T
namespace Tape
variable {T : Type}
/-- The cell at a signed offset from the head. Offsets beyond the recorded
lists read as the blank symbol `b`. -/
def get (t : Tape T) (b : T) : Int → T
| Int.ofNat 0 => t.cur
| Int.ofNat (n + 1) => t.right.getD n b
| Int.negSucc n => t.left.getD n b
/-- Write `a` under the head and move right. One `push` on `left`, one `pop`
on `right`; the new scanned symbol is the old top of `right`, or blank. -/
def moveRight (t : Tape T) (a b : T) : Tape T where
left := a :: t.left
cur := t.right.headD b
right := t.right.tail
/-- Write `a` under the head and move left. -/
def moveLeft (t : Tape T) (a b : T) : Tape T where
left := t.left.tail
cur := t.left.headD b
right := a :: t.right
/-- Write `a` under the head and stay. Both stacks are kept. -/
def stay (t : Tape T) (a : T) : Tape T where
left := t.left
cur := a
right := t.right
/-! ### The window really shifts
Each group of equations pins `get` on the whole of `Int`, so together they say
the zipper behaves as a bi-infinite blank-padded tape. These equations are
model-interface checks. The operational transfer proof below uses the
projection lemmas `apply_left`, `apply_right`, and `apply_cur`; the final
machine-to-scaffold theorem separately proves equality through `Tape.get`. -/
@[simp] theorem stay_get_zero (t : Tape T) (a b : T) : (t.stay a).get b 0 = a := rfl
@[simp] theorem stay_get_pos (t : Tape T) (a b : T) (n : Nat) :
(t.stay a).get b (Int.ofNat (n + 1)) = t.right.getD n b := rfl
@[simp] theorem stay_get_neg (t : Tape T) (a b : T) (n : Nat) :
(t.stay a).get b (Int.negSucc n) = t.left.getD n b := rfl
theorem moveRight_get_zero (t : Tape T) (a b : T) :
(t.moveRight a b).get b 0 = t.right.getD 0 b := by
obtain ⟨L, c, R⟩ := t
cases R <;> rfl
theorem moveRight_get_pos (t : Tape T) (a b : T) (n : Nat) :
(t.moveRight a b).get b (Int.ofNat (n + 1)) = t.right.getD (n + 1) b := by
obtain ⟨L, c, R⟩ := t
cases R <;> rfl
theorem moveRight_get_neg_zero (t : Tape T) (a b : T) :
(t.moveRight a b).get b (Int.negSucc 0) = a := rfl
theorem moveRight_get_neg_succ (t : Tape T) (a b : T) (n : Nat) :
(t.moveRight a b).get b (Int.negSucc (n + 1)) = t.left.getD n b := rfl
theorem moveLeft_get_zero (t : Tape T) (a b : T) :
(t.moveLeft a b).get b 0 = t.left.getD 0 b := by
obtain ⟨L, c, R⟩ := t
cases L <;> rfl
theorem moveLeft_get_pos_zero (t : Tape T) (a b : T) :
(t.moveLeft a b).get b (Int.ofNat 1) = a := rfl
theorem moveLeft_get_pos_succ (t : Tape T) (a b : T) (n : Nat) :
(t.moveLeft a b).get b (Int.ofNat (n + 2)) = t.right.getD n b := rfl
theorem moveLeft_get_neg (t : Tape T) (a b : T) (n : Nat) :
(t.moveLeft a b).get b (Int.negSucc n) = t.left.getD (n + 1) b := by
obtain ⟨L, c, R⟩ := t
cases L <;> rfl
/-! ### The zipper's stack actions
Each move is at most one action per stack, which is what lets one appended
scaffold node carry a whole multitape transition. -/
/-- The action on the `left` stack. -/
def leftOp (a : T) : Move → StackOp T
| .right => .push a
| .left => .pop
| .stay => .keep
/-- The action on the `right` stack. -/
def rightOp (a : T) : Move → StackOp T
| .right => .pop
| .left => .push a
| .stay => .keep
/-- The new scanned symbol after writing `a` and moving. -/
def nextCur (t : Tape T) (a b : T) : Move → T
| .right => t.right.headD b
| .left => t.left.headD b
| .stay => a
/-- Applying the move to the zipper. -/
def apply (t : Tape T) (a b : T) : Move → Tape T
| .right => t.moveRight a b
| .left => t.moveLeft a b
| .stay => t.stay a
/-- The zipper's `left` list is exactly what `leftOp` does to it. -/
theorem apply_left (t : Tape T) (a b : T) (mv : Move) :
(t.apply a b mv).left = (leftOp a mv).apply t.left := by
cases mv <;> rfl
/-- The zipper's `right` list is exactly what `rightOp` does to it. -/
theorem apply_right (t : Tape T) (a b : T) (mv : Move) :
(t.apply a b mv).right = (rightOp a mv).apply t.right := by
cases mv <;> rfl
/-- The new scanned symbol is a function of the finite control plus the two
stack tops, so it can live in the control. -/
theorem apply_cur (t : Tape T) (a b : T) (mv : Move) :
(t.apply a b mv).cur = nextCur t a b mv := by
cases mv <;> rfl
theorem headD_eq_head? (l : List T) (b : T) :
l.headD b = (l.head?).getD b := by
cases l <;> rfl
/-- `nextCur` depends on the tapes only through the two stack tops. -/
theorem nextCur_of_tops (t : Tape T) (a b : T) (mv : Move) :
nextCur t a b mv =
match mv with
| .right => (t.right.head?).getD b
| .left => (t.left.head?).getD b
| .stay => a := by
cases mv
· simp only [nextCur]; exact headD_eq_head? t.left b
· simp only [nextCur]; exact headD_eq_head? t.right b
· rfl
end Tape
end FPRD