use nom::{
branch::alt,
bytes::complete::{tag, tag_no_case, take_until, take_while1},
character::complete::{digit1, hex_digit1, multispace1, not_line_ending, oct_digit1},
combinator::{map, map_res, opt, recognize, value, verify},
multi::{many0, many1, separated_nonempty_list},
sequence::{preceded, separated_pair, terminated, tuple},
IResult,
};
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum PrimitiveType {
Int8,
Int16,
Int32,
Int64,
Float,
Double,
String,
Boolean,
Byte,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum ArrayDimension {
Static { size: u32 },
Dynamic { field_name: String },
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ArrayType {
pub item_type: Box<Type>,
pub dimensions: Vec<ArrayDimension>,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct StructType {
pub namespace: Option<String>,
pub name: String,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Type {
Primitive(PrimitiveType),
Array(ArrayType),
Struct(StructType),
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum ConstValue {
Int8(i8),
Int16(i16),
Int32(i32),
Int64(i64),
Float(String),
Double(String),
Boolean(bool),
Byte(u8),
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Field {
pub name: String,
pub ty: Type,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Const {
pub name: String,
pub ty: PrimitiveType,
pub value: ConstValue,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum StructMember {
Field(Field),
Const(Const),
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Struct {
pub name: String,
pub members: Vec<StructMember>,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Schema {
pub package: Option<String>,
pub structs: Vec<Struct>,
}
pub fn block_comment(input: &str) -> IResult<&str, &str> {
let (input, _) = tag("/*")(input)?;
let (input, comment) = take_until("*/")(input)?;
let (input, _) = tag("*/")(input)?;
Ok((input, comment))
}
pub fn line_comment(input: &str) -> IResult<&str, &str> {
let (input, _) = tag("//")(input)?;
let (input, comment) = not_line_ending(input)?;
Ok((input, comment))
}
pub fn ws(input: &str) -> IResult<&str, ()> {
value((), many1(alt((block_comment, line_comment, multispace1))))(input)
}
pub fn spaced_comma(input: &str) -> IResult<&str, &str> {
recognize(tuple((opt(ws), tag(","), opt(ws))))(input)
}
pub fn ident(input: &str) -> IResult<&str, &str> {
recognize(take_while1(|c: char| c.is_alphanumeric() || c == '_'))(input)
}
pub fn primitive_type(input: &str) -> IResult<&str, PrimitiveType> {
alt((
map(tag("int8_t"), |_| PrimitiveType::Int8),
map(tag("int16_t"), |_| PrimitiveType::Int16),
map(tag("int32_t"), |_| PrimitiveType::Int32),
map(tag("int64_t"), |_| PrimitiveType::Int64),
map(tag("float"), |_| PrimitiveType::Float),
map(tag("double"), |_| PrimitiveType::Double),
map(tag("string"), |_| PrimitiveType::String),
map(tag("boolean"), |_| PrimitiveType::Boolean),
map(tag("byte"), |_| PrimitiveType::Byte),
))(input)
}
pub fn field_type(input: &str) -> IResult<&str, Type> {
alt((
map(primitive_type, Type::Primitive),
map(separated_pair(ident, tag("."), ident), |(ns, n)| {
Type::Struct(StructType {
namespace: Some(ns.to_string()),
name: n.to_string(),
})
}),
map(ident, |n| {
Type::Struct(StructType {
namespace: None,
name: n.to_string(),
})
}),
))(input)
}
fn array_dimension(input: &str) -> IResult<&str, ArrayDimension> {
let (input, _) = tag("[")(input)?;
let (input, _) = opt(ws)(input)?;
let (input, dim) = alt((
map(map_res(digit1, |s: &str| s.parse::<u32>()), |size| {
ArrayDimension::Static { size }
}),
map(ident, |s| ArrayDimension::Dynamic {
field_name: s.to_string(),
}),
))(input)?;
let (input, _) = opt(ws)(input)?;
let (input, _) = tag("]")(input)?;
let (input, _) = opt(ws)(input)?;
Ok((input, dim))
}
pub fn field_decl(input: &str) -> IResult<&str, Field> {
let (input, mut ty) = field_type(input)?;
let (input, _) = ws(input)?;
let (input, name) = ident(input)?;
let (input, dims) = many0(array_dimension)(input)?;
if !dims.is_empty() {
ty = Type::Array(ArrayType {
item_type: Box::new(ty),
dimensions: dims,
});
}
Ok((
input,
Field {
ty,
name: name.to_owned(),
},
))
}
fn recognize_int(input: &str) -> IResult<&str, (String, u32)> {
let (input, minus) = opt(tag("-"))(input)?;
let minus = minus.unwrap_or("");
let (input, radix) = alt((
value(16, tag_no_case("0x")),
value(8, tag("0")),
value(10, tag("")),
))(input)?;
let (input, body) = match radix {
16 => hex_digit1(input)?,
10 => digit1(input)?,
8 => oct_digit1(input)?,
_ => unreachable!(),
};
Ok((input, (format!("{}{}", minus, body), radix)))
}
fn recognize_float(input: &str) -> IResult<&str, &str> {
recognize(tuple((
opt(tag("-")),
digit1,
opt(tuple((tag("."), digit1, opt(tuple((tag("e"), digit1)))))),
)))(input)
}
pub fn const_value(ty: PrimitiveType, input: &str) -> IResult<&str, ConstValue> {
match ty {
PrimitiveType::Int8 => map(
map_res(recognize_int, |(s, radix)| i8::from_str_radix(&s, radix)),
ConstValue::Int8,
)(input),
PrimitiveType::Int16 => map(
map_res(recognize_int, |(s, radix)| i16::from_str_radix(&s, radix)),
ConstValue::Int16,
)(input),
PrimitiveType::Int32 => map(
map_res(recognize_int, |(s, radix)| i32::from_str_radix(&s, radix)),
ConstValue::Int32,
)(input),
PrimitiveType::Int64 => map(
map_res(recognize_int, |(s, radix)| i64::from_str_radix(&s, radix)),
ConstValue::Int64,
)(input),
PrimitiveType::Float => map(
verify(recognize_float, |s: &str| s.parse::<f32>().is_ok()),
|s: &str| ConstValue::Float(s.to_owned()),
)(input),
PrimitiveType::Double => map(
verify(recognize_float, |s: &str| s.parse::<f64>().is_ok()),
|s: &str| ConstValue::Double(s.to_owned()),
)(input),
PrimitiveType::String => panic!("String constants are not supported"),
PrimitiveType::Boolean => panic!("Boolean constants are not supported"),
PrimitiveType::Byte => {
map(map_res(digit1, |s: &str| s.parse::<u8>()), ConstValue::Byte)(input)
}
}
}
fn const_name_val(ty: PrimitiveType) -> impl Fn(&str) -> IResult<&str, (&str, ConstValue)> {
move |input: &str| {
let (input, name) = ident(input)?;
let (input, _) = tuple((opt(ws), tag("="), opt(ws)))(input)?;
let (input, value) = const_value(ty, input)?;
Ok((input, (name, value)))
}
}
pub fn const_decl(input: &str) -> IResult<&str, Vec<Const>> {
let (input, _) = tuple((tag("const"), ws))(input)?;
let (input, ty) = primitive_type(input)?;
let (input, _) = ws(input)?;
let (input, name_vals) = separated_nonempty_list(spaced_comma, const_name_val(ty))(input)?;
Ok((
input,
name_vals
.into_iter()
.map(|(name, value)| Const {
name: name.to_string(),
value,
ty,
})
.collect(),
))
}
pub fn struct_member(input: &str) -> IResult<&str, Vec<StructMember>> {
alt((
map(const_decl, |cds| {
cds.into_iter().map(StructMember::Const).collect()
}),
map(field_decl, |fd| vec![StructMember::Field(fd)]),
))(input)
}
pub fn struct_decl(input: &str) -> IResult<&str, Struct> {
let (input, _) = tuple((tag("struct"), ws))(input)?;
let (input, name) = ident(input)?;
let (input, _) = tuple((ws, tag("{"), ws))(input)?;
let (input, member_vecs) = many0(terminated(
struct_member,
tuple((opt(ws), tag(";"), opt(ws))),
))(input)?;
let (input, _) = tag("}")(input)?;
let mut members = vec![];
for mv in member_vecs.into_iter() {
members.extend(mv);
}
Ok((
input,
Struct {
name: name.to_owned(),
members,
},
))
}
pub fn package_decl(input: &str) -> IResult<&str, String> {
map(
preceded(tuple((tag("package"), ws)), ident),
|name: &str| name.to_string(),
)(input)
}
pub fn schema(input: &str) -> IResult<&str, Schema> {
let (input, _) = opt(ws)(input)?;
let (input, package) = opt(terminated(
package_decl,
tuple((opt(ws), tag(";"), opt(ws))),
))(input)?;
let (input, structs) = many0(terminated(struct_decl, opt(ws)))(input)?;
Ok((input, Schema { package, structs }))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn comments_test() {
let schema_text = "
/* before */
// before
package comment_stress_test;
struct s {
/* a */
int32_t a; // a
/* b */
int32_t b; // b
/* c */
int32_t c[ /* two */ 2]; // c
/* D */
const int32_t D = /* three */ 3; // D
}
/* after */
// after
";
let (unparsed, s) = schema(schema_text).unwrap();
assert_eq!(unparsed, "");
}
}