import Mathlib
import Langlib.Grammars.Unrestricted.Definition

/-! # Indexed Grammar Definitions

This file defines indexed grammars (Aho, 1968), their derivation relation, and the induced
language predicate.

An indexed grammar extends context-free grammars by attaching a *stack of flags* (index symbols)
to each nonterminal in a sentential form. Productions can push flags onto, or pop flags from,
these stacks.

## Main declarations

- `IndexedGrammar` — the grammar structure
- `IndexedGrammar.SententialForm` — sentential forms with stacked nonterminals
- `IndexedGrammar.Transforms` — one-step derivation
- `IndexedGrammar.Derives` — reflexive–transitive closure of `Transforms`
- `IndexedGrammar.Language` — the generated language

## References

* A. V. Aho, "Indexed grammars — an extension of context-free grammars", *JACM* 15(4), 1968.
-/

open Relation List

/-- A symbol on the right-hand side of an indexed production.
    A nonterminal can optionally have a flag pushed onto its inherited stack. -/
inductive IRhsSymbol (T : Type) (N : Type) (F : Type) where
  | terminal : T → IRhsSymbol T N F
  | nonterminal : N → Option F → IRhsSymbol T N F
deriving DecidableEq

/-- A production rule of an indexed grammar.

- `lhs` : the nonterminal on the left-hand side.
- `consume` : if `some f`, the rule can only fire when `f` is on top of the nonterminal's
  stack, and pops it; if `none`, no flag is consumed.
- `rhs` : the right-hand side; each nonterminal inherits the (possibly modified) stack of
  the left-hand side, with an optional additional flag push. -/
structure IRule (T : Type) (N : Type) (F : Type) where
  lhs : N
  consume : Option F
  rhs : List (IRhsSymbol T N F)

/-- An indexed grammar that generates words over the alphabet `T`.

- `nt` : type of nonterminals
- `flag` : type of index symbols (flags)
- `initial` : the start nonterminal
- `rules` : the list of production rules -/
structure IndexedGrammar (T : Type) where
  nt : Type
  flag : Type
  initial : nt
  rules : List (IRule T nt flag)

variable {T : Type}

namespace IndexedGrammar

/-- A symbol in a sentential form of an indexed grammar: either a terminal or a nonterminal
    equipped with its current flag stack. -/
inductive ISym (g : IndexedGrammar T) where
  | terminal : T → ISym g
  | indexed : g.nt → List g.flag → ISym g

/-- Expand the right-hand side of a rule, distributing the given stack `σ` to every
    nonterminal (and pushing any specified flag on top). -/
def expandRhs (g : IndexedGrammar T) (rhs : List (IRhsSymbol T g.nt g.flag))
    (σ : List g.flag) : List (ISym g) :=
  rhs.map fun s =>
    match s with
    | IRhsSymbol.terminal t => ISym.terminal t
    | IRhsSymbol.nonterminal n none => ISym.indexed n σ
    | IRhsSymbol.nonterminal n (some f) => ISym.indexed n (f :: σ)

/-- One step of indexed-grammar transformation. -/
def Transforms (g : IndexedGrammar T) (w₁ w₂ : List (ISym g)) : Prop :=
  ∃ (r : IRule T g.nt g.flag) (u v : List (ISym g)) (σ : List g.flag),
    r ∈ g.rules ∧
    (match r.consume with
     | none   => w₁ = u ++ [ISym.indexed r.lhs σ] ++ v
     | some f => w₁ = u ++ [ISym.indexed r.lhs (f :: σ)] ++ v) ∧
    w₂ = u ++ expandRhs g r.rhs σ ++ v

/-- Any number of steps of indexed-grammar transformation. -/
def Derives (g : IndexedGrammar T) : List (ISym g) → List (ISym g) → Prop :=
  ReflTransGen (Transforms g)

/-- A word (list of terminals) is generated by the grammar if it can be derived from the
    initial nonterminal (with empty stack). -/
def Generates (g : IndexedGrammar T) (w : List T) : Prop :=
  Derives g [ISym.indexed g.initial []] (w.map ISym.terminal)

/-- The language generated by the grammar. -/
def Language (g : IndexedGrammar T) : _root_.Language T :=
  setOf (Generates g)

-- -----------------------------------------------------------------------
-- Basic derivation combinators (mirroring the project's CF/CS toolbox)
-- -----------------------------------------------------------------------

theorem deri_self (g : IndexedGrammar T) (w : List (ISym g)) :
    Derives g w w :=
  ReflTransGen.refl

theorem deri_of_tran {g : IndexedGrammar T} {w₁ w₂ : List (ISym g)}
    (h : Transforms g w₁ w₂) : Derives g w₁ w₂ :=
  ReflTransGen.single h

theorem deri_of_deri_deri {g : IndexedGrammar T} {w₁ w₂ w₃ : List (ISym g)}
    (h₁ : Derives g w₁ w₂) (h₂ : Derives g w₂ w₃) : Derives g w₁ w₃ :=
  h₁.trans h₂

theorem deri_of_deri_tran {g : IndexedGrammar T} {w₁ w₂ w₃ : List (ISym g)}
    (h₁ : Derives g w₁ w₂) (h₂ : Transforms g w₂ w₃) : Derives g w₁ w₃ :=
  h₁.tail h₂

theorem deri_of_tran_deri {g : IndexedGrammar T} {w₁ w₂ w₃ : List (ISym g)}
    (h₁ : Transforms g w₁ w₂) (h₂ : Derives g w₂ w₃) : Derives g w₁ w₃ :=
  h₂.head h₁

theorem deri_with_prefix {g : IndexedGrammar T} (p : List (ISym g))
    {w₁ w₂ : List (ISym g)} (h : Derives g w₁ w₂) :
    Derives g (p ++ w₁) (p ++ w₂) := by
  induction h with
  | refl => exact ReflTransGen.refl
  | tail _ ht ih =>
    apply deri_of_deri_tran ih
    obtain ⟨r, u, v, σ, hr, hlhs, hrhs⟩ := ht
    refine ⟨r, p ++ u, v, σ, hr, ?_, by simp_all [List.append_assoc]⟩
    revert hlhs; cases r.consume <;> intro hlhs <;> simp_all [List.append_assoc]

theorem deri_with_suffix {g : IndexedGrammar T} (s : List (ISym g))
    {w₁ w₂ : List (ISym g)} (h : Derives g w₁ w₂) :
    Derives g (w₁ ++ s) (w₂ ++ s) := by
  induction h with
  | refl => exact ReflTransGen.refl
  | tail _ ht ih =>
    apply deri_of_deri_tran ih
    obtain ⟨r, u, v, σ, hr, hlhs, hrhs⟩ := ht
    refine ⟨r, u, v ++ s, σ, hr, ?_, by simp_all [List.append_assoc]⟩
    revert hlhs; cases r.consume <;> intro hlhs <;> simp_all [List.append_assoc]

end IndexedGrammar
