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(name:"inline-table", parser:move |input: &mut Input<'i>| {
25 delimitedimpl Parser, …>(
26 INLINE_TABLE_OPEN,
27 second:cut_err(inline_table_keyvals(check).try_map(|(kv, p)| table_from_pairs(kv, p))),
28 third: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 = InlineTable::new();
41 root.set_preamble(preamble);
42 // Assuming almost all pairs will be directly in `root`
43 root.items.reserve(additional:v.len());
44
45 for (path: Vec, kv: TableKeyValue) in v {
46 let table: &mut InlineTable = descend_path(&mut root, &path)?;
47 let key: InternalString = kv.key.get_internal().into();
48 match table.items.entry(key) {
49 Entry::Vacant(o: VacantEntry<'_, InternalString, …>) => {
50 o.insert(kv);
51 }
52 Entry::Occupied(o: OccupiedEntry<'_, InternalString, …>) => {
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: usize, key: &Key) in path.iter().enumerate() {
68 let entry: &mut Value = table.entry_format(key).or_insert_with(|| {
69 let mut new_table: InlineTable = InlineTable::new();
70 new_table.set_dotted(yes:true);
71
72 Value::InlineTable(new_table)
73 });
74 match *entry {
75 Value::InlineTable(ref mut sweet_child_of_mine: &mut InlineTable) => {
76 table = sweet_child_of_mine;
77 }
78 ref v: &Value => {
79 return Err(CustomError::extend_wrong_type(path, i, actual: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: RecursionCheck = check.recursing(input)?;
105 (
106 separated0(parser: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}
182