varar_core/offsets.rs
1//! UTF-16 offset helpers ā the conversion layer the Python port needed and Java
2//! did not. All spans/offsets in the shared conformance goldens are UTF-16
3//! code-unit offsets (Java `String`/`char` are UTF-16 natively); Rust `str` is
4//! UTF-8, so byte offsets from `str::find`/the `regex` crate must be converted
5//! to UTF-16 at every span-production site. Byte offsets exist only as transient
6//! locals; every *stored* offset is UTF-16.
7
8/// UTF-16 code-unit length of `s` (Java `String.length()`).
9pub fn utf16_len(s: &str) -> usize {
10 s.chars().map(char::len_utf16).sum()
11}
12
13/// Converts a byte index within `s` to a UTF-16 code-unit offset.
14/// `byte_idx` must fall on a `char` boundary.
15pub fn utf16_index(s: &str, byte_idx: usize) -> usize {
16 utf16_len(&s[..byte_idx])
17}
18
19/// Converts a UTF-16 code-unit offset within `s` to a byte index. Clamps to
20/// `s.len()` when `u16_idx` runs past the end (mirrors JS `String.slice`).
21pub fn byte_index(s: &str, u16_idx: usize) -> usize {
22 let mut u16 = 0usize;
23 for (byte, c) in s.char_indices() {
24 if u16 >= u16_idx {
25 return byte;
26 }
27 u16 += c.len_utf16();
28 }
29 s.len()
30}
31
32/// Java `s.substring(startU16, endU16)` with UTF-16 indices.
33pub fn utf16_slice(s: &str, start_u16: usize, end_u16: usize) -> &str {
34 let start = byte_index(s, start_u16);
35 let end = byte_index(s, end_u16);
36 &s[start..end]
37}
38
39/// Java `String.trim()`: strips leading/trailing chars `<= U+0020`.
40pub fn java_trim(s: &str) -> &str {
41 s.trim_matches(|c: char| (c as u32) <= 0x20)
42}
43
44/// Java `String.strip()`: strips leading/trailing `Character.isWhitespace`.
45pub fn java_strip(s: &str) -> &str {
46 s.trim_matches(is_java_whitespace)
47}
48
49/// Java `String.stripLeading()`.
50pub fn java_strip_leading(s: &str) -> &str {
51 s.trim_start_matches(is_java_whitespace)
52}
53
54/// Java `Character.isWhitespace`: Unicode whitespace excluding the no-break
55/// spaces U+00A0/U+2007/U+202F, plus the separator range U+001CāU+001F.
56fn is_java_whitespace(c: char) -> bool {
57 match c {
58 '\u{00A0}' | '\u{2007}' | '\u{202F}' => false,
59 '\u{001C}'..='\u{001F}' => true,
60 _ => c.is_whitespace(),
61 }
62}