use winnow::{
ModalResult, Parser,
ascii::{multispace0, multispace1},
combinator::{alt, delimited, opt, preceded, repeat, separated},
error::{ErrMode, InputError, ParserError},
token::{literal, one_of, take_while},
};
use super::{
Comment, CustomEnum, CustomObject, CustomType, EnumVariant, Error, Field, Interface, List,
Method, Parameter, Type, TypeRef,
};
use alloc::{format, vec::Vec};
fn comment_as_ws<'a>(input: &mut &'a [u8]) -> ModalResult<(), InputError<&'a [u8]>> {
(
literal("#"),
take_while(0.., |c: u8| c != b'\n' && c != b'\r'),
opt(alt((literal("\r\n"), literal("\n"), literal("\r")))),
)
.void()
.parse_next(input)
}
fn ws<'a>(input: &mut &'a [u8]) -> ModalResult<(), InputError<&'a [u8]>> {
repeat(0.., alt((multispace1.void(), comment_as_ws))).parse_next(input)
}
fn whitespace_only<'a>(input: &mut &'a [u8]) -> ModalResult<(), InputError<&'a [u8]>> {
multispace0.void().parse_next(input)
}
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]>> {
(
one_of(|c: u8| c.is_ascii_alphabetic()),
take_while(0.., |c: u8| c.is_ascii_alphanumeric() || c == b'_'),
)
.take()
.map(bytes_to_str)
.parse_next(input)
}
fn type_name<'a>(input: &mut &'a [u8]) -> ModalResult<&'a str, InputError<&'a [u8]>> {
(
one_of(|c: u8| c.is_ascii_uppercase()),
take_while(0.., |c: u8| c.is_ascii_alphanumeric()),
)
.take()
.map(bytes_to_str)
.parse_next(input)
}
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 field_separator<'a>(input: &mut &'a [u8]) -> ModalResult<(), InputError<&'a [u8]>> {
(ws, literal(","), whitespace_only).void().parse_next(input)
}
fn struct_type<'a>(input: &mut &'a [u8]) -> ModalResult<Type<'a>, InputError<&'a [u8]>> {
delimited(
(literal("("), whitespace_only),
separated(0.., field, field_separator),
(ws, literal(")")),
)
.map(|fields: Vec<Field<'a>>| Type::Object(List::from(fields)))
.parse_next(input)
}
fn enum_variant<'a>(input: &mut &'a [u8]) -> ModalResult<EnumVariant<'a>, InputError<&'a [u8]>> {
let comments = parse_preceding_comments(input)?;
let name = field_name(input)?;
Ok(EnumVariant::new_owned(name, comments))
}
fn enum_type<'a>(input: &mut &'a [u8]) -> ModalResult<Type<'a>, InputError<&'a [u8]>> {
delimited(
(literal("("), whitespace_only),
separated(0.., enum_variant, field_separator),
(ws, literal(")")),
)
.map(|variants: Vec<EnumVariant<'a>>| Type::Enum(List::from(variants)))
.parse_next(input)
}
fn inline_type<'a>(input: &mut &'a [u8]) -> ModalResult<Type<'a>, InputError<&'a [u8]>> {
alt((struct_type, enum_type)).parse_next(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]>> {
(
(
one_of(|c: u8| c.is_ascii_alphabetic()),
take_while(0.., |c: u8| c.is_ascii_alphanumeric() || c == b'-'),
),
repeat::<_, _, (), _, _>(
1..,
(
literal("."),
one_of(|c: u8| c.is_ascii_alphanumeric()),
take_while(0.., |c: u8| c.is_ascii_alphanumeric() || c == b'-'),
)
.void(),
),
)
.take()
.map(bytes_to_str)
.parse_next(input)
}
fn parameter_list<'a>(
input: &mut &'a [u8],
) -> ModalResult<Vec<Parameter<'a>>, InputError<&'a [u8]>> {
delimited(
(literal("("), whitespace_only),
separated(0.., field, field_separator),
(ws, literal(")")),
)
.parse_next(input)
}
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))
}
enum FieldOrVariant<'a> {
Field(Field<'a>),
Variant(EnumVariant<'a>),
}
fn field_or_variant<'a>(
input: &mut &'a [u8],
) -> ModalResult<FieldOrVariant<'a>, InputError<&'a [u8]>> {
let comments = parse_preceding_comments(input)?;
let name = field_name(input)?;
let ty = opt(preceded((ws, literal(":"), ws), varlink_type)).parse_next(input)?;
Ok(match ty {
Some(ty) => FieldOrVariant::Field(Field::new_owned(name, ty, comments)),
None => FieldOrVariant::Variant(EnumVariant::new_owned(name, 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)?;
let items: Vec<FieldOrVariant<'a>> = delimited(
(literal("("), whitespace_only),
separated(0.., field_or_variant, field_separator),
(ws, literal(")")),
)
.parse_next(input)?;
let mut fields = Vec::new();
let mut variants = Vec::new();
for item in items {
match item {
FieldOrVariant::Field(f) => fields.push(f),
FieldOrVariant::Variant(v) => variants.push(v),
}
}
if !fields.is_empty() && !variants.is_empty() {
return Err(ErrMode::Backtrack(ParserError::from_input(input)));
}
if !variants.is_empty() {
Ok(CustomType::from(CustomEnum::new_owned(
name, variants, comments,
)))
} else {
Ok(CustomType::from(CustomObject::new_owned(
name, fields, comments,
)))
}
}
fn parse_preceding_comments<'a>(
input: &mut &'a [u8],
) -> ModalResult<Vec<Comment<'a>>, InputError<&'a [u8]>> {
repeat(
0..,
delimited(whitespace_only, comment_def, whitespace_only),
)
.parse_next(input)
}
fn comment_def<'a>(input: &mut &'a [u8]) -> ModalResult<Comment<'a>, InputError<&'a [u8]>> {
preceded(
(
literal("#"),
take_while(0.., |c: u8| c == b' ' || c == b'\t'),
),
take_while(0.., |c: u8| c != b'\n'),
)
.map(|content| Comment::new(bytes_to_str(content)))
.parse_next(input)
}
enum InterfaceMember<'a> {
Custom(CustomType<'a>),
Method(Method<'a>),
Error(Error<'a>),
}
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)?;
let members: Vec<InterfaceMember<'a>> = repeat(
0..,
preceded(
whitespace_only,
alt((
type_def.map(InterfaceMember::Custom),
method_def.map(InterfaceMember::Method),
error_def.map(InterfaceMember::Error),
)),
),
)
.parse_next(input)?;
let mut methods = Vec::new();
let mut custom_types = Vec::new();
let mut errors = Vec::new();
for m in members {
match m {
InterfaceMember::Custom(c) => custom_types.push(c),
InterfaceMember::Method(m) => methods.push(m),
InterfaceMember::Error(e) => errors.push(e),
}
}
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;