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, str::FromStr};
6use winnow::Parser;
7
8/// One level of access of a [`CellPath`]
9#[derive(Debug, Clone)]
10pub enum PathMember {
11    /// Accessing a member by string (i.e. columns of a table or [`Record`](crate::Record))
12    String {
13        val: String,
14        span: Span,
15        /// If marked as optional don't throw an error if not found but perform default handling
16        /// (e.g. return `Value::Nothing`)
17        optional: bool,
18        /// Affects column lookup
19        casing: Casing,
20    },
21    /// Accessing a member by index (i.e. row of a table or item in a list)
22    Int {
23        val: usize,
24        span: Span,
25        /// If marked as optional don't throw an error if not found but perform default handling
26        /// (e.g. return `Value::Nothing`)
27        optional: bool,
28    },
29}
30
31impl PathMember {
32    pub fn int(val: usize, optional: bool, span: Span) -> Self {
33        PathMember::Int {
34            val,
35            span,
36            optional,
37        }
38    }
39
40    pub fn string(val: String, optional: bool, casing: Casing, span: Span) -> Self {
41        PathMember::String {
42            val,
43            span,
44            optional,
45            casing,
46        }
47    }
48
49    pub fn test_int(val: usize, optional: bool) -> Self {
50        PathMember::Int {
51            val,
52            optional,
53            span: Span::test_data(),
54        }
55    }
56
57    pub fn test_string(val: impl Into<String>, optional: bool, casing: Casing) -> Self {
58        PathMember::String {
59            val: val.into(),
60            optional,
61            casing,
62            span: Span::test_data(),
63        }
64    }
65
66    pub fn make_optional(&mut self) {
67        match self {
68            PathMember::String { optional, .. } => *optional = true,
69            PathMember::Int { optional, .. } => *optional = true,
70        }
71    }
72
73    pub fn make_insensitive(&mut self) {
74        match self {
75            PathMember::String { casing, .. } => *casing = Casing::Insensitive,
76            PathMember::Int { .. } => {}
77        }
78    }
79
80    pub fn span(&self) -> Span {
81        match self {
82            PathMember::String { span, .. } => *span,
83            PathMember::Int { span, .. } => *span,
84        }
85    }
86
87    /// Update the span of a path member with a new span if the current span is unknown or test data.
88    pub fn fallback_span(&mut self, span: Span) -> Span {
89        let fallback = span;
90        match self {
91            PathMember::String { span, .. } => span.fallback(fallback),
92            PathMember::Int { span, .. } => span.fallback(fallback),
93        }
94    }
95
96    /// Returns an estimate of the memory size used by this PathMember in bytes
97    pub fn memory_size(&self) -> usize {
98        match self {
99            PathMember::String { val, .. } => std::mem::size_of::<Self>() + val.capacity(),
100            PathMember::Int { .. } => std::mem::size_of::<Self>(),
101        }
102    }
103}
104
105impl PartialEq for PathMember {
106    fn eq(&self, other: &Self) -> bool {
107        match (self, other) {
108            (
109                Self::String {
110                    val: l_val,
111                    optional: l_opt,
112                    ..
113                },
114                Self::String {
115                    val: r_val,
116                    optional: r_opt,
117                    ..
118                },
119            ) => l_val == r_val && l_opt == r_opt,
120            (
121                Self::Int {
122                    val: l_val,
123                    optional: l_opt,
124                    ..
125                },
126                Self::Int {
127                    val: r_val,
128                    optional: r_opt,
129                    ..
130                },
131            ) => l_val == r_val && l_opt == r_opt,
132            _ => false,
133        }
134    }
135}
136
137impl PartialOrd for PathMember {
138    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
139        match (self, other) {
140            (
141                PathMember::String {
142                    val: l_val,
143                    optional: l_opt,
144                    ..
145                },
146                PathMember::String {
147                    val: r_val,
148                    optional: r_opt,
149                    ..
150                },
151            ) => {
152                let val_ord = Some(l_val.cmp(r_val));
153
154                if let Some(Ordering::Equal) = val_ord {
155                    Some(l_opt.cmp(r_opt))
156                } else {
157                    val_ord
158                }
159            }
160            (
161                PathMember::Int {
162                    val: l_val,
163                    optional: l_opt,
164                    ..
165                },
166                PathMember::Int {
167                    val: r_val,
168                    optional: r_opt,
169                    ..
170                },
171            ) => {
172                let val_ord = Some(l_val.cmp(r_val));
173
174                if let Some(Ordering::Equal) = val_ord {
175                    Some(l_opt.cmp(r_opt))
176                } else {
177                    val_ord
178                }
179            }
180            (PathMember::Int { .. }, PathMember::String { .. }) => Some(Ordering::Greater),
181            (PathMember::String { .. }, PathMember::Int { .. }) => Some(Ordering::Less),
182        }
183    }
184}
185
186impl Display for PathMember {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        match self {
189            PathMember::Int { val, optional, .. } => {
190                let question_mark = if *optional { "?" } else { "" };
191                write!(f, "{val}{question_mark}")
192            }
193            PathMember::String {
194                val,
195                optional,
196                casing,
197                ..
198            } => {
199                let question_mark = if *optional { "?" } else { "" };
200                let exclamation_mark = if *casing == Casing::Insensitive {
201                    "!"
202                } else {
203                    ""
204                };
205                let val = if needs_quoting(val) {
206                    &escape_quote_string(val)
207                } else {
208                    val
209                };
210                write!(f, "{val}{exclamation_mark}{question_mark}")
211            }
212        }
213    }
214}
215
216#[derive(Debug, thiserror::Error)]
217#[error("could not parse path member {attempted:?}")]
218pub struct ParsePathMemberError {
219    attempted: String,
220}
221
222impl FromStr for PathMember {
223    type Err = ParsePathMemberError;
224
225    fn from_str(s: &str) -> Result<Self, Self::Err> {
226        parse::path_member
227            .parse(s)
228            .map_err(|_| ParsePathMemberError {
229                attempted: s.to_owned(),
230            })
231    }
232}
233
234impl Serialize for PathMember {
235    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
236    where
237        S: serde::Serializer,
238    {
239        self.to_string().serialize(serializer)
240    }
241}
242
243impl<'de> Deserialize<'de> for PathMember {
244    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
245    where
246        D: serde::Deserializer<'de>,
247    {
248        let s = String::deserialize(deserializer)?;
249        Self::from_str(&s).map_err(serde::de::Error::custom)
250    }
251}
252
253/// [`PathMember`] for testing purposes.
254///
255/// This path member may be converted via [`into_path_member`](Self::into_path_member) into a
256/// [`PathMember`] that is using a [`Span::test_data()`](crate::Span::test_data) span.
257#[doc(hidden)]
258pub struct TestPathMember<T>(T);
259
260impl<S: Into<String>> From<S> for TestPathMember<String> {
261    fn from(value: S) -> Self {
262        Self(value.into())
263    }
264}
265
266impl TestPathMember<String> {
267    pub fn into_path_member(self) -> PathMember {
268        PathMember::test_string(self.0, false, Casing::Sensitive)
269    }
270}
271
272impl From<usize> for TestPathMember<usize> {
273    fn from(value: usize) -> Self {
274        Self(value)
275    }
276}
277
278impl TestPathMember<usize> {
279    pub fn into_path_member(self) -> PathMember {
280        PathMember::test_int(self.0, false)
281    }
282}
283
284/// Represents the potentially nested access to fields/cells of a container type
285///
286/// In our current implementation for table access the order of row/column is commutative.
287/// This limits the number of possible rows to select in one [`CellPath`] to 1 as it could
288/// otherwise be ambiguous
289///
290/// ```nushell
291/// col1.0
292/// 0.col1
293/// col2
294/// 42
295/// ```
296#[derive(Debug, Clone, PartialEq, PartialOrd)]
297pub struct CellPath {
298    pub members: Vec<PathMember>,
299}
300
301impl CellPath {
302    pub fn empty() -> Self {
303        Self {
304            members: Vec::new(),
305        }
306    }
307
308    pub fn make_optional(&mut self) {
309        for member in &mut self.members {
310            member.make_optional();
311        }
312    }
313
314    pub fn make_insensitive(&mut self) {
315        for member in &mut self.members {
316            member.make_insensitive();
317        }
318    }
319
320    // Formats the cell-path as a column name, i.e. without quoting and optional markers ('?').
321    pub fn to_column_name(&self) -> String {
322        let mut s = String::new();
323
324        for member in &self.members {
325            match member {
326                PathMember::Int { val, .. } => {
327                    s += &val.to_string();
328                }
329                PathMember::String { val, .. } => {
330                    s += val;
331                }
332            }
333
334            s.push('.');
335        }
336
337        s.pop(); // Easier than checking whether to insert the '.' on every iteration.
338        s
339    }
340
341    /// Returns an estimate of the memory size used by this CellPath in bytes
342    pub fn memory_size(&self) -> usize {
343        std::mem::size_of::<Self>() + self.members.iter().map(|m| m.memory_size()).sum::<usize>()
344    }
345
346    /// Update all path members with a new span if their current span is either unknown or test data.
347    pub fn fallback_span(&mut self, span: Span) {
348        for member in self.members.iter_mut() {
349            member.fallback_span(span);
350        }
351    }
352
353    /// Like [`fallback_span`] but allows chaining.
354    ///
355    /// This method is often used in parsing of data formats and therefore is constructed newly
356    /// where chaining is more ergonomic.
357    ///
358    /// # Example
359    /// ```
360    /// # use std::str::FromStr;
361    /// # use nu_protocol::{ast::CellPath, Span};
362    /// #
363    /// # let span = Span::test_data();
364    /// #
365    /// let cell_path = CellPath::from_str("$.abc").unwrap().with_fallback_span(span);
366    /// assert_eq!(cell_path.members[0].span(), span);
367    /// ```
368    pub fn with_fallback_span(mut self, span: Span) -> Self {
369        self.fallback_span(span);
370        self
371    }
372}
373
374impl Display for CellPath {
375    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376        write!(f, "$")?;
377        for member in self.members.iter() {
378            write!(f, ".{member}")?;
379        }
380        // Empty cell-paths are `$.` not `$`
381        if self.members.is_empty() {
382            write!(f, ".")?;
383        }
384        Ok(())
385    }
386}
387
388#[derive(Debug, thiserror::Error)]
389#[error("could not parse cell path {attempted:?}")]
390pub struct ParseCellPathError {
391    attempted: String,
392}
393
394impl FromStr for CellPath {
395    type Err = ParseCellPathError;
396
397    fn from_str(s: &str) -> Result<Self, Self::Err> {
398        parse::cell_path.parse(s).map_err(|_| ParseCellPathError {
399            attempted: s.to_owned(),
400        })
401    }
402}
403
404impl Serialize for CellPath {
405    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
406    where
407        S: serde::Serializer,
408    {
409        self.to_string().serialize(serializer)
410    }
411}
412
413impl<'de> Deserialize<'de> for CellPath {
414    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
415    where
416        D: serde::Deserializer<'de>,
417    {
418        let s = String::deserialize(deserializer)?;
419        Self::from_str(&s).map_err(serde::de::Error::custom)
420    }
421}
422
423#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
424pub struct FullCellPath {
425    pub head: Expression,
426    pub tail: Vec<PathMember>,
427}
428
429mod parse {
430    use super::*;
431    use winnow::{
432        Result, Str, combinator::*, error::*, prelude::*, stream::ContainsToken, token::*,
433    };
434
435    pub fn cell_path(input: &mut &str) -> Result<CellPath> {
436        preceded(opt("$."), repeat(0.., terminated(path_member, opt('.'))))
437            .parse_next(input)
438            .map(|members| CellPath { members })
439    }
440
441    pub fn path_member(input: &mut &str) -> Result<PathMember> {
442        if input.is_empty() {
443            return Err(ParserError::from_input(input));
444        }
445
446        let member = alt((int_path_member, string_path_member)).parse_next(input)?;
447
448        // ensure there's no more content after a member
449        peek(alt((".", eof))).parse_next(input)?;
450
451        Ok(member)
452    }
453
454    fn int_path_member(input: &mut &str) -> Result<PathMember> {
455        let int = digits.parse_next(input)?;
456        let modifier = modifier.parse_next(input)?;
457        Ok(PathMember::Int {
458            val: int,
459            span: Span::unknown(),
460            optional: modifier.optional,
461        })
462    }
463
464    fn digits(input: &mut &str) -> Result<usize> {
465        let start = input.checkpoint();
466        if let Ok(prefix) = digit_prefix.parse_next(input) {
467            return match prefix {
468                DigitPrefix::Bin => bin_digits.parse_next(input),
469                DigitPrefix::Oct => oct_digits.parse_next(input),
470                DigitPrefix::Hex => hex_digits.parse_next(input),
471            };
472        }
473
474        input.reset(&start);
475        dec_digits.parse_next(input)
476    }
477
478    enum DigitPrefix {
479        Bin,
480        Oct,
481        Hex,
482    }
483
484    fn digit_prefix(input: &mut &str) -> Result<DigitPrefix> {
485        let prefix = take(2usize).parse_next(input)?;
486        Ok(match prefix {
487            "0b" => DigitPrefix::Bin,
488            "0o" => DigitPrefix::Oct,
489            "Ox" => DigitPrefix::Hex,
490            _ => return fail(input),
491        })
492    }
493
494    fn bin_digits(input: &mut &str) -> Result<usize> {
495        any_radix_digits(2, ('_', '0', '1')).parse_next(input)
496    }
497
498    fn oct_digits(input: &mut &str) -> Result<usize> {
499        any_radix_digits(8, ('_', '0'..='7')).parse_next(input)
500    }
501
502    fn dec_digits(input: &mut &str) -> Result<usize> {
503        any_radix_digits(10, ('_', '0'..='9')).parse_next(input)
504    }
505
506    fn hex_digits(input: &mut &str) -> Result<usize> {
507        any_radix_digits(16, ('_', '0'..='9', 'a'..='f', 'A'..='Z')).parse_next(input)
508    }
509
510    fn any_radix_digits<'i>(
511        radix: u32,
512        tokens: impl ContainsToken<char>,
513    ) -> impl Parser<Str<'i>, usize, ContextError> {
514        take_while(1.., tokens)
515            .map(|d: &str| d.replace('_', ""))
516            .verify(|d: &str| !d.is_empty())
517            .try_map(move |d| usize::from_str_radix(&d, radix))
518    }
519
520    fn string_path_member(input: &mut &str) -> Result<PathMember> {
521        let string = alt((
522            single_quoted_string,
523            bare_word_string,
524            double_quoted_string,
525            unquoted_string,
526        ))
527        .parse_next(input)?;
528
529        let modifier = modifier.parse_next(input)?;
530
531        Ok(PathMember::String {
532            val: string,
533            span: Span::unknown(),
534            optional: modifier.optional,
535            casing: match modifier.case_insensitive {
536                true => Casing::Insensitive,
537                false => Default::default(),
538            },
539        })
540    }
541
542    fn unquoted_string(input: &mut &str) -> Result<String> {
543        struct UnquotedTokens;
544
545        impl ContainsToken<char> for UnquotedTokens {
546            fn contains_token(&self, token: char) -> bool {
547                match token {
548                    // spaces and tabs
549                    ' ' | '\n' | '\t' => false,
550
551                    // syntax characters
552                    '!' | '?' | '.' => false,
553
554                    // brackets
555                    '(' | ')' => false,
556
557                    _ => true,
558                }
559            }
560        }
561
562        take_while(0.., UnquotedTokens)
563            .parse_next(input)
564            .map(|s| s.to_owned())
565    }
566
567    fn single_quoted_string(input: &mut &str) -> Result<String> {
568        delimited("'", take_while(0.., |c| c != '\''), "'")
569            .parse_next(input)
570            .map(|s| s.to_owned())
571    }
572
573    fn bare_word_string(input: &mut &str) -> Result<String> {
574        delimited("`", take_while(0.., |c| c != '`'), "`")
575            .parse_next(input)
576            .map(|s| s.to_owned())
577    }
578
579    fn double_quoted_string(input: &mut &str) -> Result<String> {
580        fn escaped(input: &mut &str) -> Result<char> {
581            preceded(
582                '\\',
583                alt((
584                    'n'.value('\n'),
585                    'r'.value('\r'),
586                    't'.value('\t'),
587                    '\\'.value('\\'),
588                    '/'.value('/'),
589                    '"'.value('"'),
590                )),
591            )
592            .parse_next(input)
593        }
594
595        fn char(input: &mut &str) -> Result<char> {
596            any.verify(|c| *c != '"').parse_next(input)
597        }
598
599        let content = repeat(0.., alt((escaped, char))).fold(String::new, |mut string, char| {
600            string.push(char);
601            string
602        });
603
604        delimited('"', content, '"').parse_next(input)
605    }
606
607    #[derive(Default)]
608    struct Modifier {
609        optional: bool,
610        case_insensitive: bool,
611    }
612
613    fn modifier(input: &mut &str) -> Result<Modifier> {
614        let mut modifier = Modifier::default();
615
616        loop {
617            let Some(next) = opt(alt(('!', '?'))).parse_next(input)? else {
618                break;
619            };
620
621            let expected = match (next, modifier.optional, modifier.case_insensitive) {
622                ('!', _, false) => {
623                    modifier.case_insensitive = true;
624                    continue;
625                }
626                ('?', false, _) => {
627                    modifier.optional = true;
628                    continue;
629                }
630                ('!', false, true) => "'?' or '.'",
631                ('!', true, true) => "'.'",
632                ('?', true, false) => "'!' or '.'",
633                ('?', true, true) => "'.'",
634                (c, _, _) => unreachable!("parser only returns with '!' or '?', got {c:?}"),
635            };
636
637            fail.context(StrContext::Expected(StrContextValue::Description(expected)))
638                .parse_next(input)?
639        }
640
641        Ok(modifier)
642    }
643}
644
645#[cfg(test)]
646mod test {
647    use super::*;
648    use std::cmp::Ordering::Greater;
649
650    #[test]
651    fn path_member_partial_ord() {
652        assert_eq!(
653            Some(Greater),
654            PathMember::test_int(5, true).partial_cmp(&PathMember::test_string(
655                "e",
656                true,
657                Casing::Sensitive
658            ))
659        );
660
661        assert_eq!(
662            Some(Greater),
663            PathMember::test_int(5, true).partial_cmp(&PathMember::test_int(5, false))
664        );
665
666        assert_eq!(
667            Some(Greater),
668            PathMember::test_int(6, true).partial_cmp(&PathMember::test_int(5, true))
669        );
670
671        assert_eq!(
672            Some(Greater),
673            PathMember::test_string("e", true, Casing::Sensitive)
674                .partial_cmp(&PathMember::test_string("e", false, Casing::Sensitive))
675        );
676
677        assert_eq!(
678            Some(Greater),
679            PathMember::test_string("f", true, Casing::Sensitive)
680                .partial_cmp(&PathMember::test_string("e", true, Casing::Sensitive))
681        );
682    }
683}