|
| 1 | +(* |
| 2 | +SPDX-License-Identifier: MPL-2.0 |
| 3 | +Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
| 4 | +
|
| 5 | +WokeGrammarParser.v — Coq 8.18.0 port of the universal parser metatheory in |
| 6 | +WokeGrammar.lean (Lean↔Coq parity for the expression-grammar proofs). A faithful |
| 7 | +fuel-based precedence-climbing parser, with termination (totality), the concrete |
| 8 | +precedence/associativity/rejection battery, and the universal completeness |
| 9 | +round-trip `completeness_rp` via the key lemma `prefix_rt`. |
| 10 | +*) |
| 11 | + |
| 12 | +Require Import List. |
| 13 | +Import ListNotations. |
| 14 | +Require Import PeanoNat. |
| 15 | +Require Import Lia. |
| 16 | + |
| 17 | +Inductive BinOp := Or | And | EqB | Ne | Lt | Gt | Le | Ge | Add | Sub | Mul | DivB | ModB. |
| 18 | +Inductive UnOp := Neg | Notb. |
| 19 | +Inductive Tok := |
| 20 | + | TNum (n:nat) | TIdent (s:nat) | TLpar | TRpar |
| 21 | + | TOr | TAnd | TEq | TNe | TLt | TGt | TLe | TGe |
| 22 | + | TPlus | TMinus | TStar | TSlash | TPercent | TNot. |
| 23 | +Inductive Expr := |
| 24 | + | ELit (n:nat) | EVar (s:nat) | EUn (u:UnOp) (e:Expr) | EBin (op:BinOp) (l r:Expr). |
| 25 | + |
| 26 | +Definition binLevel (op:BinOp) : nat := |
| 27 | + match op with |
| 28 | + | Or => 1 | And => 2 | EqB | Ne => 3 | Lt | Gt | Le | Ge => 4 |
| 29 | + | Add | Sub => 5 | Mul | DivB | ModB => 6 end. |
| 30 | +Definition unaryLevel := 7. |
| 31 | + |
| 32 | +Definition tokBin (t:Tok) : option BinOp := |
| 33 | + match t with |
| 34 | + | TOr => Some Or | TAnd => Some And | TEq => Some EqB | TNe => Some Ne |
| 35 | + | TLt => Some Lt | TGt => Some Gt | TLe => Some Le | TGe => Some Ge |
| 36 | + | TPlus => Some Add | TMinus => Some Sub | TStar => Some Mul |
| 37 | + | TSlash => Some DivB | TPercent => Some ModB | _ => None end. |
| 38 | + |
| 39 | +(* Precedence-climbing parser, fuel-structural (hence total — T3.3). *) |
| 40 | +Fixpoint parsePrec (fuel minl:nat) (ts:list Tok) {struct fuel} : option (Expr * list Tok) := |
| 41 | + match fuel with |
| 42 | + | 0 => None |
| 43 | + | S f => |
| 44 | + match parsePrefix f ts with |
| 45 | + | None => None |
| 46 | + | Some (lhs, rest) => parseInfix f minl lhs rest |
| 47 | + end |
| 48 | + end |
| 49 | +with parseInfix (fuel minl:nat) (lhs:Expr) (ts:list Tok) {struct fuel} : option (Expr * list Tok) := |
| 50 | + match fuel with |
| 51 | + | 0 => None |
| 52 | + | S f => |
| 53 | + match ts with |
| 54 | + | [] => Some (lhs, []) |
| 55 | + | t :: rest => |
| 56 | + match tokBin t with |
| 57 | + | None => Some (lhs, t :: rest) |
| 58 | + | Some op => |
| 59 | + if Nat.ltb minl (binLevel op) then |
| 60 | + match parsePrec f (binLevel op) rest with |
| 61 | + | None => None |
| 62 | + | Some (rhs, rest') => parseInfix f minl (EBin op lhs rhs) rest' |
| 63 | + end |
| 64 | + else Some (lhs, t :: rest) |
| 65 | + end |
| 66 | + end |
| 67 | + end |
| 68 | +with parsePrefix (fuel:nat) (ts:list Tok) {struct fuel} : option (Expr * list Tok) := |
| 69 | + match fuel with |
| 70 | + | 0 => None |
| 71 | + | S f => |
| 72 | + match ts with |
| 73 | + | TNum n :: rest => Some (ELit n, rest) |
| 74 | + | TIdent s :: rest => Some (EVar s, rest) |
| 75 | + | TLpar :: rest => |
| 76 | + match parsePrec f 0 rest with |
| 77 | + | Some (e, TRpar :: rest') => Some (e, rest') |
| 78 | + | _ => None |
| 79 | + end |
| 80 | + | TMinus :: rest => |
| 81 | + match parsePrec f unaryLevel rest with |
| 82 | + | None => None |
| 83 | + | Some (e, rest') => Some (EUn Neg e, rest') |
| 84 | + end |
| 85 | + | TNot :: rest => |
| 86 | + match parsePrec f unaryLevel rest with |
| 87 | + | None => None |
| 88 | + | Some (e, rest') => Some (EUn Notb e, rest') |
| 89 | + end |
| 90 | + | _ => None |
| 91 | + end |
| 92 | + end. |
| 93 | + |
| 94 | +Definition budget (ts:list Tok) := 2 * length ts + 2. |
| 95 | +Definition parseAll (ts:list Tok) : option Expr := |
| 96 | + match parsePrec (budget ts) 0 ts with |
| 97 | + | Some (e, []) => Some e |
| 98 | + | _ => None |
| 99 | + end. |
| 100 | + |
| 101 | +(* ---- Precedence (T6.1) and associativity (T6.3): concrete checks ---- *) |
| 102 | +Example prec_mul_over_add : |
| 103 | + parseAll [TNum 1; TPlus; TNum 2; TStar; TNum 3] |
| 104 | + = Some (EBin Add (ELit 1) (EBin Mul (ELit 2) (ELit 3))). |
| 105 | +Proof. vm_compute. reflexivity. Qed. |
| 106 | + |
| 107 | +Example prec_add_after_mul : |
| 108 | + parseAll [TNum 1; TStar; TNum 2; TPlus; TNum 3] |
| 109 | + = Some (EBin Add (EBin Mul (ELit 1) (ELit 2)) (ELit 3)). |
| 110 | +Proof. vm_compute. reflexivity. Qed. |
| 111 | + |
| 112 | +Example assoc_sub_left : |
| 113 | + parseAll [TNum 1; TMinus; TNum 2; TMinus; TNum 3] |
| 114 | + = Some (EBin Sub (EBin Sub (ELit 1) (ELit 2)) (ELit 3)). |
| 115 | +Proof. vm_compute. reflexivity. Qed. |
| 116 | + |
| 117 | +Example grouping_overrides : |
| 118 | + parseAll [TLpar; TNum 1; TPlus; TNum 2; TRpar; TStar; TNum 3] |
| 119 | + = Some (EBin Mul (EBin Add (ELit 1) (ELit 2)) (ELit 3)). |
| 120 | +Proof. vm_compute. reflexivity. Qed. |
| 121 | + |
| 122 | +Example full_ladder : |
| 123 | + parseAll [TNum 1; TOr; TNum 2; TAnd; TNum 3; TEq; TNum 4; TLt; |
| 124 | + TNum 5; TPlus; TNum 6; TStar; TNum 7] |
| 125 | + = Some (EBin Or (ELit 1) |
| 126 | + (EBin And (ELit 2) |
| 127 | + (EBin EqB (ELit 3) |
| 128 | + (EBin Lt (ELit 4) |
| 129 | + (EBin Add (ELit 5) (EBin Mul (ELit 6) (ELit 7))))))). |
| 130 | +Proof. vm_compute. reflexivity. Qed. |
| 131 | + |
| 132 | +Example unary_tighter : |
| 133 | + parseAll [TMinus; TNum 2; TStar; TNum 3] |
| 134 | + = Some (EBin Mul (EUn Neg (ELit 2)) (ELit 3)). |
| 135 | +Proof. vm_compute. reflexivity. Qed. |
| 136 | + |
| 137 | +(* ---- Rejection battery (T3.1, no over-acceptance) ---- *) |
| 138 | +Example rej_lead_op : parseAll [TPlus; TNum 1] = None. |
| 139 | +Proof. vm_compute. reflexivity. Qed. |
| 140 | +Example rej_two_atoms : parseAll [TNum 1; TNum 2] = None. |
| 141 | +Proof. vm_compute. reflexivity. Qed. |
| 142 | +Example rej_trail_op : parseAll [TNum 1; TPlus] = None. |
| 143 | +Proof. vm_compute. reflexivity. Qed. |
| 144 | +Example rej_unclosed : parseAll [TLpar; TNum 1] = None. |
| 145 | +Proof. vm_compute. reflexivity. Qed. |
| 146 | +Example rej_extra_rpar : parseAll [TLpar; TNum 1; TRpar; TRpar] = None. |
| 147 | +Proof. vm_compute. reflexivity. Qed. |
| 148 | +Example rej_empty : parseAll [] = None. |
| 149 | +Proof. vm_compute. reflexivity. Qed. |
| 150 | + |
| 151 | +(* ===== Universal metatheory: completeness + unambiguity ===== *) |
| 152 | + |
| 153 | +Definition opTok (op:BinOp) : Tok := |
| 154 | + match op with |
| 155 | + | Or => TOr | And => TAnd | EqB => TEq | Ne => TNe | Lt => TLt | Gt => TGt |
| 156 | + | Le => TLe | Ge => TGe | Add => TPlus | Sub => TMinus | Mul => TStar |
| 157 | + | DivB => TSlash | ModB => TPercent end. |
| 158 | + |
| 159 | +Lemma tokBin_opTok : forall op, tokBin (opTok op) = Some op. |
| 160 | +Proof. destruct op; reflexivity. Qed. |
| 161 | + |
| 162 | +Lemma binLevel_pos : forall op, Nat.ltb 0 (binLevel op) = true. |
| 163 | +Proof. destruct op; reflexivity. Qed. |
| 164 | + |
| 165 | +Fixpoint rp (e:Expr) : list Tok := |
| 166 | + match e with |
| 167 | + | ELit n => [TNum n] |
| 168 | + | EVar s => [TIdent s] |
| 169 | + | EUn Neg e => [TLpar; TMinus] ++ rp e ++ [TRpar] |
| 170 | + | EUn Notb e => [TLpar; TNot] ++ rp e ++ [TRpar] |
| 171 | + | EBin op l r => [TLpar] ++ rp l ++ [opTok op] ++ rp r ++ [TRpar] |
| 172 | + end. |
| 173 | + |
| 174 | +Lemma rp_len_pos : forall e, 1 <= length (rp e). |
| 175 | +Proof. intros e; destruct e; try destruct u; simpl; lia. Qed. |
| 176 | + |
| 177 | +(* One-step unfold equations (definitional). *) |
| 178 | +Lemma u_lpar : forall f rest, parsePrefix (S f) (TLpar :: rest) = |
| 179 | + match parsePrec f 0 rest with Some (e, TRpar :: r) => Some (e, r) | _ => None end. |
| 180 | +Proof. reflexivity. Qed. |
| 181 | +Lemma u_minus : forall f rest, parsePrefix (S f) (TMinus :: rest) = |
| 182 | + match parsePrec f unaryLevel rest with None => None | Some (e, r) => Some (EUn Neg e, r) end. |
| 183 | +Proof. reflexivity. Qed. |
| 184 | +Lemma u_not : forall f rest, parsePrefix (S f) (TNot :: rest) = |
| 185 | + match parsePrec f unaryLevel rest with None => None | Some (e, r) => Some (EUn Notb e, r) end. |
| 186 | +Proof. reflexivity. Qed. |
| 187 | +Lemma u_prec : forall f minl ts, parsePrec (S f) minl ts = |
| 188 | + match parsePrefix f ts with None => None | Some (lhs, rest) => parseInfix f minl lhs rest end. |
| 189 | +Proof. reflexivity. Qed. |
| 190 | +Lemma u_infix_nil : forall f minl lhs, parseInfix (S f) minl lhs [] = Some (lhs, []). |
| 191 | +Proof. reflexivity. Qed. |
| 192 | + |
| 193 | +(* Key lemma: the prefix parser recovers any fully-parenthesised expression. *) |
| 194 | +Lemma prefix_rt : forall e rest F, |
| 195 | + 2 * length (rp e) <= F -> parsePrefix F (rp e ++ rest) = Some (e, rest). |
| 196 | +Proof. |
| 197 | + induction e as [n|s|u e IH|op l IHl r IHr]; intros rest F HF. |
| 198 | + - (* ELit *) destruct F; simpl in HF; [lia|]. reflexivity. |
| 199 | + - (* EVar *) destruct F; simpl in HF; [lia|]. reflexivity. |
| 200 | + - (* EUn *) |
| 201 | + pose proof (rp_len_pos e) as Hpe. |
| 202 | + assert (Hlen: length (rp (EUn u e)) = 3 + length (rp e)) |
| 203 | + by (destruct u; cbn [rp]; rewrite !app_length; cbn [length]; lia). |
| 204 | + rewrite Hlen in HF. |
| 205 | + do 5 (destruct F as [|F]; [lia|]). |
| 206 | + destruct u. |
| 207 | + + (* Neg *) |
| 208 | + assert (Hnorm: rp (EUn Neg e) ++ rest = TLpar :: TMinus :: (rp e ++ TRpar :: rest)). |
| 209 | + { cbn [rp]. rewrite <- ?app_assoc. reflexivity. } |
| 210 | + rewrite Hnorm. |
| 211 | + assert (H1: parsePrefix (S F) (rp e ++ TRpar :: rest) = Some (e, TRpar :: rest)) |
| 212 | + by (apply IH; lia). |
| 213 | + assert (H3: parsePrec (S (S F)) unaryLevel (rp e ++ TRpar :: rest) = Some (e, TRpar :: rest)) |
| 214 | + by (rewrite u_prec, H1; reflexivity). |
| 215 | + assert (H4: parsePrefix (S (S (S F))) (TMinus :: (rp e ++ TRpar :: rest)) |
| 216 | + = Some (EUn Neg e, TRpar :: rest)) |
| 217 | + by (rewrite u_minus, H3; reflexivity). |
| 218 | + assert (H6: parsePrec (S (S (S (S F)))) 0 (TMinus :: (rp e ++ TRpar :: rest)) |
| 219 | + = Some (EUn Neg e, TRpar :: rest)) |
| 220 | + by (rewrite u_prec, H4; reflexivity). |
| 221 | + rewrite u_lpar, H6. reflexivity. |
| 222 | + + (* Notb *) |
| 223 | + assert (Hnorm: rp (EUn Notb e) ++ rest = TLpar :: TNot :: (rp e ++ TRpar :: rest)). |
| 224 | + { cbn [rp]. rewrite <- ?app_assoc. reflexivity. } |
| 225 | + rewrite Hnorm. |
| 226 | + assert (H1: parsePrefix (S F) (rp e ++ TRpar :: rest) = Some (e, TRpar :: rest)) |
| 227 | + by (apply IH; lia). |
| 228 | + assert (H3: parsePrec (S (S F)) unaryLevel (rp e ++ TRpar :: rest) = Some (e, TRpar :: rest)) |
| 229 | + by (rewrite u_prec, H1; reflexivity). |
| 230 | + assert (H4: parsePrefix (S (S (S F))) (TNot :: (rp e ++ TRpar :: rest)) |
| 231 | + = Some (EUn Notb e, TRpar :: rest)) |
| 232 | + by (rewrite u_not, H3; reflexivity). |
| 233 | + assert (H6: parsePrec (S (S (S (S F)))) 0 (TNot :: (rp e ++ TRpar :: rest)) |
| 234 | + = Some (EUn Notb e, TRpar :: rest)) |
| 235 | + by (rewrite u_prec, H4; reflexivity). |
| 236 | + rewrite u_lpar, H6. reflexivity. |
| 237 | + - (* EBin *) |
| 238 | + pose proof (rp_len_pos l) as Hpl. pose proof (rp_len_pos r) as Hpr. |
| 239 | + assert (Hlen: length (rp (EBin op l r)) = 3 + length (rp l) + length (rp r)) |
| 240 | + by (cbn [rp]; rewrite !app_length; cbn [length]; lia). |
| 241 | + rewrite Hlen in HF. |
| 242 | + do 5 (destruct F as [|F]; [lia|]). |
| 243 | + assert (Hnorm: rp (EBin op l r) ++ rest |
| 244 | + = TLpar :: (rp l ++ opTok op :: (rp r ++ TRpar :: rest))). |
| 245 | + { cbn [rp]. rewrite <- ?app_assoc. reflexivity. } |
| 246 | + rewrite Hnorm. |
| 247 | + assert (HR: parsePrefix (S F) (rp r ++ TRpar :: rest) = Some (r, TRpar :: rest)) |
| 248 | + by (apply IHr; lia). |
| 249 | + assert (HRp: parsePrec (S (S F)) (binLevel op) (rp r ++ TRpar :: rest) |
| 250 | + = Some (r, TRpar :: rest)) |
| 251 | + by (rewrite u_prec, HR; reflexivity). |
| 252 | + assert (HL: parsePrefix (S (S (S F))) (rp l ++ opTok op :: (rp r ++ TRpar :: rest)) |
| 253 | + = Some (l, opTok op :: (rp r ++ TRpar :: rest))) |
| 254 | + by (apply IHl; lia). |
| 255 | + assert (HLi: parseInfix (S (S (S F))) 0 l (opTok op :: (rp r ++ TRpar :: rest)) |
| 256 | + = Some (EBin op l r, TRpar :: rest)). |
| 257 | + { cbn [parseInfix]. rewrite tokBin_opTok. simpl Nat.ltb. rewrite binLevel_pos. |
| 258 | + rewrite HRp. reflexivity. } |
| 259 | + assert (HLp: parsePrec (S (S (S (S F)))) 0 (rp l ++ opTok op :: (rp r ++ TRpar :: rest)) |
| 260 | + = Some (EBin op l r, TRpar :: rest)) |
| 261 | + by (rewrite u_prec, HL; exact HLi). |
| 262 | + rewrite u_lpar, HLp. reflexivity. |
| 263 | +Qed. |
| 264 | + |
| 265 | +Lemma parse_closed : forall e F, 2 * length (rp e) + 2 <= F -> parsePrec F 0 (rp e) = Some (e, []). |
| 266 | +Proof. |
| 267 | + intros e F HF. pose proof (rp_len_pos e). |
| 268 | + destruct F as [|[|F]]; try lia. |
| 269 | + assert (Hp: parsePrefix (S F) (rp e) = Some (e, [])). |
| 270 | + { pose proof (prefix_rt e [] (S F)) as Hpr. rewrite app_nil_r in Hpr. apply Hpr. lia. } |
| 271 | + rewrite u_prec, Hp. apply u_infix_nil. |
| 272 | +Qed. |
| 273 | + |
| 274 | +(* T3.2 completeness (universal): every expression's concrete syntax parses back. *) |
| 275 | +Theorem completeness_rp : forall e, parseAll (rp e) = Some e. |
| 276 | +Proof. |
| 277 | + intros e. unfold parseAll. rewrite (parse_closed e (budget (rp e))). |
| 278 | + - reflexivity. |
| 279 | + - unfold budget. lia. |
| 280 | +Qed. |
| 281 | + |
| 282 | +(* T2.2 unambiguity: the parser is a function. *) |
| 283 | +Theorem parse_deterministic : forall ts e1 e2, |
| 284 | + parseAll ts = Some e1 -> parseAll ts = Some e2 -> e1 = e2. |
| 285 | +Proof. intros ts e1 e2 H1 H2. rewrite H1 in H2. injection H2; auto. Qed. |
| 286 | + |
| 287 | +(* Distinct expressions have distinct concrete forms (via the parser). *) |
| 288 | +Theorem rp_injective : forall a b, rp a = rp b -> a = b. |
| 289 | +Proof. |
| 290 | + intros a b H. |
| 291 | + assert (He: parseAll (rp a) = parseAll (rp b)) by (rewrite H; reflexivity). |
| 292 | + rewrite !completeness_rp in He. injection He; auto. |
| 293 | +Qed. |
0 commit comments