Skip to content

Commit bf3c38c

Browse files
proof(idris2): tighten Ephapax.Parse.Util to %default total (#91)
## Summary Third file in the `%default partial` → `%default total` chain for `ephapax/idris2/src/Ephapax/`. SExpr.idr (#89) was first; Stream.idr (#90) second. Two combinators in `Util.idr` recurse unboundedly on a `Stream` rather than on a structural recursor: - `many : Parser a -> Parser (List a)` — repeated application until first failure - `sepBy.sepTail` — alternating separator + element Replace both with Nat-fueled siblings (`manyFuel` / `sepTailFuel`) where fuel `= integerToNat (cast (s.len - s.index))` — the count of remaining tokens in the input stream at entry. Each recursive call decrements fuel by 1, and the inner parser is assumed to consume ≥1 token per success (the only well-formed usage). `sepBy1` already delegates to `sepBy` and inherits totality for free. ## Soundness On a *consuming* inner parser (every real combinator client), the fueled form is observationally identical to the partial original: the stream is monotonically advancing, so the parser exhausts the stream before the fuel exhausts (in the common case it's tight equality — fuel = tokens-remaining). On a degenerate non-consuming inner parser, the partial original loops forever; the fueled form returns a truncated list. Strictly safer. No `assert_total` / `assert_smaller` / `believe_me` escapes. ## Verification ``` $ IDRIS2_PREFIX=…/idris2/0.8.0 idris2 --check Ephapax/Parse/Util.idr 4/4: Building Ephapax.Parse.Util (Ephapax/Parse/Util.idr) $ idris2 --check Ephapax/Parse/Parser.idr # downstream 6/6: Building Ephapax.Parse.Parser (Ephapax/Parse/Parser.idr) ``` ## Refs - `#124` (proof-debt audit epic) - `#134` (ephapax totality sub-issue) - Companion: PR `#89` (SExpr — file 1), PR `#90` (Stream — file 2) ## Test plan - [x] `idris2 --check Ephapax/Parse/Util.idr` builds green under `%default total` - [x] Downstream `Ephapax.Parse.Parser` still builds - [ ] CI green - [ ] No `assert_*` / `believe_me` introduced (verified by inspection — see diff) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c42226b commit bf3c38c

1 file changed

Lines changed: 18 additions & 11 deletions

File tree

idris2/src/Ephapax/Parse/Util.idr

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Data.List
44
import Ephapax.Parse.Lexer
55
import Ephapax.Parse.Stream
66

7-
%default partial
7+
%default total
88

99
public export
1010
data ParseError = PE String (Maybe Pos)
@@ -56,12 +56,17 @@ expectIdent stream =
5656
public export
5757
many : Parser a -> Parser (List a)
5858
many p stream =
59-
case p stream of
60-
Left _ => Right ([], stream)
61-
Right (v, rest) =>
62-
case many p rest of
63-
Left err => Left err
64-
Right (vs, rest2) => Right (v :: vs, rest2)
59+
manyFuel (integerToNat (cast (stream.len - stream.index))) p stream
60+
where
61+
manyFuel : Nat -> Parser a -> Parser (List a)
62+
manyFuel Z _ s = Right ([], s)
63+
manyFuel (S k) p s =
64+
case p s of
65+
Left _ => Right ([], s)
66+
Right (v, rest) =>
67+
case manyFuel k p rest of
68+
Left err => Left err
69+
Right (vs, rest2) => Right (v :: vs, rest2)
6570

6671
public export
6772
optional : Parser a -> Parser (Maybe a)
@@ -156,16 +161,18 @@ sepBy : Parser a -> Parser sep -> Parser (List a)
156161
sepBy p sep stream =
157162
case p stream of
158163
Left _ => Right ([], stream)
159-
Right (v, rest) => sepTail [v] rest
164+
Right (v, rest) =>
165+
sepTailFuel (integerToNat (cast (rest.len - rest.index))) [v] rest
160166
where
161-
sepTail : List a -> Parser (List a)
162-
sepTail acc stream2 =
167+
sepTailFuel : Nat -> List a -> Parser (List a)
168+
sepTailFuel Z acc stream2 = Right (reverse acc, stream2)
169+
sepTailFuel (S k) acc stream2 =
163170
case sep stream2 of
164171
Left _ => Right (reverse acc, stream2)
165172
Right (_, rest) =>
166173
case p rest of
167174
Left err => Left err
168-
Right (v, rest2) => sepTail (v :: acc) rest2
175+
Right (v, rest2) => sepTailFuel k (v :: acc) rest2
169176

170177
public export
171178
sepBy1 : Parser a -> Parser sep -> Parser (List a)

0 commit comments

Comments
 (0)