toml_edit/parser/
key.rs

1use std::ops::RangeInclusive;
2
3use winnow::combinator::peek;
4use winnow::combinator::separated;
5use winnow::combinator::trace;
6use winnow::token::any;
7use winnow::token::take_while;
8
9use crate::key::Key;
10use crate::parser::error::CustomError;
11use crate::parser::prelude::*;
12use crate::parser::strings::{basic_string, literal_string};
13use crate::parser::trivia::{from_utf8_unchecked, ws};
14use crate::repr::{Decor, Repr};
15use crate::InternalString;
16use crate::RawString;
17
18// key = simple-key / dotted-key
19// dotted-key = simple-key 1*( dot-sep simple-key )
20pub(crate) fn key(input: &mut Input<'_>) -> ModalResult<Vec<Key>> {
21    let mut key_path = trace(
22        "dotted-key",
23        separated(
24            1..,
25            (ws.span(), simple_key, ws.span()).map(|(pre, (raw, key), suffix)| {
26                Key::new(key)
27                    .with_repr_unchecked(Repr::new_unchecked(raw))
28                    .with_dotted_decor(Decor::new(
29                        RawString::with_span(pre),
30                        RawString::with_span(suffix),
31                    ))
32            }),
33            DOT_SEP,
34        )
35        .context(StrContext::Label("key"))
36        .try_map(|k: Vec<_>| {
37            // Inserting the key will require recursion down the line
38            RecursionCheck::check_depth(k.len())?;
39            Ok::<_, CustomError>(k)
40        }),
41    )
42    .parse_next(input)?;
43
44    let mut leaf_decor = Decor::new("", "");
45    {
46        let first_dotted_decor = key_path
47            .first_mut()
48            .expect("always at least one key")
49            .dotted_decor_mut();
50        if let Some(prefix) = first_dotted_decor.prefix().cloned() {
51            leaf_decor.set_prefix(prefix);
52            first_dotted_decor.set_prefix("");
53        }
54    }
55    let last_key = &mut key_path.last_mut().expect("always at least one key");
56    {
57        let last_dotted_decor = last_key.dotted_decor_mut();
58        if let Some(suffix) = last_dotted_decor.suffix().cloned() {
59            leaf_decor.set_suffix(suffix);
60            last_dotted_decor.set_suffix("");
61        }
62    }
63
64    *last_key.leaf_decor_mut() = leaf_decor;
65
66    Ok(key_path)
67}
68
69// simple-key = quoted-key / unquoted-key
70// quoted-key = basic-string / literal-string
71pub(crate) fn simple_key(input: &mut Input<'_>) -> ModalResult<(RawString, InternalString)> {
72    trace(
73        "simple-key",
74        dispatch! {peek(any);
75            crate::parser::strings::QUOTATION_MARK => basic_string
76                .map(|s: std::borrow::Cow<'_, str>| s.as_ref().into()),
77            crate::parser::strings::APOSTROPHE => literal_string.map(|s: &str| s.into()),
78            _ => unquoted_key.map(|s: &str| s.into()),
79        }
80        .with_span()
81        .map(|(k, span)| {
82            let raw = RawString::with_span(span);
83            (raw, k)
84        }),
85    )
86    .parse_next(input)
87}
88
89// unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
90fn unquoted_key<'i>(input: &mut Input<'i>) -> ModalResult<&'i str> {
91    trace(
92        "unquoted-key",
93        take_while(1.., UNQUOTED_CHAR)
94            .map(|b| unsafe { from_utf8_unchecked(b, "`is_unquoted_char` filters out on-ASCII") }),
95    )
96    .parse_next(input)
97}
98
99pub(crate) fn is_unquoted_char(c: u8) -> bool {
100    use winnow::stream::ContainsToken;
101    UNQUOTED_CHAR.contains_token(c)
102}
103
104const UNQUOTED_CHAR: (
105    RangeInclusive<u8>,
106    RangeInclusive<u8>,
107    RangeInclusive<u8>,
108    u8,
109    u8,
110) = (b'A'..=b'Z', b'a'..=b'z', b'0'..=b'9', b'-', b'_');
111
112// dot-sep   = ws %x2E ws  ; . Period
113const DOT_SEP: u8 = b'.';
114
115#[cfg(test)]
116#[cfg(feature = "parse")]
117#[cfg(feature = "display")]
118mod test {
119    use super::*;
120
121    #[test]
122    fn keys() {
123        let cases = [
124            ("a", "a"),
125            (r#""hello\n ""#, "hello\n "),
126            (r"'hello\n '", "hello\\n "),
127        ];
128
129        for (input, expected) in cases {
130            dbg!(input);
131            let parsed = simple_key.parse(new_input(input));
132            assert_eq!(
133                parsed,
134                Ok((RawString::with_span(0..(input.len())), expected.into())),
135                "Parsing {input:?}"
136            );
137        }
138    }
139}