1pub 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#[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#[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#[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#[allow(missing_docs)]
50pub fn is_valid_expr(src: &str) -> bool {
51 parse_expr(src).is_ok()
52}
53#[allow(missing_docs)]
55pub fn is_valid_decl(src: &str) -> bool {
56 parse_decl(src).is_ok()
57}
58pub(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#[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#[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#[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#[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#[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#[allow(dead_code)]
681#[allow(missing_docs)]
682pub fn is_operator_start(c: char) -> bool {
683 matches!(
684 c,
685 '+' | '-' | '*' | '/' | '<' | '>' | '=' | '!' | '&' | '|' | '^' | '~' | '%' | '@'
686 )
687}
688#[allow(dead_code)]
690#[allow(missing_docs)]
691pub fn is_multi_char_op(s: &str) -> bool {
692 matches!(
693 s,
694 "->" | "=>" | "<=" | ">=" | "!=" | "==" | "&&" | "||" | ":=" | "::"
695 )
696}
697#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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}