toml_edit/parser/
inline_table.rs

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