1use std::fmt::{Debug, Display, Formatter};
2use std::num::NonZeroU32;
3
4#[derive(Debug, Clone, PartialEq)]
12pub struct Location {
13 pub source_name: String,
14 pub resource: String,
15 pub line: Option<NonZeroU32>,
16 pub column: Option<NonZeroU32>,
17 pub length: Option<NonZeroU32>,
19}
20
21fn position(value: usize) -> Option<NonZeroU32> {
26 NonZeroU32::new(u32::try_from(value).unwrap_or(u32::MAX))
27}
28
29impl Location {
30 pub fn at(
31 source_name: &str,
32 resource: &str,
33 line: Option<usize>,
34 column: Option<usize>,
35 length: Option<usize>,
36 ) -> Self {
37 Self {
38 source_name: source_name.to_string(),
39 resource: resource.to_string(),
40 line: line.and_then(position),
41 column: column.and_then(position),
42 length: length.and_then(position),
43 }
44 }
45
46 pub fn with_length(mut self, length: usize) -> Self {
47 self.length = position(length);
48 self
49 }
50}
51
52impl Display for Location {
53 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
54 if self.resource.is_empty() {
55 write!(f, "{}", self.source_name)?;
56 } else {
57 write!(f, "{}:{}", self.source_name, self.resource)?;
58 }
59 match (self.line, self.column) {
60 (Some(line), Some(column)) => write!(f, ":{line}:{column}"),
61 (Some(line), None) => write!(f, ":{line}"),
62 _ => Ok(()),
63 }
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69pub enum ValueType {
70 Bool,
71 Int,
72 Float,
73 String,
74 List,
75 Map,
76}
77
78impl Display for ValueType {
79 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
80 f.write_str(match self {
81 Self::Bool => "boolean",
82 Self::Int => "integer",
83 Self::Float => "float",
84 Self::String => "string",
85 Self::List => "list",
86 Self::Map => "map",
87 })
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Default)]
93pub struct Map {
94 entries: Vec<(String, LocatedValue)>,
95}
96
97impl Map {
98 pub fn new() -> Self {
99 Self::default()
100 }
101
102 pub fn len(&self) -> usize {
103 self.entries.len()
104 }
105
106 pub fn is_empty(&self) -> bool {
107 self.entries.is_empty()
108 }
109
110 pub fn contains_key(&self, key: &str) -> bool {
111 for index in (0..self.entries.len()).rev() {
112 if self.entries[index].0 == key {
113 return true;
114 }
115 }
116 false
117 }
118
119 pub fn get(&self, key: &str) -> Option<&LocatedValue> {
120 for index in (0..self.entries.len()).rev() {
121 if self.entries[index].0 == key {
122 return Some(&self.entries[index].1);
123 }
124 }
125 None
126 }
127
128 pub fn get_mut(&mut self, key: &str) -> Option<&mut LocatedValue> {
129 let mut found = None;
130 for index in (0..self.entries.len()).rev() {
131 if self.entries[index].0 == key {
132 found = Some(index);
133 break;
134 }
135 }
136 if let Some(index) = found {
137 Some(&mut self.entries[index].1)
138 } else {
139 None
140 }
141 }
142
143 pub fn insert(&mut self, key: String, value: LocatedValue) -> Option<LocatedValue> {
144 let old = self.remove(&key);
145 self.entries.push((key, value));
146 old
147 }
148
149 pub fn remove(&mut self, key: &str) -> Option<LocatedValue> {
150 let mut found = None;
151 for index in (0..self.entries.len()).rev() {
152 if self.entries[index].0 == key {
153 found = Some(index);
154 break;
155 }
156 }
157 if let Some(index) = found {
158 Some(self.entries.remove(index).1)
159 } else {
160 None
161 }
162 }
163
164 pub fn entries(&self) -> &[(String, LocatedValue)] {
165 &self.entries
166 }
167
168 pub fn entries_mut(&mut self) -> &mut Vec<(String, LocatedValue)> {
169 &mut self.entries
170 }
171}
172
173impl Display for Map {
174 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
175 if f.alternate() {
176 writeln!(f, "{{")?;
177 for index in 0..self.entries.len() {
178 if index > 0 {
179 writeln!(f, ",")?;
180 }
181 let (key, value) = &self.entries[index];
182 write!(f, " {key:?}:")?;
183 writeln!(f)?;
184 write!(f, " {value:#}")?;
185 }
186 writeln!(f)?;
187 write!(f, "}}")
188 } else {
189 write!(f, "{{")?;
190 for index in 0..self.entries.len() {
191 if index > 0 {
192 write!(f, ", ")?;
193 }
194 let (key, value) = &self.entries[index];
195 write!(f, "{key:?}: {value}")?;
196 }
197 write!(f, "}}")
198 }
199 }
200}
201
202#[derive(Debug, Clone, PartialEq)]
204pub enum Value {
205 Bool(bool),
206 Int(isize),
207 Float(f64),
208 String(String),
209 List(Vec<LocatedValue>),
210 Map(Map),
211}
212
213#[derive(Debug, Clone, PartialEq)]
218pub struct LocatedValue {
219 pub value: Value,
220 pub location: Location,
221}
222
223impl Display for LocatedValue {
224 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
225 if f.alternate() {
226 writeln!(f, "@{}", self.location)?;
227 write!(f, "{:#}", self.value)
228 } else {
229 write!(f, "{}", self.value)
230 }
231 }
232}
233
234impl Value {
235 pub fn new_map() -> Self {
236 Self::Map(Map::new())
237 }
238
239 pub fn new_list() -> Self {
240 Self::List(Vec::new())
241 }
242
243 pub fn new_string() -> Self {
244 Self::String(String::new())
245 }
246
247 pub fn is_bool(&self) -> bool {
248 matches!(self, Self::Bool(_))
249 }
250
251 pub fn as_bool(&self) -> Option<bool> {
252 match self {
253 Self::Bool(value) => Some(*value),
254 _ => None,
255 }
256 }
257
258 pub fn into_bool(self) -> Option<bool> {
259 match self {
260 Self::Bool(value) => Some(value),
261 _ => None,
262 }
263 }
264
265 pub fn bool_mut(&mut self) -> Option<&mut bool> {
266 match self {
267 Self::Bool(value) => Some(value),
268 _ => None,
269 }
270 }
271
272 pub fn is_int(&self) -> bool {
273 matches!(self, Self::Int(_))
274 }
275
276 pub fn as_int(&self) -> Option<isize> {
277 match self {
278 Self::Int(value) => Some(*value),
279 _ => None,
280 }
281 }
282
283 pub fn into_int(self) -> Option<isize> {
284 match self {
285 Self::Int(value) => Some(value),
286 _ => None,
287 }
288 }
289
290 pub fn int_mut(&mut self) -> Option<&mut isize> {
291 match self {
292 Self::Int(value) => Some(value),
293 _ => None,
294 }
295 }
296
297 pub fn is_float(&self) -> bool {
298 matches!(self, Self::Float(_))
299 }
300
301 pub fn as_float(&self) -> Option<f64> {
302 match self {
303 Self::Float(value) => Some(*value),
304 _ => None,
305 }
306 }
307
308 pub fn into_float(self) -> Option<f64> {
309 match self {
310 Self::Float(value) => Some(value),
311 _ => None,
312 }
313 }
314
315 pub fn float_mut(&mut self) -> Option<&mut f64> {
316 match self {
317 Self::Float(value) => Some(value),
318 _ => None,
319 }
320 }
321
322 pub fn is_string(&self) -> bool {
323 matches!(self, Self::String(_))
324 }
325
326 pub fn as_string(&self) -> Option<&String> {
327 match self {
328 Self::String(value) => Some(value),
329 _ => None,
330 }
331 }
332
333 pub fn into_string(self) -> Option<String> {
334 match self {
335 Self::String(value) => Some(value),
336 _ => None,
337 }
338 }
339
340 pub fn string_mut(&mut self) -> Option<&mut String> {
341 match self {
342 Self::String(value) => Some(value),
343 _ => None,
344 }
345 }
346
347 pub fn is_list(&self) -> bool {
348 matches!(self, Self::List(_))
349 }
350
351 pub fn as_list(&self) -> Option<&Vec<LocatedValue>> {
352 match self {
353 Self::List(value) => Some(value),
354 _ => None,
355 }
356 }
357
358 pub fn into_list(self) -> Option<Vec<LocatedValue>> {
359 match self {
360 Self::List(value) => Some(value),
361 _ => None,
362 }
363 }
364
365 pub fn list_mut(&mut self) -> Option<&mut Vec<LocatedValue>> {
366 match self {
367 Self::List(value) => Some(value),
368 _ => None,
369 }
370 }
371
372 pub fn is_map(&self) -> bool {
373 matches!(self, Self::Map(_))
374 }
375
376 pub fn as_map(&self) -> Option<&Map> {
377 match self {
378 Self::Map(value) => Some(value),
379 _ => None,
380 }
381 }
382
383 pub fn into_map(self) -> Option<Map> {
384 match self {
385 Self::Map(value) => Some(value),
386 _ => None,
387 }
388 }
389
390 pub fn map_mut(&mut self) -> Option<&mut Map> {
391 match self {
392 Self::Map(value) => Some(value),
393 _ => None,
394 }
395 }
396
397 pub fn type_name(&self) -> ValueType {
398 match self {
399 Self::Bool(_) => ValueType::Bool,
400 Self::Int(_) => ValueType::Int,
401 Self::Float(_) => ValueType::Float,
402 Self::String(_) => ValueType::String,
403 Self::List(_) => ValueType::List,
404 Self::Map(_) => ValueType::Map,
405 }
406 }
407}
408
409impl Display for Value {
410 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
411 match self {
412 Self::Bool(value) => write!(f, "{value}"),
413 Self::Int(value) => write!(f, "{value}"),
414 Self::Float(value) => write!(f, "{value}"),
415 Self::String(value) => write!(f, "{value:?}"),
416 Self::List(values) => {
417 if f.alternate() {
418 writeln!(f, "[")?;
419 for (index, value) in values.iter().enumerate() {
420 if index > 0 {
421 writeln!(f, ",")?;
422 }
423 write!(f, " {value:#}")?;
424 }
425 writeln!(f)?;
426 write!(f, "]")
427 } else {
428 write!(f, "[")?;
429 let mut first = true;
430 for value in values {
431 if !first {
432 write!(f, ", ")?;
433 }
434 write!(f, "{value}")?;
435 first = false;
436 }
437 write!(f, "]")
438 }
439 }
440 Self::Map(value) => {
441 if f.alternate() {
442 write!(f, "{value:#}")
443 } else {
444 write!(f, "{value}")
445 }
446 }
447 }
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454
455 fn located_string(text: &str) -> LocatedValue {
456 LocatedValue {
457 value: Value::String(text.to_string()),
458 location: Location::at("file", "test", None, None, None),
459 }
460 }
461
462 #[test]
463 fn last_key_wins() {
464 let mut map = Map::new();
465 map.insert("foo".to_string(), located_string("first"));
466 map.insert("foo".to_string(), located_string("second"));
467 assert_eq!(map.get("foo").unwrap().value.as_string().unwrap(), "second");
468 }
469
470 #[test]
471 fn default_display_is_compact() {
472 let value = LocatedValue {
473 value: Value::String("hello".to_string()),
474 location: Location::at("file", "config.yaml", Some(2), Some(5), None),
475 };
476 let message = value.to_string();
477 assert!(!message.contains('\n'));
478 assert!(!message.starts_with('@'));
479 assert_eq!(message, "\"hello\"");
480 }
481
482 #[test]
483 fn alternate_display_shows_location_and_multiline() {
484 let value = LocatedValue {
485 value: Value::String("hello".to_string()),
486 location: Location::at("file", "config.yaml", Some(2), Some(5), None),
487 };
488 let message = format!("{value:#}");
489 assert!(message.starts_with("@file:config.yaml:2:5\n"));
490 assert!(message.contains("\"hello\""));
491 }
492}