1pub mod ast;
2
3pub mod synapse {
4 use pest_derive::Parser;
5
6 #[derive(Parser)]
7 #[grammar = "synapse.pest"]
8 pub struct SynapseParser;
9}
10
11pub use synapse::SynapseParser;
12
13#[cfg(test)]
14mod synapse_tests {
15 use super::synapse::{Rule, SynapseParser};
16 use pest::Parser;
17
18 fn parses(rule: Rule, input: &str) -> bool {
19 SynapseParser::parse(rule, input)
20 .map(|mut p| {
21 p.next()
22 .map_or(false, |pair| pair.as_span().end() == input.len())
23 })
24 .unwrap_or(false)
25 }
26
27 fn parses_file(input: &str) -> bool {
28 parses(Rule::file, input)
29 }
30
31 #[test]
36 fn ident_basic() {
37 assert!(parses(Rule::ident, "foo"));
38 assert!(parses(Rule::ident, "MyType"));
39 assert!(parses(Rule::ident, "snake_case"));
40 assert!(parses(Rule::ident, "CamelCase123"));
41 }
42
43 #[test]
44 fn ident_rejects_leading_digit() {
45 assert!(!parses(Rule::ident, "1bad"));
46 }
47
48 #[test]
49 fn scoped_ident_bare() {
50 assert!(parses(Rule::scoped_ident, "Point"));
51 }
52
53 #[test]
54 fn scoped_ident_qualified() {
55 assert!(parses(Rule::scoped_ident, "geometry::Point"));
56 assert!(parses(Rule::scoped_ident, "nav::msgs::Odometry"));
57 }
58
59 #[test]
64 fn int_literals() {
65 assert!(parses(Rule::int_lit, "0"));
66 assert!(parses(Rule::int_lit, "42"));
67 assert!(parses(Rule::int_lit, "-7"));
68 }
69
70 #[test]
71 fn float_literals() {
72 assert!(parses(Rule::float_lit, "3.14"));
73 assert!(parses(Rule::float_lit, "-2.5"));
74 assert!(parses(Rule::float_lit, "1.5e-3"));
75 assert!(parses(Rule::float_lit, "1e10"));
76 assert!(!parses(Rule::float_lit, "42")); }
78
79 #[test]
80 fn bool_literals() {
81 assert!(parses(Rule::bool_lit, "true"));
82 assert!(parses(Rule::bool_lit, "false"));
83 assert!(!parses(Rule::bool_lit, "True"));
84 assert!(!parses(Rule::bool_lit, "TRUE"));
85 }
86
87 #[test]
88 fn string_literals() {
89 assert!(parses(Rule::string_lit, r#""hello""#));
90 assert!(parses(Rule::string_lit, r#""""#));
91 assert!(parses(Rule::string_lit, r#""escaped\"quote""#));
92 }
93
94 #[test]
95 fn ident_literal_for_enum_refs() {
96 assert!(parses(Rule::ident_lit, "Idle"));
97 assert!(parses(Rule::ident_lit, "DriveMode::Idle"));
98 assert!(parses(Rule::ident_lit, "pkg::Status::Active"));
99 }
100
101 #[test]
106 fn primitive_types() {
107 for t in &[
108 "f32", "f64", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "bool", "bytes",
109 ] {
110 assert!(parses(Rule::primitive_type, t), "failed: {t}");
111 }
112 }
113
114 #[test]
115 fn string_type() {
116 assert!(parses(Rule::string_type, "string"));
117 }
118
119 #[test]
120 fn type_ref_bare_and_qualified() {
121 assert!(parses(Rule::type_ref, "Point"));
122 assert!(parses(Rule::type_ref, "geometry::Point"));
123 }
124
125 #[test]
126 fn type_expr_scalar() {
127 assert!(parses(Rule::type_expr, "f64"));
128 assert!(parses(Rule::type_expr, "bool"));
129 assert!(parses(Rule::type_expr, "string"));
130 assert!(parses(Rule::type_expr, "Point"));
131 assert!(parses(Rule::type_expr, "geometry::Point"));
132 }
133
134 #[test]
135 fn type_expr_dynamic_array() {
136 assert!(parses(Rule::type_expr, "f64[]"));
137 assert!(parses(Rule::type_expr, "u8[]"));
138 assert!(parses(Rule::type_expr, "string[]"));
139 assert!(parses(Rule::type_expr, "geometry::Point[]"));
140 }
141
142 #[test]
143 fn type_expr_fixed_array() {
144 assert!(parses(Rule::type_expr, "f64[3]"));
145 assert!(parses(Rule::type_expr, "u8[256]"));
146 assert!(parses(Rule::type_expr, "f64[36]"));
147 }
148
149 #[test]
150 fn type_expr_bounded_array() {
151 assert!(parses(Rule::type_expr, "u8[<=256]"));
152 assert!(parses(Rule::type_expr, "string[<=64]")); assert!(parses(Rule::type_expr, "geometry::Point[<=100]"));
154 }
155
156 #[test]
161 fn enum_with_explicit_values() {
162 assert!(parses_file(
163 "enum Status { Idle = 0 Moving = 1 Error = 2 }"
164 ));
165 }
166
167 #[test]
168 fn enum_without_values() {
169 assert!(parses_file("enum Direction { North South East West }"));
170 }
171
172 #[test]
173 fn enum_multiline() {
174 assert!(parses_file(
175 "enum DriveMode {\n Idle = 0\n Forward = 1\n Error = 2\n}"
176 ));
177 }
178
179 #[test]
180 fn enum_mixed_values() {
181 assert!(parses_file("enum Mixed { A B = 5 C D = 10 }"));
182 }
183
184 #[test]
189 fn struct_basic() {
190 assert!(parses_file("struct Point { x: f64 y: f64 z: f64 }"));
191 }
192
193 #[test]
194 fn struct_with_defaults() {
195 assert!(parses_file(
196 "struct Point { x: f64 = 0.0 y: f64 = 0.0 z: f64 = 0.0 }"
197 ));
198 }
199
200 #[test]
201 fn struct_empty() {
202 assert!(parses_file("struct Empty {}"));
203 }
204
205 #[test]
206 fn struct_multiline() {
207 assert!(parses_file(
208 "struct Pose {\n position: Point\n orientation: Quaternion\n}"
209 ));
210 }
211
212 #[test]
217 fn message_basic() {
218 assert!(parses_file("message Ping { seq: u32 stamp: u64 }"));
219 }
220
221 #[test]
222 fn command_basic() {
223 assert!(parses_file("@mid(0x1880)\ncommand SetMode { mode: u8 }"));
224 }
225
226 #[test]
227 fn telemetry_basic() {
228 assert!(parses_file(
229 "@mid(0x0801)\ntelemetry NavState { x: f64 y: f64 }"
230 ));
231 }
232
233 #[test]
234 fn table_basic() {
235 assert!(parses_file(
236 "table NavConfig { max_speed: f64 enabled: bool }"
237 ));
238 }
239
240 #[test]
241 fn message_optional_field() {
242 assert!(parses_file(
243 "message Foo { required: i32 optional?: string }"
244 ));
245 }
246
247 #[test]
248 fn message_with_defaults() {
249 assert!(parses_file(
250 r#"message Config { label: string[<=64] = "default" retries: u8 = 3 verbose: bool = false }"#
251 ));
252 }
253
254 #[test]
255 fn message_array_fields() {
256 assert!(parses_file(
257 "message Data { raw: u8[] fixed: f64[3] bounded: u8[<=256] }"
258 ));
259 }
260
261 #[test]
262 fn message_nested_types() {
263 assert!(parses_file(
264 "message Odom { position: Point velocity: Twist path: Point[] }"
265 ));
266 }
267
268 #[test]
269 fn message_qualified_types() {
270 assert!(parses_file(
271 "message Pose { position: geometry::Point orientation: geometry::Quaternion }"
272 ));
273 }
274
275 #[test]
276 fn message_enum_default() {
277 assert!(parses_file(
278 "message State { mode: DriveMode = DriveMode::Idle speed: f64 = 0.0 }"
279 ));
280 }
281
282 #[test]
283 fn represented_enum() {
284 assert!(parses_file("enum u8 DriveMode { Idle = 0 Driving = 1 }"));
285 }
286
287 #[test]
292 fn const_float() {
293 assert!(parses_file("const PI: f64 = 3.14159265358979"));
294 assert!(parses_file("const G: f32 = 9.81"));
295 }
296
297 #[test]
298 fn const_int() {
299 assert!(parses_file("const MAX_SIZE: u32 = 100"));
300 assert!(parses_file("const MIN: i32 = -32768"));
301 }
302
303 #[test]
304 fn const_bool() {
305 assert!(parses_file("const DEBUG: bool = true"));
306 }
307
308 #[test]
309 fn const_string() {
310 assert!(parses_file(r#"const FRAME: string = "world""#));
311 }
312
313 #[test]
318 fn namespace_bare() {
319 assert!(parses_file(
320 "namespace geometry\nstruct Point { x: f64 y: f64 }"
321 ));
322 }
323
324 #[test]
325 fn namespace_qualified() {
326 assert!(parses_file("namespace nav::msgs\nmessage Odom { x: f64 }"));
327 }
328
329 #[test]
330 fn import_decl() {
331 assert!(parses_file(r#"import "geometry.syn""#));
332 assert!(parses_file(r#"import "common/types.syn""#));
333 }
334
335 #[test]
340 fn line_comments() {
341 assert!(parses_file(
342 "// top comment\nstruct S { x: f64 // x coord\n}"
343 ));
344 }
345
346 #[test]
347 fn doc_comments() {
348 assert!(parses_file(
349 "/// A struct\nstruct S {\n/// x field\nx: f64\n}"
350 ));
351 }
352
353 #[test]
354 fn old_hash_comments_are_rejected() {
355 assert!(!parses_file("# top comment\nstruct S { x: f64 }"));
356 assert!(!parses_file("## A struct\nstruct S { x: f64 }"));
357 }
358
359 #[test]
364 fn geometry_file() {
365 assert!(parses_file(
366 "namespace geometry
367
368 struct Point {
369 x: f64 = 0.0
370 y: f64 = 0.0
371 z: f64 = 0.0
372 }
373
374 struct Quaternion {
375 x: f64 = 0.0
376 y: f64 = 0.0
377 z: f64 = 0.0
378 w: f64 = 1.0
379 }
380
381 struct Pose {
382 position: Point
383 orientation: Quaternion
384 }"
385 ));
386 }
387
388 #[test]
389 fn robot_state_file() {
390 assert!(parses_file(
391 r#"import "geometry.syn"
392
393 enum DriveMode {
394 Idle = 0
395 Forward = 1
396 Turning = 2
397 Error = 3
398 }
399
400 const MAX_SPEED: f64 = 2.5
401
402 message RobotState {
403 mode: DriveMode = DriveMode::Idle
404 position: geometry::Point
405 velocity: geometry::Point
406 battery: f32 = 100.0
407 label: string[<=64] = "robot"
408 sensor_data: u8[]
409 waypoints: geometry::Point[]
410 error_code?: i32
411 }"#
412 ));
413 }
414
415 #[test]
420 fn rejects_field_without_type() {
421 assert!(!parses_file("struct S { x }"));
422 }
423
424 #[test]
425 fn rejects_missing_closing_brace() {
426 assert!(!parses_file("struct S { x: f64"));
427 }
428
429 #[test]
430 fn rejects_unknown_top_level() {
431 assert!(!parses_file("typedef i32 MyInt"));
432 }
433}