use crate::ast::*;
use nom::{
branch::alt,
bytes::complete::{tag, take_while, take_while1},
character::complete::{char, digit1, multispace0, multispace1},
combinator::{all_consuming, map, map_res, opt, recognize, value},
multi::{many0, separated_list0},
sequence::{delimited, pair, preceded, terminated},
IResult,
};
use std::path::Path;
pub fn parse_file(path: impl AsRef<Path>) -> Result<Protocol, String> {
let content =
std::fs::read_to_string(path.as_ref()).map_err(|e| format!("failed to read file: {}", e))?;
parse_protocol(&content)
}
pub fn parse_protocol(input: &str) -> Result<Protocol, String> {
let input = remove_comments(input);
let result = all_consuming(protocol_parser)(&input);
match result {
Ok((_, protocol)) => Ok(protocol),
Err(e) => Err(format!("parse error: {:?}", e)),
}
}
fn remove_comments(input: &str) -> String {
let mut result = String::with_capacity(input.len());
let mut chars = input.chars().peekable();
let mut at_line_start = true;
while let Some(c) = chars.next() {
if c == '/' {
match chars.peek() {
Some('*') => {
chars.next();
while let Some(c) = chars.next() {
if c == '*' && chars.peek() == Some(&'/') {
chars.next();
result.push(' '); break;
}
}
at_line_start = false;
}
Some('/') => {
chars.next();
while let Some(&c) = chars.peek() {
if c == '\n' {
break;
}
chars.next();
}
}
_ => {
result.push(c);
at_line_start = false;
}
}
} else if c == '#' || (c == '%' && at_line_start) {
while let Some(&c) = chars.peek() {
if c == '\n' {
result.push('\n');
at_line_start = true;
break;
}
chars.next();
}
} else if c == '\n' {
result.push(c);
at_line_start = true;
} else if c.is_whitespace() {
result.push(c);
} else {
result.push(c);
at_line_start = false;
}
}
result
}
fn resolve_well_known_constant(name: &str) -> Option<u32> {
match name {
"VIR_UUID_BUFLEN" => Some(16),
"VIR_UUID_STRING_BUFLEN" => Some(37),
_ => None,
}
}
fn ws<'a, F, O, E>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
F: FnMut(&'a str) -> IResult<&'a str, O, E>,
E: nom::error::ParseError<&'a str>,
{
delimited(multispace0, inner, multispace0)
}
fn identifier(input: &str) -> IResult<&str, &str> {
recognize(pair(
take_while1(|c: char| c.is_ascii_alphabetic() || c == '_'),
take_while(|c: char| c.is_ascii_alphanumeric() || c == '_'),
))(input)
}
fn integer(input: &str) -> IResult<&str, i64> {
alt((
map_res(
preceded(
alt((tag("0x"), tag("0X"))),
take_while1(|c: char| c.is_ascii_hexdigit()),
),
|s: &str| i64::from_str_radix(s, 16),
),
map_res(recognize(pair(opt(char('-')), digit1)), |s: &str| {
s.parse::<i64>()
}),
))(input)
}
fn const_value(input: &str) -> IResult<&str, ConstValue> {
alt((
map(integer, ConstValue::Int),
map(identifier, |s| ConstValue::Ident(s.to_string())),
))(input)
}
fn protocol_parser(input: &str) -> IResult<&str, Protocol> {
let (input, items) = many0(ws(definition))(input)?;
let mut protocol = Protocol::new("remote");
for item in items {
match item {
Definition::Const(c) => protocol.constants.push(c),
Definition::Type(t) => protocol.types.push(t),
}
}
extract_procedures(&mut protocol);
Ok((input, protocol))
}
fn extract_procedures(protocol: &mut Protocol) {
let procedure_enum = protocol
.types
.iter()
.find_map(|t| {
if let TypeDef::Enum(e) = t {
if e.name == "remote_procedure" {
return Some(e.clone());
}
}
None
});
let procedure_enum = match procedure_enum {
Some(e) => e,
None => return,
};
let struct_names: std::collections::HashSet<String> = protocol
.types
.iter()
.filter_map(|t| {
if let TypeDef::Struct(s) = t {
Some(s.name.clone())
} else {
None
}
})
.collect();
for variant in &procedure_enum.variants {
let number = match &variant.value {
Some(ConstValue::Int(n)) => *n as u32,
_ => continue,
};
let base_name = variant
.name
.strip_prefix("REMOTE_PROC_")
.unwrap_or(&variant.name)
.to_lowercase();
let args_name = format!("remote_{}_args", base_name);
let ret_name = format!("remote_{}_ret", base_name);
let args = if struct_names.contains(&args_name) {
Some(args_name)
} else {
None
};
let ret = if struct_names.contains(&ret_name) {
Some(ret_name)
} else {
None
};
protocol.procedures.push(Procedure {
name: variant.name.clone(),
number,
args,
ret,
priority: Priority::default(),
});
}
}
enum Definition {
Const(Constant),
Type(TypeDef),
}
fn definition(input: &str) -> IResult<&str, Definition> {
alt((
map(const_def, Definition::Const),
map(type_def, Definition::Type),
))(input)
}
fn const_def(input: &str) -> IResult<&str, Constant> {
let (input, _) = tag("const")(input)?;
let (input, _) = multispace1(input)?;
let (input, name) = identifier(input)?;
let (input, _) = ws(char('='))(input)?;
let (input, value) = const_value(input)?;
let (input, _) = ws(char(';'))(input)?;
Ok((
input,
Constant {
name: name.to_string(),
value,
},
))
}
fn type_def(input: &str) -> IResult<&str, TypeDef> {
alt((
map(struct_def, TypeDef::Struct),
map(enum_def, TypeDef::Enum),
map(union_def, TypeDef::Union),
map(typedef_def, TypeDef::Typedef),
))(input)
}
fn struct_def(input: &str) -> IResult<&str, StructDef> {
let (input, _) = tag("struct")(input)?;
let (input, _) = multispace1(input)?;
let (input, name) = identifier(input)?;
let (input, _) = ws(char('{'))(input)?;
let (input, fields) = many0(ws(field_def))(input)?;
let (input, _) = ws(char('}'))(input)?;
let (input, _) = ws(char(';'))(input)?;
Ok((
input,
StructDef {
name: name.to_string(),
fields,
},
))
}
fn field_def(input: &str) -> IResult<&str, Field> {
let (input, ty) = type_spec(input)?;
let (input, _) = multispace1(input)?;
let (input, name) = identifier(input)?;
let (input, ty) = array_suffix(input, ty)?;
let (input, _) = ws(char(';'))(input)?;
Ok((
input,
Field {
name: name.to_string(),
ty,
},
))
}
fn array_suffix(input: &str, base_ty: Type) -> IResult<&str, Type> {
let input_trimmed = input.trim_start();
if input_trimmed.starts_with('[') {
let (input, _) = multispace0(input)?;
let (input, _) = char('[')(input)?;
let (input, len) = ws(const_value)(input)?;
let (input, _) = char(']')(input)?;
let size = match &len {
ConstValue::Int(n) => *n as u32,
ConstValue::Ident(name) => resolve_well_known_constant(name).unwrap_or(0),
};
match &base_ty {
Type::Opaque { .. } => Ok((
input,
Type::Opaque {
len: LengthSpec::Fixed(size),
},
)),
_ => Ok((
input,
Type::Array {
elem: Box::new(base_ty),
len: LengthSpec::Fixed(size),
},
)),
}
} else if input_trimmed.starts_with('<') {
match &base_ty {
Type::String { .. } | Type::Opaque { .. } => {
let (input, _) = multispace0(input)?;
let (input, _) = char('<')(input)?;
let (input, len) = ws(opt(const_value))(input)?;
let (input, _) = char('>')(input)?;
let max = len.and_then(|v| match v {
ConstValue::Int(n) => Some(n as u32),
ConstValue::Ident(_) => None,
});
match base_ty {
Type::String { .. } => Ok((input, Type::String { max_len: max })),
Type::Opaque { .. } => Ok((
input,
Type::Opaque {
len: LengthSpec::Variable { max },
},
)),
_ => unreachable!(),
}
}
_ => {
let (input, _) = multispace0(input)?;
let (input, _) = char('<')(input)?;
let (input, len) = ws(opt(const_value))(input)?;
let (input, _) = char('>')(input)?;
let max = len.and_then(|v| match v {
ConstValue::Int(n) => Some(n as u32),
ConstValue::Ident(_) => None,
});
Ok((
input,
Type::Array {
elem: Box::new(base_ty),
len: LengthSpec::Variable { max },
},
))
}
}
} else {
Ok((input, base_ty))
}
}
fn type_spec(input: &str) -> IResult<&str, Type> {
alt((
value(Type::Void, tag("void")),
value(
Type::UHyper,
pair(tag("unsigned"), preceded(multispace1, tag("hyper"))),
),
value(
Type::UInt,
pair(tag("unsigned"), preceded(multispace1, tag("int"))),
),
map(
pair(tag("unsigned"), preceded(multispace1, tag("char"))),
|_| Type::Named("u8".to_string()),
),
map(
pair(tag("unsigned"), preceded(multispace1, tag("short"))),
|_| Type::Named("u16".to_string()),
),
value(Type::Named("i8".to_string()), tag("char")),
value(Type::Named("i16".to_string()), tag("short")),
value(Type::Hyper, tag("hyper")),
value(Type::Int, tag("int")),
value(Type::Float, tag("float")),
value(Type::Double, tag("double")),
value(Type::Bool, tag("bool")),
string_type,
opaque_type,
optional_type,
map(identifier, |s| Type::Named(s.to_string())),
))(input)
}
fn optional_type(input: &str) -> IResult<&str, Type> {
let (input, ty) = alt((
value(
Type::UHyper,
pair(tag("unsigned"), preceded(multispace1, tag("hyper"))),
),
value(
Type::UInt,
pair(tag("unsigned"), preceded(multispace1, tag("int"))),
),
value(Type::Hyper, tag("hyper")),
value(Type::Int, tag("int")),
value(Type::Float, tag("float")),
value(Type::Double, tag("double")),
value(Type::Bool, tag("bool")),
map(identifier, |s| Type::Named(s.to_string())),
))(input)?;
let (input, _) = ws(char('*'))(input)?;
Ok((input, Type::Optional(Box::new(ty))))
}
fn string_type(input: &str) -> IResult<&str, Type> {
let (input, _) = tag("string")(input)?;
let (input, max_len) = opt(delimited(char('<'), ws(opt(integer)), char('>')))(input)?;
let max_len = max_len.flatten().map(|n| n as u32);
Ok((input, Type::String { max_len }))
}
fn opaque_type(input: &str) -> IResult<&str, Type> {
let (input, _) = tag("opaque")(input)?;
Ok((
input,
Type::Opaque {
len: LengthSpec::Variable { max: None },
},
))
}
fn enum_def(input: &str) -> IResult<&str, EnumDef> {
let (input, _) = tag("enum")(input)?;
let (input, _) = multispace1(input)?;
let (input, name) = identifier(input)?;
let (input, _) = ws(char('{'))(input)?;
let (input, variants) = separated_list0(ws(char(',')), ws(enum_variant))(input)?;
let (input, _) = opt(ws(char(',')))(input)?; let (input, _) = ws(char('}'))(input)?;
let (input, _) = ws(char(';'))(input)?;
Ok((
input,
EnumDef {
name: name.to_string(),
variants,
},
))
}
fn enum_variant(input: &str) -> IResult<&str, EnumVariant> {
let (input, name) = identifier(input)?;
let (input, value) = opt(preceded(ws(char('=')), const_value))(input)?;
Ok((
input,
EnumVariant {
name: name.to_string(),
value,
},
))
}
fn union_def(input: &str) -> IResult<&str, UnionDef> {
let (input, _) = tag("union")(input)?;
let (input, _) = multispace1(input)?;
let (input, name) = identifier(input)?;
let (input, _) = ws(tag("switch"))(input)?;
let (input, _) = ws(char('('))(input)?;
let (input, disc_ty) = type_spec(input)?;
let (input, _) = multispace1(input)?;
let (input, disc_name) = identifier(input)?;
let (input, _) = ws(char(')'))(input)?;
let (input, _) = ws(char('{'))(input)?;
let (input, cases) = many0(ws(union_case))(input)?;
let (input, default) = opt(union_default)(input)?;
let (input, _) = ws(char('}'))(input)?;
let (input, _) = ws(char(';'))(input)?;
Ok((
input,
UnionDef {
name: name.to_string(),
discriminant: Field {
name: disc_name.to_string(),
ty: disc_ty,
},
cases,
default,
},
))
}
fn union_case(input: &str) -> IResult<&str, UnionCase> {
let (input, _) = tag("case")(input)?;
let (input, _) = multispace1(input)?;
let (input, value) = const_value(input)?;
let (input, _) = ws(char(':'))(input)?;
let (input, field) = alt((
map(field_def, Some),
map(terminated(tag("void"), ws(char(';'))), |_| None),
))(input)?;
Ok((
input,
UnionCase {
values: vec![value],
field,
},
))
}
fn union_default(input: &str) -> IResult<&str, Box<Type>> {
let (input, _) = ws(tag("default"))(input)?;
let (input, _) = ws(char(':'))(input)?;
let (input, field) = field_def(input)?;
Ok((input, Box::new(field.ty)))
}
fn typedef_def(input: &str) -> IResult<&str, TypedefDef> {
let (input, _) = tag("typedef")(input)?;
let (input, _) = multispace1(input)?;
let (input, target) = type_spec(input)?;
let (input, _) = multispace0(input)?;
let (input, is_pointer) = opt(char('*'))(input)?;
let (input, _) = multispace0(input)?;
let (input, name) = identifier(input)?;
let (input, target) = array_suffix(input, target)?;
let (input, _) = ws(char(';'))(input)?;
let target = if is_pointer.is_some() {
Type::Optional(Box::new(target))
} else {
target
};
Ok((
input,
TypedefDef {
name: name.to_string(),
target,
},
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remove_comments() {
let input = r#"
/* block comment */
const FOO = 1; // line comment
# preprocessor
const BAR = 2;
"#;
let result = remove_comments(input);
assert!(!result.contains("block comment"));
assert!(!result.contains("line comment"));
assert!(!result.contains("preprocessor"));
}
#[test]
fn test_parse_const() {
let input = "const FOO = 42;";
let (_, c) = const_def(input).unwrap();
assert_eq!(c.name, "FOO");
assert!(matches!(c.value, ConstValue::Int(42)));
}
#[test]
fn test_parse_struct() {
let input = r#"
struct Point {
int x;
int y;
};
"#;
let result = parse_protocol(input).unwrap();
assert_eq!(result.types.len(), 1);
if let TypeDef::Struct(s) = &result.types[0] {
assert_eq!(s.name, "Point");
assert_eq!(s.fields.len(), 2);
} else {
panic!("expected struct");
}
}
#[test]
fn test_parse_enum() {
let input = r#"
enum Color {
RED = 0,
GREEN = 1,
BLUE = 2
};
"#;
let result = parse_protocol(input).unwrap();
assert_eq!(result.types.len(), 1);
if let TypeDef::Enum(e) = &result.types[0] {
assert_eq!(e.name, "Color");
assert_eq!(e.variants.len(), 3);
} else {
panic!("expected enum");
}
}
#[test]
fn test_parse_typedef() {
let input = "typedef string remote_string<>;";
let result = parse_protocol(input).unwrap();
assert_eq!(result.types.len(), 1);
if let TypeDef::Typedef(t) = &result.types[0] {
assert_eq!(t.name, "remote_string");
} else {
panic!("expected typedef");
}
}
}