toml_edit/parser/
inline_table.rs

1use winnow::combinator::cut_err;
2use winnow::combinator::delimited;
3use winnow::combinator::separated;
4use winnow::combinator::trace;
5use winnow::token::one_of;
6
7use crate::key::Key;
8use crate::parser::error::CustomError;
9use crate::parser::key::key;
10use crate::parser::prelude::*;
11use crate::parser::trivia::ws;
12use crate::parser::value::value;
13use crate::{InlineTable, Item, RawString, Value};
14
15use indexmap::map::Entry;
16
17// ;; Inline Table
18
19// inline-table = inline-table-open inline-table-keyvals inline-table-close
20pub(crate) fn inline_table<'i>(input: &mut Input<'i>) -> ModalResult<InlineTable> {
21    trace("inline-table", move |input: &mut Input<'i>| {
22        delimited(
23            INLINE_TABLE_OPEN,
24            cut_err(inline_table_keyvals.try_map(|(kv, p)| table_from_pairs(kv, p))),
25            cut_err(INLINE_TABLE_CLOSE)
26                .context(StrContext::Label("inline table"))
27                .context(StrContext::Expected(StrContextValue::CharLiteral('}'))),
28        )
29        .parse_next(input)
30    })
31    .parse_next(input)
32}
33
34fn table_from_pairs(
35    v: Vec<(Vec<Key>, (Key, Item))>,
36    preamble: RawString,
37) -> Result<InlineTable, CustomError> {
38    let mut root = InlineTable::new();
39    root.set_preamble(preamble);
40    // Assuming almost all pairs will be directly in `root`
41    root.items.reserve(v.len());
42
43    for (path, (key, value)) in v {
44        let table = descend_path(&mut root, &path, true)?;
45
46        // "Likewise, using dotted keys to redefine tables already defined in [table] form is not allowed"
47        let mixed_table_types = table.is_dotted() == path.is_empty();
48        if mixed_table_types {
49            return Err(CustomError::DuplicateKey {
50                key: key.get().into(),
51                table: None,
52            });
53        }
54
55        match table.items.entry(key) {
56            Entry::Vacant(o) => {
57                o.insert(value);
58            }
59            Entry::Occupied(o) => {
60                return Err(CustomError::DuplicateKey {
61                    key: o.key().get().into(),
62                    table: None,
63                });
64            }
65        }
66    }
67    Ok(root)
68}
69
70fn descend_path<'a>(
71    mut table: &'a mut InlineTable,
72    path: &'a [Key],
73    dotted: bool,
74) -> Result<&'a mut InlineTable, CustomError> {
75    for (i, key) in path.iter().enumerate() {
76        table = match table.entry_format(key) {
77            crate::InlineEntry::Vacant(entry) => {
78                let mut new_table = InlineTable::new();
79                new_table.set_implicit(true);
80                new_table.set_dotted(dotted);
81
82                entry
83                    .insert(Value::InlineTable(new_table))
84                    .as_inline_table_mut()
85                    .unwrap()
86            }
87            crate::InlineEntry::Occupied(entry) => {
88                match entry.into_mut() {
89                    Value::InlineTable(ref mut sweet_child_of_mine) => {
90                        // Since tables cannot be defined more than once, redefining such tables using a
91                        // [table] header is not allowed. Likewise, using dotted keys to redefine tables
92                        // already defined in [table] form is not allowed.
93                        if dotted && !sweet_child_of_mine.is_implicit() {
94                            return Err(CustomError::DuplicateKey {
95                                key: key.get().into(),
96                                table: None,
97                            });
98                        }
99                        sweet_child_of_mine
100                    }
101                    ref v => {
102                        return Err(CustomError::extend_wrong_type(path, i, v.type_name()));
103                    }
104                }
105            }
106        };
107    }
108    Ok(table)
109}
110
111// inline-table-open  = %x7B ws     ; {
112pub(crate) const INLINE_TABLE_OPEN: u8 = b'{';
113// inline-table-close = ws %x7D     ; }
114const INLINE_TABLE_CLOSE: u8 = b'}';
115// inline-table-sep   = ws %x2C ws  ; , Comma
116const INLINE_TABLE_SEP: u8 = b',';
117// keyval-sep = ws %x3D ws ; =
118pub(crate) const KEYVAL_SEP: u8 = b'=';
119
120// inline-table-keyvals = [ inline-table-keyvals-non-empty ]
121// inline-table-keyvals-non-empty =
122// ( key keyval-sep val inline-table-sep inline-table-keyvals-non-empty ) /
123// ( key keyval-sep val )
124
125fn inline_table_keyvals(
126    input: &mut Input<'_>,
127) -> ModalResult<(Vec<(Vec<Key>, (Key, Item))>, RawString)> {
128    (
129        separated(0.., keyval, INLINE_TABLE_SEP),
130        ws.span().map(RawString::with_span),
131    )
132        .parse_next(input)
133}
134
135fn keyval(input: &mut Input<'_>) -> ModalResult<(Vec<Key>, (Key, Item))> {
136    (
137        key,
138        cut_err((
139            one_of(KEYVAL_SEP)
140                .context(StrContext::Expected(StrContextValue::CharLiteral('.')))
141                .context(StrContext::Expected(StrContextValue::CharLiteral('='))),
142            (ws.span(), value, ws.span()),
143        )),
144    )
145        .map(|(key, (_, v))| {
146            let mut path = key;
147            let key = path.pop().expect("grammar ensures at least 1");
148
149            let (pre, v, suf) = v;
150            let pre = RawString::with_span(pre);
151            let suf = RawString::with_span(suf);
152            let v = v.decorated(pre, suf);
153            (path, (key, Item::Value(v)))
154        })
155        .parse_next(input)
156}
157
158#[cfg(test)]
159#[cfg(feature = "parse")]
160#[cfg(feature = "display")]
161mod test {
162    use super::*;
163
164    #[test]
165    fn inline_tables() {
166        let inputs = [
167            r#"{}"#,
168            r#"{   }"#,
169            r#"{a = 1e165}"#,
170            r#"{ hello = "world", a = 1}"#,
171            r#"{ hello.world = "a" }"#,
172        ];
173        for input in inputs {
174            dbg!(input);
175            let mut parsed = inline_table.parse(new_input(input));
176            if let Ok(parsed) = &mut parsed {
177                parsed.despan(input);
178            }
179            assert_eq!(parsed.map(|a| a.to_string()), Ok(input.to_owned()));
180        }
181    }
182
183    #[test]
184    fn invalid_inline_tables() {
185        let invalid_inputs = [r#"{a = 1e165"#, r#"{ hello = "world", a = 2, hello = 1}"#];
186        for input in invalid_inputs {
187            dbg!(input);
188            let mut parsed = inline_table.parse(new_input(input));
189            if let Ok(parsed) = &mut parsed {
190                parsed.despan(input);
191            }
192            assert!(parsed.is_err());
193        }
194    }
195}