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