rust_yaml/
position.rs

1//! Position tracking for YAML parsing
2
3use std::fmt;
4
5/// Represents a position in the YAML input stream
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub struct Position {
8    /// Line number (1-based)
9    pub line: usize,
10    /// Column number (1-based)
11    pub column: usize,
12    /// Byte index in the input (0-based)
13    pub index: usize,
14}
15
16impl Position {
17    /// Create a new position
18    pub const fn new() -> Self {
19        Self {
20            line: 1,
21            column: 1,
22            index: 0,
23        }
24    }
25
26    /// Create a position with specific values
27    pub const fn at(line: usize, column: usize, index: usize) -> Self {
28        Self {
29            line,
30            column,
31            index,
32        }
33    }
34
35    /// Create a position at the start of input
36    pub const fn start() -> Self {
37        Self::new()
38    }
39
40    /// Advance position by one character
41    pub const fn advance(self, ch: char) -> Self {
42        if ch == '\n' {
43            Self::at(self.line + 1, 1, self.index + ch.len_utf8())
44        } else {
45            Self::at(self.line, self.column + 1, self.index + ch.len_utf8())
46        }
47    }
48
49    /// Advance position by a string
50    pub fn advance_str(mut self, s: &str) -> Self {
51        for ch in s.chars() {
52            self = self.advance(ch);
53        }
54        self
55    }
56
57    /// Advance position by multiple characters
58    pub const fn advance_by(mut self, count: usize, is_newline: bool) -> Self {
59        if is_newline {
60            self.line += count;
61            self.column = 1;
62        } else {
63            self.column += count;
64        }
65        self.index += count;
66        self
67    }
68}
69
70impl fmt::Display for Position {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        write!(f, "line {}, column {}", self.line, self.column)
73    }
74}
75
76impl Default for Position {
77    fn default() -> Self {
78        Self::start()
79    }
80}
81
82#[cfg(feature = "serde")]
83impl serde::Serialize for Position {
84    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
85    where
86        S: serde::Serializer,
87    {
88        use serde::ser::SerializeStruct;
89        let mut state = serializer.serialize_struct("Position", 3)?;
90        state.serialize_field("line", &self.line)?;
91        state.serialize_field("column", &self.column)?;
92        state.serialize_field("index", &self.index)?;
93        state.end()
94    }
95}
96
97#[cfg(feature = "serde")]
98impl<'de> serde::Deserialize<'de> for Position {
99    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
100    where
101        D: serde::Deserializer<'de>,
102    {
103        use serde::de::{self, MapAccess, Visitor};
104        use std::fmt;
105
106        #[derive(serde::Deserialize)]
107        #[serde(field_identifier, rename_all = "lowercase")]
108        enum Field {
109            Line,
110            Column,
111            Index,
112        }
113
114        struct PositionVisitor;
115
116        impl<'de> Visitor<'de> for PositionVisitor {
117            type Value = Position;
118
119            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
120                formatter.write_str("struct Position")
121            }
122
123            fn visit_map<V>(self, mut map: V) -> Result<Position, V::Error>
124            where
125                V: MapAccess<'de>,
126            {
127                let mut line = None;
128                let mut column = None;
129                let mut index = None;
130                while let Some(key) = map.next_key()? {
131                    match key {
132                        Field::Line => {
133                            if line.is_some() {
134                                return Err(de::Error::duplicate_field("line"));
135                            }
136                            line = Some(map.next_value()?);
137                        }
138                        Field::Column => {
139                            if column.is_some() {
140                                return Err(de::Error::duplicate_field("column"));
141                            }
142                            column = Some(map.next_value()?);
143                        }
144                        Field::Index => {
145                            if index.is_some() {
146                                return Err(de::Error::duplicate_field("index"));
147                            }
148                            index = Some(map.next_value()?);
149                        }
150                    }
151                }
152                let line = line.ok_or_else(|| de::Error::missing_field("line"))?;
153                let column = column.ok_or_else(|| de::Error::missing_field("column"))?;
154                let index = index.ok_or_else(|| de::Error::missing_field("index"))?;
155                Ok(Position::at(line, column, index))
156            }
157        }
158
159        const FIELDS: &[&str] = &["line", "column", "index"];
160        deserializer.deserialize_struct("Position", FIELDS, PositionVisitor)
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_position_creation() {
170        let pos = Position::at(5, 10, 42);
171        assert_eq!(pos.line, 5);
172        assert_eq!(pos.column, 10);
173        assert_eq!(pos.index, 42);
174    }
175
176    #[test]
177    fn test_position_start() {
178        let pos = Position::start();
179        assert_eq!(pos.line, 1);
180        assert_eq!(pos.column, 1);
181        assert_eq!(pos.index, 0);
182    }
183
184    #[test]
185    fn test_position_advance() {
186        let pos = Position::start();
187
188        let pos1 = pos.advance('a');
189        assert_eq!(pos1.line, 1);
190        assert_eq!(pos1.column, 2);
191        assert_eq!(pos1.index, 1);
192
193        let pos2 = pos1.advance('\n');
194        assert_eq!(pos2.line, 2);
195        assert_eq!(pos2.column, 1);
196        assert_eq!(pos2.index, 2);
197    }
198
199    #[test]
200    fn test_position_advance_str() {
201        let pos = Position::start();
202        let pos1 = pos.advance_str("hello\nworld");
203        assert_eq!(pos1.line, 2);
204        assert_eq!(pos1.column, 6);
205        assert_eq!(pos1.index, 11);
206    }
207
208    #[test]
209    fn test_position_display() {
210        let pos = Position::at(42, 13, 1000);
211        assert_eq!(format!("{}", pos), "line 42, column 13");
212    }
213
214    #[test]
215    fn test_position_ordering() {
216        let pos1 = Position::at(1, 5, 10);
217        let pos2 = Position::at(2, 3, 20);
218        let pos3 = Position::at(1, 6, 11);
219
220        assert!(pos1 < pos2);
221        assert!(pos1 < pos3);
222        assert!(pos3 < pos2);
223    }
224
225    // Temporarily commented out due to missing serde_json dependency
226    // #[cfg(feature = "serde")]
227    // #[test]
228    // fn test_position_serde() {
229    //     let pos = Position::at(10, 20, 100);
230    //     let json = serde_json::to_string(&pos).unwrap();
231    //     let deserialized: Position = serde_json::from_str(&json).unwrap();
232    //     assert_eq!(pos, deserialized);
233    // }
234}