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/// Represents the potentially nested access to fields/cells of a container type
177///
178/// In our current implementation for table access the order of row/column is commutative.
179/// This limits the number of possible rows to select in one [`CellPath`] to 1 as it could
180/// otherwise be ambiguous
181///
182/// ```nushell
183/// col1.0
184/// 0.col1
185/// col2
186/// 42
187/// ```
188#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
189pub struct CellPath {
190    pub members: Vec<PathMember>,
191}
192
193impl CellPath {
194    pub fn make_optional(&mut self) {
195        for member in &mut self.members {
196            member.make_optional();
197        }
198    }
199
200    pub fn make_insensitive(&mut self) {
201        for member in &mut self.members {
202            member.make_insensitive();
203        }
204    }
205
206    // Formats the cell-path as a column name, i.e. without quoting and optional markers ('?').
207    pub fn to_column_name(&self) -> String {
208        let mut s = String::new();
209
210        for member in &self.members {
211            match member {
212                PathMember::Int { val, .. } => {
213                    s += &val.to_string();
214                }
215                PathMember::String { val, .. } => {
216                    s += val;
217                }
218            }
219
220            s.push('.');
221        }
222
223        s.pop(); // Easier than checking whether to insert the '.' on every iteration.
224        s
225    }
226
227    /// Returns an estimate of the memory size used by this CellPath in bytes
228    pub fn memory_size(&self) -> usize {
229        std::mem::size_of::<Self>() + self.members.iter().map(|m| m.memory_size()).sum::<usize>()
230    }
231}
232
233impl Display for CellPath {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        write!(f, "$")?;
236        for member in self.members.iter() {
237            match member {
238                PathMember::Int { val, optional, .. } => {
239                    let question_mark = if *optional { "?" } else { "" };
240                    write!(f, ".{val}{question_mark}")?
241                }
242                PathMember::String {
243                    val,
244                    optional,
245                    casing,
246                    ..
247                } => {
248                    let question_mark = if *optional { "?" } else { "" };
249                    let exclamation_mark = if *casing == Casing::Insensitive {
250                        "!"
251                    } else {
252                        ""
253                    };
254                    let val = if needs_quoting(val) {
255                        &escape_quote_string(val)
256                    } else {
257                        val
258                    };
259                    write!(f, ".{val}{exclamation_mark}{question_mark}")?
260                }
261            }
262        }
263        // Empty cell-paths are `$.` not `$`
264        if self.members.is_empty() {
265            write!(f, ".")?;
266        }
267        Ok(())
268    }
269}
270
271#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
272pub struct FullCellPath {
273    pub head: Expression,
274    pub tail: Vec<PathMember>,
275}
276
277#[cfg(test)]
278mod test {
279    use super::*;
280    use std::cmp::Ordering::Greater;
281
282    #[test]
283    fn path_member_partial_ord() {
284        assert_eq!(
285            Some(Greater),
286            PathMember::test_int(5, true).partial_cmp(&PathMember::test_string(
287                "e".into(),
288                true,
289                Casing::Sensitive
290            ))
291        );
292
293        assert_eq!(
294            Some(Greater),
295            PathMember::test_int(5, true).partial_cmp(&PathMember::test_int(5, false))
296        );
297
298        assert_eq!(
299            Some(Greater),
300            PathMember::test_int(6, true).partial_cmp(&PathMember::test_int(5, true))
301        );
302
303        assert_eq!(
304            Some(Greater),
305            PathMember::test_string("e".into(), true, Casing::Sensitive).partial_cmp(
306                &PathMember::test_string("e".into(), false, Casing::Sensitive)
307            )
308        );
309
310        assert_eq!(
311            Some(Greater),
312            PathMember::test_string("f".into(), true, Casing::Sensitive).partial_cmp(
313                &PathMember::test_string("e".into(), true, Casing::Sensitive)
314            )
315        );
316    }
317}