mirror of
https://github.com/fluencelabs/wasmer
synced 2025-03-31 23:11:04 +00:00
feat(interface-types) Continue.
This commit is contained in:
parent
7ca546e5c5
commit
45ba77c5e3
@ -30,7 +30,7 @@ macro_rules! consume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
enum InterfaceType {
|
pub enum InterfaceType {
|
||||||
Int,
|
Int,
|
||||||
Float,
|
Float,
|
||||||
Any,
|
Any,
|
||||||
@ -65,7 +65,7 @@ impl TryFrom<u64> for InterfaceType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
enum AdapterKind {
|
pub enum AdapterKind {
|
||||||
Import,
|
Import,
|
||||||
Export,
|
Export,
|
||||||
HelperFunction,
|
HelperFunction,
|
||||||
@ -76,36 +76,91 @@ impl TryFrom<u8> for AdapterKind {
|
|||||||
|
|
||||||
fn try_from(code: u8) -> Result<Self, Self::Error> {
|
fn try_from(code: u8) -> Result<Self, Self::Error> {
|
||||||
Ok(match code {
|
Ok(match code {
|
||||||
0 => Self::Import,
|
0x0 => Self::Import,
|
||||||
1 => Self::Export,
|
0x1 => Self::Export,
|
||||||
2 => Self::HelperFunction,
|
0x2 => Self::HelperFunction,
|
||||||
_ => return Err("Unknown adapter kind code."),
|
_ => return Err("Unknown adapter kind code."),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
struct Export<'input> {
|
pub enum Instruction<'input> {
|
||||||
|
ArgumentGet(u64),
|
||||||
|
Call(u64),
|
||||||
|
CallExport(&'input str),
|
||||||
|
ReadUtf8,
|
||||||
|
WriteUtf8(&'input str),
|
||||||
|
AsWasm(InterfaceType),
|
||||||
|
AsInterface(InterfaceType),
|
||||||
|
TableRefAdd,
|
||||||
|
TableRefGet,
|
||||||
|
CallMethod(u64),
|
||||||
|
MakeRecord(InterfaceType),
|
||||||
|
GetField(u64, u64),
|
||||||
|
Const(InterfaceType, u64),
|
||||||
|
FoldSeq(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub struct Export<'input> {
|
||||||
name: &'input str,
|
name: &'input str,
|
||||||
input_types: Vec<InterfaceType>,
|
input_types: Vec<InterfaceType>,
|
||||||
output_types: Vec<InterfaceType>,
|
output_types: Vec<InterfaceType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
struct Type<'input> {
|
pub struct Type<'input> {
|
||||||
name: &'input str,
|
name: &'input str,
|
||||||
fields: Vec<&'input str>,
|
fields: Vec<&'input str>,
|
||||||
types: Vec<InterfaceType>,
|
types: Vec<InterfaceType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
struct ImportedFunction<'input> {
|
pub struct ImportedFunction<'input> {
|
||||||
namespace: &'input str,
|
namespace: &'input str,
|
||||||
name: &'input str,
|
name: &'input str,
|
||||||
input_types: Vec<InterfaceType>,
|
input_types: Vec<InterfaceType>,
|
||||||
output_types: Vec<InterfaceType>,
|
output_types: Vec<InterfaceType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub enum Adapter<'input> {
|
||||||
|
Import {
|
||||||
|
namespace: &'input str,
|
||||||
|
name: &'input str,
|
||||||
|
input_types: Vec<InterfaceType>,
|
||||||
|
output_types: Vec<InterfaceType>,
|
||||||
|
instructions: Vec<Instruction<'input>>,
|
||||||
|
},
|
||||||
|
Export {
|
||||||
|
name: &'input str,
|
||||||
|
input_types: Vec<InterfaceType>,
|
||||||
|
output_types: Vec<InterfaceType>,
|
||||||
|
instructions: Vec<Instruction<'input>>,
|
||||||
|
},
|
||||||
|
HelperFunction {
|
||||||
|
name: &'input str,
|
||||||
|
input_types: Vec<InterfaceType>,
|
||||||
|
output_types: Vec<InterfaceType>,
|
||||||
|
instructions: Vec<Instruction<'input>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub struct Forward<'input> {
|
||||||
|
name: &'input str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub struct Interfaces<'input> {
|
||||||
|
exports: Vec<Export<'input>>,
|
||||||
|
types: Vec<Type<'input>>,
|
||||||
|
imported_functions: Vec<ImportedFunction<'input>>,
|
||||||
|
adapters: Vec<Adapter<'input>>,
|
||||||
|
forwards: Vec<Forward<'input>>,
|
||||||
|
}
|
||||||
|
|
||||||
fn byte<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u8, E> {
|
fn byte<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u8, E> {
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
|
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
|
||||||
@ -184,116 +239,264 @@ fn ty<'input, E: ParseError<&'input [u8]>>(
|
|||||||
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
|
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (input, ty) = leb(input)?;
|
let (output, ty) = leb(input)?;
|
||||||
|
|
||||||
match InterfaceType::try_from(ty) {
|
match InterfaceType::try_from(ty) {
|
||||||
Ok(ty) => Ok((input, ty)),
|
Ok(ty) => Ok((output, ty)),
|
||||||
Err(_) => Err(Err::Error(make_error(input, ErrorKind::ParseTo))),
|
Err(_) => Err(Err::Error(make_error(input, ErrorKind::ParseTo))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse<'input, E: ParseError<&'input [u8]>>(
|
fn instructions<'input, E: ParseError<&'input [u8]>>(
|
||||||
bytes: &'input [u8],
|
input: &'input [u8],
|
||||||
) -> IResult<&'input [u8], bool, E> {
|
) -> IResult<&'input [u8], Instruction, E> {
|
||||||
let mut input = bytes;
|
let (mut input, opcode) = byte(input)?;
|
||||||
|
|
||||||
|
Ok(match opcode {
|
||||||
|
0x00 => {
|
||||||
|
consume!((input, argument_0) = leb(input)?);
|
||||||
|
(input, Instruction::ArgumentGet(argument_0))
|
||||||
|
}
|
||||||
|
|
||||||
|
0x01 => {
|
||||||
|
consume!((input, argument_0) = leb(input)?);
|
||||||
|
(input, Instruction::Call(argument_0))
|
||||||
|
}
|
||||||
|
|
||||||
|
0x02 => {
|
||||||
|
consume!((input, argument_0) = string(input)?);
|
||||||
|
(input, Instruction::CallExport(argument_0))
|
||||||
|
}
|
||||||
|
|
||||||
|
0x03 => (input, Instruction::ReadUtf8),
|
||||||
|
|
||||||
|
0x04 => {
|
||||||
|
consume!((input, argument_0) = string(input)?);
|
||||||
|
(input, Instruction::WriteUtf8(argument_0))
|
||||||
|
}
|
||||||
|
|
||||||
|
0x05 => {
|
||||||
|
consume!((input, argument_0) = ty(input)?);
|
||||||
|
(input, Instruction::AsWasm(argument_0))
|
||||||
|
}
|
||||||
|
|
||||||
|
0x06 => {
|
||||||
|
consume!((input, argument_0) = ty(input)?);
|
||||||
|
(input, Instruction::AsInterface(argument_0))
|
||||||
|
}
|
||||||
|
|
||||||
|
0x07 => (input, Instruction::TableRefAdd),
|
||||||
|
|
||||||
|
0x08 => (input, Instruction::TableRefGet),
|
||||||
|
|
||||||
|
0x09 => {
|
||||||
|
consume!((input, argument_0) = leb(input)?);
|
||||||
|
(input, Instruction::CallMethod(argument_0))
|
||||||
|
}
|
||||||
|
|
||||||
|
0x0a => {
|
||||||
|
consume!((input, argument_0) = ty(input)?);
|
||||||
|
(input, Instruction::MakeRecord(argument_0))
|
||||||
|
}
|
||||||
|
|
||||||
|
0x0c => {
|
||||||
|
consume!((input, argument_0) = leb(input)?);
|
||||||
|
consume!((input, argument_1) = leb(input)?);
|
||||||
|
(input, Instruction::GetField(argument_0, argument_1))
|
||||||
|
}
|
||||||
|
|
||||||
|
0x0d => {
|
||||||
|
consume!((input, argument_0) = ty(input)?);
|
||||||
|
consume!((input, argument_1) = leb(input)?);
|
||||||
|
(input, Instruction::Const(argument_0, argument_1))
|
||||||
|
}
|
||||||
|
|
||||||
|
0x0e => {
|
||||||
|
consume!((input, argument_0) = leb(input)?);
|
||||||
|
(input, Instruction::FoldSeq(argument_0))
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => return Err(Err::Error(make_error(input, ErrorKind::ParseTo))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exports<'input, E: ParseError<&'input [u8]>>(
|
||||||
|
input: &'input [u8],
|
||||||
|
) -> IResult<&'input [u8], Vec<Export>, E> {
|
||||||
|
let mut input = input;
|
||||||
let mut exports = vec![];
|
let mut exports = vec![];
|
||||||
|
|
||||||
|
consume!((input, number_of_exports) = leb(input)?);
|
||||||
|
|
||||||
|
for _ in 0..number_of_exports {
|
||||||
|
consume!((input, export_name) = string(input)?);
|
||||||
|
consume!((input, export_input_types) = list(input, ty)?);
|
||||||
|
consume!((input, export_output_types) = list(input, ty)?);
|
||||||
|
|
||||||
|
exports.push(Export {
|
||||||
|
name: export_name,
|
||||||
|
input_types: export_input_types,
|
||||||
|
output_types: export_output_types,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((input, exports))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn types<'input, E: ParseError<&'input [u8]>>(
|
||||||
|
input: &'input [u8],
|
||||||
|
) -> IResult<&'input [u8], Vec<Type>, E> {
|
||||||
|
let mut input = input;
|
||||||
let mut types = vec![];
|
let mut types = vec![];
|
||||||
|
|
||||||
|
consume!((input, number_of_types) = leb(input)?);
|
||||||
|
|
||||||
|
for _ in 0..number_of_types {
|
||||||
|
consume!((input, type_name) = string(input)?);
|
||||||
|
consume!((input, type_fields) = list(input, string)?);
|
||||||
|
consume!((input, type_types) = list(input, ty)?);
|
||||||
|
|
||||||
|
types.push(Type {
|
||||||
|
name: type_name,
|
||||||
|
fields: type_fields,
|
||||||
|
types: type_types,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((input, types))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn imported_functions<'input, E: ParseError<&'input [u8]>>(
|
||||||
|
input: &'input [u8],
|
||||||
|
) -> IResult<&'input [u8], Vec<ImportedFunction>, E> {
|
||||||
|
let mut input = input;
|
||||||
let mut imported_functions = vec![];
|
let mut imported_functions = vec![];
|
||||||
|
|
||||||
{
|
consume!((input, number_of_imported_functions) = leb(input)?);
|
||||||
consume!((input, number_of_exports) = leb(input)?);
|
|
||||||
d!(number_of_exports);
|
|
||||||
|
|
||||||
for _ in 0..number_of_exports {
|
for _ in 0..number_of_imported_functions {
|
||||||
consume!((input, export_name) = string(input)?);
|
consume!((input, imported_function_namespace) = string(input)?);
|
||||||
consume!((input, export_input_types) = list(input, ty)?);
|
consume!((input, imported_function_name) = string(input)?);
|
||||||
consume!((input, export_output_types) = list(input, ty)?);
|
consume!((input, imported_function_input_types) = list(input, ty)?);
|
||||||
|
consume!((input, imported_function_output_types) = list(input, ty)?);
|
||||||
|
|
||||||
exports.push(Export {
|
imported_functions.push(ImportedFunction {
|
||||||
name: export_name,
|
namespace: imported_function_namespace,
|
||||||
input_types: export_input_types,
|
name: imported_function_name,
|
||||||
output_types: export_output_types,
|
input_types: imported_function_input_types,
|
||||||
});
|
output_types: imported_function_output_types,
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
Ok((input, imported_functions))
|
||||||
consume!((input, number_of_types) = leb(input)?);
|
}
|
||||||
d!(number_of_types);
|
|
||||||
|
|
||||||
for _ in 0..number_of_types {
|
pub fn adapters<'input, E: ParseError<&'input [u8]>>(
|
||||||
consume!((input, type_name) = string(input)?);
|
input: &'input [u8],
|
||||||
consume!((input, type_fields) = list(input, string)?);
|
) -> IResult<&'input [u8], Vec<Adapter>, E> {
|
||||||
consume!((input, type_types) = list(input, ty)?);
|
let mut input = input;
|
||||||
|
let mut adapters = vec![];
|
||||||
|
|
||||||
types.push(Type {
|
consume!((input, number_of_adapters) = leb(input)?);
|
||||||
name: type_name,
|
|
||||||
fields: type_fields,
|
|
||||||
types: type_types,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
for _ in 0..number_of_adapters {
|
||||||
consume!((input, number_of_imported_functions) = leb(input)?);
|
consume!((input, adapter_kind) = byte(input)?);
|
||||||
d!(number_of_imported_functions);
|
let adapter_kind = AdapterKind::try_from(adapter_kind)
|
||||||
|
.map_err(|_| Err::Error(make_error(input, ErrorKind::ParseTo)))?;
|
||||||
|
|
||||||
for _ in 0..number_of_imported_functions {
|
match adapter_kind {
|
||||||
consume!((input, imported_function_namespace) = string(input)?);
|
AdapterKind::Import => {
|
||||||
consume!((input, imported_function_name) = string(input)?);
|
consume!((input, adapter_namespace) = string(input)?);
|
||||||
consume!((input, imported_function_input_types) = list(input, ty)?);
|
consume!((input, adapter_name) = string(input)?);
|
||||||
consume!((input, imported_function_output_types) = list(input, ty)?);
|
consume!((input, adapter_input_types) = list(input, ty)?);
|
||||||
|
consume!((input, adapter_output_types) = list(input, ty)?);
|
||||||
|
consume!((input, adapter_instructions) = list(input, instructions)?);
|
||||||
|
|
||||||
imported_functions.push(ImportedFunction {
|
adapters.push(Adapter::Import {
|
||||||
namespace: imported_function_namespace,
|
namespace: adapter_namespace,
|
||||||
name: imported_function_name,
|
name: adapter_name,
|
||||||
input_types: imported_function_input_types,
|
input_types: adapter_input_types,
|
||||||
output_types: imported_function_output_types,
|
output_types: adapter_output_types,
|
||||||
});
|
instructions: adapter_instructions,
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
AdapterKind::Export => {
|
||||||
consume!((input, number_of_adapters) = leb(input)?);
|
consume!((input, adapter_name) = string(input)?);
|
||||||
d!(number_of_adapters);
|
consume!((input, adapter_input_types) = list(input, ty)?);
|
||||||
|
consume!((input, adapter_output_types) = list(input, ty)?);
|
||||||
|
consume!((input, adapter_instructions) = list(input, instructions)?);
|
||||||
|
|
||||||
for _ in 0..number_of_adapters {
|
adapters.push(Adapter::Export {
|
||||||
consume!((input, adapter_kind) = byte(input)?);
|
name: adapter_name,
|
||||||
let adapter_kind = AdapterKind::try_from(adapter_kind)
|
input_types: adapter_input_types,
|
||||||
.map_err(|_| Err::Error(make_error(input, ErrorKind::ParseTo)))?;
|
output_types: adapter_output_types,
|
||||||
d!(&adapter_kind);
|
instructions: adapter_instructions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
match adapter_kind {
|
AdapterKind::HelperFunction => {
|
||||||
AdapterKind::Import => {
|
consume!((input, adapter_name) = string(input)?);
|
||||||
consume!((input, adapter_namespace) = string(input)?);
|
consume!((input, adapter_input_types) = list(input, ty)?);
|
||||||
d!(adapter_namespace);
|
consume!((input, adapter_output_types) = list(input, ty)?);
|
||||||
|
consume!((input, adapter_instructions) = list(input, instructions)?);
|
||||||
|
|
||||||
consume!((input, adapter_name) = string(input)?);
|
adapters.push(Adapter::HelperFunction {
|
||||||
d!(adapter_name);
|
name: adapter_name,
|
||||||
|
input_types: adapter_input_types,
|
||||||
consume!((input, adapter_input_types) = list(input, ty)?);
|
output_types: adapter_output_types,
|
||||||
d!(adapter_input_types);
|
instructions: adapter_instructions,
|
||||||
|
});
|
||||||
consume!((input, adapter_output_types) = list(input, ty)?);
|
|
||||||
d!(adapter_output_types);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => println!("kind = {:?}", adapter_kind),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d!(exports);
|
Ok((input, adapters))
|
||||||
d!(types);
|
}
|
||||||
d!(imported_functions);
|
|
||||||
|
|
||||||
Ok((&[] as &[u8], true))
|
pub fn forwards<'input, E: ParseError<&'input [u8]>>(
|
||||||
|
input: &'input [u8],
|
||||||
|
) -> IResult<&'input [u8], Vec<Forward>, E> {
|
||||||
|
let mut input = input;
|
||||||
|
let mut forwards = vec![];
|
||||||
|
|
||||||
|
consume!((input, number_of_forwards) = leb(input)?);
|
||||||
|
|
||||||
|
for _ in 0..number_of_forwards {
|
||||||
|
consume!((input, forward_name) = string(input)?);
|
||||||
|
|
||||||
|
forwards.push(Forward { name: forward_name });
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((input, forwards))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse<'input, E: ParseError<&'input [u8]>>(
|
||||||
|
bytes: &'input [u8],
|
||||||
|
) -> IResult<&'input [u8], Interfaces, E> {
|
||||||
|
let mut input = bytes;
|
||||||
|
|
||||||
|
consume!((input, exports) = exports(input)?);
|
||||||
|
consume!((input, types) = types(input)?);
|
||||||
|
consume!((input, imported_functions) = imported_functions(input)?);
|
||||||
|
consume!((input, adapters) = adapters(input)?);
|
||||||
|
consume!((input, forwards) = forwards(input)?);
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
input,
|
||||||
|
Interfaces {
|
||||||
|
exports,
|
||||||
|
types,
|
||||||
|
imported_functions,
|
||||||
|
adapters,
|
||||||
|
forwards,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::parse;
|
use super::*;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use wasmer_clif_backend::CraneliftCompiler;
|
use wasmer_clif_backend::CraneliftCompiler;
|
||||||
use wasmer_runtime_core as runtime;
|
use wasmer_runtime_core as runtime;
|
||||||
@ -326,6 +529,71 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.as_slice();
|
.as_slice();
|
||||||
|
|
||||||
parse::<()>(custom_section_bytes);
|
match parse::<()>(custom_section_bytes) {
|
||||||
|
Ok((remainder, interfaces)) => {
|
||||||
|
assert!(remainder.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
interfaces,
|
||||||
|
Interfaces {
|
||||||
|
exports: vec![
|
||||||
|
Export {
|
||||||
|
name: "strlen",
|
||||||
|
input_types: vec![InterfaceType::I32],
|
||||||
|
output_types: vec![InterfaceType::I32]
|
||||||
|
},
|
||||||
|
Export {
|
||||||
|
name: "write_null_byte",
|
||||||
|
input_types: vec![InterfaceType::I32, InterfaceType::I32],
|
||||||
|
output_types: vec![InterfaceType::I32],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
types: vec![],
|
||||||
|
imported_functions: vec![
|
||||||
|
ImportedFunction {
|
||||||
|
namespace: "host",
|
||||||
|
name: "console_log",
|
||||||
|
input_types: vec![InterfaceType::String],
|
||||||
|
output_types: vec![],
|
||||||
|
},
|
||||||
|
ImportedFunction {
|
||||||
|
namespace: "host",
|
||||||
|
name: "document_title",
|
||||||
|
input_types: vec![],
|
||||||
|
output_types: vec![InterfaceType::String],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
adapters: vec![
|
||||||
|
Adapter::Import {
|
||||||
|
namespace: "host",
|
||||||
|
name: "console_log",
|
||||||
|
input_types: vec![InterfaceType::I32],
|
||||||
|
output_types: vec![],
|
||||||
|
instructions: vec![
|
||||||
|
Instruction::ArgumentGet(0),
|
||||||
|
Instruction::ArgumentGet(0),
|
||||||
|
Instruction::CallExport("strlen"),
|
||||||
|
Instruction::ReadUtf8,
|
||||||
|
Instruction::Call(0),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Adapter::Import {
|
||||||
|
namespace: "host",
|
||||||
|
name: "document_title",
|
||||||
|
input_types: vec![],
|
||||||
|
output_types: vec![InterfaceType::I32],
|
||||||
|
instructions: vec![
|
||||||
|
Instruction::Call(1),
|
||||||
|
Instruction::WriteUtf8("alloc"),
|
||||||
|
Instruction::CallExport("write_null_byte"),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
forwards: vec![Forward { name: "main" }]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(_) => assert!(false),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user