Skip to main content

oxilean_parse/parser/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5pub use crate::ast_impl::{Decl, Located, SurfaceExpr};
6pub use crate::error_impl::{ParseError, ParseErrorKind};
7pub use crate::lexer::Lexer;
8pub use crate::parser_impl::Parser;
9pub use crate::tokens::{Span, Token, TokenKind};
10
11/// Parse a single expression from source text.
12///
13/// This is the primary entry point for parsing an isolated expression.
14///
15/// # Example
16/// ```ignore
17/// let expr = parse_expr("1 + 2").expect("valid expression");
18/// ```
19#[allow(missing_docs)]
20pub fn parse_expr(src: &str) -> Result<Located<SurfaceExpr>, ParseError> {
21    let tokens = Lexer::new(src).tokenize();
22    Parser::new(tokens).parse_expr()
23}
24/// Parse a single declaration from source text.
25#[allow(missing_docs)]
26pub fn parse_decl(src: &str) -> Result<Located<Decl>, ParseError> {
27    let tokens = Lexer::new(src).tokenize();
28    Parser::new(tokens).parse_decl()
29}
30/// Parse all declarations in a source file.
31///
32/// Returns a vector of successfully parsed declarations; stops on the
33/// first parse error.
34#[allow(missing_docs)]
35pub fn parse_decls(src: &str) -> Result<Vec<Located<Decl>>, ParseError> {
36    let tokens = Lexer::new(src).tokenize();
37    let mut parser = Parser::new(tokens);
38    let mut decls = Vec::new();
39    loop {
40        match parser.parse_decl() {
41            Ok(d) => decls.push(d),
42            Err(e) if e.is_eof() => break,
43            Err(e) => return Err(e),
44        }
45    }
46    Ok(decls)
47}
48/// Check whether a source string is parseable as an expression.
49#[allow(missing_docs)]
50pub fn is_valid_expr(src: &str) -> bool {
51    parse_expr(src).is_ok()
52}
53/// Check whether a source string is parseable as a declaration.
54#[allow(missing_docs)]
55pub fn is_valid_decl(src: &str) -> bool {
56    parse_decl(src).is_ok()
57}
58/// FNV-1a 64-bit hash.
59pub(super) fn fnv1a(data: &[u8]) -> u64 {
60    let mut hash: u64 = 14_695_981_039_346_656_037;
61    for &b in data {
62        hash ^= b as u64;
63        hash = hash.wrapping_mul(1_099_511_628_211);
64    }
65    hash
66}
67/// Collect a sequence of Results, stopping at the first error.
68#[allow(missing_docs)]
69pub fn collect_results<T, E>(iter: impl IntoIterator<Item = Result<T, E>>) -> Result<Vec<T>, E> {
70    let mut out = Vec::new();
71    for item in iter {
72        out.push(item?);
73    }
74    Ok(out)
75}
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::parser::*;
80    #[test]
81    fn test_parse_expr_nat_literal() {
82        let result = parse_expr("42");
83        assert!(result.is_ok(), "Expected Ok but got {:?}", result);
84    }
85    #[test]
86    fn test_parse_expr_var() {
87        let result = parse_expr("x");
88        assert!(result.is_ok());
89    }
90    #[test]
91    fn test_parse_expr_lambda() {
92        let result = parse_expr("fun x -> x");
93        assert!(result.is_ok(), "lambda parse failed: {:?}", result);
94    }
95    #[test]
96    fn test_parse_expr_empty_fails() {
97        let result = parse_expr("");
98        let _ = result;
99    }
100    #[test]
101    fn test_is_valid_expr_true() {
102        assert!(is_valid_expr("1"));
103    }
104    #[test]
105    fn test_is_valid_expr_false() {
106        assert!(!is_valid_expr("fun"));
107    }
108    #[test]
109    fn test_parse_decl_def() {
110        let result = parse_decl("def x : Nat := 0");
111        assert!(result.is_ok(), "def parse failed: {:?}", result);
112    }
113    #[test]
114    fn test_parse_decls_multiple() {
115        let result = parse_decls("def a : Nat := 0");
116        let _ = result;
117    }
118    #[test]
119    fn test_token_stream_from_src() {
120        let ts = TokenStream::from_src("x y z");
121        assert!(!ts.is_empty());
122    }
123    #[test]
124    fn test_token_stream_peek() {
125        let ts = TokenStream::from_src("42");
126        assert!(ts.peek().is_some());
127    }
128    #[test]
129    fn test_token_stream_next() {
130        let mut ts = TokenStream::from_src("a b");
131        let first = ts.next();
132        assert!(first.is_some());
133    }
134    #[test]
135    fn test_token_stream_remaining() {
136        let ts = TokenStream::from_src("a b c");
137        let total = ts.total_len();
138        assert!(total >= 3);
139    }
140    #[test]
141    fn test_token_stream_reset() {
142        let mut ts = TokenStream::from_src("a b");
143        ts.next();
144        ts.reset();
145        assert_eq!(ts.pos, 0);
146    }
147    #[test]
148    fn test_token_stream_collect_remaining() {
149        let ts = TokenStream::from_src("x");
150        let rem = ts.collect_remaining();
151        assert!(!rem.is_empty());
152    }
153    #[test]
154    fn test_parse_stats_default() {
155        let s = ParseStats::new();
156        assert_eq!(s.files_parsed, 0);
157        assert!(s.is_clean());
158    }
159    #[test]
160    fn test_parse_stats_avg_decls() {
161        let s = ParseStats {
162            files_parsed: 2,
163            decls_parsed: 10,
164            ..ParseStats::default()
165        };
166        assert_eq!(s.avg_decls_per_file(), 5.0);
167    }
168    #[test]
169    fn test_parse_stats_avg_decls_zero_files() {
170        let s = ParseStats::new();
171        assert_eq!(s.avg_decls_per_file(), 0.0);
172    }
173    #[test]
174    fn test_parse_stats_error_rate() {
175        let s = ParseStats {
176            decls_parsed: 10,
177            errors_total: 2,
178            ..ParseStats::default()
179        };
180        assert!((s.error_rate() - 0.2).abs() < 1e-10);
181    }
182    #[test]
183    fn test_parse_stats_display() {
184        let s = ParseStats {
185            files_parsed: 3,
186            decls_parsed: 5,
187            errors_total: 0,
188            ..ParseStats::default()
189        };
190        let t = format!("{}", s);
191        assert!(t.contains("files: 3"));
192    }
193    #[test]
194    fn test_parse_cache_key_from_src() {
195        let k1 = ParseCacheKey::from_src("hello");
196        let k2 = ParseCacheKey::from_src("hello");
197        assert_eq!(k1, k2);
198    }
199    #[test]
200    fn test_parse_cache_key_different() {
201        let k1 = ParseCacheKey::from_src("a");
202        let k2 = ParseCacheKey::from_src("b");
203        assert_ne!(k1, k2);
204    }
205    #[test]
206    fn test_parse_quality_rate_clean() {
207        assert_eq!(ParseQuality::rate(0, 0), ParseQuality::Clean);
208    }
209    #[test]
210    fn test_parse_quality_rate_failed() {
211        assert_eq!(ParseQuality::rate(1, 0), ParseQuality::Failed);
212    }
213    #[test]
214    fn test_parse_quality_rate_warnings() {
215        assert_eq!(ParseQuality::rate(0, 2), ParseQuality::WithWarnings);
216    }
217    #[test]
218    fn test_parse_quality_is_usable() {
219        assert!(ParseQuality::Clean.is_usable());
220        assert!(ParseQuality::WithWarnings.is_usable());
221        assert!(!ParseQuality::Failed.is_usable());
222    }
223    #[test]
224    fn test_parse_quality_display() {
225        assert_eq!(format!("{}", ParseQuality::Clean), "clean");
226        assert_eq!(format!("{}", ParseQuality::Failed), "failed");
227    }
228    #[test]
229    fn test_parse_batch_add() {
230        let mut b = ParseBatch::new();
231        b.add("foo.ox", "def x : Nat := 0");
232        assert_eq!(b.len(), 1);
233    }
234    #[test]
235    fn test_parse_batch_execute() {
236        let mut b = ParseBatch::new();
237        b.add("a.ox", "def x : Nat := 0");
238        b.add("b.ox", "def y : Nat := 1");
239        let session = b.execute();
240        assert_eq!(session.file_count(), 2);
241    }
242    #[test]
243    fn test_parse_session_total_decls() {
244        let mut session = ParseSession::new();
245        session.parse_file("f.ox", "def x : Nat := 0");
246        assert!(session.total_decls() >= 1);
247    }
248    #[test]
249    fn test_parse_session_all_ok() {
250        let session = ParseSession::new();
251        assert!(session.all_ok());
252    }
253    #[test]
254    fn test_parse_error_summary_clean() {
255        let session = ParseSession::new();
256        let summary = ParseErrorSummary::from_session(&session);
257        assert!(summary.is_clean());
258    }
259    #[test]
260    fn test_collect_results_ok() {
261        let items: Vec<Result<i32, &str>> = vec![Ok(1), Ok(2), Ok(3)];
262        let r = collect_results(items);
263        assert_eq!(r.expect("test operation should succeed"), vec![1, 2, 3]);
264    }
265    #[test]
266    fn test_collect_results_err() {
267        let items: Vec<Result<i32, &str>> = vec![Ok(1), Err("oops"), Ok(3)];
268        let r = collect_results(items);
269        assert_eq!(r.unwrap_err(), "oops");
270    }
271    #[test]
272    fn test_fnv1a_deterministic() {
273        let h1 = fnv1a(b"hello world");
274        let h2 = fnv1a(b"hello world");
275        assert_eq!(h1, h2);
276    }
277    #[test]
278    fn test_parse_file_result_decl_count() {
279        let mut session = ParseSession::new();
280        session.parse_file("g.ox", "def a : Nat := 0\ndef b : Nat := 0");
281        let r = &session.results[0];
282        assert!(r.decl_count() >= 1);
283    }
284}
285#[cfg(test)]
286mod extra_tests {
287    use super::*;
288    use crate::parser::*;
289    #[test]
290    fn test_parse_mode_strict() {
291        let m = ParseMode::strict();
292        assert!(!m.allow_tactics);
293        assert!(!m.recover_on_error);
294    }
295    #[test]
296    fn test_parse_mode_lenient() {
297        let m = ParseMode::lenient();
298        assert!(m.recover_on_error);
299        assert!(m.lenient);
300    }
301    #[test]
302    fn test_parse_mode_display() {
303        let m = ParseMode::default();
304        let s = format!("{}", m);
305        assert!(s.contains("ParseMode"));
306    }
307    #[test]
308    fn test_source_map_basic() {
309        let src = "line1\nline2\nline3";
310        let sm = SourceMap::new(src);
311        assert_eq!(sm.num_lines(), 3);
312    }
313    #[test]
314    fn test_source_map_offset_to_line_col_start() {
315        let src = "abc\ndef";
316        let sm = SourceMap::new(src);
317        let (line, col) = sm.offset_to_line_col(0);
318        assert_eq!(line, 1);
319        assert_eq!(col, 1);
320    }
321    #[test]
322    fn test_source_map_offset_to_line_col_second_line() {
323        let src = "abc\ndef";
324        let sm = SourceMap::new(src);
325        let (line, col) = sm.offset_to_line_col(4);
326        assert_eq!(line, 2);
327        assert_eq!(col, 1);
328    }
329    #[test]
330    fn test_source_map_source_len() {
331        let src = "hello";
332        let sm = SourceMap::new(src);
333        assert_eq!(sm.source_len(), 5);
334    }
335    #[test]
336    fn test_token_kind_set_empty() {
337        let s = TokenKindSet::empty();
338        assert!(s.is_empty());
339    }
340    #[test]
341    fn test_token_kind_set_insert_contains() {
342        let mut s = TokenKindSet::empty();
343        s.insert(3);
344        assert!(s.contains(3));
345        assert!(!s.contains(4));
346    }
347    #[test]
348    fn test_token_kind_set_union() {
349        let mut a = TokenKindSet::empty();
350        let mut b = TokenKindSet::empty();
351        a.insert(1);
352        b.insert(2);
353        let u = a.union(b);
354        assert!(u.contains(1));
355        assert!(u.contains(2));
356    }
357    #[test]
358    fn test_token_kind_set_intersect() {
359        let mut a = TokenKindSet::empty();
360        let mut b = TokenKindSet::empty();
361        a.insert(1);
362        a.insert(2);
363        b.insert(2);
364        b.insert(3);
365        let i = a.intersect(b);
366        assert!(i.contains(2));
367        assert!(!i.contains(1));
368        assert!(!i.contains(3));
369    }
370    #[test]
371    fn test_token_kind_set_out_of_range() {
372        let mut s = TokenKindSet::empty();
373        s.insert(64);
374        assert!(!s.contains(64));
375    }
376}
377#[cfg(test)]
378mod extra_parser_tests {
379    use super::*;
380    use crate::parser::*;
381    #[test]
382    fn test_parse_pipeline_new() {
383        let p = ParsePipeline::new();
384        assert_eq!(p.stage_count(), 0);
385    }
386    #[test]
387    fn test_parse_pipeline_add_stage() {
388        let mut p = ParsePipeline::new();
389        p.add_stage("lex");
390        p.add_stage("parse");
391        assert_eq!(p.stage_count(), 2);
392    }
393    #[test]
394    fn test_parse_pipeline_execute() {
395        let p = ParsePipeline::new();
396        let ts = p.execute("x y z");
397        assert!(!ts.is_empty());
398    }
399    #[test]
400    fn test_parse_pipeline_display() {
401        let mut p = ParsePipeline::new();
402        p.add_stage("lex");
403        let s = format!("{}", p);
404        assert!(s.contains("1 stages"));
405    }
406    #[test]
407    fn test_annotation_info() {
408        let span = Span::new(0, 1, 1, 1);
409        let a = ParseAnnotation::info(span, "test note");
410        assert_eq!(a.kind, AnnotationKind::Info);
411        assert_eq!(a.message, "test note");
412    }
413    #[test]
414    fn test_annotation_deprecated() {
415        let span = Span::new(0, 1, 1, 1);
416        let a = ParseAnnotation::deprecated(span, "use X instead");
417        assert_eq!(a.kind, AnnotationKind::Deprecated);
418    }
419    #[test]
420    fn test_annotation_display() {
421        let span = Span::new(0, 1, 1, 1);
422        let a = ParseAnnotation::info(span, "hello");
423        let s = format!("{}", a);
424        assert!(s.contains("info"));
425        assert!(s.contains("hello"));
426    }
427    #[test]
428    fn test_annotation_kind_display() {
429        assert_eq!(format!("{}", AnnotationKind::Suggestion), "suggestion");
430        assert_eq!(format!("{}", AnnotationKind::Deprecated), "deprecated");
431    }
432    #[test]
433    fn test_parse_buffer_new() {
434        let buf = ParseBuffer::new(10);
435        assert!(buf.is_empty());
436    }
437    #[test]
438    fn test_parse_buffer_push_pop() {
439        let mut buf = ParseBuffer::new(10);
440        let ts = TokenStream::from_src("x");
441        if let Some(tok) = ts.collect_remaining().into_iter().next() {
442            buf.push(tok.clone());
443            assert_eq!(buf.len(), 1);
444            let popped = buf.pop();
445            assert!(popped.is_some());
446        }
447    }
448    #[test]
449    fn test_parse_buffer_clear() {
450        let mut buf = ParseBuffer::new(10);
451        let ts = TokenStream::from_src("a b c");
452        for tok in ts.collect_remaining() {
453            buf.push(tok.clone());
454        }
455        buf.clear();
456        assert!(buf.is_empty());
457    }
458    #[test]
459    fn test_parse_buffer_front() {
460        let mut buf = ParseBuffer::new(10);
461        let ts = TokenStream::from_src("hello");
462        for tok in ts.collect_remaining() {
463            buf.push(tok.clone());
464        }
465        assert!(buf.front().is_some());
466    }
467}
468#[cfg(test)]
469mod extended_parser_tests {
470    use super::*;
471    use crate::parser::*;
472    #[test]
473    fn test_parse_config() {
474        let cfg = ParseConfig::default_config();
475        assert!(cfg.allow_holes);
476        assert!(cfg.recover_from_errors);
477        let strict = ParseConfig::strict();
478        assert!(strict.strict_mode);
479        assert!(!strict.recover_from_errors);
480    }
481    #[test]
482    fn test_parse_stats() {
483        let mut s = ParseStatsExt::new();
484        s.tokens_consumed = 100;
485        s.backtrack_count = 10;
486        s.nodes_created = 50;
487        s.error_count = 5;
488        assert!((s.efficiency() - 0.9).abs() < 1e-9);
489        assert!((s.error_rate() - 0.1).abs() < 1e-9);
490        let sum = s.summary();
491        assert!(sum.contains("tokens=100"));
492    }
493    #[test]
494    fn test_token_cursor() {
495        let mut cur = TokenCursor::new(10);
496        cur.advance();
497        cur.advance();
498        assert_eq!(cur.position, 2);
499        assert_eq!(cur.remaining(), 8);
500        cur.enter_scope();
501        assert_eq!(cur.depth, 1);
502        cur.exit_scope();
503        assert!(cur.is_at_root());
504    }
505    #[test]
506    fn test_checkpoint_stack() {
507        let mut cur = TokenCursor::new(10);
508        cur.advance();
509        cur.advance();
510        let mut stack = CheckpointStack::new();
511        let cp = ParseCheckpoint::save(&cur, 0);
512        stack.push(cp);
513        cur.advance();
514        assert_eq!(cur.position, 3);
515        let cp2 = stack.pop().expect("collection should not be empty");
516        cp2.restore(&mut cur);
517        assert_eq!(cur.position, 2);
518    }
519    #[test]
520    fn test_parse_trace() {
521        let mut trace = ParseTrace::new(100);
522        let idx = trace.enter("expr", 0);
523        trace.exit(idx, 5, true);
524        let idx2 = trace.enter("ty", 5);
525        trace.exit(idx2, 5, false);
526        assert_eq!(trace.success_count(), 1);
527        assert_eq!(trace.fail_count(), 1);
528        assert_eq!(trace.most_failing_rule(), Some("ty"));
529    }
530    #[test]
531    fn test_packrat_table() {
532        let mut table = PackratTable::new();
533        let entry = PackratEntry {
534            end_pos: 5,
535            success: true,
536            result_repr: "Nat".into(),
537        };
538        table.store(0, "ty", entry);
539        let found = table.lookup(0, "ty");
540        assert!(found.is_some());
541        assert_eq!(found.expect("test operation should succeed").end_pos, 5);
542        assert_eq!(table.size(), 1);
543    }
544    #[test]
545    fn test_recovery_log() {
546        let mut log = RecoveryLog::new();
547        log.record(10, RecoveryDecision::skip(1, "skip bad token"));
548        log.record(20, RecoveryDecision::abandon("unrecoverable"));
549        assert_eq!(log.count(), 2);
550        assert_eq!(log.abandon_count(), 1);
551    }
552    #[test]
553    fn test_parse_ambiguity() {
554        let mut amb = ParseAmbiguity::new(5, vec!["expr".into(), "ty".into()]);
555        assert!(!amb.is_resolved());
556        amb.resolve("expr");
557        assert!(amb.is_resolved());
558    }
559    #[test]
560    fn test_ambiguity_registry() {
561        let mut reg = AmbiguityRegistry::new();
562        let mut amb = ParseAmbiguity::new(0, vec!["a".into(), "b".into()]);
563        amb.resolve("a");
564        reg.report(amb);
565        reg.report(ParseAmbiguity::new(5, vec!["c".into()]));
566        assert_eq!(reg.count(), 2);
567        assert_eq!(reg.unresolved(), 1);
568    }
569    #[test]
570    fn test_fixity() {
571        let f = Fixity::InfixLeft(65);
572        assert_eq!(f.precedence(), 65);
573        assert!(f.is_infix());
574        assert!(!f.is_right_assoc());
575        let rf = Fixity::InfixRight(75);
576        assert!(rf.is_right_assoc());
577    }
578    #[test]
579    fn test_fixity_registry() {
580        let reg = FixityRegistry::new();
581        let plus = reg.lookup("+").expect("lookup should succeed");
582        assert_eq!(plus.precedence(), 65);
583        assert!(plus.is_infix());
584        let star = reg.lookup("*").expect("lookup should succeed");
585        assert!(star.precedence() > plus.precedence());
586    }
587    #[test]
588    fn test_lookahead_result() {
589        let r = LookaheadResult::Matches(3);
590        assert_ne!(r, LookaheadResult::NoMatch);
591        assert_ne!(r, LookaheadResult::Ambiguous);
592    }
593}
594/// Computes line and column from byte offset in source.
595#[allow(dead_code)]
596#[allow(missing_docs)]
597pub fn offset_to_line_col(source: &str, offset: usize) -> (usize, usize) {
598    let offset = offset.min(source.len());
599    let before = &source[..offset];
600    let line = before.chars().filter(|&c| c == '\n').count();
601    let col = before.rfind('\n').map_or(offset, |pos| offset - pos - 1);
602    (line, col)
603}
604/// Computes byte offset from line and column in source.
605#[allow(dead_code)]
606#[allow(missing_docs)]
607pub fn line_col_to_offset(source: &str, line: usize, col: usize) -> usize {
608    let mut cur_line = 0;
609    let mut offset = 0;
610    for ch in source.chars() {
611        if cur_line == line {
612            break;
613        }
614        if ch == '\n' {
615            cur_line += 1;
616        }
617        offset += ch.len_utf8();
618    }
619    (offset + col).min(source.len())
620}
621/// Checks if a string is a valid OxiLean identifier.
622#[allow(dead_code)]
623#[allow(missing_docs)]
624pub fn is_valid_identifier(s: &str) -> bool {
625    if s.is_empty() {
626        return false;
627    }
628    let mut chars = s.chars();
629    let first = chars
630        .next()
631        .expect("string is non-empty per is_empty check above");
632    if !first.is_alphabetic() && first != '_' {
633        return false;
634    }
635    chars.all(|c| c.is_alphanumeric() || c == '_' || c == '\'')
636}
637/// Checks if a string is a keyword in OxiLean.
638#[allow(dead_code)]
639#[allow(missing_docs)]
640pub fn is_keyword(s: &str) -> bool {
641    matches!(
642        s,
643        "def"
644            | "theorem"
645            | "lemma"
646            | "axiom"
647            | "import"
648            | "open"
649            | "namespace"
650            | "end"
651            | "let"
652            | "fun"
653            | "match"
654            | "with"
655            | "if"
656            | "then"
657            | "else"
658            | "forall"
659            | "exists"
660            | "have"
661            | "show"
662            | "from"
663            | "do"
664            | "return"
665            | "Type"
666            | "Prop"
667            | "Sort"
668            | "true"
669            | "false"
670            | "sorry"
671            | "by"
672            | "inductive"
673            | "structure"
674            | "class"
675            | "instance"
676            | "where"
677    )
678}
679/// Checks if a character is a valid start of an operator.
680#[allow(dead_code)]
681#[allow(missing_docs)]
682pub fn is_operator_start(c: char) -> bool {
683    matches!(
684        c,
685        '+' | '-' | '*' | '/' | '<' | '>' | '=' | '!' | '&' | '|' | '^' | '~' | '%' | '@'
686    )
687}
688/// Checks if a string is a multi-char operator.
689#[allow(dead_code)]
690#[allow(missing_docs)]
691pub fn is_multi_char_op(s: &str) -> bool {
692    matches!(
693        s,
694        "->" | "=>" | "<=" | ">=" | "!=" | "==" | "&&" | "||" | ":=" | "::"
695    )
696}
697/// Computes the nesting depth at a given position in source.
698#[allow(dead_code)]
699#[allow(missing_docs)]
700pub fn nesting_depth_at(source: &str, pos: usize) -> usize {
701    let end = (pos + 1).min(source.len());
702    let prefix = &source[..end];
703    let opens = prefix
704        .chars()
705        .filter(|&c| c == '(' || c == '[' || c == '{')
706        .count();
707    let closes = prefix
708        .chars()
709        .filter(|&c| c == ')' || c == ']' || c == '}')
710        .count();
711    opens.saturating_sub(closes)
712}
713/// Extracts the identifier at a given byte position.
714#[allow(dead_code)]
715#[allow(missing_docs)]
716pub fn ident_at(source: &str, pos: usize) -> Option<&str> {
717    let pos = pos.min(source.len());
718    let start = source[..pos]
719        .rfind(|c: char| !c.is_alphanumeric() && c != '_')
720        .map_or(0, |i| i + 1);
721    let end = source[pos..]
722        .find(|c: char| !c.is_alphanumeric() && c != '_')
723        .map_or(source.len(), |i| pos + i);
724    if start < end && is_valid_identifier(&source[start..end]) {
725        Some(&source[start..end])
726    } else {
727        None
728    }
729}
730#[cfg(test)]
731mod extended_parser_tests_2 {
732    use super::*;
733    use crate::parser::*;
734    #[test]
735    fn test_parse_fuel() {
736        let mut fuel = ParseFuel::new(10);
737        assert!(fuel.consume(5));
738        assert_eq!(fuel.remaining(), 5);
739        assert!(fuel.consume(5));
740        assert!(!fuel.has_fuel());
741        assert!(!fuel.consume(1));
742    }
743    #[test]
744    fn test_expected_set() {
745        let mut es = ExpectedSet::new();
746        es.add("'('");
747        es.add("identifier");
748        es.add("literal");
749        assert_eq!(es.count(), 3);
750        let msg = es.to_message();
751        assert!(msg.contains("expected"));
752        es.clear();
753        assert!(es.is_empty());
754    }
755    #[test]
756    fn test_expected_set_single() {
757        let mut es = ExpectedSet::new();
758        es.add("')'");
759        assert_eq!(es.to_message(), "expected ')'");
760    }
761    #[test]
762    fn test_pratt_context() {
763        let ctx = PrattContext::new(0);
764        let ctx2 = ctx.with_min_prec(65);
765        assert_eq!(ctx2.min_prec, 65);
766        assert_eq!(ctx2.depth, 1);
767        assert!(!ctx.is_too_deep());
768    }
769    #[test]
770    fn test_parse_stack() {
771        let mut stack = ParseStack::new();
772        stack.push(ParseFrame::new("expr", 0, 1));
773        stack.push(ParseFrame::new("ty", 5, 2).for_type());
774        assert_eq!(stack.depth(), 2);
775        assert_eq!(stack.current_rule(), Some("ty"));
776        assert!(stack.in_type());
777        assert!(!stack.in_pattern());
778        let rules = stack.rules_string();
779        assert!(rules.contains("expr > ty"));
780    }
781    #[test]
782    fn test_source_pos() {
783        let pos = SourcePos::new("foo.ox", 3, 10, 50);
784        assert_eq!(pos.display(), "foo.ox:4:11");
785    }
786    #[test]
787    fn test_offset_to_line_col() {
788        let src = "hello\nworld\nfoo";
789        let (line, col) = offset_to_line_col(src, 7);
790        assert_eq!(line, 1);
791        assert_eq!(col, 1);
792    }
793    #[test]
794    fn test_line_col_to_offset() {
795        let src = "hello\nworld";
796        let off = line_col_to_offset(src, 1, 0);
797        assert_eq!(off, 6);
798    }
799    #[test]
800    fn test_is_valid_identifier() {
801        assert!(is_valid_identifier("foo"));
802        assert!(is_valid_identifier("_bar123"));
803        assert!(is_valid_identifier("Nat"));
804        assert!(!is_valid_identifier(""));
805        assert!(!is_valid_identifier("123abc"));
806        assert!(!is_valid_identifier("a b"));
807    }
808    #[test]
809    fn test_is_keyword() {
810        assert!(is_keyword("def"));
811        assert!(is_keyword("theorem"));
812        assert!(is_keyword("fun"));
813        assert!(!is_keyword("foo"));
814        assert!(!is_keyword("Nat"));
815    }
816    #[test]
817    fn test_is_operator_start() {
818        assert!(is_operator_start('+'));
819        assert!(is_operator_start('<'));
820        assert!(!is_operator_start('a'));
821        assert!(!is_operator_start('_'));
822    }
823    #[test]
824    fn test_is_multi_char_op() {
825        assert!(is_multi_char_op("->"));
826        assert!(is_multi_char_op(":="));
827        assert!(is_multi_char_op(">="));
828        assert!(!is_multi_char_op("+"));
829        assert!(!is_multi_char_op("x"));
830    }
831    #[test]
832    fn test_nesting_depth() {
833        let src = "f (g (x + y))";
834        assert_eq!(nesting_depth_at(src, 8), 2);
835        assert_eq!(nesting_depth_at(src, 2), 1);
836    }
837    #[test]
838    fn test_ident_at() {
839        let src = "hello world";
840        assert_eq!(ident_at(src, 0), Some("hello"));
841        assert_eq!(ident_at(src, 6), Some("world"));
842    }
843    #[test]
844    fn test_comb_result() {
845        let r: CombResult<i32> = CombResult::Ok(42, 5);
846        assert!(r.is_ok());
847        assert!(!r.is_err());
848        assert_eq!(r.position(), 5);
849        let e: CombResult<i32> = CombResult::Err("fail".into(), 3);
850        assert!(e.is_err());
851        assert_eq!(e.position(), 3);
852    }
853}
854/// A simple parser combinator: optional.
855#[allow(dead_code)]
856#[allow(missing_docs)]
857pub fn parse_optional<T, F>(f: F, tokens: &[String], pos: usize) -> (Option<T>, usize)
858where
859    F: Fn(&[String], usize) -> Option<(T, usize)>,
860{
861    match f(tokens, pos) {
862        Some((val, new_pos)) => (Some(val), new_pos),
863        None => (None, pos),
864    }
865}
866/// A simple parser combinator: many0 (zero or more).
867#[allow(dead_code)]
868#[allow(missing_docs)]
869pub fn parse_many0<T, F>(f: F, tokens: &[String], mut pos: usize) -> (Vec<T>, usize)
870where
871    F: Fn(&[String], usize) -> Option<(T, usize)>,
872{
873    let mut results = Vec::new();
874    loop {
875        match f(tokens, pos) {
876            Some((val, new_pos)) if new_pos > pos => {
877                results.push(val);
878                pos = new_pos;
879            }
880            _ => break,
881        }
882    }
883    (results, pos)
884}
885/// A simple parser combinator: many1 (one or more).
886#[allow(dead_code)]
887#[allow(missing_docs)]
888pub fn parse_many1<T, F>(f: F, tokens: &[String], pos: usize) -> Option<(Vec<T>, usize)>
889where
890    F: Fn(&[String], usize) -> Option<(T, usize)>,
891{
892    let (results, new_pos) = parse_many0(f, tokens, pos);
893    if results.is_empty() {
894        None
895    } else {
896        Some((results, new_pos))
897    }
898}
899/// A simple parser combinator: separated list.
900#[allow(dead_code)]
901#[allow(missing_docs)]
902pub fn parse_sep_by<T, F>(item: F, sep: &str, tokens: &[String], pos: usize) -> (Vec<T>, usize)
903where
904    F: Fn(&[String], usize) -> Option<(T, usize)>,
905{
906    let mut results = Vec::new();
907    let mut cur = pos;
908    match item(tokens, cur) {
909        Some((v, new_pos)) => {
910            results.push(v);
911            cur = new_pos;
912        }
913        None => return (results, cur),
914    }
915    loop {
916        if tokens.get(cur).is_some_and(|t| t == sep) {
917            let next = cur + 1;
918            match item(tokens, next) {
919                Some((v, new_pos)) => {
920                    results.push(v);
921                    cur = new_pos;
922                }
923                None => break,
924            }
925        } else {
926            break;
927        }
928    }
929    (results, cur)
930}
931/// Parse a parenthesised sequence.
932#[allow(dead_code)]
933#[allow(missing_docs)]
934pub fn parse_parens<T, F>(inner: F, tokens: &[String], pos: usize) -> Option<(T, usize)>
935where
936    F: Fn(&[String], usize) -> Option<(T, usize)>,
937{
938    if tokens.get(pos).is_some_and(|t| t == "(") {
939        match inner(tokens, pos + 1) {
940            Some((val, new_pos)) if tokens.get(new_pos).is_some_and(|t| t == ")") => {
941                Some((val, new_pos + 1))
942            }
943            _ => None,
944        }
945    } else {
946        None
947    }
948}
949/// A utility to peek at token `pos` without consuming it.
950#[allow(dead_code)]
951#[allow(missing_docs)]
952pub fn peek(tokens: &[String], pos: usize) -> Option<&str> {
953    tokens.get(pos).map(|s| s.as_str())
954}
955/// Consume a specific token.
956#[allow(dead_code)]
957#[allow(missing_docs)]
958pub fn consume(tokens: &[String], pos: usize, expected: &str) -> Option<usize> {
959    if tokens.get(pos).is_some_and(|t| t == expected) {
960        Some(pos + 1)
961    } else {
962        None
963    }
964}
965/// Consume any identifier.
966#[allow(dead_code)]
967#[allow(missing_docs)]
968pub fn consume_ident(tokens: &[String], pos: usize) -> Option<(String, usize)> {
969    tokens.get(pos).and_then(|t| {
970        if is_valid_identifier(t) && !is_keyword(t) {
971            Some((t.clone(), pos + 1))
972        } else {
973            None
974        }
975    })
976}
977/// Count tokens from `pos` until `end_token` (exclusive).
978#[allow(dead_code)]
979#[allow(missing_docs)]
980pub fn count_until(tokens: &[String], pos: usize, end_token: &str) -> usize {
981    tokens[pos..]
982        .iter()
983        .take_while(|t| t.as_str() != end_token)
984        .count()
985}
986/// Find the matching closing token for an open bracket at `pos`.
987#[allow(dead_code)]
988#[allow(missing_docs)]
989pub fn find_matching_close(
990    tokens: &[String],
991    pos: usize,
992    open: &str,
993    close: &str,
994) -> Option<usize> {
995    if tokens.get(pos).is_some_and(|t| t != open) {
996        return None;
997    }
998    let mut depth = 0;
999    for (i, tok) in tokens[pos..].iter().enumerate() {
1000        if tok == open {
1001            depth += 1;
1002        } else if tok == close {
1003            depth -= 1;
1004            if depth == 0 {
1005                return Some(pos + i);
1006            }
1007        }
1008    }
1009    None
1010}
1011/// A simple token-based identifier parser (returns name + new pos).
1012#[allow(dead_code)]
1013#[allow(missing_docs)]
1014pub fn parse_ident(tokens: &[String], pos: usize) -> Option<(String, usize)> {
1015    consume_ident(tokens, pos)
1016}
1017/// A simple token-based number parser.
1018#[allow(dead_code)]
1019#[allow(missing_docs)]
1020pub fn parse_number(tokens: &[String], pos: usize) -> Option<(u64, usize)> {
1021    tokens
1022        .get(pos)
1023        .and_then(|t| t.parse::<u64>().ok())
1024        .map(|n| (n, pos + 1))
1025}
1026/// A simple token-based string literal parser.
1027#[allow(dead_code)]
1028#[allow(missing_docs)]
1029pub fn parse_string_lit(tokens: &[String], pos: usize) -> Option<(String, usize)> {
1030    tokens.get(pos).and_then(|t| {
1031        if t.starts_with('"') && t.ends_with('"') && t.len() >= 2 {
1032            Some((t[1..t.len() - 1].to_string(), pos + 1))
1033        } else {
1034            None
1035        }
1036    })
1037}
1038#[cfg(test)]
1039mod extended_parser_tests_3 {
1040    use super::*;
1041    use crate::parser::*;
1042    fn tok(s: &[&str]) -> Vec<String> {
1043        s.iter().map(|&x| x.to_string()).collect()
1044    }
1045    #[test]
1046    fn test_parse_optional() {
1047        let tokens = tok(&["foo", "bar"]);
1048        let result = parse_optional(
1049            |ts, p| {
1050                if ts.get(p).is_some_and(|t| t == "foo") {
1051                    Some(("foo", p + 1))
1052                } else {
1053                    None
1054                }
1055            },
1056            &tokens,
1057            0,
1058        );
1059        assert_eq!(result, (Some("foo"), 1));
1060        let result2 = parse_optional(
1061            |ts, p| {
1062                if ts.get(p).is_some_and(|t| t == "baz") {
1063                    Some(("baz", p + 1))
1064                } else {
1065                    None
1066                }
1067            },
1068            &tokens,
1069            0,
1070        );
1071        assert_eq!(result2, (None, 0));
1072    }
1073    #[test]
1074    fn test_parse_many0() {
1075        let tokens = tok(&["x", "x", "x", "y"]);
1076        let (results, pos) = parse_many0(
1077            |ts, p| {
1078                if ts.get(p).is_some_and(|t| t == "x") {
1079                    Some(("x", p + 1))
1080                } else {
1081                    None
1082                }
1083            },
1084            &tokens,
1085            0,
1086        );
1087        assert_eq!(results.len(), 3);
1088        assert_eq!(pos, 3);
1089    }
1090    #[test]
1091    fn test_parse_many1() {
1092        let tokens = tok(&["x", "y"]);
1093        assert!(parse_many1(
1094            |ts, p| {
1095                if ts.get(p).is_some_and(|t| t == "x") {
1096                    Some(("x", p + 1))
1097                } else {
1098                    None
1099                }
1100            },
1101            &tokens,
1102            0
1103        )
1104        .is_some());
1105        assert!(parse_many1(
1106            |ts, p| {
1107                if ts.get(p).is_some_and(|t| t == "z") {
1108                    Some(("z", p + 1))
1109                } else {
1110                    None
1111                }
1112            },
1113            &tokens,
1114            0
1115        )
1116        .is_none());
1117    }
1118    #[test]
1119    fn test_parse_sep_by() {
1120        let tokens = tok(&["a", ",", "b", ",", "c"]);
1121        let (results, pos) = parse_sep_by(
1122            |ts, p| {
1123                ts.get(p).and_then(|t| {
1124                    if t != "," {
1125                        Some((t.clone(), p + 1))
1126                    } else {
1127                        None
1128                    }
1129                })
1130            },
1131            ",",
1132            &tokens,
1133            0,
1134        );
1135        assert_eq!(
1136            results,
1137            vec!["a".to_string(), "b".to_string(), "c".to_string()]
1138        );
1139        assert_eq!(pos, 5);
1140    }
1141    #[test]
1142    fn test_parse_parens() {
1143        let tokens = tok(&["(", "x", ")"]);
1144        let result = parse_parens(|ts, p| ts.get(p).map(|t| (t.clone(), p + 1)), &tokens, 0);
1145        assert_eq!(result, Some(("x".to_string(), 3)));
1146    }
1147    #[test]
1148    fn test_peek_and_consume() {
1149        let tokens = tok(&["hello", "world"]);
1150        assert_eq!(peek(&tokens, 0), Some("hello"));
1151        assert_eq!(consume(&tokens, 0, "hello"), Some(1));
1152        assert_eq!(consume(&tokens, 0, "world"), None);
1153    }
1154    #[test]
1155    fn test_consume_ident() {
1156        let tokens = tok(&["foo", "def", "bar"]);
1157        assert_eq!(consume_ident(&tokens, 0), Some(("foo".to_string(), 1)));
1158        assert_eq!(consume_ident(&tokens, 1), None);
1159        assert_eq!(consume_ident(&tokens, 2), Some(("bar".to_string(), 3)));
1160    }
1161    #[test]
1162    fn test_count_until() {
1163        let tokens = tok(&["a", "b", "c", "end", "d"]);
1164        assert_eq!(count_until(&tokens, 0, "end"), 3);
1165    }
1166    #[test]
1167    fn test_find_matching_close() {
1168        let tokens = tok(&["(", "a", "(", "b", ")", "c", ")"]);
1169        assert_eq!(find_matching_close(&tokens, 0, "(", ")"), Some(6));
1170        assert_eq!(find_matching_close(&tokens, 2, "(", ")"), Some(4));
1171    }
1172    #[test]
1173    fn test_parse_number() {
1174        let tokens = tok(&["42", "abc"]);
1175        assert_eq!(parse_number(&tokens, 0), Some((42, 1)));
1176        assert_eq!(parse_number(&tokens, 1), None);
1177    }
1178    #[test]
1179    fn test_parse_string_lit() {
1180        let tokens = tok(&["\"hello\"", "world"]);
1181        assert_eq!(parse_string_lit(&tokens, 0), Some(("hello".to_string(), 1)));
1182        assert_eq!(parse_string_lit(&tokens, 1), None);
1183    }
1184    #[test]
1185    fn test_parse_error_simple() {
1186        let e = ParseErrorSimple::new(5, "unexpected token");
1187        assert_eq!(e.pos, 5);
1188        assert!(!e.recovered);
1189        let e2 = e.recovered();
1190        assert!(e2.recovered);
1191        let disp = format!("{}", e2);
1192        assert!(disp.contains("parse error at 5"));
1193    }
1194    #[test]
1195    fn test_parse_result_with_errors() {
1196        let r = ParseResultWithErrors::ok(42);
1197        assert!(r.is_ok());
1198        assert!(!r.has_errors());
1199        let e = ParseResultWithErrors::<i32>::err(ParseErrorSimple::new(0, "bad"));
1200        assert!(!e.is_ok());
1201        assert!(e.has_errors());
1202    }
1203    #[test]
1204    fn test_depth_limiter() {
1205        let mut lim = DepthLimiter::new(3);
1206        assert!(lim.enter());
1207        assert!(lim.enter());
1208        assert!(lim.enter());
1209        assert!(!lim.enter());
1210        lim.exit();
1211        assert!(lim.enter());
1212    }
1213}