From 41f9c231c06b7cb8b769da571ae86c38a1e3a50b Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 26 Mar 2020 13:17:36 +0100 Subject: [PATCH] feat(interface-types) Introduce `RecordType` for `InterfaceType` and `Type`. The `Type::Record` variant now is defined by `RecordType`. In addition, `InterfaceType` has a new variant: `Record`, that is also defined by `RecordType`. Encoders and decoders are updated to consider `RecordType`, which removes code duplication and simplify code. --- src/ast.rs | 15 ++- src/decoders/binary.rs | 230 +++++++++++++++++++++++++---------------- src/decoders/wat.rs | 92 ++++++++++++++--- src/encoders/binary.rs | 69 ++++++++++++- src/encoders/wat.rs | 115 ++++++++++++++++----- tests/binary.rs | 4 +- 6 files changed, 384 insertions(+), 141 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 431a3b4..f0e0308 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -48,6 +48,16 @@ pub enum InterfaceType { /// A 64-bits integer (as defiend in WebAssembly core). I64, + + /// A record. + Record(RecordType), +} + +/// Representing a record type. +#[derive(PartialEq, Debug, Clone)] +pub struct RecordType { + /// Types representing the fields. + pub fields: Vec, } /// Represents the kind of type. @@ -81,10 +91,7 @@ pub enum Type { /// ```wasm,ignore /// (@interface type (record string i32)) /// ``` - Record { - /// Types representing the fields. - fields: Vec, - }, + Record(RecordType), } /// Represents an imported function. diff --git a/src/decoders/binary.rs b/src/decoders/binary.rs index df2db2f..2ae47a9 100644 --- a/src/decoders/binary.rs +++ b/src/decoders/binary.rs @@ -7,31 +7,6 @@ use nom::{ }; use std::{convert::TryFrom, str}; -/// Parse an `InterfaceType`. -impl TryFrom for InterfaceType { - type Error = &'static str; - - fn try_from(code: u8) -> Result { - Ok(match code { - 0 => Self::S8, - 1 => Self::S16, - 2 => Self::S32, - 3 => Self::S64, - 4 => Self::U8, - 5 => Self::U16, - 6 => Self::U32, - 7 => Self::U64, - 8 => Self::F32, - 9 => Self::F64, - 10 => Self::String, - 11 => Self::Anyref, - 12 => Self::I32, - 13 => Self::I64, - _ => return Err("Unknown interface type code."), - }) - } -} - /// Parse a type kind. impl TryFrom for TypeKind { type Error = &'static str; @@ -95,6 +70,51 @@ fn uleb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'i )) } +/// Parse an interface type. +fn ty<'input, E: ParseError<&'input [u8]>>( + mut input: &'input [u8], +) -> IResult<&'input [u8], InterfaceType, E> { + if input.is_empty() { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + consume!((input, opcode) = byte(input)?); + + let ty = match opcode { + 0x00 => InterfaceType::S8, + 0x01 => InterfaceType::S16, + 0x02 => InterfaceType::S32, + 0x03 => InterfaceType::S64, + 0x04 => InterfaceType::U8, + 0x05 => InterfaceType::U16, + 0x06 => InterfaceType::U32, + 0x07 => InterfaceType::U64, + 0x08 => InterfaceType::F32, + 0x09 => InterfaceType::F64, + 0x0a => InterfaceType::String, + 0x0b => InterfaceType::Anyref, + 0x0c => InterfaceType::I32, + 0x0d => InterfaceType::I64, + 0x0e => { + consume!((input, record_type) = record_type(input)?); + + InterfaceType::Record(record_type) + } + _ => return Err(Err::Error(make_error(input, ErrorKind::ParseTo))), + }; + + Ok((input, ty)) +} + +/// Parse an record type. +fn record_type<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], RecordType, E> { + let (output, fields) = list(input, ty)?; + + Ok((output, RecordType { fields })) +} + /// Parse a UTF-8 string. fn string<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], @@ -144,22 +164,6 @@ fn list<'input, I, E: ParseError<&'input [u8]>>( Ok((input, items)) } -/// Parse a type. -fn ty<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], -) -> IResult<&'input [u8], InterfaceType, E> { - if input.is_empty() { - return Err(Err::Error(make_error(input, ErrorKind::Eof))); - } - - let (output, ty) = byte(input)?; - - match InterfaceType::try_from(ty) { - Ok(ty) => Ok((output, ty)), - Err(_) => Err(Err::Error(make_error(input, ErrorKind::ParseTo))), - } -} - /// Parse an instruction with its arguments. fn instruction<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], @@ -261,9 +265,9 @@ fn types<'input, E: ParseError<&'input [u8]>>( } TypeKind::Record => { - consume!((input, fields) = list(input, ty)?); + consume!((input, record_type) = record_type(input)?); - types.push(Type::Record { fields }); + types.push(Type::Record(record_type)); } } } @@ -575,6 +579,95 @@ mod tests { ); } + #[test] + fn test_ty() { + let input = &[ + 0x0f, // list of 15 items + 0x00, // S8 + 0x01, // S16 + 0x02, // S32 + 0x03, // S64 + 0x04, // U8 + 0x05, // U16 + 0x06, // U32 + 0x07, // U64 + 0x08, // F32 + 0x09, // F64 + 0x0a, // String + 0x0b, // Anyref + 0x0c, // I32 + 0x0d, // I64 + 0x0e, 0x01, 0x02, // Record + 0x01, + ]; + let output = Ok(( + &[0x01][..], + vec![ + InterfaceType::S8, + InterfaceType::S16, + InterfaceType::S32, + InterfaceType::S64, + InterfaceType::U8, + InterfaceType::U16, + InterfaceType::U32, + InterfaceType::U64, + InterfaceType::F32, + InterfaceType::F64, + InterfaceType::String, + InterfaceType::Anyref, + InterfaceType::I32, + InterfaceType::I64, + InterfaceType::Record(RecordType { + fields: vec![InterfaceType::S32], + }), + ], + )); + + assert_eq!(list::<_, ()>(input, ty), output); + } + + #[test] + fn test_record_type() { + let input = &[ + 0x03, // list of 3 items + 0x01, // 1 field + 0x0a, // String + 0x02, // 2 fields + 0x0a, // String + 0x0c, // I32 + 0x03, // 3 fields + 0x0a, // String + 0x0e, // Record + 0x02, // 2 fields + 0x0c, // I32 + 0x0c, // I32 + 0x09, // F64 + 0x01, + ]; + let output = Ok(( + &[0x01][..], + vec![ + RecordType { + fields: vec![InterfaceType::String], + }, + RecordType { + fields: vec![InterfaceType::String, InterfaceType::I32], + }, + RecordType { + fields: vec![ + InterfaceType::String, + InterfaceType::Record(RecordType { + fields: vec![InterfaceType::I32, InterfaceType::I32], + }), + InterfaceType::F64, + ], + }, + ], + )); + + assert_eq!(list::<_, ()>(input, record_type), output); + } + #[test] fn test_string() { let input = &[ @@ -602,50 +695,7 @@ mod tests { ]; let output = Ok((&[0x07][..], vec!["a", "bc"])); - assert_eq!(list::<&str, ()>(input, string), output); - } - - #[test] - fn test_ty() { - let input = &[ - 0x0e, // list of 14 items - 0x00, // S8 - 0x01, // S16 - 0x02, // S32 - 0x03, // S64 - 0x04, // U8 - 0x05, // U16 - 0x06, // U32 - 0x07, // U64 - 0x08, // F32 - 0x09, // F64 - 0x0a, // String - 0x0b, // Anyref - 0x0c, // I32 - 0x0d, // I64 - 0x01, - ]; - let output = Ok(( - &[0x01][..], - vec![ - InterfaceType::S8, - InterfaceType::S16, - InterfaceType::S32, - InterfaceType::S64, - InterfaceType::U8, - InterfaceType::U16, - InterfaceType::U32, - InterfaceType::U64, - InterfaceType::F32, - InterfaceType::F64, - InterfaceType::String, - InterfaceType::Anyref, - InterfaceType::I32, - InterfaceType::I64, - ], - )); - - assert_eq!(list::(input, ty), output); + assert_eq!(list::<_, ()>(input, string), output); } #[test] @@ -734,7 +784,7 @@ mod tests { ], )); - assert_eq!(list::(input, instruction), output); + assert_eq!(list::<_, ()>(input, instruction), output); } #[test] @@ -787,9 +837,9 @@ mod tests { inputs: vec![InterfaceType::S32, InterfaceType::S32], outputs: vec![InterfaceType::S32], }, - Type::Record { + Type::Record(RecordType { fields: vec![InterfaceType::S32, InterfaceType::S32], - }, + }), ], )); diff --git a/src/decoders/wat.rs b/src/decoders/wat.rs index 7cbcfd5..fb81630 100644 --- a/src/decoders/wat.rs +++ b/src/decoders/wat.rs @@ -14,6 +14,7 @@ mod keyword { custom_keyword!(implement); custom_keyword!(r#type = "type"); custom_keyword!(record); + custom_keyword!(field); // New types. custom_keyword!(s8); @@ -126,12 +127,32 @@ impl Parse<'_> for InterfaceType { parser.parse::()?; Ok(InterfaceType::I64) + } else if lookahead.peek::() { + Ok(InterfaceType::Record(parser.parse()?)) } else { Err(lookahead.error()) } } } +impl Parse<'_> for RecordType { + fn parse(parser: Parser<'_>) -> Result { + parser.parse::()?; + + let mut fields = vec![]; + + while !parser.is_empty() { + fields.push(parser.parens(|parser| { + parser.parse::()?; + + parser.parse() + })?); + } + + Ok(RecordType { fields }) + } +} + impl<'a> Parse<'a> for Instruction { #[allow(clippy::cognitive_complexity)] fn parse(parser: Parser<'a>) -> Result { @@ -425,15 +446,7 @@ impl<'a> Parse<'a> for Type { outputs: output_types, }) } else if lookahead.peek::() { - parser.parse::()?; - - let mut fields = vec![]; - - while !parser.is_empty() { - fields.push(parser.parse()?); - } - - Ok(Type::Record { fields }) + Ok(Type::Record(parser.parse()?)) } else { Err(lookahead.error()) } @@ -622,8 +635,21 @@ mod tests { #[test] fn test_interface_type() { let inputs = vec![ - "s8", "s16", "s32", "s64", "u8", "u16", "u32", "u64", "f32", "f64", "string", "anyref", - "i32", "i64", + "s8", + "s16", + "s32", + "s64", + "u8", + "u16", + "u32", + "u64", + "f32", + "f64", + "string", + "anyref", + "i32", + "i64", + "record (field string)", ]; let outputs = vec![ InterfaceType::S8, @@ -640,6 +666,9 @@ mod tests { InterfaceType::Anyref, InterfaceType::I32, InterfaceType::I64, + InterfaceType::Record(RecordType { + fields: vec![InterfaceType::String], + }), ]; assert_eq!(inputs.len(), outputs.len()); @@ -652,6 +681,41 @@ mod tests { } } + #[test] + fn test_record_type() { + let inputs = vec![ + "record (field string)", + "record (field string) (field i32)", + "record (field string) (field record (field i32) (field i32)) (field f64)", + ]; + let outputs = vec![ + RecordType { + fields: vec![InterfaceType::String], + }, + RecordType { + fields: vec![InterfaceType::String, InterfaceType::I32], + }, + RecordType { + fields: vec![ + InterfaceType::String, + InterfaceType::Record(RecordType { + fields: vec![InterfaceType::I32, InterfaceType::I32], + }), + InterfaceType::F64, + ], + }, + ]; + + assert_eq!(inputs.len(), outputs.len()); + + for (input, output) in inputs.iter().zip(outputs.iter()) { + assert_eq!( + &parser::parse::(&buffer(input)).unwrap(), + output + ); + } + } + #[test] fn test_instructions() { let inputs = vec![ @@ -790,10 +854,10 @@ mod tests { #[test] fn test_type_record() { - let input = buffer(r#"(@interface type (record string i32))"#); - let output = Interface::Type(Type::Record { + let input = buffer(r#"(@interface type (record (field string) (field i32)))"#); + let output = Interface::Type(Type::Record(RecordType { fields: vec![InterfaceType::String, InterfaceType::I32], - }); + })); assert_eq!(parser::parse::(&input).unwrap(), output); } diff --git a/src/encoders/binary.rs b/src/encoders/binary.rs index e012fa1..2be2dad 100644 --- a/src/encoders/binary.rs +++ b/src/encoders/binary.rs @@ -108,10 +108,24 @@ where InterfaceType::Anyref => 0x0b_u8.to_bytes(writer), InterfaceType::I32 => 0x0c_u8.to_bytes(writer), InterfaceType::I64 => 0x0d_u8.to_bytes(writer), + InterfaceType::Record(record_type) => { + 0x0e_u8.to_bytes(writer)?; + record_type.to_bytes(writer) + } } } } +/// Encode a `RecordType` into bytes. +impl ToBytes for RecordType +where + W: Write, +{ + fn to_bytes(&self, writer: &mut W) -> io::Result<()> { + self.fields.to_bytes(writer) + } +} + /// Encode a `TypeKind` into bytes. impl ToBytes for TypeKind where @@ -156,7 +170,7 @@ where outputs.to_bytes(writer)?; } - Type::Record { fields } => { + Type::Record(RecordType { fields }) => { TypeKind::Record.to_bytes(writer)?; fields.to_bytes(writer)?; } @@ -422,6 +436,55 @@ mod tests { assert_to_bytes!(InterfaceType::Anyref, &[0x0b]); assert_to_bytes!(InterfaceType::I32, &[0x0c]); assert_to_bytes!(InterfaceType::I64, &[0x0d]); + assert_to_bytes!( + InterfaceType::Record(RecordType { + fields: vec![InterfaceType::String] + }), + &[0x0e, 0x01, 0x0a] + ); + } + + #[test] + fn test_record_type() { + assert_to_bytes!( + RecordType { + fields: vec![InterfaceType::String] + }, + &[ + 0x01, // 1 field + 0x0a, // String + ] + ); + assert_to_bytes!( + RecordType { + fields: vec![InterfaceType::String, InterfaceType::I32] + }, + &[ + 0x02, // 2 fields + 0x0a, // String + 0x0c, // I32 + ] + ); + assert_to_bytes!( + RecordType { + fields: vec![ + InterfaceType::String, + InterfaceType::Record(RecordType { + fields: vec![InterfaceType::I32, InterfaceType::I32], + }), + InterfaceType::F64, + ], + }, + &[ + 0x03, // 3 fields + 0x0a, // String + 0x0e, // Record + 0x02, // 2 fields + 0x0c, // I32 + 0x0c, // I32 + 0x09, // F64 + ] + ); } #[test] @@ -471,9 +534,9 @@ mod tests { #[test] fn test_type_record() { assert_to_bytes!( - Type::Record { + Type::Record(RecordType { fields: vec![InterfaceType::I32, InterfaceType::I64], - }, + }), &[ 0x01, // record type 0x02, // list of 2 items diff --git a/src/encoders/wat.rs b/src/encoders/wat.rs index 8cc6679..772bf89 100644 --- a/src/encoders/wat.rs +++ b/src/encoders/wat.rs @@ -61,24 +61,41 @@ use std::string::ToString; impl ToString for &InterfaceType { fn to_string(&self) -> String { match self { - InterfaceType::S8 => "s8".into(), - InterfaceType::S16 => "s16".into(), - InterfaceType::S32 => "s32".into(), - InterfaceType::S64 => "s64".into(), - InterfaceType::U8 => "u8".into(), - InterfaceType::U16 => "u16".into(), - InterfaceType::U32 => "u32".into(), - InterfaceType::U64 => "u64".into(), - InterfaceType::F32 => "f32".into(), - InterfaceType::F64 => "f64".into(), - InterfaceType::String => "string".into(), - InterfaceType::Anyref => "anyref".into(), - InterfaceType::I32 => "i32".into(), - InterfaceType::I64 => "i64".into(), + InterfaceType::S8 => "s8".to_string(), + InterfaceType::S16 => "s16".to_string(), + InterfaceType::S32 => "s32".to_string(), + InterfaceType::S64 => "s64".to_string(), + InterfaceType::U8 => "u8".to_string(), + InterfaceType::U16 => "u16".to_string(), + InterfaceType::U32 => "u32".to_string(), + InterfaceType::U64 => "u64".to_string(), + InterfaceType::F32 => "f32".to_string(), + InterfaceType::F64 => "f64".to_string(), + InterfaceType::String => "string".to_string(), + InterfaceType::Anyref => "anyref".to_string(), + InterfaceType::I32 => "i32".to_string(), + InterfaceType::I64 => "i64".to_string(), + InterfaceType::Record(record_type) => record_type.to_string(), } } } +impl ToString for &RecordType { + fn to_string(&self) -> String { + format!( + "record{fields}", + fields = self + .fields + .iter() + .fold(String::new(), |mut accumulator, interface_type| { + accumulator.push(' '); + accumulator.push_str(&format!("(field {})", &interface_type.to_string())); + accumulator + }), + ) + } +} + /// Encode an `Instruction` into a string. impl ToString for &Instruction { fn to_string(&self) -> String { @@ -174,15 +191,9 @@ impl<'input> ToString for &Type { outputs = output_types_to_result(&outputs), ), - Type::Record { fields } => format!( - r#"(@interface type (record{fields}))"#, - fields = fields - .iter() - .fold(String::new(), |mut accumulator, interface_type| { - accumulator.push(' '); - accumulator.push_str(&interface_type.to_string()); - accumulator - }), + Type::Record(record_type) => format!( + r#"(@interface type ({record_type}))"#, + record_type = record_type.to_string(), ), } } @@ -354,10 +365,58 @@ mod tests { (&InterfaceType::Anyref).to_string(), (&InterfaceType::I32).to_string(), (&InterfaceType::I64).to_string(), + (&InterfaceType::Record(RecordType { + fields: vec![InterfaceType::String], + })) + .to_string(), ]; let outputs = vec![ - "s8", "s16", "s32", "s64", "u8", "u16", "u32", "u64", "f32", "f64", "string", "anyref", - "i32", "i64", + "s8", + "s16", + "s32", + "s64", + "u8", + "u16", + "u32", + "u64", + "f32", + "f64", + "string", + "anyref", + "i32", + "i64", + "record (field string)", + ]; + + assert_eq!(inputs, outputs); + } + + #[test] + fn test_record_type() { + let inputs = vec![ + (&RecordType { + fields: vec![InterfaceType::String], + }) + .to_string(), + (&RecordType { + fields: vec![InterfaceType::String, InterfaceType::I32], + }) + .to_string(), + (&RecordType { + fields: vec![ + InterfaceType::String, + InterfaceType::Record(RecordType { + fields: vec![InterfaceType::I32, InterfaceType::I32], + }), + InterfaceType::F64, + ], + }) + .to_string(), + ]; + let outputs = vec![ + "record (field string)", + "record (field string) (field i32)", + "record (field string) (field record (field i32) (field i32)) (field f64)", ]; assert_eq!(inputs, outputs); @@ -473,9 +532,9 @@ mod tests { outputs: vec![], }) .to_string(), - (&Type::Record { + (&Type::Record(RecordType { fields: vec![InterfaceType::String, InterfaceType::I32], - }) + })) .to_string(), ]; let outputs = vec![ @@ -487,7 +546,7 @@ mod tests { r#"(@interface type (func (result i32)))"#, r#"(@interface type (func))"#, - r#"(@interface type (record string i32))"#, + r#"(@interface type (record (field string) (field i32)))"#, ]; assert_eq!(inputs, outputs); diff --git a/tests/binary.rs b/tests/binary.rs index 96d1c77..2238c97 100644 --- a/tests/binary.rs +++ b/tests/binary.rs @@ -15,9 +15,9 @@ fn test_binary_encoding_decoding_roundtrip() { inputs: vec![InterfaceType::I32, InterfaceType::I32], outputs: vec![InterfaceType::S32], }, - Type::Record { + Type::Record(RecordType { fields: vec![InterfaceType::String, InterfaceType::I32], - }, + }), ], imports: vec![Import { namespace: "a",