runic_kit/
span.rs

1//! This module defines the `Span` struct, which represents a span of text in a source file.
2//! It also provides utilities for working with spans.
3
4/// A `Span` represents a contiguous region in a source file, defined by its start and end byte indices.
5#[derive(Debug)]
6pub struct Span {
7    /// The starting byte index of the span (inclusive).
8    pub start: usize,
9    /// The ending byte index of the span (exclusive).
10    pub end: usize,
11}
12
13impl Span {
14    /// Creates a new `Span` from the given start and end byte indices.
15    ///
16    /// # Panics
17    ///
18    /// Panics if `start` is greater than or equal to `end`.
19    pub fn new(start: usize, end: usize) -> Self {
20        assert!(start < end, "Span start must be less than end");
21        Span { start, end }
22    }
23}
24
25/// Converts a byte index in the source string to a (line, column) tuple.
26///
27/// Lines and columns are 1-based.
28///
29/// Column of the newline character is + 1 of the last character in the line.
30///
31/// # Usage
32///
33/// ```rust
34/// use runic_kit::span::location_to_line_col;
35///
36/// let source = "Hello\nWorld";
37/// let index = 6; // Byte index of 'W'
38/// let (line, col) = location_to_line_col(source, index);
39/// assert_eq!((line, col), (2, 1)); // 'W' is on line 2, column 1
40/// ```
41pub fn location_to_line_col(source: &str, index: usize) -> (usize, usize) {
42    let mut line = 1;
43    let mut col = 1;
44
45    for (i, c) in source.char_indices() {
46        if i == index {
47            break;
48        }
49
50        if c == '\n' {
51            line += 1;
52            col = 1;
53        } else {
54            col += 1;
55        }
56    }
57
58    (line, col)
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_span_new() {
67        let span = Span::new(5, 10);
68        assert_eq!(span.start, 5);
69        assert_eq!(span.end, 10);
70    }
71
72    #[test]
73    #[should_panic(expected = "Span start must be less than end")]
74    fn test_span_new_invalid() {
75        Span::new(10, 5);
76    }
77
78    #[test]
79    fn test_location_to_line_col() {
80        let source = "Hello\nWorld";
81        assert_eq!(location_to_line_col(source, 0), (1, 1)); // 'H'
82        assert_eq!(location_to_line_col(source, 4), (1, 5)); // 'o'
83        assert_eq!(location_to_line_col(source, 5), (1, 6)); // '\n'
84        assert_eq!(location_to_line_col(source, 6), (2, 1)); // 'W'
85        assert_eq!(location_to_line_col(source, 10), (2, 5)); // 'd'
86    }
87}