Skip to main content

nu_protocol/ast/
cell_path.rs

1use super::Expression;
2use crate::{Span, casing::Casing};
3use nu_utils::{escape_quote_string, needs_quoting};
4use serde::{Deserialize, Serialize};
5use std::{cmp::Ordering, fmt::Display};
6
7/// One level of access of a [`CellPath`]
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub enum PathMember {
10    /// Accessing a member by string (i.e. columns of a table or [`Record`](crate::Record))
11    String {
12        val: String,
13        span: Span,
14        /// If marked as optional don't throw an error if not found but perform default handling
15        /// (e.g. return `Value::Nothing`)
16        optional: bool,
17        /// Affects column lookup
18        casing: Casing,
19    },
20    /// Accessing a member by index (i.e. row of a table or item in a list)
21    Int {
22        val: usize,
23        span: Span,
24        /// If marked as optional don't throw an error if not found but perform default handling
25        /// (e.g. return `Value::Nothing`)
26        optional: bool,
27    },
28}
29
30impl PathMember {
31    pub fn int(val: usize, optional: bool, span: Span) -> Self {
32        PathMember::Int {
33            val,
34            span,
35            optional,
36        }
37    }
38
39    pub fn string(val: String, optional: bool, casing: Casing, span: Span) -> Self {
40        PathMember::String {
41            val,
42            span,
43            optional,
44            casing,
45        }
46    }
47
48    pub fn test_int(val: usize, optional: bool) -> Self {
49        PathMember::Int {
50            val,
51            optional,
52            span: Span::test_data(),
53        }
54    }
55
56    pub fn test_string(val: String, optional: bool, casing: Casing) -> Self {
57        PathMember::String {
58            val,
59            optional,
60            casing,
61            span: Span::test_data(),
62        }
63    }
64
65    pub fn make_optional(&mut self) {
66        match self {
67            PathMember::String { optional, .. } => *optional = true,
68            PathMember::Int { optional, .. } => *optional = true,
69        }
70    }
71
72    pub fn make_insensitive(&mut self) {
73        match self {
74            PathMember::String { casing, .. } => *casing = Casing::Insensitive,
75            PathMember::Int { .. } => {}
76        }
77    }
78
79    pub fn span(&self) -> Span {
80        match self {
81            PathMember::String { span, .. } => *span,
82            PathMember::Int { span, .. } => *span,
83        }
84    }
85
86    /// Returns an estimate of the memory size used by this PathMember in bytes
87    pub fn memory_size(&self) -> usize {
88        match self {
89            PathMember::String { val, .. } => std::mem::size_of::<Self>() + val.capacity(),
90            PathMember::Int { .. } => std::mem::size_of::<Self>(),
91        }
92    }
93}
94
95impl PartialEq for PathMember {
96    fn eq(&self, other: &Self) -> bool {
97        match (self, other) {
98            (
99                Self::String {
100                    val: l_val,
101                    optional: l_opt,
102                    ..
103                },
104                Self::String {
105                    val: r_val,
106                    optional: r_opt,
107                    ..
108                },
109            ) => l_val == r_val && l_opt == r_opt,
110            (
111                Self::Int {
112                    val: l_val,
113                    optional: l_opt,
114                    ..
115                },
116                Self::Int {
117                    val: r_val,
118                    optional: r_opt,
119                    ..
120                },
121            ) => l_val == r_val && l_opt == r_opt,
122            _ => false,
123        }
124    }
125}
126
127impl PartialOrd for PathMember {
128    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
129        match (self, other) {
130            (
131                PathMember::String {
132                    val: l_val,
133                    optional: l_opt,
134                    ..
135                },
136                PathMember::String {
137                    val: r_val,
138                    optional: r_opt,
139                    ..
140                },
141            ) => {
142                let val_ord = Some(l_val.cmp(r_val));
143
144                if let Some(Ordering::Equal) = val_ord {
145                    Some(l_opt.cmp(r_opt))
146                } else {
147                    val_ord
148                }
149            }
150            (
151                PathMember::Int {
152                    val: l_val,
153                    optional: l_opt,
154                    ..
155                },
156                PathMember::Int {
157                    val: r_val,
158                    optional: r_opt,
159                    ..
160                },
161            ) => {
162                let val_ord = Some(l_val.cmp(r_val));
163
164                if let Some(Ordering::Equal) = val_ord {
165                    Some(l_opt.cmp(r_opt))
166                } else {
167                    val_ord
168                }
169            }
170            (PathMember::Int { .. }, PathMember::String { .. }) => Some(Ordering::Greater),
171            (PathMember::String { .. }, PathMember::Int { .. }) => Some(Ordering::Less),
172        }
173    }
174}
175
176/// [`PathMember`] for testing purposes.
177///
178/// This path member may be converted via [`into_path_member`](Self::into_path_member) into a
179/// [`PathMember`] that is using a [`Span::test_data()`](crate::Span::test_data) span.
180#[doc(hidden)]
181pub struct TestPathMember<T>(T);
182
183impl<S: Into<String>> From<S> for TestPathMember<String> {
184    fn from(value: S) -> Self {
185        Self(value.into())
186    }
187}
188
189impl TestPathMember<String> {
190    pub fn into_path_member(self) -> PathMember {
191        PathMember::test_string(self.0, false, Casing::Sensitive)
192    }
193}
194
195impl From<usize> for TestPathMember<usize> {
196    fn from(value: usize) -> Self {
197        Self(value)
198    }
199}
200
201impl TestPathMember<usize> {
202    pub fn into_path_member(self) -> PathMember {
203        PathMember::test_int(self.0, false)
204    }
205}
206
207/// Represents the potentially nested access to fields/cells of a container type
208///
209/// In our current implementation for table access the order of row/column is commutative.
210/// This limits the number of possible rows to select in one [`CellPath`] to 1 as it could
211/// otherwise be ambiguous
212///
213/// ```nushell
214/// col1.0
215/// 0.col1
216/// col2
217/// 42
218/// ```
219#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
220pub struct CellPath {
221    pub members: Vec<PathMember>,
222}
223
224impl CellPath {
225    pub fn make_optional(&mut self) {
226        for member in &mut self.members {
227            member.make_optional();
228        }
229    }
230
231    pub fn make_insensitive(&mut self) {
232        for member in &mut self.members {
233            member.make_insensitive();
234        }
235    }
236
237    // Formats the cell-path as a column name, i.e. without quoting and optional markers ('?').
238    pub fn to_column_name(&self) -> String {
239        let mut s = String::new();
240
241        for member in &self.members {
242            match member {
243                PathMember::Int { val, .. } => {
244                    s += &val.to_string();
245                }
246                PathMember::String { val, .. } => {
247                    s += val;
248                }
249            }
250
251            s.push('.');
252        }
253
254        s.pop(); // Easier than checking whether to insert the '.' on every iteration.
255        s
256    }
257
258    /// Returns an estimate of the memory size used by this CellPath in bytes
259    pub fn memory_size(&self) -> usize {
260        std::mem::size_of::<Self>() + self.members.iter().map(|m| m.memory_size()).sum::<usize>()
261    }
262}
263
264impl Display for CellPath {
265    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266        write!(f, "$")?;
267        for member in self.members.iter() {
268            match member {
269                PathMember::Int { val, optional, .. } => {
270                    let question_mark = if *optional { "?" } else { "" };
271                    write!(f, ".{val}{question_mark}")?
272                }
273                PathMember::String {
274                    val,
275                    optional,
276                    casing,
277                    ..
278                } => {
279                    let question_mark = if *optional { "?" } else { "" };
280                    let exclamation_mark = if *casing == Casing::Insensitive {
281                        "!"
282                    } else {
283                        ""
284                    };
285                    let val = if needs_quoting(val) {
286                        &escape_quote_string(val)
287                    } else {
288                        val
289                    };
290                    write!(f, ".{val}{exclamation_mark}{question_mark}")?
291                }
292            }
293        }
294        // Empty cell-paths are `$.` not `$`
295        if self.members.is_empty() {
296            write!(f, ".")?;
297        }
298        Ok(())
299    }
300}
301
302#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
303pub struct FullCellPath {
304    pub head: Expression,
305    pub tail: Vec<PathMember>,
306}
307
308#[cfg(test)]
309mod test {
310    use super::*;
311    use std::cmp::Ordering::Greater;
312
313    #[test]
314    fn path_member_partial_ord() {
315        assert_eq!(
316            Some(Greater),
317            PathMember::test_int(5, true).partial_cmp(&PathMember::test_string(
318                "e".into(),
319                true,
320                Casing::Sensitive
321            ))
322        );
323
324        assert_eq!(
325            Some(Greater),
326            PathMember::test_int(5, true).partial_cmp(&PathMember::test_int(5, false))
327        );
328
329        assert_eq!(
330            Some(Greater),
331            PathMember::test_int(6, true).partial_cmp(&PathMember::test_int(5, true))
332        );
333
334        assert_eq!(
335            Some(Greater),
336            PathMember::test_string("e".into(), true, Casing::Sensitive).partial_cmp(
337                &PathMember::test_string("e".into(), false, Casing::Sensitive)
338            )
339        );
340
341        assert_eq!(
342            Some(Greater),
343            PathMember::test_string("f".into(), true, Casing::Sensitive).partial_cmp(
344                &PathMember::test_string("e".into(), true, Casing::Sensitive)
345            )
346        );
347    }
348}