lean4-htt/tests/lean/run/string_termination.lean
Markus Himmel 8fe260de55
feat: termination arguments for String.ValidPos and String.Slice.Pos (#10933)
This PR adds the basic infrastructure to perform termination proofs
about `String.ValidPos` and `String.Slice.Pos`.

We choose approach where the intended way to do termination arguments is
to argue about the position itself rather than some projection of it
like `remainingBytes`.

The types `String.ValidPos` and `String.Slice.Pos` are equipped with a
`WellFoundedRelation` instance given by the greater-than relation. This
means that if a function takes a position `p` and performs a recursive
call on `q`, then the decreasing obligation will be `p < q`. This works
well in the common case where `q` is `p.next h`, in which case the goal
`p < p.next h` is solved by the simplifier.

For stepping through a string backwards, we introduce a type synonym
with a `WellFoundedRelation` instance given by the less-than relation.
This means that if a function takes a position `p` and performs a
recursive call on `q` and specifies `termination_by p.down`, then the
decreasing obligation will be `q < p`. This works well in the case where
`q` is `p.prev h`, in which case the goal `p.prev h < p` is solved by
the simplifier.

For termination arguments invoving multiple strings, the lower-level
primitive `p.remainingBytes` (landing in `Nat`) is also available.

In a future PR, we will additionally provide the necessary typeclasses
instances to register `String.ValidPos` and `String.Slice.Pos` with
`grind` to make complex termination arguments more convenient in user
code.
2025-10-27 10:05:44 +00:00

42 lines
1.1 KiB
Text

module
-- Test that termination proofs for stepping through a string with `next` and `prev` works.
def isConsonant (i : String.ValidPos str) : Bool :=
match i.get! with
| 'a' | 'e' | 'i' | 'o' | 'u' => false
| 'y' =>
if h : i = str.startValidPos then true
else !isConsonant (i.prev h)
| _ => true
termination_by i.down
def measure₁ (word : String) : Nat :=
let rec aux (pos : String.ValidPos word) (inVowel : Bool) (count : Nat) : Nat :=
match h : pos.next? with
| some next =>
if !isConsonant pos then
aux next true count
else if inVowel then
aux next false (count + 1)
else
aux next false count
| none => count
termination_by pos
aux word.startValidPos false 0
def measure₂ (word : String) : Nat :=
let rec aux (pos : String.ValidPos word) (inVowel : Bool) (count : Nat) : Nat :=
if h : pos = word.endValidPos then count
else
let next := pos.next h
if !isConsonant pos then
aux next true count
else if inVowel then
aux next false (count + 1)
else
aux next false count
termination_by pos
aux word.startValidPos false 0