nu_protocol/ast/
cell_path.rs

1use super::Expression;
2use crate::Span;
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    },
18    /// Accessing a member by index (i.e. row of a table or item in a list)
19    Int {
20        val: usize,
21        span: Span,
22        /// If marked as optional don't throw an error if not found but perform default handling
23        /// (e.g. return `Value::Nothing`)
24        optional: bool,
25    },
26}
27
28impl PathMember {
29    pub fn int(val: usize, optional: bool, span: Span) -> Self {
30        PathMember::Int {
31            val,
32            span,
33            optional,
34        }
35    }
36
37    pub fn string(val: String, optional: bool, span: Span) -> Self {
38        PathMember::String {
39            val,
40            span,
41            optional,
42        }
43    }
44
45    pub fn test_int(val: usize, optional: bool) -> Self {
46        PathMember::Int {
47            val,
48            optional,
49            span: Span::test_data(),
50        }
51    }
52
53    pub fn test_string(val: String, optional: bool) -> Self {
54        PathMember::String {
55            val,
56            optional,
57            span: Span::test_data(),
58        }
59    }
60
61    pub fn make_optional(&mut self) {
62        match self {
63            PathMember::String {
64                ref mut optional, ..
65            } => *optional = true,
66            PathMember::Int {
67                ref mut optional, ..
68            } => *optional = true,
69        }
70    }
71
72    pub fn span(&self) -> Span {
73        match self {
74            PathMember::String { span, .. } => *span,
75            PathMember::Int { span, .. } => *span,
76        }
77    }
78}
79
80impl PartialEq for PathMember {
81    fn eq(&self, other: &Self) -> bool {
82        match (self, other) {
83            (
84                Self::String {
85                    val: l_val,
86                    optional: l_opt,
87                    ..
88                },
89                Self::String {
90                    val: r_val,
91                    optional: r_opt,
92                    ..
93                },
94            ) => l_val == r_val && l_opt == r_opt,
95            (
96                Self::Int {
97                    val: l_val,
98                    optional: l_opt,
99                    ..
100                },
101                Self::Int {
102                    val: r_val,
103                    optional: r_opt,
104                    ..
105                },
106            ) => l_val == r_val && l_opt == r_opt,
107            _ => false,
108        }
109    }
110}
111
112impl PartialOrd for PathMember {
113    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
114        match (self, other) {
115            (
116                PathMember::String {
117                    val: l_val,
118                    optional: l_opt,
119                    ..
120                },
121                PathMember::String {
122                    val: r_val,
123                    optional: r_opt,
124                    ..
125                },
126            ) => {
127                let val_ord = Some(l_val.cmp(r_val));
128
129                if let Some(Ordering::Equal) = val_ord {
130                    Some(l_opt.cmp(r_opt))
131                } else {
132                    val_ord
133                }
134            }
135            (
136                PathMember::Int {
137                    val: l_val,
138                    optional: l_opt,
139                    ..
140                },
141                PathMember::Int {
142                    val: r_val,
143                    optional: r_opt,
144                    ..
145                },
146            ) => {
147                let val_ord = Some(l_val.cmp(r_val));
148
149                if let Some(Ordering::Equal) = val_ord {
150                    Some(l_opt.cmp(r_opt))
151                } else {
152                    val_ord
153                }
154            }
155            (PathMember::Int { .. }, PathMember::String { .. }) => Some(Ordering::Greater),
156            (PathMember::String { .. }, PathMember::Int { .. }) => Some(Ordering::Less),
157        }
158    }
159}
160
161/// Represents the potentially nested access to fields/cells of a container type
162///
163/// In our current implementation for table access the order of row/column is commutative.
164/// This limits the number of possible rows to select in one [`CellPath`] to 1 as it could
165/// otherwise be ambiguous
166///
167/// ```nushell
168/// col1.0
169/// 0.col1
170/// col2
171/// 42
172/// ```
173#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
174pub struct CellPath {
175    pub members: Vec<PathMember>,
176}
177
178impl CellPath {
179    pub fn make_optional(&mut self) {
180        for member in &mut self.members {
181            member.make_optional();
182        }
183    }
184
185    // Formats the cell-path as a column name, i.e. without quoting and optional markers ('?').
186    pub fn to_column_name(&self) -> String {
187        let mut s = String::new();
188
189        for member in &self.members {
190            match member {
191                PathMember::Int { val, .. } => {
192                    s += &val.to_string();
193                }
194                PathMember::String { val, .. } => {
195                    s += val;
196                }
197            }
198
199            s.push('.');
200        }
201
202        s.pop(); // Easier than checking whether to insert the '.' on every iteration.
203        s
204    }
205}
206
207impl Display for CellPath {
208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        write!(f, "$")?;
210        for member in self.members.iter() {
211            match member {
212                PathMember::Int { val, optional, .. } => {
213                    let question_mark = if *optional { "?" } else { "" };
214                    write!(f, ".{val}{question_mark}")?
215                }
216                PathMember::String { val, optional, .. } => {
217                    let question_mark = if *optional { "?" } else { "" };
218                    let val = if needs_quoting(val) {
219                        &escape_quote_string(val)
220                    } else {
221                        val
222                    };
223                    write!(f, ".{val}{question_mark}")?
224                }
225            }
226        }
227        Ok(())
228    }
229}
230
231#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
232pub struct FullCellPath {
233    pub head: Expression,
234    pub tail: Vec<PathMember>,
235}
236
237#[cfg(test)]
238mod test {
239    use super::*;
240    use std::cmp::Ordering::Greater;
241
242    #[test]
243    fn path_member_partial_ord() {
244        assert_eq!(
245            Some(Greater),
246            PathMember::test_int(5, true).partial_cmp(&PathMember::test_string("e".into(), true))
247        );
248
249        assert_eq!(
250            Some(Greater),
251            PathMember::test_int(5, true).partial_cmp(&PathMember::test_int(5, false))
252        );
253
254        assert_eq!(
255            Some(Greater),
256            PathMember::test_int(6, true).partial_cmp(&PathMember::test_int(5, true))
257        );
258
259        assert_eq!(
260            Some(Greater),
261            PathMember::test_string("e".into(), true)
262                .partial_cmp(&PathMember::test_string("e".into(), false))
263        );
264
265        assert_eq!(
266            Some(Greater),
267            PathMember::test_string("f".into(), true)
268                .partial_cmp(&PathMember::test_string("e".into(), true))
269        );
270    }
271}