Skip to main content

flams_utils/sourcerefs/
convert.rs

1#![allow(clippy::cast_possible_truncation)]
2use std::marker::PhantomData;
3
4use super::{ByteOffset, CharOffset, LineCol, OffsetPosition, StringPosition, Utf16Offset};
5
6pub fn char_to_byte(c: CharOffset, source: &str) -> ByteOffset {
7    ByteOffset(source.chars().take(c.0).map(char::len_utf8).sum())
8}
9
10pub fn utf16_to_byte(c: Utf16Offset, source: &str) -> ByteOffset {
11    let mut curr = 0;
12    let mut ret = 0;
13    for u in source.chars() {
14        if curr >= c.0 {
15            break;
16        }
17        curr += u.len_utf16();
18        ret += u.len_utf8();
19    }
20    ByteOffset(ret)
21}
22
23pub fn linecolbyte_to_byte(c: LineCol<ByteOffset>, source: &str) -> ByteOffset {
24    let off = skip_lines(source, c.line as _);
25    ByteOffset(off + (c.col as usize))
26}
27
28pub fn linecolchar_to_byte(c: LineCol<CharOffset>, source: &str) -> ByteOffset {
29    let off = skip_lines(source, c.line as _) as _;
30    let source = &source[off..];
31    ByteOffset(off) + char_to_byte(CharOffset(c.col as usize), source)
32}
33
34pub fn linecolutf16_to_byte(c: LineCol<Utf16Offset>, source: &str) -> ByteOffset {
35    let off = skip_lines(source, c.line as _);
36    let source = &source[off..];
37    ByteOffset(off) + utf16_to_byte(Utf16Offset(c.col as _), source)
38}
39
40pub fn byte_to_char(c: ByteOffset, source: &str) -> CharOffset {
41    CharOffset(source[..c.0].chars().count())
42}
43
44pub fn utf16_to_char(c: Utf16Offset, source: &str) -> CharOffset {
45    let mut curr = 0;
46    let mut ret = 0;
47    for u in source.chars() {
48        if curr >= c.0 {
49            break;
50        }
51        curr += u.len_utf16();
52        ret += 1;
53    }
54    CharOffset(ret)
55}
56
57pub fn linecolbyte_to_char(lc: LineCol<ByteOffset>, source: &str) -> CharOffset {
58    // is it more efficient to iterate over chars directly?
59    let off = skip_lines(source, lc.line as _);
60    CharOffset(source[..off + (lc.col as usize)].chars().count())
61}
62
63pub fn linecolchar_to_char(lc: LineCol<CharOffset>, source: &str) -> CharOffset {
64    // is it more efficient to iterate over chars directly?
65    let off = skip_lines(source, lc.line as _);
66    CharOffset(source[..off].chars().count() + (lc.col as usize))
67}
68
69pub fn linecolutf16_to_char(lc: LineCol<Utf16Offset>, source: &str) -> CharOffset {
70    // is it more efficient to iterate over chars directly?
71    let off = skip_lines(source, lc.line as _);
72    let chars = source[..off].chars().count();
73    let source = &source[off..];
74    utf16_to_char(Utf16Offset(lc.col as _), source) + CharOffset(chars)
75}
76
77pub fn byte_to_utf16(c: ByteOffset, source: &str) -> Utf16Offset {
78    Utf16Offset(source[..c.0].chars().map(char::len_utf16).sum())
79}
80
81pub fn char_to_utf16(c: CharOffset, source: &str) -> Utf16Offset {
82    Utf16Offset(source.chars().take(c.0).map(char::len_utf16).sum())
83}
84
85pub fn linecolbyte_to_utf16(lc: LineCol<ByteOffset>, source: &str) -> Utf16Offset {
86    // is it more efficient to iterate over chars directly?
87    let off = skip_lines(source, lc.line as _);
88    Utf16Offset(
89        source[..off + (lc.col as usize)]
90            .chars()
91            .map(char::len_utf16)
92            .sum(),
93    )
94}
95
96pub fn linecolchar_to_utf16(lc: LineCol<CharOffset>, source: &str) -> Utf16Offset {
97    // is it more efficient to iterate over chars directly?
98    let off = skip_lines(source, lc.line as _);
99    Utf16Offset(
100        source[..off].chars().map(char::len_utf16).sum::<usize>()
101            + source[off..]
102                .chars()
103                .take(lc.col as _)
104                .map(char::len_utf16)
105                .sum::<usize>(),
106    )
107}
108
109pub fn linecolutf16_to_utf16(lc: LineCol<Utf16Offset>, source: &str) -> Utf16Offset {
110    let off = skip_lines(source, lc.line as _);
111    Utf16Offset(source[..off].chars().map(char::len_utf16).sum::<usize>() + (lc.col as usize))
112}
113
114// -----------------------------------------------------
115
116pub fn byte_to_linecol_byte(c: ByteOffset, source: &str) -> LineCol<ByteOffset> {
117    let source = &source[..c.0];
118    let (lines, off) = count_lines(source);
119    LineCol {
120        line: lines as _,
121        col: (source.len() - (off as usize)) as _,
122        __phantom: PhantomData,
123    }
124}
125
126pub fn char_to_linecol_byte(mut co: CharOffset, source: &str) -> LineCol<ByteOffset> {
127    let mut bytes = 0;
128    let mut line = 0;
129    let mut char_iter = source.chars().peekable();
130    while co.0 != 0
131        && let Some(c) = char_iter.next()
132    {
133        co.0 -= 1;
134        if c == '\n' {
135            bytes = 0;
136            line += 1;
137        } else if c == '\r' {
138            bytes = 0;
139            line += 1;
140            if char_iter.peek().copied() == Some('\n') {
141                let _ = char_iter.next();
142                co.0 -= 1;
143            }
144        } else {
145            bytes += c.len_utf8();
146        }
147    }
148    LineCol {
149        line,
150        col: bytes as _,
151        __phantom: PhantomData,
152    }
153}
154
155pub fn utf16_to_linecol_byte(mut co: Utf16Offset, source: &str) -> LineCol<ByteOffset> {
156    let mut bytes = 0;
157    let mut line = 0;
158    let mut char_iter = source.chars().peekable();
159    while co.0 != 0
160        && let Some(c) = char_iter.next()
161    {
162        co.0 -= c.len_utf16();
163        if c == '\n' {
164            bytes = 0;
165            line += 1;
166        } else if c == '\r' {
167            bytes = 0;
168            line += 1;
169            if char_iter.peek().copied() == Some('\n') {
170                let _ = char_iter.next();
171                co.0 -= 1;
172            }
173        } else {
174            bytes += c.len_utf8();
175        }
176    }
177    LineCol {
178        line,
179        col: bytes as _,
180        __phantom: PhantomData,
181    }
182}
183
184pub fn byte_to_linecol_char(c: ByteOffset, source: &str) -> LineCol<CharOffset> {
185    let source = &source[..c.0];
186    let (line, off) = count_lines(source);
187    LineCol {
188        line,
189        col: source[off as usize..].chars().count() as _,
190        __phantom: PhantomData,
191    }
192}
193
194pub fn char_to_linecol_char(c: CharOffset, source: &str) -> LineCol<CharOffset> {
195    let (line, bytes, total) = count_lines_chars(source, c.0);
196    LineCol {
197        line,
198        col: source[total - bytes as usize..total].chars().count() as _,
199        __phantom: PhantomData,
200    }
201}
202
203pub fn utf16_to_linecol_char(c: Utf16Offset, source: &str) -> LineCol<CharOffset> {
204    let (line, bytes, total) = count_lines_utf16(source, c.0);
205    LineCol {
206        line,
207        col: source[total - bytes as usize..total].chars().count() as _,
208        __phantom: PhantomData,
209    }
210}
211
212pub fn byte_to_linecol_utf16(c: ByteOffset, source: &str) -> LineCol<Utf16Offset> {
213    let source = &source[..c.0];
214    let (line, offset) = count_lines(source);
215    LineCol {
216        line,
217        col: source[offset as usize..]
218            .chars()
219            .map(|c| c.len_utf16() as u32)
220            .sum(),
221        __phantom: PhantomData,
222    }
223}
224
225pub fn char_to_linecol_utf16(c: CharOffset, source: &str) -> LineCol<Utf16Offset> {
226    let (line, bytes, total) = count_lines_chars(source, c.0);
227    LineCol {
228        line,
229        col: source[total - bytes as usize..total]
230            .chars()
231            .map(|c| c.len_utf16() as u32)
232            .sum(),
233        __phantom: PhantomData,
234    }
235}
236
237pub fn utf16_to_linecol_utf16(c: Utf16Offset, source: &str) -> LineCol<Utf16Offset> {
238    let (line, bytes, total) = count_lines_utf16(source, c.0);
239    LineCol {
240        line,
241        col: source[total - bytes as usize..total]
242            .chars()
243            .map(|c| c.len_utf16() as u32)
244            .sum(),
245
246        __phantom: PhantomData,
247    }
248}
249
250// -----------------------------------------------------
251
252pub fn linecol_to_linecol<P1: OffsetPosition, P2: OffsetPosition>(
253    lc: LineCol<P1>,
254    source: &str,
255) -> LineCol<P2>
256where
257    LineCol<P1>: StringPosition,
258    LineCol<P2>: StringPosition,
259{
260    let zero = LineCol {
261        line: lc.line,
262        col: 0,
263        __phantom: PhantomData,
264    };
265    let ByteOffset(offset) = linecolbyte_to_byte(zero, source);
266    let source = &source[offset..];
267    LineCol {
268        line: lc.line,
269        col: P2::len(source).into() as _,
270        __phantom: PhantomData,
271    }
272}
273
274// -----------------------------------------------------
275
276// line, byte-offset after last line break,total-bytes
277pub fn count_lines_chars(source: &str, mut chars: usize) -> (u32, u32, usize) {
278    let mut bytes: u32 = 0;
279    let mut line = 0;
280    let mut total: usize = 0;
281    let mut char_iter = source.chars().peekable();
282    while chars != 0
283        && let Some(c) = char_iter.next()
284    {
285        chars -= 1;
286        if c == '\n' {
287            total += (bytes + 1) as usize;
288            bytes = 0;
289            line += 1;
290        } else if c == '\r' {
291            total += (bytes + 1) as usize;
292            bytes = 0;
293            line += 1;
294            if char_iter.peek().copied() == Some('\n') {
295                total += 1;
296                let _ = char_iter.next();
297                chars -= 1;
298            }
299        } else {
300            bytes += c.len_utf8() as u32;
301        }
302    }
303    total += bytes as usize;
304    (line, bytes, total)
305}
306
307// line, byte-offset after last line break,total-bytes
308pub fn count_lines_utf16(source: &str, mut utfs: usize) -> (u32, u32, usize) {
309    let mut bytes: u32 = 0;
310    let mut line: u32 = 0;
311    let mut total: usize = 0;
312    let mut char_iter = source.chars().peekable();
313    while utfs != 0
314        && let Some(c) = char_iter.next()
315    {
316        utfs -= c.len_utf16();
317        if c == '\n' {
318            total += (bytes + 1) as usize;
319            bytes = 0;
320            line += 1;
321        } else if c == '\r' {
322            total += (bytes + 1) as usize;
323            bytes = 0;
324            line += 1;
325            if char_iter.peek().copied() == Some('\n') {
326                total += 1;
327                let _ = char_iter.next();
328                utfs -= 1;
329            }
330        } else {
331            bytes += c.len_utf8() as u32;
332        }
333    }
334    total += bytes as usize;
335    (line, bytes, total)
336}
337
338// (lines,offset of last line start)
339pub fn count_lines(source: &str) -> (u32, u32) {
340    let source = source.as_bytes();
341    let mut le_iter = memchr::memchr2_iter(b'\r', b'\n', source);
342    let mut off = 0;
343    let mut lines = 0;
344    while let Some(i) = le_iter.next() {
345        lines += 1;
346        let first = source[i];
347        if first == b'\r' && source.get(i + 1).copied() == Some(b'\n') {
348            off = i + 2;
349            let _ = le_iter.next();
350        } else {
351            off = i + 1;
352        }
353    }
354    (lines, off as _)
355}
356
357pub fn skip_lines(source: &str, mut lines: u32) -> usize {
358    let source = source.as_bytes();
359    let mut le_iter = memchr::memchr2_iter(b'\r', b'\n', source);
360    let mut off = 0;
361    while lines > 0
362        && let Some(i) = le_iter.next()
363    {
364        lines -= 1;
365        let first = source[i];
366        if first == b'\r' && source.get(i + 1).copied() == Some(b'\n') {
367            off = i + 2;
368            let _ = le_iter.next();
369        } else {
370            off = i + 1;
371        }
372    }
373    off
374}