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}
105
106impl Display for ValueType {
107 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
108 f.write_str(match self {
109 Self::Bool => "boolean",
110 Self::Int => "integer",
111 Self::Float => "float",
112 Self::String => "string",
113 Self::List => "list",
114 Self::Map => "map",
115 Self::Null => "null",
116 })
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Default)]
122pub struct Map {
123 entries: Vec<(String, LocatedValue)>,
124}
125
126impl Map {
127 pub fn new() -> Self {
128 Self::default()
129 }
130
131 pub fn len(&self) -> usize {
132 self.entries.len()
133 }
134
135 pub fn is_empty(&self) -> bool {
136 self.entries.is_empty()
137 }
138
139 pub fn contains_key(&self, key: &str) -> bool {
140 for index in (0..self.entries.len()).rev() {
141 if self.entries[index].0 == key {
142 return true;
143 }
144 }
145 false
146 }
147
148 pub fn get(&self, key: &str) -> Option<&LocatedValue> {
149 for index in (0..self.entries.len()).rev() {
150 if self.entries[index].0 == key {
151 return Some(&self.entries[index].1);
152 }
153 }
154 None
155 }
156
157 pub fn get_mut(&mut self, key: &str) -> Option<&mut LocatedValue> {
158 let mut found = None;
159 for index in (0..self.entries.len()).rev() {
160 if self.entries[index].0 == key {
161 found = Some(index);
162 break;
163 }
164 }
165 if let Some(index) = found {
166 Some(&mut self.entries[index].1)
167 } else {
168 None
169 }
170 }
171
172 pub fn insert(&mut self, key: String, value: LocatedValue) -> Option<LocatedValue> {
173 let old = self.remove(&key);
174 self.entries.push((key, value));
175 old
176 }
177
178 pub fn remove(&mut self, key: &str) -> Option<LocatedValue> {
179 let mut found = None;
180 for index in (0..self.entries.len()).rev() {
181 if self.entries[index].0 == key {
182 found = Some(index);
183 break;
184 }
185 }
186 if let Some(index) = found {
187 Some(self.entries.remove(index).1)
188 } else {
189 None
190 }
191 }
192
193 pub fn entries(&self) -> &[(String, LocatedValue)] {
194 &self.entries
195 }
196
197 pub fn entries_mut(&mut self) -> &mut Vec<(String, LocatedValue)> {
198 &mut self.entries
199 }
200}
201
202impl Display for Map {
203 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
204 let alternate = f.alternate();
205 let mut map = f.debug_map();
206 for (key, value) in &self.entries {
207 if alternate {
208 map.entry(key, &format_args!("{:#}", value));
209 } else {
210 map.entry(key, &format_args!("{}", value));
211 }
212 }
213 map.finish()
214 }
215}
216
217#[derive(Debug, Clone, PartialEq)]
219pub enum Value {
220 Bool(bool),
221 Int(isize),
222 Float(f64),
223 String(String),
224 List(Vec<LocatedValue>),
225 Map(Map),
226 Null,
227}
228
229#[derive(Debug, Clone, PartialEq, Default)]
234pub struct Comment {
235 before: Vec<String>,
236 after: Option<String>,
237}
238
239impl Comment {
240 pub fn new() -> Self {
241 Self::default()
242 }
243
244 pub fn before(&self) -> &[String] {
246 &self.before
247 }
248
249 pub fn before_mut(&mut self) -> &mut Vec<String> {
250 &mut self.before
251 }
252
253 pub fn after(&self) -> Option<&str> {
255 self.after.as_deref()
256 }
257
258 pub fn after_mut(&mut self) -> &mut Option<String> {
259 &mut self.after
260 }
261
262 pub fn with_before(mut self, lines: impl IntoIterator<Item = impl Into<String>>) -> Self {
264 self.before = lines.into_iter().map(|l| l.into()).collect();
265 self
266 }
267
268 pub fn with_after(mut self, text: Option<impl Into<String>>) -> Self {
270 self.after = text.map(|t| t.into());
271 self
272 }
273
274 pub fn set_before(&mut self, lines: impl IntoIterator<Item = impl Into<String>>) {
276 self.before = lines.into_iter().map(|l| l.into()).collect();
277 }
278
279 pub fn set_after(&mut self, text: Option<impl Into<String>>) {
281 self.after = text.map(|t| t.into());
282 }
283}
284
285#[derive(Debug, Clone, PartialEq)]
291pub struct LocatedValue {
292 value: Value,
293 location: Location,
294 comment: Comment,
295}
296
297impl LocatedValue {
298 pub fn new(value: impl Into<Value>, location: impl Into<Location>) -> Self {
300 Self {
301 value: value.into(),
302 location: location.into(),
303 comment: Comment::new(),
304 }
305 }
306
307 pub fn value(&self) -> &Value {
310 &self.value
311 }
312
313 pub fn value_mut(&mut self) -> &mut Value {
314 &mut self.value
315 }
316
317 pub fn into_value(self) -> Value {
318 self.value
319 }
320
321 pub fn with_value(mut self, value: impl Into<Value>) -> Self {
322 self.value = value.into();
323 self
324 }
325
326 pub fn set_value(&mut self, value: impl Into<Value>) {
327 self.value = value.into();
328 }
329
330 pub fn location(&self) -> &Location {
333 &self.location
334 }
335
336 pub fn location_mut(&mut self) -> &mut Location {
337 &mut self.location
338 }
339
340 pub fn with_location(mut self, location: impl Into<Location>) -> Self {
341 self.location = location.into();
342 self
343 }
344
345 pub fn set_location(&mut self, location: impl Into<Location>) {
346 self.location = location.into();
347 }
348
349 pub fn comment(&self) -> &Comment {
352 &self.comment
353 }
354
355 pub fn comment_mut(&mut self) -> &mut Comment {
356 &mut self.comment
357 }
358
359 pub fn with_comment(mut self, comment: Comment) -> Self {
360 self.comment = comment;
361 self
362 }
363
364 pub fn set_comment(&mut self, comment: Comment) {
365 self.comment = comment;
366 }
367}
368
369impl Display for LocatedValue {
370 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
371 if f.alternate() {
372 let mut map = f.debug_map();
373 map.entry(&"value", &format_args!("{:#}", self.value));
374 map.entry(
375 &"location",
376 &format_args!("{:?}", self.location.to_string()),
377 );
378 if !self.comment.before.is_empty() || self.comment.after.is_some() {
379 map.entry(&"comment_before", &self.comment.before.as_slice());
380 if let Some(after) = &self.comment.after {
381 map.entry(&"comment_after", &after.as_str());
382 }
383 }
384 map.finish()
385 } else {
386 write!(f, "{}", self.value)
387 }
388 }
389}
390
391impl AsRef<Value> for Value {
392 fn as_ref(&self) -> &Value {
393 self
394 }
395}
396
397impl AsRef<Value> for LocatedValue {
398 fn as_ref(&self) -> &Value {
399 &self.value
400 }
401}
402
403impl Value {
404 pub fn new_map() -> Self {
405 Self::Map(Map::new())
406 }
407
408 pub fn new_list() -> Self {
409 Self::List(Vec::new())
410 }
411
412 pub fn new_string() -> Self {
413 Self::String(String::new())
414 }
415
416 pub fn is_bool(&self) -> bool {
417 matches!(self, Self::Bool(_))
418 }
419
420 pub fn as_bool(&self) -> Option<bool> {
421 match self {
422 Self::Bool(value) => Some(*value),
423 _ => None,
424 }
425 }
426
427 pub fn into_bool(self) -> Option<bool> {
428 match self {
429 Self::Bool(value) => Some(value),
430 _ => None,
431 }
432 }
433
434 pub fn bool_mut(&mut self) -> Option<&mut bool> {
435 match self {
436 Self::Bool(value) => Some(value),
437 _ => None,
438 }
439 }
440
441 pub fn is_int(&self) -> bool {
442 matches!(self, Self::Int(_))
443 }
444
445 pub fn as_int(&self) -> Option<isize> {
446 match self {
447 Self::Int(value) => Some(*value),
448 _ => None,
449 }
450 }
451
452 pub fn into_int(self) -> Option<isize> {
453 match self {
454 Self::Int(value) => Some(value),
455 _ => None,
456 }
457 }
458
459 pub fn int_mut(&mut self) -> Option<&mut isize> {
460 match self {
461 Self::Int(value) => Some(value),
462 _ => None,
463 }
464 }
465
466 pub fn is_float(&self) -> bool {
467 matches!(self, Self::Float(_))
468 }
469
470 pub fn as_float(&self) -> Option<f64> {
471 match self {
472 Self::Float(value) => Some(*value),
473 _ => None,
474 }
475 }
476
477 pub fn into_float(self) -> Option<f64> {
478 match self {
479 Self::Float(value) => Some(value),
480 _ => None,
481 }
482 }
483
484 pub fn float_mut(&mut self) -> Option<&mut f64> {
485 match self {
486 Self::Float(value) => Some(value),
487 _ => None,
488 }
489 }
490
491 pub fn is_string(&self) -> bool {
492 matches!(self, Self::String(_))
493 }
494
495 pub fn as_string(&self) -> Option<&String> {
496 match self {
497 Self::String(value) => Some(value),
498 _ => None,
499 }
500 }
501
502 pub fn into_string(self) -> Option<String> {
503 match self {
504 Self::String(value) => Some(value),
505 _ => None,
506 }
507 }
508
509 pub fn string_mut(&mut self) -> Option<&mut String> {
510 match self {
511 Self::String(value) => Some(value),
512 _ => None,
513 }
514 }
515
516 pub fn is_list(&self) -> bool {
517 matches!(self, Self::List(_))
518 }
519
520 pub fn as_list(&self) -> Option<&Vec<LocatedValue>> {
521 match self {
522 Self::List(value) => Some(value),
523 _ => None,
524 }
525 }
526
527 pub fn into_list(self) -> Option<Vec<LocatedValue>> {
528 match self {
529 Self::List(value) => Some(value),
530 _ => None,
531 }
532 }
533
534 pub fn list_mut(&mut self) -> Option<&mut Vec<LocatedValue>> {
535 match self {
536 Self::List(value) => Some(value),
537 _ => None,
538 }
539 }
540
541 pub fn is_map(&self) -> bool {
542 matches!(self, Self::Map(_))
543 }
544
545 pub fn as_map(&self) -> Option<&Map> {
546 match self {
547 Self::Map(value) => Some(value),
548 _ => None,
549 }
550 }
551
552 pub fn into_map(self) -> Option<Map> {
553 match self {
554 Self::Map(value) => Some(value),
555 _ => None,
556 }
557 }
558
559 pub fn map_mut(&mut self) -> Option<&mut Map> {
560 match self {
561 Self::Map(value) => Some(value),
562 _ => None,
563 }
564 }
565
566 pub fn is_null(&self) -> bool {
567 matches!(self, Self::Null)
568 }
569
570 pub fn type_name(&self) -> ValueType {
571 match self {
572 Self::Bool(_) => ValueType::Bool,
573 Self::Int(_) => ValueType::Int,
574 Self::Float(_) => ValueType::Float,
575 Self::String(_) => ValueType::String,
576 Self::List(_) => ValueType::List,
577 Self::Map(_) => ValueType::Map,
578 Self::Null => ValueType::Null,
579 }
580 }
581}
582
583impl Display for Value {
584 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
585 match self {
586 Self::Bool(value) => write!(f, "{value}"),
587 Self::Int(value) => write!(f, "{value}"),
588 Self::Float(value) => write!(f, "{value}"),
589 Self::String(value) => write!(f, "{value:?}"),
590 Self::List(values) => {
591 let alternate = f.alternate();
592 let mut list = f.debug_list();
593 for value in values {
594 if alternate {
595 list.entry(&format_args!("{:#}", value));
596 } else {
597 list.entry(&format_args!("{}", value));
598 }
599 }
600 list.finish()
601 }
602 Self::Map(value) => Display::fmt(value, f),
603 Self::Null => f.write_str("null"),
604 }
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use super::*;
611
612 fn located_string(text: &str) -> LocatedValue {
613 LocatedValue::new(
614 Value::String(text.to_string()),
615 Location::at("file", "test", None, None, None),
616 )
617 }
618
619 #[test]
620 fn as_ref_value_accepts_all_forms() {
621 fn take<V: AsRef<Value>>(value: V) -> Value {
622 value.as_ref().clone()
623 }
624 let value = Value::Int(7);
625 let located = LocatedValue::new(
626 Value::Int(7),
627 Location::at("file", "test", None, None, None),
628 );
629 assert_eq!(take(value.clone()), value);
630 assert_eq!(take(&value), value);
631 assert_eq!(take(located.clone()), value);
632 assert_eq!(take(&located), value);
633 }
634
635 #[test]
636 fn last_key_wins() {
637 let mut map = Map::new();
638 map.insert("foo".to_string(), located_string("first"));
639 map.insert("foo".to_string(), located_string("second"));
640 assert_eq!(
641 map.get("foo").unwrap().value().as_string().unwrap(),
642 "second"
643 );
644 }
645
646 #[test]
647 fn default_display_is_compact() {
648 let value = LocatedValue::new(
649 Value::String("hello".to_string()),
650 Location::at("file", "config.yaml", Some(2), Some(5), None),
651 );
652 let message = value.to_string();
653 assert!(!message.contains('\n'));
654 assert!(!message.starts_with('@'));
655 assert_eq!(message, "\"hello\"");
656 }
657
658 #[test]
659 fn alternate_display_shows_location_and_multiline() {
660 let value = LocatedValue::new(
661 Value::String("hello".to_string()),
662 Location::at("file", "config.yaml", Some(2), Some(5), None),
663 );
664 let message = format!("{value:#}");
665 assert_eq!(
666 message,
667 "{\n \"value\": \"hello\",\n \"location\": \"file:config.yaml:2:5\",\n}"
668 );
669 assert!(!message.contains('@'));
670 }
671
672 #[test]
673 fn value_accessors_and_constructors() {
674 let mut value = Value::Bool(true);
675 assert!(value.is_bool());
676 assert_eq!(value.as_bool(), Some(true));
677 assert_eq!(value.type_name(), ValueType::Bool);
678 if let Some(flag) = value.bool_mut() {
679 *flag = false;
680 }
681 assert_eq!(value.into_bool(), Some(false));
682
683 let list = Value::new_list();
684 assert!(list.is_list());
685 let map = Value::new_map();
686 assert!(map.is_map());
687 let text = Value::new_string();
688 assert!(text.is_string());
689 }
690
691 #[test]
692 fn map_remove_get_mut_and_display() {
693 let mut map = Map::new();
694 map.insert("a".to_string(), located_string("one"));
695 map.insert("b".to_string(), located_string("two"));
696 assert_eq!(map.len(), 2);
697 assert!(map.contains_key("a"));
698 assert!(map.get_mut("b").is_some());
699 let removed = map.remove("a");
700 assert!(removed.is_some());
701 assert!(!map.contains_key("a"));
702
703 let compact = format!("{map}");
704 assert!(compact.contains("b"));
705 let detailed = format!("{map:#}");
706 assert!(detailed.contains("location"));
707 }
708
709 #[test]
710 fn location_display_and_with_length() {
711 let location = Location::at("file", "", Some(1), Some(2), None).with_length(3);
712 assert_eq!(location.to_string(), "file:1:2");
713 let resourceful = Location::at("file", "cfg.yml", Some(4), None, None);
714 assert_eq!(resourceful.to_string(), "file:cfg.yml:4");
715 }
716
717 #[test]
718 fn comment_attached_to_located_value() {
719 let lv = LocatedValue::new(
720 Value::String("debug".into()),
721 Location::at("file", "baz.toml", Some(4), Some(9), None),
722 )
723 .with_comment(
724 Comment::new()
725 .with_before(["# log level: debug, info, warn, error"])
726 .with_after(Some("# inline")),
727 );
728 assert_eq!(
729 lv.comment().before(),
730 &["# log level: debug, info, warn, error"]
731 );
732 assert_eq!(lv.comment().after(), Some("# inline"));
733 assert_eq!(lv.value().as_string().unwrap(), "debug");
734 }
735
736 #[test]
737 fn comment_alternate_display_shows_comment_fields() {
738 let lv = LocatedValue::new(
739 Value::String("debug".into()),
740 Location::at("file", "baz.toml", Some(4), Some(9), None),
741 )
742 .with_comment(Comment::new().with_before(["# level comment"]));
743 let text = format!("{lv:#}");
744 assert!(text.contains("\"comment_before\""));
745 assert!(text.contains("level comment"));
746 }
747
748 #[test]
749 fn value_list_and_map_display_modes() {
750 let list = Value::List(vec![located_string("a"), located_string("b")]);
751 assert!(format!("{list}").contains("a"));
752 assert!(format!("{list:#}").contains("location"));
753
754 let mut map = Map::new();
755 map.insert("k".to_string(), located_string("v"));
756 let map_value = Value::Map(map);
757 assert!(format!("{map_value}").contains("k"));
758 }
759}