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