1use pest::{Parser, error::Error, iterators::Pair};
2
3use crate::synapse::{Rule, SynapseParser};
4
5use super::*;
6
7pub fn parse(input: &str) -> Result<SynFile, Error<Rule>> {
10 let file_pair = SynapseParser::parse(Rule::file, input)?.next().unwrap();
11 Ok(build_file(file_pair))
12}
13
14fn build_file(pair: Pair<Rule>) -> SynFile {
17 let mut items = Vec::new();
18 for pair in pair.into_inner() {
19 match pair.as_rule() {
20 Rule::namespace_decl => items.push(Item::Namespace(build_namespace(pair))),
21 Rule::import_decl => items.push(Item::Import(build_import(pair))),
22 Rule::const_decl => items.push(Item::Const(build_const(pair))),
23 Rule::enum_def => items.push(Item::Enum(build_enum(pair))),
24 Rule::struct_def => items.push(Item::Struct(build_struct(pair))),
25 Rule::table_def => items.push(Item::Table(build_struct(pair))),
26 Rule::command_group_def => items.extend(build_command_group(pair)),
27 Rule::telemetry_def => {
28 items.push(Item::Telemetry(build_packet(
29 pair,
30 PacketKind::Telemetry,
31 None,
32 )));
33 }
34 Rule::message_def => {
35 items.push(Item::Message(build_packet(pair, PacketKind::Message, None)));
36 }
37 Rule::EOI => {}
38 rule => unreachable!("unexpected rule: {rule:?}"),
39 }
40 }
41 SynFile { items }
42}
43
44fn build_namespace(pair: Pair<Rule>) -> NamespaceDecl {
45 let scoped = pair.into_inner().next().unwrap();
46 NamespaceDecl {
47 name: build_scoped_ident(scoped),
48 }
49}
50
51fn build_import(pair: Pair<Rule>) -> ImportDecl {
52 let s = pair.into_inner().next().unwrap().as_str();
53 ImportDecl {
54 path: s[1..s.len() - 1].to_string(),
55 }
56}
57
58fn build_const(pair: Pair<Rule>) -> ConstDecl {
59 let mut inner = pair.into_inner().peekable();
60 let doc = extract_doc(&mut inner);
61 let attrs = extract_attrs(&mut inner);
62 let name = inner.next().unwrap().as_str().to_string();
63 let ty = build_type_expr(inner.next().unwrap());
64 let value = build_literal(inner.next().unwrap());
65 ConstDecl {
66 name,
67 ty,
68 value,
69 doc,
70 attrs,
71 }
72}
73
74fn build_enum(pair: Pair<Rule>) -> EnumDef {
75 let mut inner = pair.into_inner().peekable();
76 let doc = extract_doc(&mut inner);
77 let attrs = extract_attrs(&mut inner);
78 let first = inner.next().unwrap();
79 let (repr, name) = if first.as_rule() == Rule::primitive_type {
80 let repr = build_primitive_type(first);
81 (Some(repr), inner.next().unwrap().as_str().to_string())
82 } else {
83 (None, first.as_str().to_string())
84 };
85 let variants = inner.map(build_enum_variant).collect();
86 EnumDef {
87 name,
88 repr,
89 variants,
90 doc,
91 attrs,
92 }
93}
94
95fn build_enum_variant(pair: Pair<Rule>) -> EnumVariant {
96 let mut inner = pair.into_inner().peekable();
97 let doc = extract_doc(&mut inner);
98 let name = inner.next().unwrap().as_str().to_string();
99 let value = inner.next().map(|p| p.as_str().parse::<i64>().unwrap());
100 EnumVariant { name, value, doc }
101}
102
103fn build_struct(pair: Pair<Rule>) -> StructDef {
104 let mut inner = pair.into_inner().peekable();
105 let doc = extract_doc(&mut inner);
106 let attrs = extract_attrs(&mut inner);
107 let name = inner.next().unwrap().as_str().to_string();
108 let fields = inner.map(build_field).collect();
109 StructDef {
110 name,
111 fields,
112 doc,
113 attrs,
114 }
115}
116
117fn build_command_group(pair: Pair<Rule>) -> Vec<Item> {
118 let mut inner = pair.into_inner().peekable();
119 let _doc = extract_doc(&mut inner);
120 let group = inner.next().unwrap().as_str().to_string();
121 inner
122 .map(|pair| Item::Command(build_packet(pair, PacketKind::Command, Some(group.clone()))))
123 .collect()
124}
125
126fn build_packet(pair: Pair<Rule>, kind: PacketKind, command_group: Option<String>) -> MessageDef {
127 let mut inner = pair.into_inner().peekable();
128 let doc = extract_doc(&mut inner);
129 let attrs = extract_attrs(&mut inner);
130 let name = inner.next().unwrap().as_str().to_string();
131 let fields = inner.map(build_field).collect();
132 MessageDef {
133 kind,
134 command_group,
135 name,
136 fields,
137 doc,
138 attrs,
139 }
140}
141
142fn build_field(pair: Pair<Rule>) -> FieldDef {
143 let mut inner = pair.into_inner().peekable();
144 let doc = extract_doc(&mut inner);
145 let name = inner.next().unwrap().as_str().to_string();
146
147 let next = inner.next().unwrap();
148 let (optional, type_pair) = if next.as_rule() == Rule::optional_marker {
149 (true, inner.next().unwrap())
150 } else {
151 (false, next)
152 };
153
154 let ty = build_type_expr(type_pair);
155 let default = inner.next().map(build_literal);
156
157 FieldDef {
158 name,
159 optional,
160 ty,
161 default,
162 doc,
163 }
164}
165
166fn extract_doc<'i>(
168 inner: &mut std::iter::Peekable<impl Iterator<Item = Pair<'i, Rule>>>,
169) -> Vec<String> {
170 if inner.peek().map(|p| p.as_rule()) == Some(Rule::doc_block) {
171 inner
172 .next()
173 .unwrap()
174 .into_inner()
175 .map(|p| {
176 p.as_str()
177 .strip_prefix("///")
178 .unwrap_or("")
179 .trim()
180 .to_string()
181 })
182 .collect()
183 } else {
184 vec![]
185 }
186}
187
188fn extract_attrs<'i>(
190 inner: &mut std::iter::Peekable<impl Iterator<Item = Pair<'i, Rule>>>,
191) -> Vec<Attribute> {
192 let mut attrs = vec![];
193 while inner.peek().map(|p| p.as_rule()) == Some(Rule::attribute) {
194 let attr = inner.next().unwrap();
195 let mut ai = attr.into_inner();
196 let name = ai.next().unwrap().as_str().to_string();
197 let value = build_literal(ai.next().unwrap());
198 attrs.push(Attribute { name, value });
199 }
200 attrs
201}
202
203fn build_type_expr(pair: Pair<Rule>) -> TypeExpr {
204 let mut inner = pair.into_inner();
205 let base = build_base_type(inner.next().unwrap());
206 let array = inner.next().map(build_array_suffix);
207 TypeExpr { base, array }
208}
209
210fn build_base_type(pair: Pair<Rule>) -> BaseType {
211 let inner = pair.into_inner().next().unwrap();
212 match inner.as_rule() {
213 Rule::string_type => BaseType::String,
214 Rule::primitive_type => BaseType::Primitive(build_primitive_type(inner)),
215 Rule::type_ref => BaseType::Ref(build_scoped_ident(inner.into_inner().next().unwrap())),
216 r => unreachable!("unexpected base_type rule: {:?}", r),
217 }
218}
219
220fn build_primitive_type(pair: Pair<Rule>) -> PrimitiveType {
221 const PRIMITIVES: &[(&str, PrimitiveType)] = &[
222 ("f32", PrimitiveType::F32),
223 ("f64", PrimitiveType::F64),
224 ("i8", PrimitiveType::I8),
225 ("i16", PrimitiveType::I16),
226 ("i32", PrimitiveType::I32),
227 ("i64", PrimitiveType::I64),
228 ("u8", PrimitiveType::U8),
229 ("u16", PrimitiveType::U16),
230 ("u32", PrimitiveType::U32),
231 ("u64", PrimitiveType::U64),
232 ("bool", PrimitiveType::Bool),
233 ("bytes", PrimitiveType::Bytes),
234 ];
235
236 let primitive = pair.as_str();
237 PRIMITIVES
238 .iter()
239 .find_map(|(name, ty)| (*name == primitive).then_some(*ty))
240 .unwrap_or_else(|| unreachable!("unknown primitive: {}", primitive))
241}
242
243fn build_array_suffix(pair: Pair<Rule>) -> ArraySuffix {
244 let Some(size) = pair.into_inner().next() else {
245 return ArraySuffix::Dynamic;
246 };
247 build_sized_array_suffix(size)
248}
249
250fn build_sized_array_suffix(pair: Pair<Rule>) -> ArraySuffix {
251 let inner = pair.into_inner().next().unwrap();
252 match inner.as_rule() {
253 Rule::bounded_size => {
254 let n = inner
255 .into_inner()
256 .next()
257 .unwrap()
258 .as_str()
259 .parse::<u64>()
260 .unwrap();
261 ArraySuffix::Bounded(n)
262 }
263 Rule::pos_int => ArraySuffix::Fixed(inner.as_str().parse::<u64>().unwrap()),
264 r => unreachable!("unexpected array_size rule: {:?}", r),
265 }
266}
267
268fn build_literal(pair: Pair<Rule>) -> Literal {
269 let inner = pair.into_inner().next().unwrap();
270 if matches!(
271 inner.as_rule(),
272 Rule::float_lit | Rule::hex_lit | Rule::int_lit
273 ) {
274 return build_numeric_literal(inner);
275 }
276 match inner.as_rule() {
277 Rule::bool_lit => build_bool_literal(inner),
278 Rule::string_lit => build_string_literal(inner),
279 Rule::ident_lit => Literal::Ident(build_scoped_ident(inner.into_inner().next().unwrap())),
280 r => unreachable!("unexpected literal rule: {:?}", r),
281 }
282}
283
284fn build_numeric_literal(pair: Pair<Rule>) -> Literal {
285 match pair.as_rule() {
286 Rule::float_lit => Literal::Float(pair.as_str().parse::<f64>().unwrap()),
287 Rule::hex_lit => {
288 let s = pair.as_str();
289 let digits = &s[2..]; Literal::Hex(u64::from_str_radix(digits, 16).unwrap())
291 }
292 Rule::int_lit => Literal::Int(pair.as_str().parse::<i64>().unwrap()),
293 r => unreachable!("unexpected numeric literal rule: {:?}", r),
294 }
295}
296
297fn build_bool_literal(pair: Pair<Rule>) -> Literal {
298 Literal::Bool(pair.as_str() == "true")
299}
300
301fn build_string_literal(pair: Pair<Rule>) -> Literal {
302 let s = pair.as_str();
303 Literal::Str(unescape(&s[1..s.len() - 1]))
304}
305
306fn build_scoped_ident(pair: Pair<Rule>) -> ScopedIdent {
307 pair.into_inner().map(|p| p.as_str().to_string()).collect()
308}
309
310fn unescape(s: &str) -> String {
311 let mut out = String::with_capacity(s.len());
312 let mut chars = s.chars();
313 while let Some(c) = chars.next() {
314 if c == '\\' {
315 push_escape(&mut out, chars.next());
316 } else {
317 out.push(c);
318 }
319 }
320 out
321}
322
323fn push_escape(out: &mut String, escaped: Option<char>) {
324 let Some(escaped) = escaped else {
325 out.push('\\');
326 return;
327 };
328
329 match escaped {
330 'n' => out.push('\n'),
331 't' => out.push('\t'),
332 'r' => out.push('\r'),
333 c => push_quoted_escape(out, c),
334 }
335}
336
337fn push_quoted_escape(out: &mut String, escaped: char) {
338 match escaped {
339 '\\' => out.push('\\'),
340 '"' => out.push('"'),
341 c => {
342 out.push('\\');
343 out.push(c);
344 }
345 }
346}