toml_edit/parser/
inline_table.rs1use 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
17pub(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 root.items.reserve(v.len());
42
43 for (path, (key, value)) in v {
44 let table = descend_path(&mut root, &path, true)?;
45
46 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 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
111pub(crate) const INLINE_TABLE_OPEN: u8 = b'{';
113const INLINE_TABLE_CLOSE: u8 = b'}';
115const INLINE_TABLE_SEP: u8 = b',';
117pub(crate) const KEYVAL_SEP: u8 = b'=';
119
120fn 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}