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