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.
This commit is contained in:
Ivan Enderlin 2020-03-26 13:17:36 +01:00
parent b528e965c5
commit 41f9c231c0
6 changed files with 384 additions and 141 deletions

View File

@ -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<InterfaceType>,
}
/// 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<InterfaceType>,
},
Record(RecordType),
}
/// Represents an imported function.

View File

@ -7,31 +7,6 @@ use nom::{
};
use std::{convert::TryFrom, str};
/// Parse an `InterfaceType`.
impl TryFrom<u8> for InterfaceType {
type Error = &'static str;
fn try_from(code: u8) -> Result<Self, Self::Error> {
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<u8> 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::<InterfaceType, ()>(input, ty), output);
assert_eq!(list::<_, ()>(input, string), output);
}
#[test]
@ -734,7 +784,7 @@ mod tests {
],
));
assert_eq!(list::<Instruction, ()>(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],
},
}),
],
));

View File

@ -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::<keyword::i64>()?;
Ok(InterfaceType::I64)
} else if lookahead.peek::<keyword::record>() {
Ok(InterfaceType::Record(parser.parse()?))
} else {
Err(lookahead.error())
}
}
}
impl Parse<'_> for RecordType {
fn parse(parser: Parser<'_>) -> Result<Self> {
parser.parse::<keyword::record>()?;
let mut fields = vec![];
while !parser.is_empty() {
fields.push(parser.parens(|parser| {
parser.parse::<keyword::field>()?;
parser.parse()
})?);
}
Ok(RecordType { fields })
}
}
impl<'a> Parse<'a> for Instruction {
#[allow(clippy::cognitive_complexity)]
fn parse(parser: Parser<'a>) -> Result<Self> {
@ -425,15 +446,7 @@ impl<'a> Parse<'a> for Type {
outputs: output_types,
})
} else if lookahead.peek::<keyword::record>() {
parser.parse::<keyword::record>()?;
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::<RecordType>(&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::<Interface>(&input).unwrap(), output);
}

View File

@ -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<W> ToBytes<W> 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<W> ToBytes<W> 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

View File

@ -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);

View File

@ -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",