/- Copyright (c) 2025 Harmonic. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -/ import Mathlib import Langlib.Classes.DeterministicContextFree.Definition import Langlib.Utilities.ClosurePredicates /-! # DCF Closure Under Intersection with a Regular Language This file proves that the intersection of a deterministic context-free language with a regular language is again deterministic context-free. ## Strategy Given a DPDA `M` over `(Q, T, S)` and a DFA `D` over `(T, σ)`, we build a **product DPDA** over `(Q × σ, T, S)` that simulates both machines in parallel: - On reading input symbol `a` with stack top `Z`: the DPDA component transitions via `M.transition q a Z` and the DFA component transitions via `D.step s a`. - On an ε-transition with stack top `Z`: only the DPDA component moves (via `M.epsilon_transition q Z`); the DFA state stays the same. - Acceptance: the product state `(q, s)` is accepting iff `q ∈ M.final_states` and `s ∈ D.accept`. ## Main declarations - `DPDA.productDFA` : the product DPDA×DFA construction - `DPDA.productDFA_correct` : the product accepts `M.acceptsByFinalState ⊓ D.accepts` - `DCF_inter_regular` : DCFLs are closed under intersection with regular languages -/ open PDA noncomputable section variable {Q T S σ : Type} [Fintype Q] [Fintype T] [Fintype S] [Fintype σ] namespace DPDA /-- The **product DPDA×DFA**: runs a DPDA and a DFA in parallel. -/ def productDFA (M : DPDA Q T S) (D : DFA T σ) : DPDA (Q × σ) T S where initial_state := (M.initial_state, D.start) start_symbol := M.start_symbol final_states := {p | p.1 ∈ M.final_states ∧ p.2 ∈ D.accept} transition := fun ⟨q, s⟩ a Z => match M.transition q a Z with | some (q', γ) => some ((q', D.step s a), γ) | none => none epsilon_transition := fun ⟨q, s⟩ Z => match M.epsilon_transition q Z with | some (q', γ) => some ((q', s), γ) | none => none no_mixed := by intro ⟨q, s⟩ Z hε a simp only at hε ⊢ have : M.epsilon_transition q Z ≠ none := by intro h; simp [h] at hε have := M.no_mixed q Z this a simp [this] /- ----------------------------------------------------------------------- One-step projection lemmas ----------------------------------------------------------------------- One step in the product projects to one step in the DPDA. -/ theorem productDFA_step_projects (M : DPDA Q T S) (D : DFA T σ) {c₁ c₂ : @PDA.conf (Q × σ) T S _ _ _ (M.productDFA D).toPDA} (h : @PDA.Reaches₁ (Q × σ) T S _ _ _ (M.productDFA D).toPDA c₁ c₂) : @PDA.Reaches₁ Q T S _ _ _ M.toPDA ⟨c₁.state.1, c₁.input, c₁.stack⟩ ⟨c₂.state.1, c₂.input, c₂.stack⟩ := by rcases c₁ with ⟨ ⟨ q₁, s₁ ⟩, w₁, γ₁ ⟩ ; rcases c₂ with ⟨ ⟨ q₂, s₂ ⟩, w₂, γ₂ ⟩ ; simp_all +decide [ Reaches₁ ]; cases w₁ <;> cases γ₁ <;> simp_all +decide [ step ]; · obtain ⟨ β, hβ, rfl, rfl ⟩ := h; cases hβ' : M.epsilon_transition q₁ ‹_› <;> simp_all +decide [ DPDA.toPDA, PDA.transition_fun' ]; · unfold DPDA.productDFA at hβ; aesop; · unfold DPDA.productDFA at hβ; aesop; · unfold DPDA.toPDA at *; simp_all +decide [ PDA.transition_fun, PDA.transition_fun' ] ; unfold DPDA.productDFA at *; cases h' : M.transition q₁ ‹_› ‹_› <;> cases h'' : M.epsilon_transition q₁ ‹_› <;> simp_all +decide [ Set.mem_singleton_iff ]; have := M.no_mixed q₁ ‹_›; aesop; /- One step in the product: the DFA state is updated by the consumed input. -/ theorem productDFA_step_dfa (M : DPDA Q T S) (D : DFA T σ) {c₁ c₂ : @PDA.conf (Q × σ) T S _ _ _ (M.productDFA D).toPDA} (h : @PDA.Reaches₁ (Q × σ) T S _ _ _ (M.productDFA D).toPDA c₁ c₂) : ∃ consumed : List T, c₁.input = consumed ++ c₂.input ∧ c₂.state.2 = consumed.foldl D.step c₁.state.2 := by rcases c₁ with ⟨ ⟨ q₁, s₁ ⟩, x₁, σ₁ ⟩ ; rcases c₂ with ⟨ ⟨ q₂, s₂ ⟩, x₂, σ₂ ⟩ ; rcases x₁ with ( _ | ⟨ a, x₁ ⟩ ) <;> rcases σ₁ with ( _ | ⟨ Z, σ₁ ⟩ ) <;> simp_all +decide [ Reaches₁ ]; · cases h; · cases h; rename_i h; obtain ⟨ β, hβ₁, hβ₂ ⟩ := h; unfold DPDA.toPDA at hβ₁; unfold DPDA.productDFA at hβ₁; cases h : M.epsilon_transition q₁ Z <;> simp_all +decide; · cases h; · cases h; · unfold DPDA.toPDA at *; unfold DPDA.productDFA at *; cases h : M.transition q₁ a Z <;> simp_all +decide [ Set.mem_setOf_eq ]; exact ⟨ [ a ], rfl, rfl ⟩; · unfold DPDA.productDFA at *; unfold DPDA.toPDA at *; cases h : M.epsilon_transition q₁ Z <;> simp_all +decide [ Set.mem_setOf_eq ] /- One step in the DPDA lifts to one step in the product. -/ theorem productDFA_step_lift (M : DPDA Q T S) (D : DFA T σ) (q : Q) (s : σ) (w : List T) (γ : List S) (q' : Q) (w' : List T) (γ' : List S) (hstep : @PDA.Reaches₁ Q T S _ _ _ M.toPDA ⟨q, w, γ⟩ ⟨q', w', γ'⟩) : ∃ s', @PDA.Reaches₁ (Q × σ) T S _ _ _ (M.productDFA D).toPDA ⟨(q, s), w, γ⟩ ⟨(q', s'), w', γ'⟩ := by unfold PDA.Reaches₁ at *; cases w <;> cases γ <;> simp_all +decide [ step ]; · unfold DPDA.toPDA at *; unfold DPDA.productDFA at *; cases h : M.epsilon_transition q ‹_› <;> simp_all +decide [ Set.mem_setOf_eq ]; grind; · simp_all +decide [ DPDA.toPDA, PDA.transition_fun, PDA.transition_fun' ]; unfold DPDA.productDFA at *; cases h : M.transition q ‹_› ‹_› <;> cases h' : M.epsilon_transition q ‹_› <;> simp_all +decide; · grind; · grind; · have := M.no_mixed q ‹_›; aesop; /- ----------------------------------------------------------------------- Multi-step projection lemmas ----------------------------------------------------------------------- Multi-step reachability in the product projects to multi-step reachability in the DPDA. -/ theorem productDFA_reaches_projects (M : DPDA Q T S) (D : DFA T σ) (q : Q) (s : σ) (w : List T) (γ : List S) (q' : Q) (s' : σ) (w' : List T) (γ' : List S) (h : @PDA.Reaches (Q × σ) T S _ _ _ (M.productDFA D).toPDA ⟨(q, s), w, γ⟩ ⟨(q', s'), w', γ'⟩) : @PDA.Reaches Q T S _ _ _ M.toPDA ⟨q, w, γ⟩ ⟨q', w', γ'⟩ := by have h_proj : ∀ {c₁ c₂ : @PDA.conf (Q × σ) T S _ _ _ (M.productDFA D).toPDA}, @PDA.Reaches₁ (Q × σ) T S _ _ _ (M.productDFA D).toPDA c₁ c₂ → @PDA.Reaches₁ Q T S _ _ _ M.toPDA ⟨c₁.state.1, c₁.input, c₁.stack⟩ ⟨c₂.state.1, c₂.input, c₂.stack⟩ := by exact?; have h_proj : ∀ {c₁ c₂ : @PDA.conf (Q × σ) T S _ _ _ (M.productDFA D).toPDA}, @PDA.Reaches (Q × σ) T S _ _ _ (M.productDFA D).toPDA c₁ c₂ → @PDA.Reaches Q T S _ _ _ M.toPDA ⟨c₁.state.1, c₁.input, c₁.stack⟩ ⟨c₂.state.1, c₂.input, c₂.stack⟩ := by intros c₁ c₂ h; induction h; aesop; exact Relation.ReflTransGen.tail ‹_› ( h_proj ‹_› ); exact h_proj h /- Multi-step reachability in the product tracks the DFA state correctly. -/ theorem productDFA_reaches_dfa_state (M : DPDA Q T S) (D : DFA T σ) (q : Q) (s : σ) (w : List T) (γ : List S) (q' : Q) (s' : σ) (w' : List T) (γ' : List S) (h : @PDA.Reaches (Q × σ) T S _ _ _ (M.productDFA D).toPDA ⟨(q, s), w, γ⟩ ⟨(q', s'), w', γ'⟩) : ∃ consumed : List T, w = consumed ++ w' ∧ s' = consumed.foldl D.step s := by obtain ⟨ n, hn ⟩ := PDA.reaches_iff_reachesIn.mp h; induction' n with n ih generalizing q s w γ q' s' w' γ'; · cases hn ; aesop; · rcases hn with ⟨ c, hc ⟩; rename_i c hc₁ hc₂; obtain ⟨ consumed₁, hconsumed₁, hconsumed₂ ⟩ := ih q s w γ c.state.1 c.state.2 c.input c.stack ( by exact? ) hc₁; obtain ⟨ consumed₂, hconsumed₃, hconsumed₄ ⟩ := productDFA_step_dfa M D hc₂; grind +splitIndPred /- Multi-step: DPDA reachability lifts to product reachability. -/ theorem productDFA_reaches_lift (M : DPDA Q T S) (D : DFA T σ) (q : Q) (s : σ) (w : List T) (γ : List S) (q' : Q) (w' : List T) (γ' : List S) (hreach : @PDA.Reaches Q T S _ _ _ M.toPDA ⟨q, w, γ⟩ ⟨q', w', γ'⟩) : ∃ s', @PDA.Reaches (Q × σ) T S _ _ _ (M.productDFA D).toPDA ⟨(q, s), w, γ⟩ ⟨(q', s'), w', γ'⟩ := by have h_ind : ∀ (c₁ c₂ : PDA.conf M.toPDA), @PDA.Reaches Q T S _ _ _ M.toPDA c₁ c₂ → ∀ s : σ, ∃ s' : σ, @PDA.Reaches (Q × σ) T S _ _ _ (M.productDFA D).toPDA ⟨(c₁.state, s), c₁.input, c₁.stack⟩ ⟨(c₂.state, s'), c₂.input, c₂.stack⟩ := by intro c₁ c₂ hreach s induction' hreach with c₁ c₂ hstep ih generalizing s; · exact ⟨ s, by rfl ⟩; · rename_i h; obtain ⟨ s', hs' ⟩ := h s; obtain ⟨ s'', hs'' ⟩ := productDFA_step_lift M D c₁.state s' c₁.input c₁.stack c₂.state c₂.input c₂.stack ih; exact ⟨ s'', hs'.trans ( Relation.ReflTransGen.single hs'' ) ⟩; exact h_ind _ _ hreach s /- The product DPDA accepts exactly the intersection of the DPDA language and the DFA language. -/ theorem productDFA_correct (M : DPDA Q T S) (D : DFA T σ) : (M.productDFA D).acceptsByFinalState = M.acceptsByFinalState ⊓ D.accepts := by ext w; constructor; · rintro ⟨ q', s', γ', h ⟩; constructor; · use q'.1; exact ⟨ s'.1, γ', productDFA_reaches_projects M D _ _ _ _ _ _ _ _ h ⟩; · obtain ⟨ consumed, hconsumed ⟩ := productDFA_reaches_dfa_state M D _ _ _ _ _ _ _ _ h; cases s' ; aesop; · intro hw; obtain ⟨q', γ', hq', hw'⟩ := hw.left; obtain ⟨s', hs'⟩ := productDFA_reaches_lift M D M.toPDA.initial_state D.start w [M.toPDA.start_symbol] q' [] hq' hw'; obtain ⟨consumed, hconsumed⟩ := productDFA_reaches_dfa_state M D M.toPDA.initial_state D.start w [M.toPDA.start_symbol] q' s' [] hq' hs'; use (q', s'); exact ⟨ ⟨ γ', by simpa [ hconsumed ] using hw.2 ⟩, _, hs' ⟩ end DPDA /-- The class of deterministic context-free languages is closed under intersection with regular languages. -/ theorem DCF_inter_regular {T : Type} [Fintype T] (L₁ : Language T) (L₂ : Language T) (h₁ : is_DCF L₁) (h₂ : L₂.IsRegular) : is_DCF (L₁ ⊓ L₂) := by obtain ⟨Q, S, hQ, hS, M, rfl⟩ := h₁ obtain ⟨σ, hσ, D, rfl⟩ := h₂ exact ⟨Q × σ, S, inferInstance, hS, M.productDFA D, M.productDFA_correct D⟩ /-- The class of deterministic context-free languages is closed under intersection with regular languages. -/ theorem DCF_closedUnderIntersectionWithRegular {T : Type} [Fintype T] : ClosedUnderIntersectionWithRegular (α := T) is_DCF := by intro L hL R hR exact DCF_inter_regular L R hL hR end