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