1use core::fmt::Display;
2
3use crate::{prelude_internal::*, value::PartialObjectKey};
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, Plural)]
6pub struct EurePath(pub Vec<PathSegment>);
7
8impl EurePath {
9 pub fn root() -> Self {
11 EurePath(Vec::new())
12 }
13
14 pub fn is_root(&self) -> bool {
16 self.0.is_empty()
17 }
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21pub enum PathSegment {
22 Ident(Identifier),
24 Extension(Identifier),
26 Value(ObjectKey),
28 PartialValue(PartialObjectKey),
30 TupleIndex(u8),
32 ArrayIndex(Option<usize>),
34 HoleKey(Option<Identifier>),
36}
37
38impl PathSegment {
39 pub fn from_partial_object_key(key: PartialObjectKey) -> Self {
40 match key {
41 PartialObjectKey::Number(n) => Self::Value(ObjectKey::Number(n)),
42 PartialObjectKey::String(s) => Self::Value(ObjectKey::String(s)),
43 PartialObjectKey::Hole(label) => Self::HoleKey(label),
44 PartialObjectKey::Tuple(items) => Self::PartialValue(PartialObjectKey::Tuple(items)),
45 }
46 }
47}
48
49impl Display for EurePath {
50 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51 if self.0.is_empty() {
52 return write!(f, "(root)");
53 }
54 for (i, segment) in self.0.iter().enumerate() {
55 let is_first = i == 0;
56 match segment {
57 PathSegment::Ident(id) => {
58 if !is_first {
59 write!(f, ".")?;
60 }
61 write!(f, "{}", id)?;
62 }
63 PathSegment::Extension(id) => {
64 if !is_first {
65 write!(f, ".")?;
66 }
67 write!(f, "${}", id)?;
68 }
69 PathSegment::Value(key) => {
70 if !is_first {
71 write!(f, ".")?;
72 }
73 write!(f, "{}", key)?;
74 }
75 PathSegment::PartialValue(key) => {
76 if !is_first {
77 write!(f, ".")?;
78 }
79 write!(f, "{}", key)?;
80 }
81 PathSegment::TupleIndex(index) => {
82 if !is_first {
83 write!(f, ".")?;
84 }
85 write!(f, "#{}", index)?;
86 }
87 PathSegment::ArrayIndex(Some(index)) => write!(f, "[{}]", index)?,
88 PathSegment::ArrayIndex(None) => write!(f, "[]")?,
89 PathSegment::HoleKey(None) => {
90 if !is_first {
91 write!(f, ".")?;
92 }
93 write!(f, "!")?;
94 }
95 PathSegment::HoleKey(Some(label)) => {
96 if !is_first {
97 write!(f, ".")?;
98 }
99 write!(f, "!{}", label)?;
100 }
101 }
102 }
103 Ok(())
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use alloc::format;
110
111 use super::*;
112 use crate::value::{ObjectKey, PartialObjectKey, Tuple};
113
114 #[test]
115 fn test_display_empty_path() {
116 let path = EurePath::root();
117 assert_eq!(format!("{}", path), "(root)");
118 }
119
120 #[test]
121 fn test_display_single_ident() {
122 let path = EurePath(vec![PathSegment::Ident(Identifier::new_unchecked("name"))]);
123 assert_eq!(format!("{}", path), "name");
124 }
125
126 #[test]
127 fn test_display_nested_idents() {
128 let path = EurePath(vec![
129 PathSegment::Ident(Identifier::new_unchecked("a")),
130 PathSegment::Ident(Identifier::new_unchecked("b")),
131 PathSegment::Ident(Identifier::new_unchecked("c")),
132 ]);
133 assert_eq!(format!("{}", path), "a.b.c");
134 }
135
136 #[test]
137 fn test_display_extension() {
138 let path = EurePath(vec![PathSegment::Extension(Identifier::new_unchecked(
139 "variant",
140 ))]);
141 assert_eq!(format!("{}", path), "$variant");
142 }
143
144 #[test]
145 fn test_display_array_index() {
146 let path = EurePath(vec![
147 PathSegment::Ident(Identifier::new_unchecked("items")),
148 PathSegment::ArrayIndex(Some(0)),
149 ]);
150 assert_eq!(format!("{}", path), "items[0]");
151 }
152
153 #[test]
154 fn test_display_array_index_none() {
155 let path = EurePath(vec![
156 PathSegment::Ident(Identifier::new_unchecked("items")),
157 PathSegment::ArrayIndex(None),
158 ]);
159 assert_eq!(format!("{}", path), "items[]");
160 }
161
162 #[test]
163 fn test_display_tuple_index() {
164 let path = EurePath(vec![
165 PathSegment::Ident(Identifier::new_unchecked("point")),
166 PathSegment::TupleIndex(1),
167 ]);
168 assert_eq!(format!("{}", path), "point.#1");
169 }
170
171 #[test]
172 fn test_display_string_key() {
173 let path = EurePath(vec![PathSegment::Value(ObjectKey::String(
174 "hello".to_string(),
175 ))]);
176 assert_eq!(format!("{}", path), "\"hello\"");
177 }
178
179 #[test]
180 fn test_display_string_key_with_spaces() {
181 let path = EurePath(vec![PathSegment::Value(ObjectKey::String(
182 "hello world".to_string(),
183 ))]);
184 assert_eq!(format!("{}", path), "\"hello world\"");
185 }
186
187 #[test]
188 fn test_display_string_key_with_quotes() {
189 let path = EurePath(vec![PathSegment::Value(ObjectKey::String(
190 "say \"hi\"".to_string(),
191 ))]);
192 assert_eq!(format!("{}", path), "\"say \\\"hi\\\"\"");
193 }
194
195 #[test]
196 fn test_display_number_key() {
197 let path = EurePath(vec![PathSegment::Value(ObjectKey::Number(42.into()))]);
198 assert_eq!(format!("{}", path), "42");
199 }
200
201 #[test]
202 fn test_display_bool_key() {
203 let path = EurePath(vec![PathSegment::Value(ObjectKey::String("true".into()))]);
205 assert_eq!(format!("{}", path), "\"true\"");
206 }
207
208 #[test]
209 fn test_display_complex_path() {
210 let path = EurePath(vec![
211 PathSegment::Ident(Identifier::new_unchecked("config")),
212 PathSegment::Extension(Identifier::new_unchecked("eure")),
213 PathSegment::Ident(Identifier::new_unchecked("items")),
214 PathSegment::ArrayIndex(Some(0)),
215 PathSegment::Value(ObjectKey::String("key with space".to_string())),
216 ]);
217 assert_eq!(
218 format!("{}", path),
219 "config.$eure.items[0].\"key with space\""
220 );
221 }
222
223 #[test]
224 fn test_display_partial_tuple_key() {
225 let path = EurePath(vec![PathSegment::PartialValue(PartialObjectKey::Tuple(
226 Tuple(vec![
227 PartialObjectKey::Number(1.into()),
228 PartialObjectKey::Hole(None),
229 ]),
230 ))]);
231 assert_eq!(format!("{}", path), "(1, !)");
232 }
233}