1use std::fmt::{Debug, Display, Formatter};
2use std::num::NonZeroU32;
3use tanzim_source::Source;
4
5#[derive(Debug, Clone, PartialEq)]
13pub struct Location {
14 pub source: Source,
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 in_source(
32 source: Source,
33 line: Option<usize>,
34 column: Option<usize>,
35 length: Option<usize>,
36 ) -> Self {
37 Self {
38 source,
39 line: line.and_then(position),
40 column: column.and_then(position),
41 length: length.and_then(position),
42 }
43 }
44
45 pub fn at(
48 source_name: &str,
49 resource: &str,
50 line: Option<usize>,
51 column: Option<usize>,
52 length: Option<usize>,
53 ) -> Self {
54 Self::in_source(
55 Source::named(source_name).with_resource(resource),
56 line,
57 column,
58 length,
59 )
60 }
61
62 pub fn source_name(&self) -> &str {
64 self.source.source()
65 }
66
67 pub fn resource(&self) -> &str {
69 self.source.resource()
70 }
71
72 pub fn with_length(mut self, length: usize) -> Self {
73 self.length = position(length);
74 self
75 }
76}
77
78impl Display for Location {
79 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
80 let resource = self.source.resource();
81 if resource.is_empty() {
82 write!(f, "{}", self.source.source())?;
83 } else {
84 write!(f, "{}:{}", self.source.source(), resource)?;
85 }
86 match (self.line, self.column) {
87 (Some(line), Some(column)) => write!(f, ":{line}:{column}"),
88 (Some(line), None) => write!(f, ":{line}"),
89 _ => Ok(()),
90 }
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
96pub enum ValueType {
97 Bool,
98 Int,
99 Float,
100 String,
101 List,
102 Map,
103 Null,
104 Comment,
105}
106
107impl Display for ValueType {
108 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
109 f.write_str(match self {
110 Self::Bool => "boolean",
111 Self::Int => "integer",
112 Self::Float => "float",
113 Self::String => "string",
114 Self::List => "list",
115 Self::Map => "map",
116 Self::Null => "null",
117 Self::Comment => "comment",
118 })
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Default)]
124pub struct Map {
125 entries: Vec<(String, LocatedValue)>,
126}
127
128impl Map {
129 pub fn new() -> Self {
130 Self::default()
131 }
132
133 pub fn len(&self) -> usize {
134 self.entries.len()
135 }
136
137 pub fn is_empty(&self) -> bool {
138 self.entries.is_empty()
139 }
140
141 pub fn contains_key(&self, key: &str) -> bool {
142 for index in (0..self.entries.len()).rev() {
143 if self.entries[index].0 == key {
144 return true;
145 }
146 }
147 false
148 }
149
150 pub fn get(&self, key: &str) -> Option<&LocatedValue> {
151 for index in (0..self.entries.len()).rev() {
152 if self.entries[index].0 == key {
153 return Some(&self.entries[index].1);
154 }
155 }
156 None
157 }
158
159 pub fn get_mut(&mut self, key: &str) -> Option<&mut LocatedValue> {
160 let mut found = None;
161 for index in (0..self.entries.len()).rev() {
162 if self.entries[index].0 == key {
163 found = Some(index);
164 break;
165 }
166 }
167 if let Some(index) = found {
168 Some(&mut self.entries[index].1)
169 } else {
170 None
171 }
172 }
173
174 pub fn insert(&mut self, key: String, value: LocatedValue) -> Option<LocatedValue> {
175 let old = self.remove(&key);
176 self.entries.push((key, value));
177 old
178 }
179
180 pub fn remove(&mut self, key: &str) -> Option<LocatedValue> {
181 let mut found = None;
182 for index in (0..self.entries.len()).rev() {
183 if self.entries[index].0 == key {
184 found = Some(index);
185 break;
186 }
187 }
188 if let Some(index) = found {
189 Some(self.entries.remove(index).1)
190 } else {
191 None
192 }
193 }
194
195 pub fn entries(&self) -> &[(String, LocatedValue)] {
196 &self.entries
197 }
198
199 pub fn entries_mut(&mut self) -> &mut Vec<(String, LocatedValue)> {
200 &mut self.entries
201 }
202}
203
204impl Display for Map {
205 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
206 let alternate = f.alternate();
207 let mut map = f.debug_map();
208 for (key, value) in &self.entries {
209 if alternate {
210 map.entry(key, &format_args!("{:#}", value));
211 } else {
212 map.entry(key, &format_args!("{}", value));
213 }
214 }
215 map.finish()
216 }
217}
218
219#[derive(Debug, Clone, PartialEq)]
221pub enum Value {
222 Bool(bool),
223 Int(isize),
224 Float(f64),
225 String(String),
226 List(Vec<LocatedValue>),
227 Map(Map),
228 Null,
229 Comment(String),
230}
231
232#[derive(Debug, Clone, PartialEq)]
237pub struct LocatedValue {
238 pub value: Value,
239 pub location: Location,
240}
241
242impl Display for LocatedValue {
243 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
244 if f.alternate() {
245 let mut map = f.debug_map();
246 map.entry(&"value", &format_args!("{:#}", self.value));
247 map.entry(
248 &"location",
249 &format_args!("{:?}", self.location.to_string()),
250 );
251 map.finish()
252 } else {
253 write!(f, "{}", self.value)
254 }
255 }
256}
257
258impl AsRef<Value> for Value {
259 fn as_ref(&self) -> &Value {
260 self
261 }
262}
263
264impl AsRef<Value> for LocatedValue {
265 fn as_ref(&self) -> &Value {
266 &self.value
267 }
268}
269
270impl Value {
271 pub fn new_map() -> Self {
272 Self::Map(Map::new())
273 }
274
275 pub fn new_list() -> Self {
276 Self::List(Vec::new())
277 }
278
279 pub fn new_string() -> Self {
280 Self::String(String::new())
281 }
282
283 pub fn is_bool(&self) -> bool {
284 matches!(self, Self::Bool(_))
285 }
286
287 pub fn as_bool(&self) -> Option<bool> {
288 match self {
289 Self::Bool(value) => Some(*value),
290 _ => None,
291 }
292 }
293
294 pub fn into_bool(self) -> Option<bool> {
295 match self {
296 Self::Bool(value) => Some(value),
297 _ => None,
298 }
299 }
300
301 pub fn bool_mut(&mut self) -> Option<&mut bool> {
302 match self {
303 Self::Bool(value) => Some(value),
304 _ => None,
305 }
306 }
307
308 pub fn is_int(&self) -> bool {
309 matches!(self, Self::Int(_))
310 }
311
312 pub fn as_int(&self) -> Option<isize> {
313 match self {
314 Self::Int(value) => Some(*value),
315 _ => None,
316 }
317 }
318
319 pub fn into_int(self) -> Option<isize> {
320 match self {
321 Self::Int(value) => Some(value),
322 _ => None,
323 }
324 }
325
326 pub fn int_mut(&mut self) -> Option<&mut isize> {
327 match self {
328 Self::Int(value) => Some(value),
329 _ => None,
330 }
331 }
332
333 pub fn is_float(&self) -> bool {
334 matches!(self, Self::Float(_))
335 }
336
337 pub fn as_float(&self) -> Option<f64> {
338 match self {
339 Self::Float(value) => Some(*value),
340 _ => None,
341 }
342 }
343
344 pub fn into_float(self) -> Option<f64> {
345 match self {
346 Self::Float(value) => Some(value),
347 _ => None,
348 }
349 }
350
351 pub fn float_mut(&mut self) -> Option<&mut f64> {
352 match self {
353 Self::Float(value) => Some(value),
354 _ => None,
355 }
356 }
357
358 pub fn is_string(&self) -> bool {
359 matches!(self, Self::String(_))
360 }
361
362 pub fn as_string(&self) -> Option<&String> {
363 match self {
364 Self::String(value) => Some(value),
365 _ => None,
366 }
367 }
368
369 pub fn into_string(self) -> Option<String> {
370 match self {
371 Self::String(value) => Some(value),
372 _ => None,
373 }
374 }
375
376 pub fn string_mut(&mut self) -> Option<&mut String> {
377 match self {
378 Self::String(value) => Some(value),
379 _ => None,
380 }
381 }
382
383 pub fn is_list(&self) -> bool {
384 matches!(self, Self::List(_))
385 }
386
387 pub fn as_list(&self) -> Option<&Vec<LocatedValue>> {
388 match self {
389 Self::List(value) => Some(value),
390 _ => None,
391 }
392 }
393
394 pub fn into_list(self) -> Option<Vec<LocatedValue>> {
395 match self {
396 Self::List(value) => Some(value),
397 _ => None,
398 }
399 }
400
401 pub fn list_mut(&mut self) -> Option<&mut Vec<LocatedValue>> {
402 match self {
403 Self::List(value) => Some(value),
404 _ => None,
405 }
406 }
407
408 pub fn is_map(&self) -> bool {
409 matches!(self, Self::Map(_))
410 }
411
412 pub fn as_map(&self) -> Option<&Map> {
413 match self {
414 Self::Map(value) => Some(value),
415 _ => None,
416 }
417 }
418
419 pub fn into_map(self) -> Option<Map> {
420 match self {
421 Self::Map(value) => Some(value),
422 _ => None,
423 }
424 }
425
426 pub fn map_mut(&mut self) -> Option<&mut Map> {
427 match self {
428 Self::Map(value) => Some(value),
429 _ => None,
430 }
431 }
432
433 pub fn is_null(&self) -> bool {
434 matches!(self, Self::Null)
435 }
436
437 pub fn is_comment(&self) -> bool {
438 matches!(self, Self::Comment(_))
439 }
440
441 pub fn as_comment(&self) -> Option<&str> {
442 match self {
443 Self::Comment(value) => Some(value),
444 _ => None,
445 }
446 }
447
448 pub fn into_comment(self) -> Option<String> {
449 match self {
450 Self::Comment(value) => Some(value),
451 _ => None,
452 }
453 }
454
455 pub fn type_name(&self) -> ValueType {
456 match self {
457 Self::Bool(_) => ValueType::Bool,
458 Self::Int(_) => ValueType::Int,
459 Self::Float(_) => ValueType::Float,
460 Self::String(_) => ValueType::String,
461 Self::List(_) => ValueType::List,
462 Self::Map(_) => ValueType::Map,
463 Self::Null => ValueType::Null,
464 Self::Comment(_) => ValueType::Comment,
465 }
466 }
467}
468
469impl Display for Value {
470 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
471 match self {
472 Self::Bool(value) => write!(f, "{value}"),
473 Self::Int(value) => write!(f, "{value}"),
474 Self::Float(value) => write!(f, "{value}"),
475 Self::String(value) => write!(f, "{value:?}"),
476 Self::List(values) => {
477 let alternate = f.alternate();
478 let mut list = f.debug_list();
479 for value in values {
480 if alternate {
481 list.entry(&format_args!("{:#}", value));
482 } else {
483 list.entry(&format_args!("{}", value));
484 }
485 }
486 list.finish()
487 }
488 Self::Map(value) => Display::fmt(value, f),
489 Self::Null => f.write_str("null"),
490 Self::Comment(value) => f.write_str(value),
491 }
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498
499 fn located_string(text: &str) -> LocatedValue {
500 LocatedValue {
501 value: Value::String(text.to_string()),
502 location: Location::at("file", "test", None, None, None),
503 }
504 }
505
506 #[test]
507 fn as_ref_value_accepts_all_forms() {
508 fn take<V: AsRef<Value>>(value: V) -> Value {
509 value.as_ref().clone()
510 }
511 let value = Value::Int(7);
512 let located = LocatedValue {
513 value: Value::Int(7),
514 location: Location::at("file", "test", None, None, None),
515 };
516 assert_eq!(take(value.clone()), value); assert_eq!(take(&value), value); assert_eq!(take(located.clone()), value); assert_eq!(take(&located), value); }
521
522 #[test]
523 fn last_key_wins() {
524 let mut map = Map::new();
525 map.insert("foo".to_string(), located_string("first"));
526 map.insert("foo".to_string(), located_string("second"));
527 assert_eq!(map.get("foo").unwrap().value.as_string().unwrap(), "second");
528 }
529
530 #[test]
531 fn default_display_is_compact() {
532 let value = LocatedValue {
533 value: Value::String("hello".to_string()),
534 location: Location::at("file", "config.yaml", Some(2), Some(5), None),
535 };
536 let message = value.to_string();
537 assert!(!message.contains('\n'));
538 assert!(!message.starts_with('@'));
539 assert_eq!(message, "\"hello\"");
540 }
541
542 #[test]
543 fn alternate_display_shows_location_and_multiline() {
544 let value = LocatedValue {
545 value: Value::String("hello".to_string()),
546 location: Location::at("file", "config.yaml", Some(2), Some(5), None),
547 };
548 let message = format!("{value:#}");
549 assert_eq!(
550 message,
551 "{\n \"value\": \"hello\",\n \"location\": \"file:config.yaml:2:5\",\n}"
552 );
553 assert!(!message.contains('@'));
554 }
555
556 #[test]
557 fn value_accessors_and_constructors() {
558 let mut value = Value::Bool(true);
559 assert!(value.is_bool());
560 assert_eq!(value.as_bool(), Some(true));
561 assert_eq!(value.type_name(), ValueType::Bool);
562 if let Some(flag) = value.bool_mut() {
563 *flag = false;
564 }
565 assert_eq!(value.into_bool(), Some(false));
566
567 let list = Value::new_list();
568 assert!(list.is_list());
569 let map = Value::new_map();
570 assert!(map.is_map());
571 let text = Value::new_string();
572 assert!(text.is_string());
573 }
574
575 #[test]
576 fn map_remove_get_mut_and_display() {
577 let mut map = Map::new();
578 map.insert("a".to_string(), located_string("one"));
579 map.insert("b".to_string(), located_string("two"));
580 assert_eq!(map.len(), 2);
581 assert!(map.contains_key("a"));
582 assert!(map.get_mut("b").is_some());
583 let removed = map.remove("a");
584 assert!(removed.is_some());
585 assert!(!map.contains_key("a"));
586
587 let compact = format!("{map}");
588 assert!(compact.contains("b"));
589 let detailed = format!("{map:#}");
590 assert!(detailed.contains("location"));
591 }
592
593 #[test]
594 fn location_display_and_with_length() {
595 let location = Location::at("file", "", Some(1), Some(2), None).with_length(3);
596 assert_eq!(location.to_string(), "file:1:2");
597 let resourceful = Location::at("file", "cfg.yml", Some(4), None, None);
598 assert_eq!(resourceful.to_string(), "file:cfg.yml:4");
599 }
600
601 #[test]
602 fn value_list_and_map_display_modes() {
603 let list = Value::List(vec![located_string("a"), located_string("b")]);
604 assert!(format!("{list}").contains("a"));
605 assert!(format!("{list:#}").contains("location"));
606
607 let mut map = Map::new();
608 map.insert("k".to_string(), located_string("v"));
609 let map_value = Value::Map(map);
610 assert!(format!("{map_value}").contains("k"));
611 }
612}