use winnow::{
ModalResult, Parser,
ascii::multispace0,
combinator::{alt, separated},
error::{ErrMode, InputError, ParserError},
token::{literal, take_while},
};
use super::{
Comment, CustomEnum, CustomObject, CustomType, EnumVariant, Error, Field, Interface, List,
Method, Parameter, Type, TypeRef,
};
use alloc::{format, vec::Vec};
fn ws<'a>(input: &mut &'a [u8]) -> ModalResult<(), InputError<&'a [u8]>> {
loop {
let start_len = input.len();
multispace0::<_, InputError<&'a [u8]>>
.parse_next(input)
.ok();
if input.starts_with(b"#") {
*input = &input[1..];
while !input.is_empty() {
match input[0] {
b'\n' | b'\r' => {
if input.starts_with(b"\r\n") {
*input = &input[2..];
} else {
*input = &input[1..];
}
break;
}
_ => {
*input = &input[1..];
}
}
}
}
if input.len() == start_len {
break;
}
}
Ok(())
}
fn whitespace_only<'a>(input: &mut &'a [u8]) -> ModalResult<(), InputError<&'a [u8]>> {
multispace0::<_, InputError<&'a [u8]>>
.parse_next(input)
.ok();
Ok(())
}
fn bytes_to_str(bytes: &[u8]) -> &str {
core::str::from_utf8(bytes).unwrap()
}
fn field_name<'a>(input: &mut &'a [u8]) -> ModalResult<&'a str, InputError<&'a [u8]>> {
let start = *input;
let mut pos = 0;
if pos >= input.len() || !input[pos].is_ascii_alphabetic() {
return Err(ErrMode::Backtrack(ParserError::from_input(input)));
}
pos += 1;
while pos < input.len() && (input[pos].is_ascii_alphanumeric() || input[pos] == b'_') {
pos += 1;
}
let name_bytes = &start[0..pos];
*input = &input[pos..];
Ok(bytes_to_str(name_bytes))
}
fn type_name<'a>(input: &mut &'a [u8]) -> ModalResult<&'a str, InputError<&'a [u8]>> {
let start = *input;
if input.is_empty() || !input[0].is_ascii_uppercase() {
return Err(ErrMode::Backtrack(ParserError::from_input(input)));
}
let mut end = 1;
while end < input.len() && input[end].is_ascii_alphanumeric() {
end += 1;
}
let name_bytes = &start[0..end];
*input = &input[end..];
Ok(bytes_to_str(name_bytes))
}
fn primitive_type<'a>(input: &mut &'a [u8]) -> ModalResult<Type<'a>, InputError<&'a [u8]>> {
alt((
literal("bool").map(|_| Type::Bool),
literal("int").map(|_| Type::Int),
literal("float").map(|_| Type::Float),
literal("string").map(|_| Type::String),
literal("object").map(|_| Type::ForeignObject),
literal("any").map(|_| Type::Any),
))
.parse_next(input)
}
fn field<'a>(input: &mut &'a [u8]) -> ModalResult<Field<'a>, InputError<&'a [u8]>> {
let comments = parse_preceding_comments(input)?;
let name = field_name(input)?;
ws(input)?;
literal(":").parse_next(input)?;
ws(input)?;
let ty = varlink_type(input)?;
Ok(Field::new_owned(name, ty, comments))
}
fn struct_type<'a>(input: &mut &'a [u8]) -> ModalResult<Type<'a>, InputError<&'a [u8]>> {
literal("(").parse_next(input)?;
ws(input)?;
let fields: Vec<Field<'a>> = separated(0.., field, (ws, literal(","), ws)).parse_next(input)?;
ws(input)?;
literal(")").parse_next(input)?;
Ok(Type::Object(List::from(fields)))
}
fn enum_type<'a>(input: &mut &'a [u8]) -> ModalResult<Type<'a>, InputError<&'a [u8]>> {
literal("(").parse_next(input)?;
ws(input)?;
let variant_names: Vec<&str> =
separated(0.., field_name, (ws, literal(","), ws)).parse_next(input)?;
ws(input)?;
literal(")").parse_next(input)?;
let variants: Vec<EnumVariant<'a>> = variant_names
.into_iter()
.map(|name| EnumVariant::new(name, &[]))
.collect();
Ok(Type::Enum(List::from(variants)))
}
fn inline_type<'a>(input: &mut &'a [u8]) -> ModalResult<Type<'a>, InputError<&'a [u8]>> {
if let Some(pos) = input.iter().position(|&b| b == b')') {
let content = &input[1..pos]; if content.contains(&b':') {
struct_type(input)
} else {
enum_type(input)
}
} else {
Err(ErrMode::Backtrack(ParserError::from_input(input)))
}
}
fn element_type<'a>(input: &mut &'a [u8]) -> ModalResult<Type<'a>, InputError<&'a [u8]>> {
alt((primitive_type, type_name.map(Type::Custom), inline_type)).parse_next(input)
}
fn optional_type<'a>(input: &mut &'a [u8]) -> ModalResult<Type<'a>, InputError<&'a [u8]>> {
literal("?").parse_next(input)?;
let inner = non_optional_type(input)?;
Ok(Type::Optional(TypeRef::new_owned(inner)))
}
fn non_optional_type<'a>(input: &mut &'a [u8]) -> ModalResult<Type<'a>, InputError<&'a [u8]>> {
alt((array_type, map_type, element_type)).parse_next(input)
}
fn array_type<'a>(input: &mut &'a [u8]) -> ModalResult<Type<'a>, InputError<&'a [u8]>> {
literal("[]").parse_next(input)?;
let inner = varlink_type(input)?;
Ok(Type::Array(TypeRef::new_owned(inner)))
}
fn map_type<'a>(input: &mut &'a [u8]) -> ModalResult<Type<'a>, InputError<&'a [u8]>> {
literal("[string]").parse_next(input)?;
let inner = varlink_type(input)?;
Ok(Type::Map(TypeRef::new_owned(inner)))
}
fn varlink_type<'a>(input: &mut &'a [u8]) -> ModalResult<Type<'a>, InputError<&'a [u8]>> {
alt((optional_type, array_type, map_type, element_type)).parse_next(input)
}
fn interface_name<'a>(input: &mut &'a [u8]) -> ModalResult<&'a str, InputError<&'a [u8]>> {
let start = *input;
let mut pos = 0;
if pos >= input.len() || !input[pos].is_ascii_alphabetic() {
return Err(ErrMode::Backtrack(ParserError::from_input(input)));
}
pos += 1;
while pos < input.len() && (input[pos].is_ascii_alphanumeric() || input[pos] == b'-') {
pos += 1;
}
let mut found_dot = false;
while pos < input.len() && input[pos] == b'.' {
found_dot = true;
pos += 1;
if pos >= input.len() || !input[pos].is_ascii_alphanumeric() {
break;
}
pos += 1;
while pos < input.len() && (input[pos].is_ascii_alphanumeric() || input[pos] == b'-') {
pos += 1;
}
}
if !found_dot {
return Err(ErrMode::Backtrack(ParserError::from_input(input)));
}
let name_bytes = &start[0..pos];
*input = &input[pos..];
Ok(bytes_to_str(name_bytes))
}
fn parameter_list<'a>(
input: &mut &'a [u8],
) -> ModalResult<Vec<Parameter<'a>>, InputError<&'a [u8]>> {
literal("(").parse_next(input)?;
whitespace_only(input)?;
let mut params = Vec::new();
if literal::<_, _, InputError<&'a [u8]>>(")")
.parse_next(input)
.is_ok()
{
return Ok(params);
}
loop {
let comments = parse_preceding_comments(input)?;
let name = field_name(input)?;
ws(input)?;
literal(":").parse_next(input)?;
ws(input)?;
let ty = varlink_type(input)?;
params.push(Parameter::new_owned(name, ty, comments));
whitespace_only(input)?;
if literal::<_, _, InputError<&'a [u8]>>(",")
.parse_next(input)
.is_ok()
{
whitespace_only(input)?;
} else if literal::<_, _, InputError<&'a [u8]>>(")")
.parse_next(input)
.is_ok()
{
break;
} else {
return Err(ErrMode::Backtrack(ParserError::from_input(input)));
}
}
Ok(params)
}
fn method_def<'a>(input: &mut &'a [u8]) -> ModalResult<Method<'a>, InputError<&'a [u8]>> {
let comments = parse_preceding_comments(input)?;
literal("method").parse_next(input)?;
take_while(1.., |c: u8| c.is_ascii_whitespace()).parse_next(input)?;
let name = type_name(input)?;
ws(input)?;
let input_params = parameter_list(input)?;
ws(input)?;
literal("->").parse_next(input)?;
ws(input)?;
let output_params = parameter_list(input)?;
Ok(Method::new_owned(
name,
input_params,
output_params,
comments,
))
}
fn error_def<'a>(input: &mut &'a [u8]) -> ModalResult<Error<'a>, InputError<&'a [u8]>> {
let comments = parse_preceding_comments(input)?;
literal("error").parse_next(input)?;
take_while(1.., |c: u8| c.is_ascii_whitespace()).parse_next(input)?;
let name = type_name(input)?;
ws(input)?;
let params = parameter_list(input)?;
Ok(Error::new_owned(name, params, comments))
}
fn type_def<'a>(input: &mut &'a [u8]) -> ModalResult<CustomType<'a>, InputError<&'a [u8]>> {
let comments = parse_preceding_comments(input)?;
literal("type").parse_next(input)?;
take_while(1.., |c: u8| c.is_ascii_whitespace()).parse_next(input)?;
let name = type_name(input)?;
ws(input)?;
literal("(").parse_next(input)?;
whitespace_only(input)?;
let mut fields = Vec::new();
let mut variants: Vec<EnumVariant<'a>> = Vec::new();
let mut has_typed_fields = false;
let mut has_untyped_fields = false;
if literal::<_, _, InputError<&'a [u8]>>(")")
.parse_next(input)
.is_ok()
{
return Ok(CustomType::from(CustomObject::new_owned(
name, fields, comments,
)));
}
loop {
let field_comments = parse_preceding_comments(input)?;
let field_name = field_name(input)?;
whitespace_only(input)?;
if literal::<_, _, InputError<&'a [u8]>>(":")
.parse_next(input)
.is_ok()
{
whitespace_only(input)?;
let ty = varlink_type(input)?;
fields.push(Field::new_owned(field_name, ty, field_comments));
has_typed_fields = true;
} else {
variants.push(EnumVariant::new_owned(field_name, field_comments));
has_untyped_fields = true;
}
whitespace_only(input)?;
if literal::<_, _, InputError<&'a [u8]>>(",")
.parse_next(input)
.is_ok()
{
whitespace_only(input)?;
} else if literal::<_, _, InputError<&'a [u8]>>(")")
.parse_next(input)
.is_ok()
{
break;
} else {
return Err(ErrMode::Backtrack(ParserError::from_input(input)));
}
}
if has_typed_fields && has_untyped_fields {
return Err(ErrMode::Backtrack(ParserError::from_input(input)));
}
if has_typed_fields {
Ok(CustomType::from(CustomObject::new_owned(
name, fields, comments,
)))
} else {
Ok(CustomType::from(CustomEnum::new_owned(
name, variants, comments,
)))
}
}
fn parse_preceding_comments<'a>(
input: &mut &'a [u8],
) -> ModalResult<Vec<Comment<'a>>, InputError<&'a [u8]>> {
let mut comments = Vec::new();
while !input.is_empty() {
let checkpoint = *input;
whitespace_only(input)?;
if input.is_empty() {
break;
}
if let Ok(comment) = comment_def(input) {
comments.push(comment);
whitespace_only(input)?;
} else {
*input = checkpoint;
break;
}
}
Ok(comments)
}
fn comment_def<'a>(input: &mut &'a [u8]) -> ModalResult<Comment<'a>, InputError<&'a [u8]>> {
literal("#").parse_next(input)?;
while !input.is_empty() && (input[0] == b' ' || input[0] == b'\t') {
*input = &input[1..];
}
let line_content = take_while(0.., |c: u8| c != b'\n').parse_next(input)?;
let comment_text = bytes_to_str(line_content);
Ok(Comment::new(comment_text))
}
fn interface_def<'a>(input: &mut &'a [u8]) -> ModalResult<Interface<'a>, InputError<&'a [u8]>> {
let comments = parse_preceding_comments(input)?;
literal("interface").parse_next(input)?;
take_while(1.., |c: u8| c.is_ascii_whitespace()).parse_next(input)?;
let name = interface_name(input)?;
whitespace_only(input)?;
let mut methods = Vec::new();
let mut custom_types = Vec::new();
let mut errors = Vec::new();
while !input.is_empty() {
whitespace_only(input)?;
if input.is_empty() {
break;
}
enum ParsedMember<'a> {
Custom(CustomType<'a>),
Method(Method<'a>),
Error(Error<'a>),
}
let result = alt((
type_def.map(ParsedMember::Custom),
method_def.map(ParsedMember::Method),
error_def.map(ParsedMember::Error),
))
.parse_next(input);
match result {
Ok(ParsedMember::Custom(custom_type)) => custom_types.push(custom_type),
Ok(ParsedMember::Method(method)) => methods.push(method),
Ok(ParsedMember::Error(error)) => errors.push(error),
Err(_) => break,
}
}
Ok(Interface::new_owned(
name,
methods,
custom_types,
errors,
comments,
))
}
pub(super) fn parse_interface(input: &str) -> Result<Interface<'_>, crate::Error> {
parse_from_str(input, interface_def)
}
fn parse_from_str<'a, T>(
input: &'a str,
parser: impl Fn(&mut &'a [u8]) -> ModalResult<T, InputError<&'a [u8]>>,
) -> Result<T, crate::Error> {
use alloc::string::ToString;
let input_bytes = input.trim().as_bytes();
if input_bytes.is_empty() {
return Err(crate::Error::IdlParse("Input is empty".to_string()));
}
let mut input_mut = input_bytes;
match parser(&mut input_mut) {
Ok(result) => {
let _ = ws(&mut input_mut);
if input_mut.is_empty() {
Ok(result)
} else {
Err(crate::Error::IdlParse(format!(
"Unexpected remaining input: {:?}",
core::str::from_utf8(input_mut).map_or("<invalid UTF-8>", |s| s)
)))
}
}
Err(err) => Err(crate::Error::IdlParse(format!("Parse error: {err}"))),
}
}
#[cfg(test)]
mod tests;