toml_edit/parser/
inline_table.rs

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