-- File: lean/PathSpec.lean -- Path Canonicalization Specification -- Agents migrate a C path library to Rust (Vec segment lists). -- Path = List String where each element is a non-empty segment (no "/" markers). namespace PathSpec abbrev Path := List String -- Internal: reversed accumulator, head = most recently added segment def normalizeAux (acc : Path) : Path → Path | [] => acc.reverse | seg :: rest => if seg == "." then normalizeAux acc rest else if seg == ".." then match acc with | [] => normalizeAux [] rest -- clamp at root | _ :: acc' => normalizeAux acc' rest else normalizeAux (seg :: acc) rest -- Remove "." segments; resolve ".." clamped at root def normalize (p : Path) : Path := normalizeAux [] p -- Join two segment lists: normalize the concatenation def join (base rel : Path) : Path := normalize (base ++ rel) -- Path depth = segment count def depth (p : Path) : Nat := p.length -- A path is in normal form if it contains no "." or ".." segments def isNormal (p : Path) : Bool := p.all (fun s => s != "." && s != "..") -- Helper: elements of normalizeAux output are never "." or ".." theorem normalizeAux_no_special (acc p : Path) (hacc : ∀ s ∈ acc, s ≠ "." ∧ s ≠ "..") : ∀ s ∈ normalizeAux acc p, s ≠ "." ∧ s ≠ ".." := by induction p generalizing acc with | nil => intro s hs simp only [normalizeAux] at hs exact hacc s (List.mem_reverse.mp hs) | cons seg rest ih => intro s hs cases h1 : (seg == ".") · -- (seg == ".") = false cases h2 : (seg == "..") · -- (seg == "..") = false — seg is normal, pushed onto acc simp only [normalizeAux, h1, h2] at hs apply ih (seg :: acc) _ s hs intro t ht rcases List.mem_cons.mp ht with rfl | ht' · exact ⟨fun heq => by simp [heq] at h1, fun heq => by simp [heq] at h2⟩ · exact hacc t ht' · -- (seg == "..") = true — pop acc cases acc with | nil => simp only [normalizeAux, h1, h2, ↓reduceIte] at hs exact ih [] (fun _ hm => by simp at hm) s hs | cons x acc' => simp only [normalizeAux, h1, h2, ↓reduceIte] at hs exact ih acc' (fun t ht => hacc t (List.mem_cons.mpr (Or.inr ht))) s hs · -- (seg == ".") = true — skip simp only [normalizeAux, h1, ↓reduceIte] at hs exact ih acc hacc s hs -- Helper: normalizeAux on a path with no special segments equals acc.reverse ++ p theorem normalizeAux_on_normal (acc p : Path) (hp : ∀ s ∈ p, s ≠ "." ∧ s ≠ "..") : normalizeAux acc p = acc.reverse ++ p := by induction p generalizing acc with | nil => simp [normalizeAux] | cons seg rest ih => obtain ⟨hne1, hne2⟩ := hp seg (List.mem_cons.mpr (Or.inl rfl)) have h1 : (seg == ".") = false := beq_eq_false_iff_ne.mpr hne1 have h2 : (seg == "..") = false := beq_eq_false_iff_ne.mpr hne2 simp only [normalizeAux, h1, h2] rw [ih (seg :: acc) (fun s hs => hp s (List.mem_cons.mpr (Or.inr hs)))] simp [List.reverse_cons, List.append_assoc] -- Normalization is idempotent theorem normalize_idempotent (p : Path) : normalize (normalize p) = normalize p := by unfold normalize rw [normalizeAux_on_normal [] (normalizeAux [] p) (normalizeAux_no_special [] p (fun _ h => by simp at h))] simp -- DRT checks (native_decide: ground equalities) example : normalize ["usr", "local", "..", "bin"] = ["usr", "bin"] := by native_decide example : normalize [".", "a", ".", "b"] = ["a", "b"] := by native_decide example : normalize ["..", "a"] = ["a"] := by native_decide example : normalize ["a", "b", "..", ".."] = [] := by native_decide example : normalize ["a", ".", "b", "..", "c"] = ["a", "c"] := by native_decide example : join ["usr", "local"] ["bin"] = ["usr", "local", "bin"] := by native_decide example : join ["a", "b"] [".."] = ["a"] := by native_decide example : depth ["a", "b", "c"] = 3 := by native_decide example : depth [] = 0 := by native_decide end PathSpec