rust_lcm_codegen/parser.rs
1//! LCM type definition file parser
2use nom::{
3 branch::alt,
4 bytes::complete::{tag, tag_no_case, take_until, take_while1},
5 character::complete::{digit1, hex_digit1, multispace1, not_line_ending, oct_digit1},
6 combinator::{map, map_res, opt, recognize, value, verify},
7 multi::{many0, many1, separated_nonempty_list},
8 sequence::{preceded, separated_pair, terminated, tuple},
9 IResult,
10};
11
12#[derive(Debug, Eq, PartialEq, Copy, Clone)]
13pub enum PrimitiveType {
14 Int8,
15 Int16,
16 Int32,
17 Int64,
18 Float,
19 Double,
20 String,
21 Boolean,
22 Byte,
23}
24
25#[derive(Debug, Eq, PartialEq, Clone)]
26pub enum ArrayDimension {
27 Static { size: u32 },
28 Dynamic { field_name: String },
29}
30
31#[derive(Debug, Eq, PartialEq, Clone)]
32pub struct ArrayType {
33 pub item_type: Box<Type>,
34 pub dimensions: Vec<ArrayDimension>,
35}
36
37#[derive(Debug, Eq, PartialEq, Clone)]
38pub struct StructType {
39 pub namespace: Option<String>,
40 pub name: String,
41}
42
43#[derive(Debug, Eq, PartialEq, Clone)]
44pub enum Type {
45 Primitive(PrimitiveType),
46 Array(ArrayType),
47 Struct(StructType),
48}
49
50#[derive(Debug, Eq, PartialEq, Clone)]
51pub enum ConstValue {
52 Int8(i8),
53 Int16(i16),
54 Int32(i32),
55 Int64(i64),
56 // Keep floats and doubles as strings, since f32/f64 don't have Eq defined on them
57 Float(String),
58 Double(String),
59 Boolean(bool),
60 Byte(u8),
61}
62
63#[derive(Debug, Eq, PartialEq, Clone)]
64pub struct Field {
65 pub name: String,
66 pub ty: Type,
67}
68
69#[derive(Debug, Eq, PartialEq, Clone)]
70pub struct Const {
71 pub name: String,
72 pub ty: PrimitiveType,
73 pub value: ConstValue,
74}
75
76#[derive(Debug, Eq, PartialEq, Clone)]
77pub enum StructMember {
78 Field(Field),
79 Const(Const),
80}
81
82#[derive(Debug, Eq, PartialEq, Clone)]
83pub struct Struct {
84 pub name: String,
85 pub members: Vec<StructMember>,
86}
87
88#[derive(Debug, Eq, PartialEq, Clone)]
89pub struct Schema {
90 pub package: Option<String>,
91 pub structs: Vec<Struct>,
92}
93
94/// Match a C-style comment
95///
96/// ```
97/// # use nom::{Err, error::ErrorKind, Needed};
98/// use rust_lcm_codegen::parser::block_comment;
99///
100/// assert_eq!(block_comment("/* test */"), Ok(("", " test ")));
101/// ```
102pub fn block_comment(input: &str) -> IResult<&str, &str> {
103 let (input, _) = tag("/*")(input)?;
104 let (input, comment) = take_until("*/")(input)?;
105 let (input, _) = tag("*/")(input)?;
106
107 Ok((input, comment))
108}
109
110/// Match a C++-style comment
111///
112/// ```
113/// # use nom::{Err, error::ErrorKind, Needed};
114/// use rust_lcm_codegen::parser::line_comment;
115///
116/// assert_eq!(line_comment("// test\nmore"), Ok(("\nmore", " test")));
117/// ```
118pub fn line_comment(input: &str) -> IResult<&str, &str> {
119 let (input, _) = tag("//")(input)?;
120 let (input, comment) = not_line_ending(input)?;
121
122 Ok((input, comment))
123}
124
125/// Whitespace parser. This includes regular spaces, newlines, and comments.
126pub fn ws(input: &str) -> IResult<&str, ()> {
127 value((), many1(alt((block_comment, line_comment, multispace1))))(input)
128}
129
130/// Match a comma, optionally surrounded by spaces
131///
132/// ```
133/// # use nom::{Err, error::ErrorKind, Needed};
134/// use rust_lcm_codegen::parser::spaced_comma;
135///
136/// assert_eq!(spaced_comma(","), Ok(("", ",")));
137/// assert_eq!(spaced_comma(" ,\t"), Ok(("", " ,\t")));
138/// assert_eq!(spaced_comma("x"), Err(Err::Error(("x", ErrorKind::Tag))));
139/// ```
140pub fn spaced_comma(input: &str) -> IResult<&str, &str> {
141 recognize(tuple((opt(ws), tag(","), opt(ws))))(input)
142}
143
144/// Names that can be used for structs, packages, or fields
145/// ```
146/// # use nom::{Err, error::ErrorKind, Needed};
147/// use rust_lcm_codegen::parser::ident;
148///
149/// assert_eq!(ident("foo"), Ok(("", "foo")));
150/// assert_eq!(ident("foo_bar"), Ok(("", "foo_bar")));
151/// assert_eq!(ident(""), Err(Err::Error(("", ErrorKind::TakeWhile1))));
152/// ```
153pub fn ident(input: &str) -> IResult<&str, &str> {
154 recognize(take_while1(|c: char| c.is_alphanumeric() || c == '_'))(input)
155}
156
157/// Parse an LCM primitive type
158///
159/// ```
160/// # use nom::{Err, error::ErrorKind, Needed};
161/// use rust_lcm_codegen::parser::{primitive_type, PrimitiveType};
162///
163/// assert_eq!(primitive_type("int8_t"), Ok(("", PrimitiveType::Int8)));
164/// assert_eq!(primitive_type("int16_t"), Ok(("", PrimitiveType::Int16)));
165/// assert_eq!(primitive_type("int32_t"), Ok(("", PrimitiveType::Int32)));
166/// assert_eq!(primitive_type("int64_t"), Ok(("", PrimitiveType::Int64)));
167/// assert_eq!(primitive_type("float"), Ok(("", PrimitiveType::Float)));
168/// assert_eq!(primitive_type("double"), Ok(("", PrimitiveType::Double)));
169/// assert_eq!(primitive_type("string"), Ok(("", PrimitiveType::String)));
170/// assert_eq!(primitive_type("boolean"), Ok(("", PrimitiveType::Boolean)));
171/// assert_eq!(primitive_type("byte"), Ok(("", PrimitiveType::Byte)));
172///
173/// assert_eq!(primitive_type(""), Err(Err::Error(("", ErrorKind::Tag))));
174/// assert_eq!(primitive_type("foo"), Err(Err::Error(("foo", ErrorKind::Tag))));
175/// ```
176pub fn primitive_type(input: &str) -> IResult<&str, PrimitiveType> {
177 alt((
178 map(tag("int8_t"), |_| PrimitiveType::Int8),
179 map(tag("int16_t"), |_| PrimitiveType::Int16),
180 map(tag("int32_t"), |_| PrimitiveType::Int32),
181 map(tag("int64_t"), |_| PrimitiveType::Int64),
182 map(tag("float"), |_| PrimitiveType::Float),
183 map(tag("double"), |_| PrimitiveType::Double),
184 map(tag("string"), |_| PrimitiveType::String),
185 map(tag("boolean"), |_| PrimitiveType::Boolean),
186 map(tag("byte"), |_| PrimitiveType::Byte),
187 ))(input)
188}
189
190/// Parse the type part of a field decl.
191/// ```
192/// # use nom::{Err, error::ErrorKind, Needed};
193/// use rust_lcm_codegen::parser::{field_type, Type, PrimitiveType, StructType};
194///
195/// assert_eq!(field_type("int8_t"), Ok(("", Type::Primitive(PrimitiveType::Int8))));
196///
197/// assert_eq!(field_type("foo.bar"),
198/// Ok(("", Type::Struct(StructType { namespace: Some("foo".to_string()), name: "bar".to_string() }))));
199///
200/// assert_eq!(field_type("foo"),
201/// Ok(("", Type::Struct(StructType { namespace: None, name: "foo".to_string() }))));
202///
203/// ```
204pub fn field_type(input: &str) -> IResult<&str, Type> {
205 alt((
206 map(primitive_type, Type::Primitive),
207 map(separated_pair(ident, tag("."), ident), |(ns, n)| {
208 Type::Struct(StructType {
209 namespace: Some(ns.to_string()),
210 name: n.to_string(),
211 })
212 }),
213 map(ident, |n| {
214 Type::Struct(StructType {
215 namespace: None,
216 name: n.to_string(),
217 })
218 }),
219 ))(input)
220}
221
222fn array_dimension(input: &str) -> IResult<&str, ArrayDimension> {
223 let (input, _) = tag("[")(input)?;
224 let (input, _) = opt(ws)(input)?;
225
226 let (input, dim) = alt((
227 map(map_res(digit1, |s: &str| s.parse::<u32>()), |size| {
228 ArrayDimension::Static { size }
229 }),
230 map(ident, |s| ArrayDimension::Dynamic {
231 field_name: s.to_string(),
232 }),
233 ))(input)?;
234
235 let (input, _) = opt(ws)(input)?;
236 let (input, _) = tag("]")(input)?;
237 let (input, _) = opt(ws)(input)?;
238
239 Ok((input, dim))
240}
241
242/// Parse a field declaration, inside a struct, including array dimensions.
243/// (doesn't handle the semicolon or preceding whitespace)
244///
245/// ```
246/// # use nom::{Err, error::ErrorKind, Needed};
247/// use rust_lcm_codegen::parser::{field_decl, Field, PrimitiveType, Type, StructType, ArrayDimension, ArrayType};
248///
249/// assert_eq!(
250/// field_decl("int8_t foo"),
251/// Ok((
252/// "",
253/// Field {
254/// name: "foo".to_owned(),
255/// ty: Type::Primitive(PrimitiveType::Int8)
256/// }
257/// ))
258/// );
259///
260/// assert_eq!(
261/// field_decl("ns.name foo[dim][2]"),
262/// Ok((
263/// "",
264/// Field {
265/// name: "foo".to_owned(),
266/// ty: Type::Array(ArrayType {
267/// item_type: Box::new(
268/// Type::Struct(StructType { namespace: Some("ns".to_string()),
269/// name: "name".to_string() })
270/// ),
271/// dimensions: vec![
272/// ArrayDimension::Dynamic { field_name: "dim".to_string() },
273/// ArrayDimension::Static { size: 2 },
274/// ]
275/// })
276/// }
277/// ))
278/// );
279///
280/// assert_eq!(field_decl(""), Err(Err::Error(("", ErrorKind::TakeWhile1))));
281/// assert_eq!(field_decl("int8_t *!@"), Err(Err::Error(("*!@", ErrorKind::TakeWhile1))));
282///
283/// ```
284pub fn field_decl(input: &str) -> IResult<&str, Field> {
285 let (input, mut ty) = field_type(input)?;
286 let (input, _) = ws(input)?;
287 let (input, name) = ident(input)?;
288 let (input, dims) = many0(array_dimension)(input)?;
289
290 if !dims.is_empty() {
291 ty = Type::Array(ArrayType {
292 item_type: Box::new(ty),
293 dimensions: dims,
294 });
295 }
296
297 Ok((
298 input,
299 Field {
300 ty,
301 name: name.to_owned(),
302 },
303 ))
304}
305
306/// Recognize something that looks like an integer, positive or negative. Return a tuple of the
307/// content part of the string and the radix.
308fn recognize_int(input: &str) -> IResult<&str, (String, u32)> {
309 let (input, minus) = opt(tag("-"))(input)?;
310 let minus = minus.unwrap_or("");
311
312 let (input, radix) = alt((
313 value(16, tag_no_case("0x")),
314 value(8, tag("0")),
315 value(10, tag("")),
316 ))(input)?;
317
318 let (input, body) = match radix {
319 16 => hex_digit1(input)?,
320 10 => digit1(input)?,
321 8 => oct_digit1(input)?,
322 _ => unreachable!(),
323 };
324
325 Ok((input, (format!("{}{}", minus, body), radix)))
326}
327
328/// Recognize something that looks like an float, positive or negative. Keep it a string.
329fn recognize_float(input: &str) -> IResult<&str, &str> {
330 recognize(tuple((
331 opt(tag("-")),
332 digit1,
333 opt(tuple((tag("."), digit1, opt(tuple((tag("e"), digit1)))))),
334 )))(input)
335}
336
337/// Parse a const value of the given type.
338///
339/// ```
340/// # use nom::{Err, error::ErrorKind, Needed};
341/// use rust_lcm_codegen::parser::{const_value, ConstValue, PrimitiveType};
342///
343/// assert_eq!(const_value(PrimitiveType::Int8, "42"), Ok(("", ConstValue::Int8( 42))));
344/// assert_eq!(const_value(PrimitiveType::Int8, "-42"), Ok(("", ConstValue::Int8( -42))));
345/// assert_eq!(const_value(PrimitiveType::Int8, "0x2f"), Ok(("", ConstValue::Int8( 0x2f))));
346/// assert_eq!(const_value(PrimitiveType::Int8, "-0x2f"), Ok(("", ConstValue::Int8(-0x2f))));
347/// assert_eq!(const_value(PrimitiveType::Int8, "022"), Ok(("", ConstValue::Int8( 0o22))));
348/// assert_eq!(const_value(PrimitiveType::Int8, "-022"), Ok(("", ConstValue::Int8(-0o22))));
349/// assert_eq!(const_value(PrimitiveType::Int8, "1024"),
350/// Err(Err::Error(("1024", ErrorKind::MapRes))));
351///
352/// assert_eq!(const_value(PrimitiveType::Int16, "1024"), Ok(("", ConstValue::Int16( 1024))));
353/// assert_eq!(const_value(PrimitiveType::Int16, "-1024"), Ok(("", ConstValue::Int16(-1024))));
354/// assert_eq!(const_value(PrimitiveType::Int16, "32768"),
355/// Err(Err::Error(("32768", ErrorKind::MapRes))));
356///
357/// assert_eq!(const_value(PrimitiveType::Int32, "32768"), Ok(("", ConstValue::Int32( 32768))));
358/// assert_eq!(const_value(PrimitiveType::Int32, "-32768"), Ok(("", ConstValue::Int32(-32768))));
359/// assert_eq!(const_value(PrimitiveType::Int32, "2147483648"),
360/// Err(Err::Error(("2147483648", ErrorKind::MapRes))));
361///
362/// assert_eq!(const_value(PrimitiveType::Int64, "2147483648"), Ok(("", ConstValue::Int64( 2147483648))));
363/// assert_eq!(const_value(PrimitiveType::Int64, "-2147483648"), Ok(("", ConstValue::Int64(-2147483648))));
364/// assert_eq!(const_value(PrimitiveType::Int64, "92233720368547758073"),
365/// Err(Err::Error(("92233720368547758073", ErrorKind::MapRes))));
366///
367/// assert_eq!(const_value(PrimitiveType::Float, "10"), Ok(("", ConstValue::Float( "10".to_owned()))));
368/// assert_eq!(const_value(PrimitiveType::Float, "10.35"), Ok(("", ConstValue::Float( "10.35".to_owned()))));
369/// assert_eq!(const_value(PrimitiveType::Float, "-10.35"), Ok(("", ConstValue::Float( "-10.35".to_owned()))));
370/// assert_eq!(const_value(PrimitiveType::Float, "10.35e12"), Ok(("", ConstValue::Float( "10.35e12".to_owned()))));
371/// assert_eq!(const_value(PrimitiveType::Float, "-10.35e12"), Ok(("", ConstValue::Float( "-10.35e12".to_owned()))));
372/// assert_eq!(const_value(PrimitiveType::Float, "10.35e12000"), Ok(("", ConstValue::Float("10.35e12000".to_owned()))));
373/// assert_eq!(const_value(PrimitiveType::Float, "asdf"),
374/// Err(Err::Error(("asdf", ErrorKind::Digit))));
375///
376/// assert_eq!(const_value(PrimitiveType::Double, "10"), Ok(("", ConstValue::Double( "10".to_owned()))));
377/// assert_eq!(const_value(PrimitiveType::Double, "10.35"), Ok(("", ConstValue::Double( "10.35".to_owned()))));
378/// assert_eq!(const_value(PrimitiveType::Double, "-10.35"), Ok(("", ConstValue::Double( "-10.35".to_owned()))));
379/// assert_eq!(const_value(PrimitiveType::Double, "10.35e12"), Ok(("", ConstValue::Double( "10.35e12".to_owned()))));
380/// assert_eq!(const_value(PrimitiveType::Double, "-10.35e12"), Ok(("", ConstValue::Double( "-10.35e12".to_owned()))));
381/// assert_eq!(const_value(PrimitiveType::Double, "10.35e12000"), Ok(("", ConstValue::Double("10.35e12000".to_owned()))));
382/// assert_eq!(const_value(PrimitiveType::Double, "asdf"),
383/// Err(Err::Error(("asdf", ErrorKind::Digit))));
384///
385/// assert_eq!(const_value(PrimitiveType::Byte, "42"), Ok(("", ConstValue::Byte( 42))));
386/// assert_eq!(const_value(PrimitiveType::Byte, "-42"), Err(Err::Error(("-42", ErrorKind::Digit))));
387/// assert_eq!(const_value(PrimitiveType::Byte, "1024"), Err(Err::Error(("1024", ErrorKind::MapRes))));
388///
389/// ```
390pub fn const_value(ty: PrimitiveType, input: &str) -> IResult<&str, ConstValue> {
391 match ty {
392 PrimitiveType::Int8 => map(
393 map_res(recognize_int, |(s, radix)| i8::from_str_radix(&s, radix)),
394 ConstValue::Int8,
395 )(input),
396 PrimitiveType::Int16 => map(
397 map_res(recognize_int, |(s, radix)| i16::from_str_radix(&s, radix)),
398 ConstValue::Int16,
399 )(input),
400 PrimitiveType::Int32 => map(
401 map_res(recognize_int, |(s, radix)| i32::from_str_radix(&s, radix)),
402 ConstValue::Int32,
403 )(input),
404 PrimitiveType::Int64 => map(
405 map_res(recognize_int, |(s, radix)| i64::from_str_radix(&s, radix)),
406 ConstValue::Int64,
407 )(input),
408 PrimitiveType::Float => map(
409 verify(recognize_float, |s: &str| s.parse::<f32>().is_ok()),
410 |s: &str| ConstValue::Float(s.to_owned()),
411 )(input),
412 PrimitiveType::Double => map(
413 verify(recognize_float, |s: &str| s.parse::<f64>().is_ok()),
414 |s: &str| ConstValue::Double(s.to_owned()),
415 )(input),
416 PrimitiveType::String => panic!("String constants are not supported"),
417 PrimitiveType::Boolean => panic!("Boolean constants are not supported"),
418 PrimitiveType::Byte => {
419 map(map_res(digit1, |s: &str| s.parse::<u8>()), ConstValue::Byte)(input)
420 }
421 }
422}
423
424fn const_name_val(ty: PrimitiveType) -> impl Fn(&str) -> IResult<&str, (&str, ConstValue)> {
425 move |input: &str| {
426 let (input, name) = ident(input)?;
427 let (input, _) = tuple((opt(ws), tag("="), opt(ws)))(input)?;
428 let (input, value) = const_value(ty, input)?;
429
430 Ok((input, (name, value)))
431 }
432}
433
434/// Parse a const declaration, inside a struct. (doesn't handle the semicolon or
435/// preceding whitespace)
436///
437/// ```
438/// # use nom::{Err, error::ErrorKind, Needed};
439/// use rust_lcm_codegen::parser::{const_decl, Const, ConstValue, PrimitiveType};
440///
441/// assert_eq!(
442/// const_decl("const int32_t YELLOW=1, GOLDENROD=2, CANARY=3"),
443/// Ok((
444/// "",
445/// vec![
446/// Const {
447/// name: "YELLOW".to_owned(),
448/// ty: PrimitiveType::Int32,
449/// value: ConstValue::Int32(1)
450/// },
451/// Const {
452/// name: "GOLDENROD".to_owned(),
453/// ty: PrimitiveType::Int32,
454/// value: ConstValue::Int32(2)
455/// },
456/// Const {
457/// name: "CANARY".to_owned(),
458/// ty: PrimitiveType::Int32,
459/// value: ConstValue::Int32(3)
460/// },
461/// ]
462/// ))
463/// );
464///
465/// assert_eq!(
466/// const_decl("const double PI=3.14159"),
467/// Ok((
468/// "",
469/// vec![Const {
470/// name: "PI".to_owned(),
471/// ty: PrimitiveType::Double,
472/// value: ConstValue::Double("3.14159".to_owned())
473/// }]
474/// ))
475/// );
476///
477///
478/// ```
479pub fn const_decl(input: &str) -> IResult<&str, Vec<Const>> {
480 let (input, _) = tuple((tag("const"), ws))(input)?;
481 let (input, ty) = primitive_type(input)?;
482 let (input, _) = ws(input)?;
483 let (input, name_vals) = separated_nonempty_list(spaced_comma, const_name_val(ty))(input)?;
484
485 Ok((
486 input,
487 name_vals
488 .into_iter()
489 .map(|(name, value)| Const {
490 name: name.to_string(),
491 value,
492 ty,
493 })
494 .collect(),
495 ))
496}
497
498pub fn struct_member(input: &str) -> IResult<&str, Vec<StructMember>> {
499 alt((
500 map(const_decl, |cds| {
501 cds.into_iter().map(StructMember::Const).collect()
502 }),
503 map(field_decl, |fd| vec![StructMember::Field(fd)]),
504 ))(input)
505}
506
507/// Parse a whole struct declaration.
508///
509/// ```
510/// # use nom::{Err, error::ErrorKind, Needed};
511/// use rust_lcm_codegen::parser::{struct_decl, Struct, Const, ConstValue, PrimitiveType, StructMember, Field, Type};
512///
513/// assert_eq!(
514/// struct_decl("struct empty_struct { }"),
515/// Ok((
516/// "",
517/// Struct {
518/// name: "empty_struct".to_string(),
519/// members: vec![],
520/// }
521/// ))
522/// );
523///
524/// assert_eq!(
525/// struct_decl("struct my_struct {\n const int32_t YELLOW=1;\n int32_t color;\n}"),
526/// Ok((
527/// "",
528/// Struct {
529/// name: "my_struct".to_string(),
530/// members: vec![
531/// StructMember::Const(
532/// Const {
533/// name: "YELLOW".to_owned(),
534/// ty: PrimitiveType::Int32,
535/// value: ConstValue::Int32(1)
536/// }
537/// ),
538/// StructMember::Field(
539/// Field {
540/// name: "color".to_owned(),
541/// ty: Type::Primitive(PrimitiveType::Int32),
542/// }
543/// ),
544/// ]
545/// }
546/// ))
547/// );
548/// ```
549pub fn struct_decl(input: &str) -> IResult<&str, Struct> {
550 let (input, _) = tuple((tag("struct"), ws))(input)?;
551 let (input, name) = ident(input)?;
552 let (input, _) = tuple((ws, tag("{"), ws))(input)?;
553
554 let (input, member_vecs) = many0(terminated(
555 struct_member,
556 tuple((opt(ws), tag(";"), opt(ws))),
557 ))(input)?;
558
559 let (input, _) = tag("}")(input)?;
560
561 let mut members = vec![];
562 for mv in member_vecs.into_iter() {
563 members.extend(mv);
564 }
565
566 Ok((
567 input,
568 Struct {
569 name: name.to_owned(),
570 members,
571 },
572 ))
573}
574
575/// Parse a package line, not including the semicolon.
576///
577/// ```
578/// # use nom::{Err, error::ErrorKind, Needed};
579/// use rust_lcm_codegen::parser::package_decl;
580///
581/// assert_eq!(
582/// package_decl("package my_package"),
583/// Ok((
584/// "",
585/// "my_package".to_string(),
586/// ))
587/// );
588/// ```
589pub fn package_decl(input: &str) -> IResult<&str, String> {
590 map(
591 preceded(tuple((tag("package"), ws)), ident),
592 |name: &str| name.to_string(),
593 )(input)
594}
595
596/// Parse an entire schema file
597/// ```
598/// # use nom::{Err, error::ErrorKind, Needed};
599/// use rust_lcm_codegen::parser::{schema, Schema, Struct, Const, ConstValue, PrimitiveType, StructMember, Field};
600///
601/// assert_eq!(
602/// schema("package test;\n\nstruct empty { }\nstruct empty2 { }"),
603/// Ok((
604/// "",
605/// Schema {
606/// package: Some("test".to_string()),
607/// structs: vec![
608/// Struct {
609/// name: "empty".to_string(),
610/// members: vec![],
611/// },
612/// Struct {
613/// name: "empty2".to_string(),
614/// members: vec![],
615/// }
616/// ]
617/// },
618/// ))
619/// );
620///
621/// assert_eq!(
622/// schema("struct empty { }"),
623/// Ok((
624/// "",
625/// Schema {
626/// package: None,
627/// structs: vec![
628/// Struct {
629/// name: "empty".to_string(),
630/// members: vec![],
631/// }
632/// ]
633/// },
634/// ))
635/// );
636/// ```
637pub fn schema(input: &str) -> IResult<&str, Schema> {
638 let (input, _) = opt(ws)(input)?;
639 let (input, package) = opt(terminated(
640 package_decl,
641 tuple((opt(ws), tag(";"), opt(ws))),
642 ))(input)?;
643
644 let (input, structs) = many0(terminated(struct_decl, opt(ws)))(input)?;
645
646 Ok((input, Schema { package, structs }))
647}
648
649#[cfg(test)]
650mod test {
651 use super::*;
652
653 #[test]
654 fn comments_test() {
655 let schema_text = "
656/* before */
657// before
658package comment_stress_test;
659
660struct s {
661 /* a */
662 int32_t a; // a
663
664 /* b */
665 int32_t b; // b
666
667 /* c */
668 int32_t c[ /* two */ 2]; // c
669
670 /* D */
671 const int32_t D = /* three */ 3; // D
672}
673
674/* after */
675// after
676";
677
678 let (unparsed, s) = schema(schema_text).unwrap();
679 assert_eq!(unparsed, "");
680 }
681}