Research source · Plain text

Compile.lean

site/public/formal/persistent-stacks/lean/FPRDTransfer/Compile.lean

465 lines. Source is displayed for inspection; it is not executed by this page.

File fingerprint

SHA-256: e6861b5f072bde0a56174cdb912d78f4e8501441b9fb5195066f71a5e0ad3338

/-
The compiler from a letter-synchronous multi-stack machine to an exact LMR
scaffolding automaton, and its correctness for every input word.

The compiled transition uses only `Unfold.labelOf` at depth 0 and depth 1 of
the tree unfolding, and names its targets by the port words `[]`, `[j]`, and
`[j, j]`.  It never inspects node identity, never compares two port words for
aliasing, never uses `SELF`, and appends exactly one node per input symbol.
-/

import FPRDTransfer.StackEncoding

namespace FPRD

variable {Sym Q A : Type} {s : Nat}

/-- Stack tops, read from the root label of the unfolding. -/
def readTops (n : Unfold s (Tuple s A) 2) (j : Fin s) : Option A :=
  n.labelOf.bind fun g => g j

/-- Second elements, read from the depth-1 label along port `j`.  A missing
edge and an unlabelled node both yield `none`. -/
def readSeconds (n : Unfold s (Tuple s A) 2) (j : Fin s) : Option A :=
  (n.child j).labelOf.bind fun g => g j

/-- The compiled transition function. -/
def compileDelta (M : StackMachine Sym Q A s) (q : Q) (x : Sym)
    (n : Unfold s (Tuple s A) 2) : Q × Tuple s A × (Fin s → Target s) :=
  let r := M.step q x (readTops n)
  (r.1,
    (fun j =>
      match r.2 j with
      | .push a => some a
      | .keep => readTops n j
      | .pop => readSeconds n j),
    fun j =>
      match r.2 j with
      | .push _ => .path []
      | .keep => .path [j]
      | .pop => .path [j, j])

/-- The compiled scaffolding automaton: degree `s`, distance `2`. -/
def compile (M : StackMachine Sym Q A s) : Automaton Sym Q (Tuple s A) s 2 where
  delta := compileDelta M
  init := M.init
  accept := M.accept

theorem compile_distanceOk (M : StackMachine Sym Q A s) : (compile M).DistanceOk := by
  intro q x n j p h
  simp only [compile, compileDelta] at h
  cases hop : (M.step q x (readTops n)).2 j with
  | push a =>
    rw [hop] at h; injection h with h'; subst h'; simp
  | keep =>
    rw [hop] at h; injection h with h'; subst h'; simp
  | pop =>
    rw [hop] at h; injection h with h'; subst h'; simp

theorem compile_selfFree (M : StackMachine Sym Q A s) : (compile M).SelfFree := by
  intro q x n j
  simp only [compile, compileDelta]
  cases (M.step q x (readTops n)).2 j <;> simp

/-! ### Reading the unfolding agrees with the scaffold -/

theorem readTops_eq (S : Scaffold s (Tuple s A)) (v : Nat) (j : Fin s) :
    readTops (S.viewAt 2 v) j = labelComp S v j := rfl

theorem child_labelOf (S : Scaffold s (Tuple s A)) (v : Nat) (j : Fin s) :
    ((S.viewAt 2 v).child j).labelOf = (S.edge v j).bind fun m => S.label m := by
  rw [Scaffold.viewAt_child]
  cases S.edge v j with
  | none => rfl
  | some m => exact S.viewAt_labelOf 1 m

theorem readSeconds_eq (S : Scaffold s (Tuple s A)) (v : Nat) (j : Fin s) :
    readSeconds (S.viewAt 2 v) j = labelCompOpt S (S.edge v j) j := by
  rw [readSeconds, child_labelOf]
  cases S.edge v j with
  | none => rfl
  | some m => rfl

/-! ### Resolving the three port words -/

theorem resolve_nil (S : Scaffold s (Tuple s A)) (t new : Nat) :
    S.resolve t new (.path []) = some t := rfl

theorem resolve_one (S : Scaffold s (Tuple s A)) (t new : Nat) (j : Fin s) :
    S.resolve t new (.path [j]) = S.edge t j :=
  S.follow_single t j

theorem resolve_two (S : Scaffold s (Tuple s A)) (t new : Nat) (j : Fin s) :
    S.resolve t new (.path [j, j]) = (S.edge t j).bind (fun m => S.edge m j) := by
  show S.follow t [j, j] = _
  cases he : S.edge t j with
  | none => rw [S.follow_cons_none t j [j] he]; rfl
  | some m => rw [S.follow_cons_some t j [j] m he, S.follow_single m j]; rfl

/-! ### Decoding is unchanged below the new node -/

theorem decode_append_below (S : Scaffold s (Tuple s A)) (g : Tuple s A)
    (tgt : Fin s → Target s) (j : Fin s) (u : Nat) (hu : u ≤ S.top) :
    decode (S.append g tgt) j u = decode S j u := by
  apply decode_congr
  · intro w hw
    exact S.append_label_old g tgt (by omega)
  · intro w hw
    exact S.append_edge_old g tgt (by omega) j

theorem decodeOpt_append_below (S : Scaffold s (Tuple s A)) (g : Tuple s A)
    (tgt : Fin s → Target s) (j : Fin s) (o : Option Nat)
    (ho : ∀ m, o = some m → m ≤ S.top) :
    decodeOpt (S.append g tgt) j o = decodeOpt S j o := by
  cases ho' : o with
  | none => rfl
  | some m =>
    simp only [decodeOpt_some]
    exact decode_append_below S g tgt j m (ho m ho')

/-! ### The simulation invariant -/

/-- The invariant relating a machine configuration to a compiled scaffold.

`clean` is the invariant the informal argument omits; it is what makes a pop of
an empty stack decode to the empty stack rather than resurrecting a stale
symbol reachable through a leftover port. -/
structure Sim (c : Config Q A s) (q : Q) (S : Scaffold s (Tuple s A)) : Prop where
  ctrl : c.ctrl = q
  stacks : ∀ j, decode S j S.top = c.stacks j
  backward : S.Backward
  clean : CleanEmpty S
  aboveLabel : ∀ v, S.top < v → S.label v = none
  aboveEdge : ∀ v, S.top < v → ∀ i, S.edge v i = none

theorem sim_init (M : StackMachine Sym Q A s) :
    Sim M.initConfig (compile M).init (Scaffold.base s (Tuple s A)) where
  ctrl := rfl
  stacks := fun j => by
    simp only [StackMachine.initConfig]
    exact base_decode j _
  backward := Scaffold.base_backward s (Tuple s A)
  clean := base_cleanEmpty
  aboveLabel := fun _ _ => rfl
  aboveEdge := fun _ _ _ => rfl

/-- The tops the compiled transition reads are exactly the machine's tops. -/
theorem sim_tops {c : Config Q A s} {q : Q} {S : Scaffold s (Tuple s A)}
    (h : Sim c q S) : readTops (S.viewAt 2 S.top) = fun j => (c.stacks j).head? := by
  funext j
  rw [readTops_eq, ← decode_head, h.stacks j]

/-! ### The new node, in terms of the scaffold rather than the unfolding

`newLabel` and `newTarget` restate the compiled transition's output using
`labelComp` and `Scaffold.edge`.  `compileDelta_label` and
`compileDelta_target` show they agree with what the transition actually emits
from the unfolding, so the rest of the proof never mentions `Unfold` again. -/

/-- The label the compiler puts on the new node. -/
def newLabel (S : Scaffold s (Tuple s A)) (ops : Fin s → StackOp A) : Tuple s A :=
  fun j =>
    match ops j with
    | .push a => some a
    | .keep => labelComp S S.top j
    | .pop => labelCompOpt S (S.edge S.top j) j

/-- The port words the compiler names for the new node. -/
def newTarget (ops : Fin s → StackOp A) : Fin s → Target s :=
  fun j =>
    match ops j with
    | .push _ => .path []
    | .keep => .path [j]
    | .pop => .path [j, j]

theorem newTarget_ne_self (ops : Fin s → StackOp A) (j : Fin s) :
    newTarget ops j ≠ .self := by
  simp only [newTarget]
  cases ops j <;> simp

theorem compileDelta_label (M : StackMachine Sym Q A s) (q : Q) (x : Sym)
    (S : Scaffold s (Tuple s A)) :
    (compileDelta M q x (S.viewAt 2 S.top)).2.1 =
      newLabel S (M.step q x (readTops (S.viewAt 2 S.top))).2 := by
  funext j
  simp only [compileDelta, newLabel]
  cases (M.step q x (readTops (S.viewAt 2 S.top))).2 j with
  | push a => rfl
  | keep => exact readTops_eq S S.top j
  | pop => exact readSeconds_eq S S.top j

theorem compileDelta_target (M : StackMachine Sym Q A s) (q : Q) (x : Sym)
    (S : Scaffold s (Tuple s A)) :
    (compileDelta M q x (S.viewAt 2 S.top)).2.2 =
      newTarget (M.step q x (readTops (S.viewAt 2 S.top))).2 := by
  funext j
  simp only [compileDelta, newTarget]

/-! ### The new node decodes to the machine's action -/

/-- The heart of the bridge: after appending the compiler's node, stack `j`
decodes to exactly the result of stack `j`'s action.

The `pop`-of-an-empty-stack case is where `CleanEmpty` is consumed. -/
theorem decode_new (S : Scaffold s (Tuple s A)) (hb : S.Backward) (hc : CleanEmpty S)
    (stacks : Fin s → List A) (hst : ∀ j, decode S j S.top = stacks j)
    (ops : Fin s → StackOp A) (j : Fin s) :
    decode (S.append (newLabel S ops) (newTarget ops)) j (S.top + 1)
      = (ops j).apply (stacks j) := by
  have hb' : (S.append (newLabel S ops) (newTarget ops)).Backward :=
    S.append_backward hb _ _ (newTarget_ne_self ops)
  have hlab : labelComp (S.append (newLabel S ops) (newTarget ops)) (S.top + 1) j
      = newLabel S ops j := by
    simp only [labelComp, Scaffold.append_label_new]
    rfl
  have hedge : (S.append (newLabel S ops) (newTarget ops)).edge (S.top + 1) j
      = S.resolve S.top (S.top + 1) (newTarget ops j) := by
    rw [Scaffold.append_edge_new]
  have hbelow : ∀ o : Option Nat, (∀ m, o = some m → m ≤ S.top) →
      decodeOpt (S.append (newLabel S ops) (newTarget ops)) j o = decodeOpt S j o :=
    fun o ho => decodeOpt_append_below S _ _ j o ho
  -- edges out of the old top land strictly below it
  have htop_lt : ∀ m, S.edge S.top j = some m → m < S.top := fun m hm => hb _ _ _ hm
  cases hop : ops j with
  | push a =>
    have hl : labelComp (S.append (newLabel S ops) (newTarget ops)) (S.top + 1) j
        = some a := by rw [hlab]; simp only [newLabel, hop]
    have ht : (S.append (newLabel S ops) (newTarget ops)).edge (S.top + 1) j
        = some S.top := by rw [hedge]; simp only [newTarget, hop]; rfl
    rw [decode_cons _ hb' j (S.top + 1) a hl, ht, decodeOpt_some,
      decode_append_below S _ _ j S.top (Nat.le_refl _), hst j]
    rfl
  | keep =>
    have ht : (S.append (newLabel S ops) (newTarget ops)).edge (S.top + 1) j
        = S.edge S.top j := by
      rw [hedge]; simp only [newTarget, hop]; exact resolve_one S S.top (S.top + 1) j
    cases hc0 : labelComp S S.top j with
    | none =>
      have hl : labelComp (S.append (newLabel S ops) (newTarget ops)) (S.top + 1) j
          = none := by rw [hlab]; simp only [newLabel, hop]; exact hc0
      have hzero : stacks j = [] := by rw [← hst j]; exact decode_empty S j S.top hc0
      rw [decode_empty _ j _ hl, hzero]
      rfl
    | some a =>
      have hl : labelComp (S.append (newLabel S ops) (newTarget ops)) (S.top + 1) j
          = some a := by rw [hlab]; simp only [newLabel, hop]; exact hc0
      rw [decode_cons _ hb' j (S.top + 1) a hl, ht,
        hbelow (S.edge S.top j) (fun m hm => Nat.le_of_lt (htop_lt m hm)),
        ← decode_cons S hb j S.top a hc0, hst j]
      rfl
  | pop =>
    have ht : (S.append (newLabel S ops) (newTarget ops)).edge (S.top + 1) j
        = (S.edge S.top j).bind (fun m => S.edge m j) := by
      rw [hedge]; simp only [newTarget, hop]; exact resolve_two S S.top (S.top + 1) j
    cases hc0 : labelComp S S.top j with
    | none =>
      -- empty stack: CleanEmpty forces the new label component to be none
      have hclean : labelCompOpt S (S.edge S.top j) j = none := hc S.top j hc0
      have hl : labelComp (S.append (newLabel S ops) (newTarget ops)) (S.top + 1) j
          = none := by rw [hlab]; simp only [newLabel, hop]; exact hclean
      have hempty : stacks j = [] := by rw [← hst j]; exact decode_empty S j S.top hc0
      rw [decode_empty _ j _ hl, hempty]
      rfl
    | some a =>
      have hsplit : stacks j = a :: decodeOpt S j (S.edge S.top j) := by
        rw [← hst j]; exact decode_cons S hb j S.top a hc0
      cases he : S.edge S.top j with
      | none =>
        have hl : labelComp (S.append (newLabel S ops) (newTarget ops)) (S.top + 1) j
            = none := by rw [hlab]; simp only [newLabel, hop, he]; rfl
        rw [decode_empty _ j _ hl, hsplit, he]
        rfl
      | some m =>
        have hmlt : m < S.top := htop_lt m he
        cases hcm : labelComp S m j with
        | none =>
          have hl : labelComp (S.append (newLabel S ops) (newTarget ops)) (S.top + 1) j
              = none := by rw [hlab]; simp only [newLabel, hop, he]; exact hcm
          rw [decode_empty _ j _ hl, hsplit, he, decodeOpt_some,
            decode_empty S j m hcm]
          rfl
        | some b =>
          have hl : labelComp (S.append (newLabel S ops) (newTarget ops)) (S.top + 1) j
              = some b := by rw [hlab]; simp only [newLabel, hop, he]; exact hcm
          have ht2 : (S.append (newLabel S ops) (newTarget ops)).edge (S.top + 1) j
              = S.edge m j := by rw [ht, he]; rfl
          have hbound : ∀ m', S.edge m j = some m' → m' ≤ S.top := by
            intro m' hm'
            have : m' < m := hb _ _ _ hm'
            omega
          rw [decode_cons _ hb' j (S.top + 1) b hl, ht2, hbelow (S.edge m j) hbound,
            hsplit, he, decodeOpt_some, decode_cons S hb j m b hcm]
          rfl


/-! ### Auxiliary rewriting of labels below the new node -/

theorem labelComp_append_old (S : Scaffold s (Tuple s A)) (g : Tuple s A)
    (tgt : Fin s → Target s) {v : Nat} (hv : v ≠ S.top + 1) (j : Fin s) :
    labelComp (S.append g tgt) v j = labelComp S v j := by
  simp only [labelComp, S.append_label_old g tgt hv]

theorem labelCompOpt_append_below (S : Scaffold s (Tuple s A)) (g : Tuple s A)
    (tgt : Fin s → Target s) (j : Fin s) (o : Option Nat)
    (ho : ∀ m, o = some m → m ≤ S.top) :
    labelCompOpt (S.append g tgt) o j = labelCompOpt S o j := by
  cases ho' : o with
  | none => rfl
  | some m =>
    simp only [labelCompOpt_some]
    exact labelComp_append_old S g tgt (by have := ho m ho'; omega) j

theorem newTarget_isPath (ops : Fin s → StackOp A) (j : Fin s) :
    ∃ p, newTarget ops j = Target.path p := by
  simp only [newTarget]
  cases ops j
  · exact ⟨[], rfl⟩
  · exact ⟨[j], rfl⟩
  · exact ⟨[j, j], rfl⟩

/-- Every target the compiler names lands at or below the old top.  This is
`Scaffold.follow_le` applied to the three port words. -/
theorem resolve_newTarget_le (S : Scaffold s (Tuple s A)) (hb : S.Backward)
    (ops : Fin s → StackOp A) (j : Fin s) (m : Nat)
    (h : S.resolve S.top (S.top + 1) (newTarget ops j) = some m) : m ≤ S.top := by
  obtain ⟨p, hp⟩ := newTarget_isPath ops j
  rw [hp] at h
  exact S.follow_le hb p S.top m h

/-! ### `CleanEmpty` is preserved -/

theorem cleanEmpty_append (S : Scaffold s (Tuple s A)) (hb : S.Backward)
    (hc : CleanEmpty S) (hae : ∀ v, S.top < v → ∀ i, S.edge v i = none)
    (ops : Fin s → StackOp A) :
    CleanEmpty (S.append (newLabel S ops) (newTarget ops)) := by
  intro v j hv
  by_cases hvt : v = S.top + 1
  · -- the new node
    subst hvt
    have hedge : (S.append (newLabel S ops) (newTarget ops)).edge (S.top + 1) j
        = S.resolve S.top (S.top + 1) (newTarget ops j) := by rw [Scaffold.append_edge_new]
    have hlow : labelCompOpt (S.append (newLabel S ops) (newTarget ops))
        (S.resolve S.top (S.top + 1) (newTarget ops j)) j
        = labelCompOpt S (S.resolve S.top (S.top + 1) (newTarget ops j)) j :=
      labelCompOpt_append_below S _ _ j _ (resolve_newTarget_le S hb ops j)
    rw [hedge, hlow]
    have hnl : newLabel S ops j = none := by
      rw [← hv]
      simp only [labelComp, Scaffold.append_label_new]
      rfl
    simp only [newLabel] at hnl
    cases hop : ops j with
    | push a =>
      rw [hop] at hnl
      exact absurd hnl (by simp)
    | keep =>
      rw [hop] at hnl
      have := hc S.top j hnl
      simp only [newTarget, hop]
      rw [resolve_one S S.top (S.top + 1) j]
      exact this
    | pop =>
      rw [hop] at hnl
      simp only [newTarget, hop]
      rw [resolve_two S S.top (S.top + 1) j]
      cases he : S.edge S.top j with
      | none => rfl
      | some m =>
        rw [he] at hnl
        simp only [labelCompOpt_some] at hnl
        exact hc m j hnl
  · -- an old node, or a node above the new top
    rw [S.append_edge_old (newLabel S ops) (newTarget ops) hvt j]
    rw [labelComp_append_old S _ _ hvt j] at hv
    by_cases hle : v ≤ S.top
    · have hb' : ∀ m, S.edge v j = some m → m ≤ S.top := by
        intro m hm
        have := hb v j m hm
        omega
      rw [labelCompOpt_append_below S _ _ j _ hb']
      exact hc v j hv
    · have : S.edge v j = none := hae v (by omega) j
      rw [this]
      rfl

/-! ### One step of the simulation -/

theorem compile_step_eq (M : StackMachine Sym Q A s) (q : Q)
    (S : Scaffold s (Tuple s A)) (x : Sym) :
    (compile M).step (q, S) x =
      ((M.step q x (readTops (S.viewAt 2 S.top))).1,
        S.append (newLabel S (M.step q x (readTops (S.viewAt 2 S.top))).2)
          (newTarget (M.step q x (readTops (S.viewAt 2 S.top))).2)) := by
  show ((compileDelta M q x (S.viewAt 2 S.top)).1,
      S.append (compileDelta M q x (S.viewAt 2 S.top)).2.1
        (compileDelta M q x (S.viewAt 2 S.top)).2.2) = _
  rw [compileDelta_label, compileDelta_target]
  rfl

theorem sim_step (M : StackMachine Sym Q A s) {c : Config Q A s}
    {p : Q × Scaffold s (Tuple s A)} (h : Sim c p.1 p.2) (x : Sym) :
    Sim (M.stepConfig c x) ((compile M).step p x).1 ((compile M).step p x).2 := by
  obtain ⟨q, S⟩ := p
  have htops : readTops (S.viewAt 2 S.top) = fun j => (c.stacks j).head? := sim_tops h
  have hstep : M.step c.ctrl x (fun j => (c.stacks j).head?)
      = M.step q x (readTops (S.viewAt 2 S.top)) := by
    rw [h.ctrl, htops]
  rw [compile_step_eq M q S x]
  refine
    { ctrl := ?_, stacks := ?_, backward := ?_, clean := ?_
      aboveLabel := ?_, aboveEdge := ?_ }
  · show (M.step c.ctrl x (fun j => (c.stacks j).head?)).1 = _
    rw [hstep]
  · intro j
    show decode _ j (S.top + 1) = ((M.step c.ctrl x (fun j => (c.stacks j).head?)).2 j).apply _
    rw [hstep]
    exact decode_new S h.backward h.clean c.stacks h.stacks _ j
  · exact S.append_backward h.backward _ _ (newTarget_ne_self _)
  · exact cleanEmpty_append S h.backward h.clean h.aboveEdge _
  · intro v hvv
    have hv' : S.top + 1 < v := hvv
    rw [S.append_label_old _ _ (by omega : v ≠ S.top + 1)]
    exact h.aboveLabel v (by show S.top < v; omega)
  · intro v hvv i
    have hv' : S.top + 1 < v := hvv
    rw [S.append_edge_old _ _ (by omega : v ≠ S.top + 1) i]
    exact h.aboveEdge v (by show S.top < v; omega) i

/-! ### The theorem: simulation holds after every prefix -/

theorem sim_run (M : StackMachine Sym Q A s) :
    ∀ (w : List Sym) (c : Config Q A s) (p : Q × Scaffold s (Tuple s A)),
      Sim c p.1 p.2 →
      Sim (w.foldl M.stepConfig c) (w.foldl (compile M).step p).1
        (w.foldl (compile M).step p).2 := by
  intro w
  induction w with
  | nil => intro c p h; exact h
  | cons x w ih =>
    intro c p h
    simp only [List.foldl_cons]
    exact ih (M.stepConfig c x) ((compile M).step p x) (sim_step M h x)

/-- **Stack compiler correctness.**  For every input word, the compiled LMR
scaffolding automaton and the source machine have the same control state, and
every logical stack decoded from the scaffold's top equals the source stack. -/
theorem compile_correct (M : StackMachine Sym Q A s) (w : List Sym) :
    Sim (M.run w) ((compile M).run w).1 ((compile M).run w).2 :=
  sim_run M w M.initConfig (M.init, Scaffold.base s (Tuple s A)) (sim_init M)

theorem compile_ctrl (M : StackMachine Sym Q A s) (w : List Sym) :
    (M.run w).ctrl = ((compile M).run w).1 :=
  (compile_correct M w).ctrl

theorem compile_stacks (M : StackMachine Sym Q A s) (w : List Sym) (j : Fin s) :
    decode ((compile M).run w).2 j ((compile M).run w).2.top = (M.run w).stacks j :=
  (compile_correct M w).stacks j

/-- The compiled automaton decides exactly the source machine's language. -/
theorem compile_decides (M : StackMachine Sym Q A s) (w : List Sym) :
    (compile M).decides w = M.decides w := by
  show (compile M).accept ((compile M).run w).1 = M.accept (M.run w).ctrl
  rw [compile_ctrl M w]
  rfl

end FPRD