1use oxilean_kernel::{Declaration, Environment, Expr, Level, Name};
6
7use super::types::{BytePos, LineCol, Span, StringBuilder, SubstringFinder2};
8
9pub fn indent(text: &str, n: usize) -> String {
13 let pad = " ".repeat(n);
14 text.lines()
15 .map(|line| {
16 if line.trim().is_empty() {
17 String::new()
18 } else {
19 format!("{pad}{line}")
20 }
21 })
22 .collect::<Vec<_>>()
23 .join("\n")
24}
25pub fn normalize_whitespace(text: &str) -> String {
29 text.split_whitespace().collect::<Vec<_>>().join(" ")
30}
31pub fn words(text: &str) -> Vec<&str> {
33 text.split_whitespace().collect()
34}
35pub fn join(parts: &[impl AsRef<str>], sep: &str) -> String {
37 parts
38 .iter()
39 .map(|s| s.as_ref())
40 .collect::<Vec<_>>()
41 .join(sep)
42}
43pub fn count_occurrences(haystack: &str, needle: &str) -> usize {
45 if needle.is_empty() {
46 return 0;
47 }
48 let mut count = 0;
49 let mut pos = 0;
50 while let Some(idx) = haystack[pos..].find(needle) {
51 count += 1;
52 pos += idx + needle.len();
53 }
54 count
55}
56pub fn truncate(s: &str, max_len: usize, suffix: &str) -> String {
61 let chars: Vec<char> = s.chars().collect();
62 if chars.len() <= max_len {
63 s.to_string()
64 } else {
65 let suffix_chars: Vec<char> = suffix.chars().collect();
66 let take = max_len.saturating_sub(suffix_chars.len());
67 let mut result: String = chars[..take].iter().collect();
68 result.push_str(suffix);
69 result
70 }
71}
72pub fn pad_right(s: &str, width: usize) -> String {
74 let len = s.chars().count();
75 if len >= width {
76 s.to_string()
77 } else {
78 format!("{s}{}", " ".repeat(width - len))
79 }
80}
81pub fn pad_left(s: &str, width: usize) -> String {
83 let len = s.chars().count();
84 if len >= width {
85 s.to_string()
86 } else {
87 format!("{}{s}", " ".repeat(width - len))
88 }
89}
90pub fn center(s: &str, width: usize) -> String {
94 let len = s.chars().count();
95 if len >= width {
96 return s.to_string();
97 }
98 let total_pad = width - len;
99 let left_pad = total_pad / 2;
100 let right_pad = total_pad - left_pad;
101 format!("{}{s}{}", " ".repeat(left_pad), " ".repeat(right_pad))
102}
103#[allow(clippy::while_let_on_iterator)]
105pub fn camel_to_snake(s: &str) -> String {
106 let mut result = String::with_capacity(s.len() + 4);
107 let mut chars = s.chars().peekable();
108 while let Some(c) = chars.next() {
109 if c.is_uppercase() {
110 if !result.is_empty() {
111 result.push('_');
112 }
113 result.push(
114 c.to_lowercase()
115 .next()
116 .expect("to_lowercase always yields at least one char"),
117 );
118 } else {
119 result.push(c);
120 }
121 }
122 result
123}
124pub fn snake_to_camel(s: &str) -> String {
126 let mut result = String::with_capacity(s.len());
127 let mut capitalise_next = false;
128 for c in s.chars() {
129 if c == '_' {
130 capitalise_next = true;
131 } else if capitalise_next {
132 result.push(
133 c.to_uppercase()
134 .next()
135 .expect("to_uppercase always yields at least one char"),
136 );
137 capitalise_next = false;
138 } else {
139 result.push(c);
140 }
141 }
142 result
143}
144pub fn snake_to_pascal(s: &str) -> String {
146 let camel = snake_to_camel(s);
147 let mut chars = camel.chars();
148 match chars.next() {
149 None => String::new(),
150 Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
151 }
152}
153pub fn is_valid_ident(s: &str) -> bool {
158 if s.is_empty() {
159 return false;
160 }
161 let mut chars = s.chars();
162 let first = chars
163 .next()
164 .expect("s is non-empty: checked by early return");
165 if !first.is_alphabetic() && first != '_' {
166 return false;
167 }
168 chars.all(|c| c.is_alphanumeric() || c == '_' || c == '\'')
169}
170pub fn escape_for_display(s: &str) -> String {
174 let mut result = String::with_capacity(s.len());
175 for c in s.chars() {
176 match c {
177 '\n' => result.push_str("\\n"),
178 '\t' => result.push_str("\\t"),
179 '\r' => result.push_str("\\r"),
180 '\\' => result.push_str("\\\\"),
181 c if c.is_control() => {
182 result.push_str(&format!("\\u{:04X}", c as u32));
183 }
184 c => result.push(c),
185 }
186 }
187 result
188}
189pub fn repeat_str(s: &str, n: usize) -> String {
191 s.repeat(n)
192}
193pub fn strip_prefix<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
195 s.strip_prefix(prefix)
196}
197pub fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> {
199 s.strip_suffix(suffix)
200}
201pub fn strip_delimiters<'a>(s: &'a str, open: &str, close: &str) -> Option<&'a str> {
205 let inner = s.strip_prefix(open)?.strip_suffix(close)?;
206 Some(inner)
207}
208pub fn replace_first(s: &str, from: &str, to: &str) -> String {
210 if let Some(pos) = s.find(from) {
211 format!("{}{}{}", &s[..pos], to, &s[pos + from.len()..])
212 } else {
213 s.to_string()
214 }
215}
216pub fn split_once_or_whole<'a>(s: &'a str, sep: &str) -> (&'a str, &'a str) {
220 s.split_once(sep).unwrap_or((s, ""))
221}
222pub fn word_wrap(text: &str, width: usize) -> Vec<String> {
226 let mut lines = Vec::new();
227 let mut current_line = String::new();
228 for word in text.split_whitespace() {
229 if current_line.is_empty() {
230 current_line.push_str(word);
231 } else if current_line.len() + 1 + word.len() <= width {
232 current_line.push(' ');
233 current_line.push_str(word);
234 } else {
235 lines.push(current_line.clone());
236 current_line = word.to_string();
237 }
238 }
239 if !current_line.is_empty() {
240 lines.push(current_line);
241 }
242 lines
243}
244pub fn dotted_name_to_components(name: &str) -> Vec<&str> {
246 name.split('.').collect()
247}
248pub fn components_to_dotted_name(components: &[impl AsRef<str>]) -> String {
250 join(components, ".")
251}
252pub fn dotted_name_last(name: &str) -> &str {
256 name.rsplit('.').next().unwrap_or(name)
257}
258pub fn dotted_name_namespace(name: &str) -> &str {
262 match name.rfind('.') {
263 Some(pos) => &name[..pos],
264 None => "",
265 }
266}
267pub fn mangle_name(name: &str) -> String {
271 name.chars()
272 .map(|c| {
273 if c == '.' {
274 '_'
275 } else if c.is_alphanumeric() || c == '_' {
276 c
277 } else {
278 '_'
279 }
280 })
281 .collect()
282}
283pub fn in_namespace(name: &str, ns: &str) -> bool {
287 if ns.is_empty() {
288 return true;
289 }
290 name.starts_with(&format!("{ns}."))
291}
292impl From<StringBuilder> for String {
293 fn from(sb: StringBuilder) -> String {
294 sb.finish()
295 }
296}
297pub fn byte_pos_to_line_col(src: &str, pos: BytePos) -> LineCol {
301 let offset = pos.to_usize().min(src.len());
302 let prefix = &src[..offset];
303 let line = prefix.chars().filter(|&c| c == '\n').count() as u32 + 1;
304 let col = prefix.chars().rev().take_while(|&c| c != '\n').count() as u32 + 1;
305 LineCol::new(line, col)
306}
307pub fn build_line_starts(src: &str) -> Vec<BytePos> {
309 let mut starts = vec![BytePos(0)];
310 for (i, c) in src.char_indices() {
311 if c == '\n' {
312 starts.push(BytePos((i + 1) as u32));
313 }
314 }
315 starts
316}
317pub fn format_list<T: std::fmt::Display>(items: &[T], sep: &str) -> String {
319 items
320 .iter()
321 .map(|x| x.to_string())
322 .collect::<Vec<_>>()
323 .join(sep)
324}
325pub fn format_paren_list<T: std::fmt::Display>(items: &[T]) -> String {
327 if items.is_empty() {
328 "()".to_string()
329 } else {
330 format!("({})", format_list(items, ", "))
331 }
332}
333pub fn format_opt<T: std::fmt::Display>(opt: &Option<T>, default: &str) -> String {
335 match opt {
336 Some(v) => v.to_string(),
337 None => default.to_string(),
338 }
339}
340pub fn indent_continuation(text: &str, first: usize, cont: usize) -> String {
342 let mut lines = text.lines();
343 let first_line = match lines.next() {
344 None => return String::new(),
345 Some(l) => format!("{}{l}", " ".repeat(first)),
346 };
347 let rest: Vec<String> = lines.map(|l| format!("{}{l}", " ".repeat(cont))).collect();
348 if rest.is_empty() {
349 first_line
350 } else {
351 format!("{}\n{}", first_line, rest.join("\n"))
352 }
353}
354pub fn hr(n: usize) -> String {
356 "─".repeat(n)
357}
358pub fn box_title(title: &str, width: usize) -> String {
360 let pad = width.saturating_sub(title.chars().count() + 4);
361 format!("┌─ {title} {}", "─".repeat(pad))
362}
363pub fn lean4_check(expr: &str) -> String {
365 format!("#check {expr}")
366}
367pub fn lean4_theorem_stub(name: &str, ty: &str) -> String {
369 format!("theorem {name} : {ty} := by\n sorry")
370}
371pub fn lean4_def_stub(name: &str, ty: &str, body: &str) -> String {
373 format!("def {name} : {ty} := {body}")
374}
375pub fn lean4_quote_ident(s: &str) -> String {
377 format!("`{s}`")
378}
379#[cfg(test)]
380mod tests {
381 use super::*;
382 #[test]
383 fn test_indent_basic() {
384 let result = indent("hello\nworld", 2);
385 assert_eq!(result, " hello\n world");
386 }
387 #[test]
388 fn test_indent_blank_line() {
389 let result = indent("hello\n\nworld", 2);
390 assert_eq!(result, " hello\n\n world");
391 }
392 #[test]
393 fn test_normalize_whitespace() {
394 assert_eq!(normalize_whitespace(" hello world "), "hello world");
395 }
396 #[test]
397 fn test_count_occurrences() {
398 assert_eq!(count_occurrences("aababab", "ab"), 3);
399 assert_eq!(count_occurrences("hello", ""), 0);
400 assert_eq!(count_occurrences("hello", "xyz"), 0);
401 }
402 #[test]
403 fn test_truncate() {
404 assert_eq!(truncate("hello world", 8, "..."), "hello...");
405 assert_eq!(truncate("hi", 10, "..."), "hi");
406 }
407 #[test]
408 fn test_pad_right() {
409 assert_eq!(pad_right("hi", 5), "hi ");
410 assert_eq!(pad_right("hello", 3), "hello");
411 }
412 #[test]
413 fn test_pad_left() {
414 assert_eq!(pad_left("hi", 5), " hi");
415 }
416 #[test]
417 fn test_camel_to_snake() {
418 assert_eq!(camel_to_snake("CamelCase"), "camel_case");
419 assert_eq!(camel_to_snake("helloWorld"), "hello_world");
420 }
421 #[test]
422 fn test_snake_to_camel() {
423 assert_eq!(snake_to_camel("hello_world"), "helloWorld");
424 assert_eq!(snake_to_camel("foo_bar_baz"), "fooBarBaz");
425 }
426 #[test]
427 fn test_snake_to_pascal() {
428 assert_eq!(snake_to_pascal("hello_world"), "HelloWorld");
429 }
430 #[test]
431 fn test_is_valid_ident() {
432 assert!(is_valid_ident("foo"));
433 assert!(is_valid_ident("_bar"));
434 assert!(is_valid_ident("foo'"));
435 assert!(!is_valid_ident(""));
436 assert!(!is_valid_ident("1foo"));
437 assert!(!is_valid_ident("foo-bar"));
438 }
439 #[test]
440 fn test_escape_for_display() {
441 assert_eq!(escape_for_display("hello\nworld"), "hello\\nworld");
442 assert_eq!(escape_for_display("a\\b"), "a\\\\b");
443 }
444 #[test]
445 fn test_word_wrap() {
446 let wrapped = word_wrap("hello world foo bar", 10);
447 for line in &wrapped {
448 assert!(line.len() <= 10, "line too long: {line:?}");
449 }
450 }
451 #[test]
452 fn test_string_builder() {
453 let mut sb = StringBuilder::new();
454 sb.push_str("hello");
455 sb.push(' ');
456 sb.push_str("world");
457 assert_eq!(sb.finish(), "hello world");
458 }
459 #[test]
460 fn test_string_builder_newline() {
461 let mut sb = StringBuilder::new();
462 sb.push_str("line1");
463 sb.indent();
464 sb.newline();
465 sb.push_str("line2");
466 let s = sb.finish();
467 assert!(s.contains('\n'));
468 assert!(s.contains(" line2"));
469 }
470 #[test]
471 fn test_byte_pos_span() {
472 let s = Span::from_offsets(0, 5);
473 assert_eq!(s.len(), 5);
474 assert!(s.contains(BytePos(0)));
475 assert!(s.contains(BytePos(4)));
476 assert!(!s.contains(BytePos(5)));
477 }
478 #[test]
479 fn test_span_merge() {
480 let a = Span::from_offsets(0, 5);
481 let b = Span::from_offsets(3, 10);
482 let m = a.merge(b);
483 assert_eq!(m.start.0, 0);
484 assert_eq!(m.end.0, 10);
485 }
486 #[test]
487 fn test_span_slice() {
488 let src = "hello world";
489 let sp = Span::from_offsets(6, 11);
490 assert_eq!(sp.slice(src), Some("world"));
491 }
492 #[test]
493 fn test_byte_pos_to_line_col() {
494 let src = "abc\ndef\nghi";
495 let lc = byte_pos_to_line_col(src, BytePos(4));
496 assert_eq!(lc.line, 2);
497 assert_eq!(lc.col, 1);
498 }
499 #[test]
500 fn test_dotted_name_last() {
501 assert_eq!(dotted_name_last("Foo.bar.Baz"), "Baz");
502 assert_eq!(dotted_name_last("NoDot"), "NoDot");
503 }
504 #[test]
505 fn test_dotted_name_namespace() {
506 assert_eq!(dotted_name_namespace("Foo.bar.Baz"), "Foo.bar");
507 assert_eq!(dotted_name_namespace("NoDot"), "");
508 }
509 #[test]
510 fn test_in_namespace() {
511 assert!(in_namespace("Nat.succ", "Nat"));
512 assert!(!in_namespace("Int.succ", "Nat"));
513 assert!(in_namespace("anything", ""));
514 }
515 #[test]
516 fn test_mangle_name() {
517 assert_eq!(mangle_name("Foo.bar"), "Foo_bar");
518 }
519 #[test]
520 fn test_center() {
521 let s = center("hi", 6);
522 assert_eq!(s.len(), 6);
523 assert!(s.contains("hi"));
524 }
525 #[test]
526 fn test_join() {
527 assert_eq!(join(&["a", "b", "c"], ", "), "a, b, c");
528 }
529 #[test]
530 fn test_words() {
531 assert_eq!(words(" hello world "), vec!["hello", "world"]);
532 }
533 #[test]
534 fn test_strip_delimiters() {
535 assert_eq!(strip_delimiters("(hello)", "(", ")"), Some("hello"));
536 assert_eq!(strip_delimiters("hello", "(", ")"), None);
537 }
538 #[test]
539 fn test_build_line_starts() {
540 let src = "ab\ncd\nef";
541 let starts = build_line_starts(src);
542 assert_eq!(starts.len(), 3);
543 assert_eq!(starts[1].0, 3);
544 }
545}
546pub fn build_string_env(env: &mut oxilean_kernel::Environment) -> Result<(), String> {
551 use oxilean_kernel::{BinderInfo as Bi, Declaration, Expr, Level, Name};
552 let mut add = |name: &str, ty: Expr| -> Result<(), String> {
553 match env.add(Declaration::Axiom {
554 name: Name::str(name),
555 univ_params: vec![],
556 ty,
557 }) {
558 Ok(()) | Err(_) => Ok(()),
559 }
560 };
561 let cst = |s: &str| -> Expr { Expr::Const(Name::str(s), vec![]) };
562 let app = |f: Expr, a: Expr| -> Expr { Expr::App(Box::new(f), Box::new(a)) };
563 let arr = |a: Expr, b: Expr| -> Expr {
564 Expr::Pi(Bi::Default, Name::Anonymous, Box::new(a), Box::new(b))
565 };
566 let type1 = || -> Expr { Expr::Sort(Level::succ(Level::zero())) };
567 let nat_ty = || -> Expr { cst("Nat") };
568 let bool_ty = || -> Expr { cst("Bool") };
569 let string_ty = || -> Expr { cst("String") };
570 let char_ty = || -> Expr { cst("Char") };
571 let option_of = |ty: Expr| -> Expr { app(cst("Option"), ty) };
572 add("String", type1())?;
573 add("Char", type1())?;
574 add("String.length", arr(string_ty(), nat_ty()))?;
575 add(
576 "String.append",
577 arr(string_ty(), arr(string_ty(), string_ty())),
578 )?;
579 add("String.mk", arr(app(cst("List"), char_ty()), string_ty()))?;
580 add("String.data", arr(string_ty(), app(cst("List"), char_ty())))?;
581 add("String.isEmpty", arr(string_ty(), bool_ty()))?;
582 add("String.get", arr(string_ty(), arr(nat_ty(), char_ty())))?;
583 add(
584 "String.contains",
585 arr(string_ty(), arr(char_ty(), bool_ty())),
586 )?;
587 add(
588 "String.startsWith",
589 arr(string_ty(), arr(string_ty(), bool_ty())),
590 )?;
591 add(
592 "String.endsWith",
593 arr(string_ty(), arr(string_ty(), bool_ty())),
594 )?;
595 add(
596 "String.intercalate",
597 arr(string_ty(), arr(app(cst("List"), string_ty()), string_ty())),
598 )?;
599 add(
600 "String.splitOn",
601 arr(string_ty(), arr(string_ty(), app(cst("List"), string_ty()))),
602 )?;
603 add("String.trim", arr(string_ty(), string_ty()))?;
604 add("String.toNat?", arr(string_ty(), option_of(nat_ty())))?;
605 add(
606 "String.toList",
607 arr(string_ty(), app(cst("List"), char_ty())),
608 )?;
609 add("Char.val", arr(char_ty(), nat_ty()))?;
610 add("Char.ofNat", arr(nat_ty(), char_ty()))?;
611 add("Char.isAlpha", arr(char_ty(), bool_ty()))?;
612 add("Char.isDigit", arr(char_ty(), bool_ty()))?;
613 add("Char.isAlphanum", arr(char_ty(), bool_ty()))?;
614 add("Char.isWhitespace", arr(char_ty(), bool_ty()))?;
615 add("Char.isLower", arr(char_ty(), bool_ty()))?;
616 add("Char.isUpper", arr(char_ty(), bool_ty()))?;
617 add("Char.toLower", arr(char_ty(), char_ty()))?;
618 add("Char.toUpper", arr(char_ty(), char_ty()))?;
619 Ok(())
620}
621#[allow(dead_code)]
625pub fn deduplicate_consecutive(s: &str) -> String {
626 let mut result = String::with_capacity(s.len());
627 let mut prev: Option<char> = None;
628 for c in s.chars() {
629 if Some(c) != prev {
630 result.push(c);
631 prev = Some(c);
632 }
633 }
634 result
635}
636#[allow(dead_code)]
638pub fn is_palindrome(s: &str) -> bool {
639 let chars: Vec<char> = s.chars().collect();
640 let rev: Vec<char> = chars.iter().rev().cloned().collect();
641 chars == rev
642}
643#[allow(dead_code)]
645pub fn char_count(s: &str) -> usize {
646 s.chars().count()
647}
648#[allow(dead_code)]
650pub fn nth_char(s: &str, n: usize) -> Option<char> {
651 s.chars().nth(n)
652}
653#[allow(dead_code)]
655pub fn reverse_str(s: &str) -> String {
656 s.chars().rev().collect()
657}
658#[allow(dead_code)]
662pub fn chunk_chars(s: &str, n: usize) -> Vec<String> {
663 if n == 0 {
664 return vec![];
665 }
666 let chars: Vec<char> = s.chars().collect();
667 chars.chunks(n).map(|c| c.iter().collect()).collect()
668}
669#[allow(dead_code)]
671pub fn is_blank(s: &str) -> bool {
672 s.chars().all(|c| c.is_ascii_whitespace())
673}
674#[allow(dead_code)]
678pub fn strip_line_comment(line: &str) -> &str {
679 if let Some(pos) = line.find("--") {
680 &line[..pos]
681 } else {
682 line
683 }
684}
685#[allow(dead_code)]
689pub fn apply_substitutions(s: &str, subs: &[(&str, &str)]) -> String {
690 let mut result = s.to_string();
691 for (from, to) in subs {
692 result = result.replace(from, to);
693 }
694 result
695}
696#[allow(dead_code)]
700pub fn split_by_chars<'a>(s: &'a str, delimiters: &[char]) -> Vec<&'a str> {
701 s.split(|c: char| delimiters.contains(&c))
702 .filter(|s| !s.is_empty())
703 .collect()
704}
705#[allow(dead_code)]
709pub fn interleave_char(s: &str, sep: char) -> String {
710 let chars: Vec<char> = s.chars().collect();
711 if chars.is_empty() {
712 return String::new();
713 }
714 let mut result = String::with_capacity(s.len() * 2);
715 for (i, c) in chars.iter().enumerate() {
716 result.push(*c);
717 if i + 1 < chars.len() {
718 result.push(sep);
719 }
720 }
721 result
722}
723#[allow(dead_code)]
725pub fn slugify(s: &str) -> String {
726 s.chars()
727 .map(|c| {
728 if c.is_alphanumeric() {
729 c.to_ascii_lowercase()
730 } else {
731 '-'
732 }
733 })
734 .collect::<String>()
735 .split('-')
736 .filter(|s| !s.is_empty())
737 .collect::<Vec<_>>()
738 .join("-")
739}
740#[allow(dead_code)]
742pub fn title_case(s: &str) -> String {
743 s.split_whitespace()
744 .map(|word| {
745 let mut chars = word.chars();
746 match chars.next() {
747 None => String::new(),
748 Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
749 }
750 })
751 .collect::<Vec<_>>()
752 .join(" ")
753}
754#[allow(dead_code)]
756pub fn to_upper(s: &str) -> String {
757 s.to_uppercase()
758}
759#[allow(dead_code)]
761pub fn to_lower(s: &str) -> String {
762 s.to_lowercase()
763}
764#[allow(dead_code)]
766pub fn capitalize(s: &str) -> String {
767 let mut chars = s.chars();
768 match chars.next() {
769 None => String::new(),
770 Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
771 }
772}
773#[allow(dead_code)]
775pub fn decapitalize(s: &str) -> String {
776 let mut chars = s.chars();
777 match chars.next() {
778 None => String::new(),
779 Some(c) => c.to_lowercase().collect::<String>() + chars.as_str(),
780 }
781}
782#[allow(dead_code)]
786pub fn glob_match(pattern: &str, s: &str) -> bool {
787 let p: Vec<char> = pattern.chars().collect();
788 let t: Vec<char> = s.chars().collect();
789 glob_helper(&p, &t)
790}
791pub fn glob_helper(p: &[char], t: &[char]) -> bool {
792 match (p.first(), t.first()) {
793 (None, None) => true,
794 (Some(&'*'), _) => glob_helper(&p[1..], t) || (!t.is_empty() && glob_helper(p, &t[1..])),
795 (Some(&'?'), Some(_)) => glob_helper(&p[1..], &t[1..]),
796 (Some(a), Some(b)) if a == b => glob_helper(&p[1..], &t[1..]),
797 _ => false,
798 }
799}
800#[allow(dead_code)]
802pub fn edit_distance(a: &str, b: &str) -> usize {
803 let a: Vec<char> = a.chars().collect();
804 let b: Vec<char> = b.chars().collect();
805 let m = a.len();
806 let n = b.len();
807 let mut dp = vec![vec![0usize; n + 1]; m + 1];
808 for (i, row) in dp.iter_mut().enumerate().take(m + 1) {
809 row[0] = i;
810 }
811 for (j, val) in dp[0].iter_mut().enumerate().take(n + 1) {
812 *val = j;
813 }
814 for i in 1..=m {
815 for j in 1..=n {
816 dp[i][j] = if a[i - 1] == b[j - 1] {
817 dp[i - 1][j - 1]
818 } else {
819 1 + dp[i - 1][j].min(dp[i][j - 1]).min(dp[i - 1][j - 1])
820 };
821 }
822 }
823 dp[m][n]
824}
825#[allow(dead_code)]
827pub fn closest_match<'a>(query: &str, candidates: &[&'a str]) -> Option<&'a str> {
828 candidates
829 .iter()
830 .min_by_key(|&&c| edit_distance(query, c))
831 .copied()
832}
833#[allow(dead_code)]
837pub fn template_format(template: &str, vars: &[(&str, &str)]) -> String {
838 let mut result = template.to_string();
839 for (key, val) in vars {
840 result = result.replace(&format!("{{{}}}", key), val);
841 }
842 result
843}
844#[allow(dead_code)]
846pub fn word_count(s: &str) -> usize {
847 s.split_whitespace().count()
848}
849#[allow(dead_code)]
851pub fn sentence_count(s: &str) -> usize {
852 s.chars().filter(|&c| matches!(c, '.' | '!' | '?')).count()
853}
854#[allow(dead_code)]
856pub fn is_all_digits(s: &str) -> bool {
857 !s.is_empty() && s.chars().all(|c| c.is_ascii_digit())
858}
859#[allow(dead_code)]
861pub fn is_all_alpha(s: &str) -> bool {
862 !s.is_empty() && s.chars().all(|c| c.is_ascii_alphabetic())
863}
864#[cfg(test)]
865mod extra_string_tests {
866 use super::*;
867 #[test]
868 fn test_deduplicate_consecutive() {
869 assert_eq!(deduplicate_consecutive("aabbcc"), "abc");
870 assert_eq!(deduplicate_consecutive("abab"), "abab");
871 }
872 #[test]
873 fn test_is_palindrome() {
874 assert!(is_palindrome("racecar"));
875 assert!(!is_palindrome("hello"));
876 assert!(is_palindrome("a"));
877 assert!(is_palindrome(""));
878 }
879 #[test]
880 fn test_char_count() {
881 assert_eq!(char_count("hello"), 5);
882 assert_eq!(char_count(""), 0);
883 }
884 #[test]
885 fn test_nth_char() {
886 assert_eq!(nth_char("hello", 1), Some('e'));
887 assert_eq!(nth_char("hello", 10), None);
888 }
889 #[test]
890 fn test_reverse_str() {
891 assert_eq!(reverse_str("abc"), "cba");
892 assert_eq!(reverse_str(""), "");
893 }
894 #[test]
895 fn test_chunk_chars() {
896 let chunks = chunk_chars("abcdefg", 3);
897 assert_eq!(chunks, vec!["abc", "def", "g"]);
898 }
899 #[test]
900 fn test_is_blank() {
901 assert!(is_blank(" \t"));
902 assert!(!is_blank(" a "));
903 }
904 #[test]
905 fn test_strip_line_comment() {
906 assert_eq!(strip_line_comment("code -- comment"), "code ");
907 assert_eq!(strip_line_comment("no comment"), "no comment");
908 }
909 #[test]
910 fn test_apply_substitutions() {
911 let subs = &[("foo", "bar"), ("baz", "qux")];
912 assert_eq!(apply_substitutions("foo baz", subs), "bar qux");
913 }
914 #[test]
915 fn test_split_by_chars() {
916 let parts = split_by_chars("a,b;c", &[',', ';']);
917 assert_eq!(parts, vec!["a", "b", "c"]);
918 }
919 #[test]
920 fn test_interleave_char() {
921 assert_eq!(interleave_char("abc", '-'), "a-b-c");
922 assert_eq!(interleave_char("", '-'), "");
923 }
924 #[test]
925 fn test_slugify() {
926 assert_eq!(slugify("Hello World!"), "hello-world");
927 }
928 #[test]
929 fn test_title_case() {
930 assert_eq!(title_case("hello world"), "Hello World");
931 }
932 #[test]
933 fn test_capitalize() {
934 assert_eq!(capitalize("hello"), "Hello");
935 assert_eq!(capitalize(""), "");
936 }
937 #[test]
938 fn test_decapitalize() {
939 assert_eq!(decapitalize("Hello"), "hello");
940 }
941 #[test]
942 fn test_glob_match() {
943 assert!(glob_match("*.rs", "main.rs"));
944 assert!(glob_match("foo?bar", "fooXbar"));
945 assert!(!glob_match("*.rs", "main.txt"));
946 assert!(glob_match("*", "anything"));
947 }
948 #[test]
949 fn test_edit_distance() {
950 assert_eq!(edit_distance("kitten", "sitting"), 3);
951 assert_eq!(edit_distance("", "abc"), 3);
952 assert_eq!(edit_distance("abc", "abc"), 0);
953 }
954 #[test]
955 fn test_closest_match() {
956 let candidates = &["intro", "apply", "exact"];
957 let closest = closest_match("intro2", candidates);
958 assert_eq!(closest, Some("intro"));
959 }
960 #[test]
961 fn test_template_format() {
962 let t = "Hello, {name}! You are {age} years old.";
963 let result = template_format(t, &[("name", "Alice"), ("age", "30")]);
964 assert_eq!(result, "Hello, Alice! You are 30 years old.");
965 }
966 #[test]
967 fn test_word_count() {
968 assert_eq!(word_count("hello world foo"), 3);
969 assert_eq!(word_count(""), 0);
970 }
971 #[test]
972 fn test_is_all_digits() {
973 assert!(is_all_digits("12345"));
974 assert!(!is_all_digits("123a5"));
975 assert!(!is_all_digits(""));
976 }
977 #[test]
978 fn test_is_all_alpha() {
979 assert!(is_all_alpha("abc"));
980 assert!(!is_all_alpha("abc1"));
981 }
982}
983pub(super) fn str_ext2_levenshtein(a: &str, b: &str) -> usize {
984 let a: Vec<char> = a.chars().collect();
985 let b: Vec<char> = b.chars().collect();
986 let m = a.len();
987 let n = b.len();
988 let mut dp = vec![vec![0usize; n + 1]; m + 1];
989 for i in 0..=m {
990 dp[i][0] = i;
991 }
992 for j in 0..=n {
993 dp[0][j] = j;
994 }
995 for i in 1..=m {
996 for j in 1..=n {
997 dp[i][j] = if a[i - 1] == b[j - 1] {
998 dp[i - 1][j - 1]
999 } else {
1000 1 + dp[i - 1][j].min(dp[i][j - 1]).min(dp[i - 1][j - 1])
1001 };
1002 }
1003 }
1004 dp[m][n]
1005}
1006pub fn str_ext2_kmp_failure(pattern: &[char]) -> Vec<usize> {
1007 let m = pattern.len();
1008 let mut fail = vec![0usize; m];
1009 let mut k = 0usize;
1010 for i in 1..m {
1011 while k > 0 && pattern[k] != pattern[i] {
1012 k = fail[k - 1];
1013 }
1014 if pattern[k] == pattern[i] {
1015 k += 1;
1016 }
1017 fail[i] = k;
1018 }
1019 fail
1020}
1021pub(super) fn str_ext2_kmp_search(text: &str, pattern: &str) -> Vec<usize> {
1022 let t: Vec<char> = text.chars().collect();
1023 let p: Vec<char> = pattern.chars().collect();
1024 if p.is_empty() {
1025 return vec![];
1026 }
1027 let fail = str_ext2_kmp_failure(&p);
1028 let mut positions = Vec::new();
1029 let mut q = 0usize;
1030 for (i, &tc) in t.iter().enumerate() {
1031 while q > 0 && p[q] != tc {
1032 q = fail[q - 1];
1033 }
1034 if p[q] == tc {
1035 q += 1;
1036 }
1037 if q == p.len() {
1038 positions.push(i + 1 - p.len());
1039 q = fail[q - 1];
1040 }
1041 }
1042 positions
1043}
1044pub(super) fn str_ext2_rabin_karp(
1045 text: &str,
1046 pattern: &str,
1047 base: u64,
1048 modulus: u64,
1049) -> Vec<usize> {
1050 let t: Vec<char> = text.chars().collect();
1051 let p: Vec<char> = pattern.chars().collect();
1052 let n = t.len();
1053 let m = p.len();
1054 if m == 0 || m > n {
1055 return vec![];
1056 }
1057 let mut pat_hash = 0u64;
1058 let mut win_hash = 0u64;
1059 let mut high_pow = 1u64;
1060 for i in 0..m {
1061 pat_hash = (pat_hash.wrapping_mul(base).wrapping_add(p[i] as u64)) % modulus;
1062 win_hash = (win_hash.wrapping_mul(base).wrapping_add(t[i] as u64)) % modulus;
1063 if i + 1 < m {
1064 high_pow = high_pow.wrapping_mul(base) % modulus;
1065 }
1066 }
1067 let mut positions = Vec::new();
1068 if win_hash == pat_hash && t[..m] == p[..] {
1069 positions.push(0);
1070 }
1071 for i in 1..=(n - m) {
1072 win_hash = (win_hash
1073 + modulus.wrapping_sub((t[i - 1] as u64).wrapping_mul(high_pow) % modulus))
1074 .wrapping_mul(base)
1075 .wrapping_add(t[i + m - 1] as u64)
1076 % modulus;
1077 if win_hash == pat_hash && t[i..i + m] == p[..] {
1078 positions.push(i);
1079 }
1080 }
1081 positions
1082}
1083pub fn str_ext2_suffix_array(s: &str) -> Vec<usize> {
1084 let n = s.len();
1085 let mut sa: Vec<usize> = (0..n).collect();
1086 sa.sort_by(|&a, &b| s[a..].cmp(&s[b..]));
1087 sa
1088}
1089pub fn str_ext2_lcp_array(s: &str, sa: &[usize]) -> Vec<usize> {
1090 let n = s.len();
1091 if n == 0 {
1092 return vec![];
1093 }
1094 let chars: Vec<char> = s.chars().collect();
1095 let mut rank = vec![0usize; n];
1096 for (i, &sai) in sa.iter().enumerate() {
1097 rank[sai] = i;
1098 }
1099 let mut lcp = vec![0usize; n];
1100 let mut h = 0usize;
1101 for i in 0..n {
1102 if rank[i] > 0 {
1103 let j = sa[rank[i] - 1];
1104 while i + h < n && j + h < n && chars[i + h] == chars[j + h] {
1105 h += 1;
1106 }
1107 lcp[rank[i]] = h;
1108 if h > 0 {
1109 h -= 1;
1110 }
1111 }
1112 }
1113 lcp
1114}
1115pub fn str_ext2_aho_corasick_naive(text: &str, patterns: &[&str]) -> Vec<(usize, usize)> {
1116 let mut results = Vec::new();
1117 for (pat_idx, &pattern) in patterns.iter().enumerate() {
1118 if pattern.is_empty() {
1119 continue;
1120 }
1121 let mut start = 0;
1122 while let Some(pos) = text[start..].find(pattern) {
1123 results.push((start + pos, pat_idx));
1124 start += pos + 1;
1125 }
1126 }
1127 results.sort();
1128 results
1129}
1130pub fn str_ext2_as_list_char_ty() -> Expr {
1131 use oxilean_kernel::BinderInfo;
1132 let _type1 = Expr::Sort(Level::succ(Level::zero()));
1133 Expr::Pi(
1134 BinderInfo::Default,
1135 Name::str("s"),
1136 Box::new(Expr::Const(Name::str("String"), vec![])),
1137 Box::new(Expr::App(
1138 Box::new(Expr::Const(Name::str("List"), vec![])),
1139 Box::new(Expr::Const(Name::str("Char"), vec![])),
1140 )),
1141 )
1142}
1143pub fn str_ext2_concat_assoc_ty() -> Expr {
1144 use oxilean_kernel::BinderInfo;
1145 let s = || Expr::Const(Name::str("String"), vec![]);
1146 let arr = |a: Expr, b: Expr| {
1147 Expr::Pi(
1148 BinderInfo::Default,
1149 Name::str("_"),
1150 Box::new(a),
1151 Box::new(b),
1152 )
1153 };
1154 arr(s(), arr(s(), arr(s(), s())))
1155}
1156pub fn str_ext2_concat_left_id_ty() -> Expr {
1157 use oxilean_kernel::BinderInfo;
1158 let s = Expr::Const(Name::str("String"), vec![]);
1159 Expr::Pi(
1160 BinderInfo::Default,
1161 Name::str("s"),
1162 Box::new(s.clone()),
1163 Box::new(s),
1164 )
1165}
1166pub fn str_ext2_concat_right_id_ty() -> Expr {
1167 use oxilean_kernel::BinderInfo;
1168 let s = Expr::Const(Name::str("String"), vec![]);
1169 Expr::Pi(
1170 BinderInfo::Default,
1171 Name::str("s"),
1172 Box::new(s.clone()),
1173 Box::new(s),
1174 )
1175}
1176pub fn str_ext2_length_append_ty() -> Expr {
1177 use oxilean_kernel::BinderInfo;
1178 let s = || Expr::Const(Name::str("String"), vec![]);
1179 let n = || Expr::Const(Name::str("Nat"), vec![]);
1180 let arr = |a: Expr, b: Expr| {
1181 Expr::Pi(
1182 BinderInfo::Default,
1183 Name::str("_"),
1184 Box::new(a),
1185 Box::new(b),
1186 )
1187 };
1188 arr(s(), arr(s(), n()))
1189}
1190pub fn str_ext2_length_empty_ty() -> Expr {
1191 Expr::Const(Name::str("Nat"), vec![])
1192}
1193pub fn str_ext2_dec_eq_ty() -> Expr {
1194 use oxilean_kernel::BinderInfo;
1195 let s = || Expr::Const(Name::str("String"), vec![]);
1196 let b = Expr::Const(Name::str("Bool"), vec![]);
1197 Expr::Pi(
1198 BinderInfo::Default,
1199 Name::str("_"),
1200 Box::new(s()),
1201 Box::new(Expr::Pi(
1202 BinderInfo::Default,
1203 Name::str("_"),
1204 Box::new(s()),
1205 Box::new(b),
1206 )),
1207 )
1208}
1209pub fn str_ext2_lex_lt_irrefl_ty() -> Expr {
1210 use oxilean_kernel::BinderInfo;
1211 let s = Expr::Const(Name::str("String"), vec![]);
1212 let b = Expr::Const(Name::str("Bool"), vec![]);
1213 Expr::Pi(
1214 BinderInfo::Default,
1215 Name::str("s"),
1216 Box::new(s),
1217 Box::new(b),
1218 )
1219}
1220pub fn str_ext2_lex_lt_trans_ty() -> Expr {
1221 use oxilean_kernel::BinderInfo;
1222 let s = || Expr::Const(Name::str("String"), vec![]);
1223 let b = Expr::Const(Name::str("Bool"), vec![]);
1224 let arr = |a: Expr, b: Expr| {
1225 Expr::Pi(
1226 BinderInfo::Default,
1227 Name::str("_"),
1228 Box::new(a),
1229 Box::new(b),
1230 )
1231 };
1232 arr(s(), arr(s(), arr(s(), b)))
1233}
1234pub fn str_ext2_lex_total_ty() -> Expr {
1235 use oxilean_kernel::BinderInfo;
1236 let s = || Expr::Const(Name::str("String"), vec![]);
1237 let b = Expr::Const(Name::str("Bool"), vec![]);
1238 let arr = |a: Expr, b: Expr| {
1239 Expr::Pi(
1240 BinderInfo::Default,
1241 Name::str("_"),
1242 Box::new(a),
1243 Box::new(b),
1244 )
1245 };
1246 arr(s(), arr(s(), b))
1247}
1248pub fn str_ext2_substring_ty() -> Expr {
1249 use oxilean_kernel::BinderInfo;
1250 let s = Expr::Const(Name::str("String"), vec![]);
1251 let n = || Expr::Const(Name::str("Nat"), vec![]);
1252 let arr = |a: Expr, b: Expr| {
1253 Expr::Pi(
1254 BinderInfo::Default,
1255 Name::str("_"),
1256 Box::new(a),
1257 Box::new(b),
1258 )
1259 };
1260 arr(s.clone(), arr(n(), arr(n(), s)))
1261}
1262pub fn str_ext2_slice_len_ty() -> Expr {
1263 use oxilean_kernel::BinderInfo;
1264 let n = || Expr::Const(Name::str("Nat"), vec![]);
1265 let s = Expr::Const(Name::str("String"), vec![]);
1266 let arr = |a: Expr, b: Expr| {
1267 Expr::Pi(
1268 BinderInfo::Default,
1269 Name::str("_"),
1270 Box::new(a),
1271 Box::new(b),
1272 )
1273 };
1274 arr(s, arr(n(), arr(n(), n())))
1275}
1276pub fn str_ext2_prefix_refl_ty() -> Expr {
1277 use oxilean_kernel::BinderInfo;
1278 let s = Expr::Const(Name::str("String"), vec![]);
1279 let b = Expr::Const(Name::str("Bool"), vec![]);
1280 Expr::Pi(
1281 BinderInfo::Default,
1282 Name::str("s"),
1283 Box::new(s),
1284 Box::new(b),
1285 )
1286}
1287pub fn str_ext2_prefix_trans_ty() -> Expr {
1288 use oxilean_kernel::BinderInfo;
1289 let s = || Expr::Const(Name::str("String"), vec![]);
1290 let b = Expr::Const(Name::str("Bool"), vec![]);
1291 let arr = |a: Expr, b: Expr| {
1292 Expr::Pi(
1293 BinderInfo::Default,
1294 Name::str("_"),
1295 Box::new(a),
1296 Box::new(b),
1297 )
1298 };
1299 arr(s(), arr(s(), arr(s(), b)))
1300}
1301pub fn str_ext2_suffix_refl_ty() -> Expr {
1302 use oxilean_kernel::BinderInfo;
1303 let s = Expr::Const(Name::str("String"), vec![]);
1304 let b = Expr::Const(Name::str("Bool"), vec![]);
1305 Expr::Pi(
1306 BinderInfo::Default,
1307 Name::str("s"),
1308 Box::new(s),
1309 Box::new(b),
1310 )
1311}
1312pub fn str_ext2_split_join_ty() -> Expr {
1313 use oxilean_kernel::BinderInfo;
1314 let s = || Expr::Const(Name::str("String"), vec![]);
1315 let arr = |a: Expr, b: Expr| {
1316 Expr::Pi(
1317 BinderInfo::Default,
1318 Name::str("_"),
1319 Box::new(a),
1320 Box::new(b),
1321 )
1322 };
1323 arr(s(), arr(s(), s()))
1324}
1325pub fn str_ext2_join_split_ty() -> Expr {
1326 use oxilean_kernel::BinderInfo;
1327 let s = || Expr::Const(Name::str("String"), vec![]);
1328 let lst = || {
1329 Expr::App(
1330 Box::new(Expr::Const(Name::str("List"), vec![])),
1331 Box::new(s()),
1332 )
1333 };
1334 let arr = |a: Expr, b: Expr| {
1335 Expr::Pi(
1336 BinderInfo::Default,
1337 Name::str("_"),
1338 Box::new(a),
1339 Box::new(b),
1340 )
1341 };
1342 arr(s(), arr(lst(), lst()))
1343}
1344pub fn str_ext2_trim_idempotent_ty() -> Expr {
1345 use oxilean_kernel::BinderInfo;
1346 let s = Expr::Const(Name::str("String"), vec![]);
1347 Expr::Pi(
1348 BinderInfo::Default,
1349 Name::str("s"),
1350 Box::new(s.clone()),
1351 Box::new(s),
1352 )
1353}
1354pub fn str_ext2_to_upper_idempotent_ty() -> Expr {
1355 use oxilean_kernel::BinderInfo;
1356 let s = Expr::Const(Name::str("String"), vec![]);
1357 Expr::Pi(
1358 BinderInfo::Default,
1359 Name::str("s"),
1360 Box::new(s.clone()),
1361 Box::new(s),
1362 )
1363}
1364pub fn str_ext2_to_lower_idempotent_ty() -> Expr {
1365 use oxilean_kernel::BinderInfo;
1366 let s = Expr::Const(Name::str("String"), vec![]);
1367 Expr::Pi(
1368 BinderInfo::Default,
1369 Name::str("s"),
1370 Box::new(s.clone()),
1371 Box::new(s),
1372 )
1373}
1374pub fn str_ext2_contains_refl_ty() -> Expr {
1375 use oxilean_kernel::BinderInfo;
1376 let s = Expr::Const(Name::str("String"), vec![]);
1377 let b = Expr::Const(Name::str("Bool"), vec![]);
1378 Expr::Pi(
1379 BinderInfo::Default,
1380 Name::str("s"),
1381 Box::new(s),
1382 Box::new(b),
1383 )
1384}
1385pub fn str_ext2_starts_with_refl_ty() -> Expr {
1386 use oxilean_kernel::BinderInfo;
1387 let s = Expr::Const(Name::str("String"), vec![]);
1388 let b = Expr::Const(Name::str("Bool"), vec![]);
1389 Expr::Pi(
1390 BinderInfo::Default,
1391 Name::str("s"),
1392 Box::new(s),
1393 Box::new(b),
1394 )
1395}
1396pub fn str_ext2_ends_with_refl_ty() -> Expr {
1397 use oxilean_kernel::BinderInfo;
1398 let s = Expr::Const(Name::str("String"), vec![]);
1399 let b = Expr::Const(Name::str("Bool"), vec![]);
1400 Expr::Pi(
1401 BinderInfo::Default,
1402 Name::str("s"),
1403 Box::new(s),
1404 Box::new(b),
1405 )
1406}
1407pub fn str_ext2_find_replace_ty() -> Expr {
1408 use oxilean_kernel::BinderInfo;
1409 let s = || Expr::Const(Name::str("String"), vec![]);
1410 let arr = |a: Expr, b: Expr| {
1411 Expr::Pi(
1412 BinderInfo::Default,
1413 Name::str("_"),
1414 Box::new(a),
1415 Box::new(b),
1416 )
1417 };
1418 arr(s(), arr(s(), arr(s(), s())))
1419}
1420pub fn str_ext2_replace_id_ty() -> Expr {
1421 use oxilean_kernel::BinderInfo;
1422 let s = || Expr::Const(Name::str("String"), vec![]);
1423 let arr = |a: Expr, b: Expr| {
1424 Expr::Pi(
1425 BinderInfo::Default,
1426 Name::str("_"),
1427 Box::new(a),
1428 Box::new(b),
1429 )
1430 };
1431 arr(s(), arr(s(), s()))
1432}
1433pub fn str_ext2_unicode_valid_ty() -> Expr {
1434 use oxilean_kernel::BinderInfo;
1435 let s = Expr::Const(Name::str("String"), vec![]);
1436 let b = Expr::Const(Name::str("Bool"), vec![]);
1437 Expr::Pi(
1438 BinderInfo::Default,
1439 Name::str("s"),
1440 Box::new(s),
1441 Box::new(b),
1442 )
1443}
1444pub fn str_ext2_utf8_roundtrip_ty() -> Expr {
1445 use oxilean_kernel::BinderInfo;
1446 let s = Expr::Const(Name::str("String"), vec![]);
1447 Expr::Pi(
1448 BinderInfo::Default,
1449 Name::str("s"),
1450 Box::new(s.clone()),
1451 Box::new(s),
1452 )
1453}
1454pub fn str_ext2_utf16_len_ty() -> Expr {
1455 use oxilean_kernel::BinderInfo;
1456 let s = Expr::Const(Name::str("String"), vec![]);
1457 let n = Expr::Const(Name::str("Nat"), vec![]);
1458 Expr::Pi(
1459 BinderInfo::Default,
1460 Name::str("s"),
1461 Box::new(s),
1462 Box::new(n),
1463 )
1464}
1465pub fn str_ext2_char_to_string_ty() -> Expr {
1466 use oxilean_kernel::BinderInfo;
1467 let c = Expr::Const(Name::str("Char"), vec![]);
1468 let s = Expr::Const(Name::str("String"), vec![]);
1469 Expr::Pi(
1470 BinderInfo::Default,
1471 Name::str("c"),
1472 Box::new(c),
1473 Box::new(s),
1474 )
1475}
1476pub fn str_ext2_string_to_nat_ty() -> Expr {
1477 use oxilean_kernel::BinderInfo;
1478 let s = Expr::Const(Name::str("String"), vec![]);
1479 let opt_n = Expr::App(
1480 Box::new(Expr::Const(Name::str("Option"), vec![])),
1481 Box::new(Expr::Const(Name::str("Nat"), vec![])),
1482 );
1483 Expr::Pi(
1484 BinderInfo::Default,
1485 Name::str("s"),
1486 Box::new(s),
1487 Box::new(opt_n),
1488 )
1489}
1490pub fn str_ext2_format_parse_ty() -> Expr {
1491 use oxilean_kernel::BinderInfo;
1492 let s = Expr::Const(Name::str("String"), vec![]);
1493 let opt_s = Expr::App(
1494 Box::new(Expr::Const(Name::str("Option"), vec![])),
1495 Box::new(s.clone()),
1496 );
1497 Expr::Pi(
1498 BinderInfo::Default,
1499 Name::str("s"),
1500 Box::new(s),
1501 Box::new(opt_s),
1502 )
1503}
1504pub fn str_ext2_hash_consistent_ty() -> Expr {
1505 use oxilean_kernel::BinderInfo;
1506 let s = || Expr::Const(Name::str("String"), vec![]);
1507 let n = Expr::Const(Name::str("Nat"), vec![]);
1508 let arr = |a: Expr, b: Expr| {
1509 Expr::Pi(
1510 BinderInfo::Default,
1511 Name::str("_"),
1512 Box::new(a),
1513 Box::new(b),
1514 )
1515 };
1516 arr(s(), arr(s(), n))
1517}
1518pub fn str_ext2_hash_det_ty() -> Expr {
1519 use oxilean_kernel::BinderInfo;
1520 let s = Expr::Const(Name::str("String"), vec![]);
1521 let n = Expr::Const(Name::str("Nat"), vec![]);
1522 Expr::Pi(
1523 BinderInfo::Default,
1524 Name::str("s"),
1525 Box::new(s),
1526 Box::new(n),
1527 )
1528}
1529pub fn str_ext2_kmp_correct_ty() -> Expr {
1530 use oxilean_kernel::BinderInfo;
1531 let s = || Expr::Const(Name::str("String"), vec![]);
1532 let lst = || {
1533 Expr::App(
1534 Box::new(Expr::Const(Name::str("List"), vec![])),
1535 Box::new(Expr::Const(Name::str("Nat"), vec![])),
1536 )
1537 };
1538 let arr = |a: Expr, b: Expr| {
1539 Expr::Pi(
1540 BinderInfo::Default,
1541 Name::str("_"),
1542 Box::new(a),
1543 Box::new(b),
1544 )
1545 };
1546 arr(s(), arr(s(), lst()))
1547}
1548pub fn str_ext2_rabin_karp_correct_ty() -> Expr {
1549 use oxilean_kernel::BinderInfo;
1550 let s = || Expr::Const(Name::str("String"), vec![]);
1551 let lst = || {
1552 Expr::App(
1553 Box::new(Expr::Const(Name::str("List"), vec![])),
1554 Box::new(Expr::Const(Name::str("Nat"), vec![])),
1555 )
1556 };
1557 let arr = |a: Expr, b: Expr| {
1558 Expr::Pi(
1559 BinderInfo::Default,
1560 Name::str("_"),
1561 Box::new(a),
1562 Box::new(b),
1563 )
1564 };
1565 arr(s(), arr(s(), lst()))
1566}
1567pub fn str_ext2_aho_corasick_ty() -> Expr {
1568 use oxilean_kernel::BinderInfo;
1569 let s = || Expr::Const(Name::str("String"), vec![]);
1570 let lst = || {
1571 Expr::App(
1572 Box::new(Expr::Const(Name::str("List"), vec![])),
1573 Box::new(Expr::Const(Name::str("Nat"), vec![])),
1574 )
1575 };
1576 let arr = |a: Expr, b: Expr| {
1577 Expr::Pi(
1578 BinderInfo::Default,
1579 Name::str("_"),
1580 Box::new(a),
1581 Box::new(b),
1582 )
1583 };
1584 arr(s(), lst())
1585}
1586pub fn str_ext2_suffix_array_sorted_ty() -> Expr {
1587 use oxilean_kernel::BinderInfo;
1588 let s = Expr::Const(Name::str("String"), vec![]);
1589 let lst = Expr::App(
1590 Box::new(Expr::Const(Name::str("List"), vec![])),
1591 Box::new(Expr::Const(Name::str("Nat"), vec![])),
1592 );
1593 Expr::Pi(
1594 BinderInfo::Default,
1595 Name::str("s"),
1596 Box::new(s),
1597 Box::new(lst),
1598 )
1599}
1600pub fn str_ext2_lcp_array_ty() -> Expr {
1601 use oxilean_kernel::BinderInfo;
1602 let s = Expr::Const(Name::str("String"), vec![]);
1603 let n = Expr::Const(Name::str("Nat"), vec![]);
1604 Expr::Pi(
1605 BinderInfo::Default,
1606 Name::str("s"),
1607 Box::new(s),
1608 Box::new(n),
1609 )
1610}
1611pub fn str_ext2_edit_dist_zero_ty() -> Expr {
1612 use oxilean_kernel::BinderInfo;
1613 let s = Expr::Const(Name::str("String"), vec![]);
1614 let n = Expr::Const(Name::str("Nat"), vec![]);
1615 Expr::Pi(
1616 BinderInfo::Default,
1617 Name::str("s"),
1618 Box::new(s),
1619 Box::new(n),
1620 )
1621}
1622pub fn str_ext2_edit_dist_sym_ty() -> Expr {
1623 use oxilean_kernel::BinderInfo;
1624 let s = || Expr::Const(Name::str("String"), vec![]);
1625 let n = Expr::Const(Name::str("Nat"), vec![]);
1626 let arr = |a: Expr, b: Expr| {
1627 Expr::Pi(
1628 BinderInfo::Default,
1629 Name::str("_"),
1630 Box::new(a),
1631 Box::new(b),
1632 )
1633 };
1634 arr(s(), arr(s(), n))
1635}
1636pub fn str_ext2_edit_dist_triangle_ty() -> Expr {
1637 use oxilean_kernel::BinderInfo;
1638 let s = || Expr::Const(Name::str("String"), vec![]);
1639 let b = Expr::Const(Name::str("Bool"), vec![]);
1640 let arr = |a: Expr, b: Expr| {
1641 Expr::Pi(
1642 BinderInfo::Default,
1643 Name::str("_"),
1644 Box::new(a),
1645 Box::new(b),
1646 )
1647 };
1648 arr(s(), arr(s(), arr(s(), b)))
1649}
1650pub fn register_string_extended_axioms(env: &mut Environment) {
1652 let axioms: &[(&str, fn() -> Expr)] = &[
1653 ("String.Ext.AsListChar", str_ext2_as_list_char_ty),
1654 ("String.Ext.ConcatAssoc", str_ext2_concat_assoc_ty),
1655 ("String.Ext.ConcatLeftId", str_ext2_concat_left_id_ty),
1656 ("String.Ext.ConcatRightId", str_ext2_concat_right_id_ty),
1657 ("String.Ext.LengthAppend", str_ext2_length_append_ty),
1658 ("String.Ext.LengthEmpty", str_ext2_length_empty_ty),
1659 ("String.Ext.DecEq", str_ext2_dec_eq_ty),
1660 ("String.Ext.LexLtIrrefl", str_ext2_lex_lt_irrefl_ty),
1661 ("String.Ext.LexLtTrans", str_ext2_lex_lt_trans_ty),
1662 ("String.Ext.LexTotal", str_ext2_lex_total_ty),
1663 ("String.Ext.Substring", str_ext2_substring_ty),
1664 ("String.Ext.SliceLen", str_ext2_slice_len_ty),
1665 ("String.Ext.PrefixRefl", str_ext2_prefix_refl_ty),
1666 ("String.Ext.PrefixTrans", str_ext2_prefix_trans_ty),
1667 ("String.Ext.SuffixRefl", str_ext2_suffix_refl_ty),
1668 ("String.Ext.SplitJoin", str_ext2_split_join_ty),
1669 ("String.Ext.JoinSplit", str_ext2_join_split_ty),
1670 ("String.Ext.TrimIdempotent", str_ext2_trim_idempotent_ty),
1671 (
1672 "String.Ext.ToUpperIdempotent",
1673 str_ext2_to_upper_idempotent_ty,
1674 ),
1675 (
1676 "String.Ext.ToLowerIdempotent",
1677 str_ext2_to_lower_idempotent_ty,
1678 ),
1679 ("String.Ext.ContainsRefl", str_ext2_contains_refl_ty),
1680 ("String.Ext.StartsWithRefl", str_ext2_starts_with_refl_ty),
1681 ("String.Ext.EndsWithRefl", str_ext2_ends_with_refl_ty),
1682 ("String.Ext.FindReplace", str_ext2_find_replace_ty),
1683 ("String.Ext.ReplaceId", str_ext2_replace_id_ty),
1684 ("String.Ext.UnicodeValid", str_ext2_unicode_valid_ty),
1685 ("String.Ext.Utf8Roundtrip", str_ext2_utf8_roundtrip_ty),
1686 ("String.Ext.Utf16Len", str_ext2_utf16_len_ty),
1687 ("String.Ext.CharToString", str_ext2_char_to_string_ty),
1688 ("String.Ext.StringToNat", str_ext2_string_to_nat_ty),
1689 ("String.Ext.FormatParse", str_ext2_format_parse_ty),
1690 ("String.Ext.HashConsistent", str_ext2_hash_consistent_ty),
1691 ("String.Ext.HashDet", str_ext2_hash_det_ty),
1692 ("String.Ext.KmpCorrect", str_ext2_kmp_correct_ty),
1693 (
1694 "String.Ext.RabinKarpCorrect",
1695 str_ext2_rabin_karp_correct_ty,
1696 ),
1697 ("String.Ext.AhoCorasick", str_ext2_aho_corasick_ty),
1698 (
1699 "String.Ext.SuffixArraySorted",
1700 str_ext2_suffix_array_sorted_ty,
1701 ),
1702 ("String.Ext.LcpArray", str_ext2_lcp_array_ty),
1703 ("String.Ext.EditDistZero", str_ext2_edit_dist_zero_ty),
1704 ("String.Ext.EditDistSym", str_ext2_edit_dist_sym_ty),
1705 (
1706 "String.Ext.EditDistTriangle",
1707 str_ext2_edit_dist_triangle_ty,
1708 ),
1709 ];
1710 for (name, ty_fn) in axioms {
1711 let _ = env.add(Declaration::Axiom {
1712 name: Name::str(*name),
1713 univ_params: vec![],
1714 ty: ty_fn(),
1715 });
1716 }
1717}
1718pub fn str_concat_assoc_check(a: &str, b: &str, c: &str) -> bool {
1720 let left = format!("{}{}{}", a, b, c);
1721 let right = format!("{}{}{}", a, b, c);
1722 left == right
1723}
1724pub fn str_left_id_check(s: &str) -> bool {
1726 format!("{}{}", "", s) == s
1727}
1728pub fn str_right_id_check(s: &str) -> bool {
1730 format!("{}{}", s, "") == s
1731}
1732pub fn str_length_additive(s: &str, t: &str) -> bool {
1734 let combined = format!("{}{}", s, t);
1735 combined.chars().count() == s.chars().count() + t.chars().count()
1736}
1737pub fn str_lex_lt(a: &str, b: &str) -> bool {
1739 a < b
1740}
1741pub fn str_lex_lt_irrefl(s: &str) -> bool {
1743 !str_lex_lt(s, s)
1744}
1745pub fn str_lex_lt_trans(a: &str, b: &str, c: &str) -> bool {
1747 if str_lex_lt(a, b) && str_lex_lt(b, c) {
1748 str_lex_lt(a, c)
1749 } else {
1750 true
1751 }
1752}
1753pub fn str_substring(s: &str, start: usize, len: usize) -> String {
1755 s.chars().skip(start).take(len).collect()
1756}
1757pub fn str_starts_with(s: &str, prefix: &str) -> bool {
1759 s.starts_with(prefix)
1760}
1761pub fn str_ends_with(s: &str, suffix: &str) -> bool {
1763 s.ends_with(suffix)
1764}
1765pub fn str_prefix_refl(s: &str) -> bool {
1767 s.starts_with(s)
1768}
1769pub fn str_prefix_trans(a: &str, b: &str, c: &str) -> bool {
1771 if b.starts_with(a) && c.starts_with(b) {
1772 c.starts_with(a)
1773 } else {
1774 true
1775 }
1776}
1777pub fn str_split_join_roundtrip(s: &str, sep: &str) -> bool {
1779 if sep.is_empty() {
1780 return true;
1781 }
1782 let parts: Vec<&str> = s.split(sep).collect();
1783 let rejoined = parts.join(sep);
1784 rejoined == s
1785}
1786pub fn str_trim_idempotent(s: &str) -> bool {
1788 let trimmed = s.trim();
1789 trimmed.trim() == trimmed
1790}
1791pub fn str_to_upper_idempotent(s: &str) -> bool {
1793 let upper = s.to_uppercase();
1794 upper.to_uppercase() == upper
1795}
1796pub fn str_to_lower_idempotent(s: &str) -> bool {
1798 let lower = s.to_lowercase();
1799 lower.to_lowercase() == lower
1800}
1801pub fn str_contains_refl(s: &str) -> bool {
1803 s.contains(s)
1804}
1805pub fn str_find_replace(s: &str, from: &str, to: &str) -> String {
1807 if from.is_empty() {
1808 return s.to_string();
1809 }
1810 s.replace(from, to)
1811}
1812pub fn str_replace_id(s: &str, p: &str) -> bool {
1814 str_find_replace(s, p, p) == s
1815}
1816pub fn str_utf8_roundtrip(s: &str) -> bool {
1818 let encoded = s.as_bytes();
1819 String::from_utf8(encoded.to_vec()).as_deref() == Ok(s)
1820}
1821pub fn str_utf16_len(s: &str) -> usize {
1823 s.encode_utf16().count()
1824}
1825pub fn str_char_to_string(c: char) -> String {
1827 c.to_string()
1828}
1829pub fn str_hash_consistent(a: &str, b: &str) -> bool {
1831 if a == b {
1832 use super::functions::*;
1833 use std::collections::hash_map::DefaultHasher;
1834 use std::fmt;
1835 use std::hash::{Hash, Hasher};
1836 let mut h1 = DefaultHasher::new();
1837 let mut h2 = DefaultHasher::new();
1838 a.hash(&mut h1);
1839 b.hash(&mut h2);
1840 h1.finish() == h2.finish()
1841 } else {
1842 true
1843 }
1844}
1845pub fn str_kmp_search(text: &str, pattern: &str) -> Vec<usize> {
1847 str_ext2_kmp_search(text, pattern)
1848}
1849pub fn str_rabin_karp_search(text: &str, pattern: &str) -> Vec<usize> {
1851 str_ext2_rabin_karp(text, pattern, 31, 1_000_000_007)
1852}
1853pub fn str_aho_corasick_search(text: &str, patterns: &[&str]) -> Vec<(usize, usize)> {
1855 str_ext2_aho_corasick_naive(text, patterns)
1856}
1857pub fn str_suffix_array(s: &str) -> Vec<usize> {
1859 str_ext2_suffix_array(s)
1860}
1861pub fn str_lcp_array(s: &str) -> Vec<usize> {
1863 let sa = str_suffix_array(s);
1864 str_ext2_lcp_array(s, &sa)
1865}
1866pub fn str_levenshtein(a: &str, b: &str) -> usize {
1868 str_ext2_levenshtein(a, b)
1869}
1870pub fn str_edit_dist_zero(s: &str) -> bool {
1872 str_levenshtein(s, s) == 0
1873}
1874pub fn str_edit_dist_sym(a: &str, b: &str) -> bool {
1876 str_levenshtein(a, b) == str_levenshtein(b, a)
1877}
1878pub fn str_edit_dist_triangle(a: &str, b: &str, c: &str) -> bool {
1880 str_levenshtein(a, c) <= str_levenshtein(a, b) + str_levenshtein(b, c)
1881}
1882pub fn str_kmp_matches_naive(text: &str, pattern: &str) -> bool {
1884 let kmp = str_kmp_search(text, pattern);
1885 let naive = SubstringFinder2::new(text, pattern).find_all();
1886 kmp == naive
1887}
1888pub fn str_rabin_karp_matches_kmp(text: &str, pattern: &str) -> bool {
1890 let rk = str_rabin_karp_search(text, pattern);
1891 let kmp = str_kmp_search(text, pattern);
1892 rk == kmp
1893}
1894pub fn str_suffix_array_sorted(s: &str) -> bool {
1896 let sa = str_suffix_array(s);
1897 for i in 1..sa.len() {
1898 if s[sa[i - 1]..] > s[sa[i]..] {
1899 return false;
1900 }
1901 }
1902 true
1903}
1904pub fn str_suffix_array_len(s: &str) -> bool {
1906 str_suffix_array(s).len() == s.len()
1907}
1908pub fn str_unicode_valid(s: &str) -> bool {
1910 std::str::from_utf8(s.as_bytes()).is_ok()
1911}
1912pub fn str_count_alnum(s: &str) -> usize {
1914 s.chars().filter(|c| c.is_alphanumeric()).count()
1915}
1916pub fn str_count_whitespace(s: &str) -> usize {
1918 s.chars().filter(|c| c.is_whitespace()).count()
1919}
1920pub fn str_dec_eq_refl(s: &str) -> bool {
1922 #[allow(clippy::eq_op)]
1923 let result = s == s;
1924 result
1925}
1926pub fn str_dec_eq_sym(a: &str, b: &str) -> bool {
1928 #[allow(clippy::eq_op)]
1929 let result = (a == b) == (b == a);
1930 result
1931}