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 let alternate = f.alternate();
176 let mut map = f.debug_map();
177 for (key, value) in &self.entries {
178 if alternate {
179 map.entry(key, &format_args!("{:#}", value));
180 } else {
181 map.entry(key, &format_args!("{}", value));
182 }
183 }
184 map.finish()
185 }
186}
187
188#[derive(Debug, Clone, PartialEq)]
190pub enum Value {
191 Bool(bool),
192 Int(isize),
193 Float(f64),
194 String(String),
195 List(Vec<LocatedValue>),
196 Map(Map),
197}
198
199#[derive(Debug, Clone, PartialEq)]
204pub struct LocatedValue {
205 pub value: Value,
206 pub location: Location,
207}
208
209impl Display for LocatedValue {
210 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
211 if f.alternate() {
212 let mut map = f.debug_map();
213 map.entry(&"value", &format_args!("{:#}", self.value));
214 map.entry(
215 &"location",
216 &format_args!("{:?}", self.location.to_string()),
217 );
218 map.finish()
219 } else {
220 write!(f, "{}", self.value)
221 }
222 }
223}
224
225impl AsRef<Value> for Value {
226 fn as_ref(&self) -> &Value {
227 self
228 }
229}
230
231impl AsRef<Value> for LocatedValue {
232 fn as_ref(&self) -> &Value {
233 &self.value
234 }
235}
236
237impl Value {
238 pub fn new_map() -> Self {
239 Self::Map(Map::new())
240 }
241
242 pub fn new_list() -> Self {
243 Self::List(Vec::new())
244 }
245
246 pub fn new_string() -> Self {
247 Self::String(String::new())
248 }
249
250 pub fn is_bool(&self) -> bool {
251 matches!(self, Self::Bool(_))
252 }
253
254 pub fn as_bool(&self) -> Option<bool> {
255 match self {
256 Self::Bool(value) => Some(*value),
257 _ => None,
258 }
259 }
260
261 pub fn into_bool(self) -> Option<bool> {
262 match self {
263 Self::Bool(value) => Some(value),
264 _ => None,
265 }
266 }
267
268 pub fn bool_mut(&mut self) -> Option<&mut bool> {
269 match self {
270 Self::Bool(value) => Some(value),
271 _ => None,
272 }
273 }
274
275 pub fn is_int(&self) -> bool {
276 matches!(self, Self::Int(_))
277 }
278
279 pub fn as_int(&self) -> Option<isize> {
280 match self {
281 Self::Int(value) => Some(*value),
282 _ => None,
283 }
284 }
285
286 pub fn into_int(self) -> Option<isize> {
287 match self {
288 Self::Int(value) => Some(value),
289 _ => None,
290 }
291 }
292
293 pub fn int_mut(&mut self) -> Option<&mut isize> {
294 match self {
295 Self::Int(value) => Some(value),
296 _ => None,
297 }
298 }
299
300 pub fn is_float(&self) -> bool {
301 matches!(self, Self::Float(_))
302 }
303
304 pub fn as_float(&self) -> Option<f64> {
305 match self {
306 Self::Float(value) => Some(*value),
307 _ => None,
308 }
309 }
310
311 pub fn into_float(self) -> Option<f64> {
312 match self {
313 Self::Float(value) => Some(value),
314 _ => None,
315 }
316 }
317
318 pub fn float_mut(&mut self) -> Option<&mut f64> {
319 match self {
320 Self::Float(value) => Some(value),
321 _ => None,
322 }
323 }
324
325 pub fn is_string(&self) -> bool {
326 matches!(self, Self::String(_))
327 }
328
329 pub fn as_string(&self) -> Option<&String> {
330 match self {
331 Self::String(value) => Some(value),
332 _ => None,
333 }
334 }
335
336 pub fn into_string(self) -> Option<String> {
337 match self {
338 Self::String(value) => Some(value),
339 _ => None,
340 }
341 }
342
343 pub fn string_mut(&mut self) -> Option<&mut String> {
344 match self {
345 Self::String(value) => Some(value),
346 _ => None,
347 }
348 }
349
350 pub fn is_list(&self) -> bool {
351 matches!(self, Self::List(_))
352 }
353
354 pub fn as_list(&self) -> Option<&Vec<LocatedValue>> {
355 match self {
356 Self::List(value) => Some(value),
357 _ => None,
358 }
359 }
360
361 pub fn into_list(self) -> Option<Vec<LocatedValue>> {
362 match self {
363 Self::List(value) => Some(value),
364 _ => None,
365 }
366 }
367
368 pub fn list_mut(&mut self) -> Option<&mut Vec<LocatedValue>> {
369 match self {
370 Self::List(value) => Some(value),
371 _ => None,
372 }
373 }
374
375 pub fn is_map(&self) -> bool {
376 matches!(self, Self::Map(_))
377 }
378
379 pub fn as_map(&self) -> Option<&Map> {
380 match self {
381 Self::Map(value) => Some(value),
382 _ => None,
383 }
384 }
385
386 pub fn into_map(self) -> Option<Map> {
387 match self {
388 Self::Map(value) => Some(value),
389 _ => None,
390 }
391 }
392
393 pub fn map_mut(&mut self) -> Option<&mut Map> {
394 match self {
395 Self::Map(value) => Some(value),
396 _ => None,
397 }
398 }
399
400 pub fn type_name(&self) -> ValueType {
401 match self {
402 Self::Bool(_) => ValueType::Bool,
403 Self::Int(_) => ValueType::Int,
404 Self::Float(_) => ValueType::Float,
405 Self::String(_) => ValueType::String,
406 Self::List(_) => ValueType::List,
407 Self::Map(_) => ValueType::Map,
408 }
409 }
410}
411
412impl Display for Value {
413 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
414 match self {
415 Self::Bool(value) => write!(f, "{value}"),
416 Self::Int(value) => write!(f, "{value}"),
417 Self::Float(value) => write!(f, "{value}"),
418 Self::String(value) => write!(f, "{value:?}"),
419 Self::List(values) => {
420 let alternate = f.alternate();
421 let mut list = f.debug_list();
422 for value in values {
423 if alternate {
424 list.entry(&format_args!("{:#}", value));
425 } else {
426 list.entry(&format_args!("{}", value));
427 }
428 }
429 list.finish()
430 }
431 Self::Map(value) => Display::fmt(value, f),
432 }
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439
440 fn located_string(text: &str) -> LocatedValue {
441 LocatedValue {
442 value: Value::String(text.to_string()),
443 location: Location::at("file", "test", None, None, None),
444 }
445 }
446
447 #[test]
448 fn as_ref_value_accepts_all_forms() {
449 fn take<V: AsRef<Value>>(value: V) -> Value {
450 value.as_ref().clone()
451 }
452 let value = Value::Int(7);
453 let located = LocatedValue {
454 value: Value::Int(7),
455 location: Location::at("file", "test", None, None, None),
456 };
457 assert_eq!(take(value.clone()), value); assert_eq!(take(&value), value); assert_eq!(take(located.clone()), value); assert_eq!(take(&located), value); }
462
463 #[test]
464 fn last_key_wins() {
465 let mut map = Map::new();
466 map.insert("foo".to_string(), located_string("first"));
467 map.insert("foo".to_string(), located_string("second"));
468 assert_eq!(map.get("foo").unwrap().value.as_string().unwrap(), "second");
469 }
470
471 #[test]
472 fn default_display_is_compact() {
473 let value = LocatedValue {
474 value: Value::String("hello".to_string()),
475 location: Location::at("file", "config.yaml", Some(2), Some(5), None),
476 };
477 let message = value.to_string();
478 assert!(!message.contains('\n'));
479 assert!(!message.starts_with('@'));
480 assert_eq!(message, "\"hello\"");
481 }
482
483 #[test]
484 fn alternate_display_shows_location_and_multiline() {
485 let value = LocatedValue {
486 value: Value::String("hello".to_string()),
487 location: Location::at("file", "config.yaml", Some(2), Some(5), None),
488 };
489 let message = format!("{value:#}");
490 assert_eq!(
491 message,
492 "{\n \"value\": \"hello\",\n \"location\": \"file:config.yaml:2:5\",\n}"
493 );
494 assert!(!message.contains('@'));
495 }
496}