1use std::ops::RangeInclusive;
2
3use winnow::combinator::peek;
4use winnow::combinator::separated1;
5use winnow::token::any;
6use winnow::token::take_while;
7use winnow::trace::trace;
8
9use crate::key::Key;
10use crate::parser::errors::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<'_>) -> PResult<Vec<Key>> {
21 traceimpl Parser, …>(
22 name:"dotted-key",
23 parser:separated1(
24 (ws.span(), simple_key, ws.span()).map(|(pre: Range, (raw: RawString, key: InternalString), suffix: Range)| {
25 KeyKey::new(key)
26 .with_repr_unchecked(Repr::new_unchecked(raw))
27 .with_decor(Decor::new(
28 prefix:RawString::with_span(pre),
29 suffix:RawString::with_span(suffix),
30 ))
31 }),
32 DOT_SEP,
33 )
34 .context(StrContext::Label("key"))
35 .try_map(|k: Vec<_>| {
36 // Inserting the key will require recursion down the line
37 RecursionCheck::check_depth(k.len())?;
38 Ok::<_, CustomError>(k)
39 }),
40 )
41 .parse_next(input)
42}
43
44// simple-key = quoted-key / unquoted-key
45// quoted-key = basic-string / literal-string
46pub(crate) fn simple_key(input: &mut Input<'_>) -> PResult<(RawString, InternalString)> {
47 traceimpl Parser, …>(
48 name:"simple-key",
49 parser:dispatch! {peek(any);
50 crate::parser::strings::QUOTATION_MARK => basic_string
51 .map(|s: std::borrow::Cow<'_, str>| s.as_ref().into()),
52 crate::parser::strings::APOSTROPHE => literal_string.map(|s: &str| s.into()),
53 _ => unquoted_key.map(|s: &str| s.into()),
54 }
55 .with_span()
56 .map(|(k: InternalString, span: Range)| {
57 let raw: RawString = RawString::with_span(span);
58 (raw, k)
59 }),
60 )
61 .parse_next(input)
62}
63
64// unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
65fn unquoted_key<'i>(input: &mut Input<'i>) -> PResult<&'i str> {
66 traceimpl Parser, …>(
67 name:"unquoted-key",
68 parser:take_while(range:1.., UNQUOTED_CHAR)
69 .map(|b: &[u8]| unsafe { from_utf8_unchecked(bytes:b, safety_justification:"`is_unquoted_char` filters out on-ASCII") }),
70 )
71 .parse_next(input)
72}
73
74pub(crate) fn is_unquoted_char(c: u8) -> bool {
75 use winnow::stream::ContainsToken;
76 UNQUOTED_CHAR.contains_token(c)
77}
78
79const UNQUOTED_CHAR: (
80 RangeInclusive<u8>,
81 RangeInclusive<u8>,
82 RangeInclusive<u8>,
83 u8,
84 u8,
85) = (b'A'..=b'Z', b'a'..=b'z', b'0'..=b'9', b'-', b'_');
86
87// dot-sep = ws %x2E ws ; . Period
88const DOT_SEP: u8 = b'.';
89
90#[cfg(test)]
91mod test {
92 use super::*;
93
94 #[test]
95 fn keys() {
96 let cases = [
97 ("a", "a"),
98 (r#""hello\n ""#, "hello\n "),
99 (r#"'hello\n '"#, "hello\\n "),
100 ];
101
102 for (input, expected) in cases {
103 dbg!(input);
104 let parsed = simple_key.parse(new_input(input));
105 assert_eq!(
106 parsed,
107 Ok((RawString::with_span(0..(input.len())), expected.into())),
108 "Parsing {input:?}"
109 );
110 }
111 }
112}
113