diff --git a/Cargo.toml b/Cargo.toml index 4dc0153..7ab8ef1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ edition = "2018" [dependencies] nom = "5.1" +wast = "8.0" \ No newline at end of file diff --git a/src/ast.rs b/src/ast.rs index d8b82f5..d0c8cab 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -5,7 +5,7 @@ use crate::interpreter::Instruction; use std::str; /// Represents the types supported by WIT. -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Debug)] pub enum InterfaceType { /// An integer. Int, @@ -190,7 +190,7 @@ pub struct Forward<'input> { /// Represents a set of interfaces, i.e. it entirely describes a WIT /// definition. -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Default, Debug)] pub struct Interfaces<'input> { /// All the exported functions. pub exports: Vec>, diff --git a/src/decoders/binary.rs b/src/decoders/binary.rs index e77238e..6beafc6 100644 --- a/src/decoders/binary.rs +++ b/src/decoders/binary.rs @@ -1,4 +1,4 @@ -//! Parse the WIT binary representation into an AST. +//! Parse the WIT binary representation into an [AST](crate::ast). use crate::{ast::*, interpreter::Instruction}; use nom::{ @@ -432,7 +432,8 @@ fn interfaces<'input, E: ParseError<&'input [u8]>>( } /// Parse a sequence of bytes, expecting it to be a valid WIT binary -/// representation, into an `ast::Interfaces`. +/// representation, into an [`Interfaces`](crate::ast::Interfaces) +/// structure. /// /// # Example /// diff --git a/src/decoders/mod.rs b/src/decoders/mod.rs index 0ac9d88..399bb89 100644 --- a/src/decoders/mod.rs +++ b/src/decoders/mod.rs @@ -1,4 +1,6 @@ //! Reads the AST from a particular data representation; for instance, -//! `decoders::binary` reads the AST from a binary. +//! [`decoders::binary`](binary) reads the [AST](crate::ast) +//! from a binary. pub mod binary; +pub mod wat; diff --git a/src/decoders/wat.rs b/src/decoders/wat.rs new file mode 100644 index 0000000..7ef1d54 --- /dev/null +++ b/src/decoders/wat.rs @@ -0,0 +1,881 @@ +//! Parse the WIT textual representation into an [AST](crate::ast). + +use crate::{ast::*, interpreter::Instruction}; +pub use wast::parser::ParseBuffer as Buffer; +use wast::{ + parser::{self, Cursor, Parse, Parser, Peek, Result}, + Id, LParen, +}; + +mod keyword { + pub use wast::{ + custom_keyword, + kw::{anyref, export, f32, f64, func, i32, i64, import, param, result}, + }; + + // New keywords. + custom_keyword!(adapt); + custom_keyword!(forward); + + // New types. + custom_keyword!(int); + custom_keyword!(float); + custom_keyword!(any); + custom_keyword!(string); + custom_keyword!(seq); + + // Instructions. + custom_keyword!(argument_get = "arg.get"); + custom_keyword!(call); + custom_keyword!(call_export = "call-export"); + custom_keyword!(read_utf8 = "read-utf8"); + custom_keyword!(write_utf8 = "write-utf8"); + custom_keyword!(as_wasm = "as-wasm"); + custom_keyword!(as_interface = "as-interface"); + custom_keyword!(table_ref_add = "table-ref-add"); + custom_keyword!(table_ref_get = "table-ref-get"); + custom_keyword!(call_method = "call-method"); + custom_keyword!(make_record = "make-record"); + custom_keyword!(get_field = "get-field"); + custom_keyword!(r#const = "const"); + custom_keyword!(fold_seq = "fold-seq"); + custom_keyword!(add); + custom_keyword!(mem_to_seq = "mem-to-seq"); + custom_keyword!(load); + custom_keyword!(seq_new = "seq.new"); + custom_keyword!(list_push = "list.push"); + custom_keyword!(repeat_until = "repeat-until"); +} + +/// Issue: Uppercased keyword aren't supported for the moment. +impl Parse<'_> for InterfaceType { + fn parse(parser: Parser<'_>) -> Result { + let mut lookahead = parser.lookahead1(); + + if lookahead.peek::() { + parser.parse::()?; + + Ok(InterfaceType::Int) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(InterfaceType::Float) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(InterfaceType::Any) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(InterfaceType::String) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(InterfaceType::Seq) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(InterfaceType::I32) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(InterfaceType::I64) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(InterfaceType::F32) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(InterfaceType::F64) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(InterfaceType::AnyRef) + } else { + Err(lookahead.error()) + } + } +} + +impl<'a> Parse<'a> for Instruction<'a> { + fn parse(parser: Parser<'a>) -> Result { + let mut lookahead = parser.lookahead1(); + + if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::ArgumentGet { + index: parser.parse()?, + }) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::Call { + function_index: parser.parse::()? as usize, + }) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::CallExport { + export_name: parser.parse()?, + }) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::ReadUtf8) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::WriteUtf8 { + allocator_name: parser.parse()?, + }) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::AsWasm(parser.parse()?)) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::AsInterface(parser.parse()?)) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::TableRefAdd) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::TableRefGet) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::CallMethod(parser.parse()?)) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::MakeRecord(parser.parse()?)) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::GetField(parser.parse()?, parser.parse()?)) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::Const(parser.parse()?, parser.parse()?)) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::FoldSeq(parser.parse()?)) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::Add(parser.parse()?)) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::MemToSeq(parser.parse()?, parser.parse()?)) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::Load(parser.parse()?, parser.parse()?)) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::SeqNew(parser.parse()?)) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::ListPush) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok(Instruction::RepeatUntil(parser.parse()?, parser.parse()?)) + } else { + Err(lookahead.error()) + } + } +} + +struct AtInterface; + +impl Peek for AtInterface { + fn peek(cursor: Cursor<'_>) -> bool { + cursor.reserved().map(|(string, _)| string) == Some("@interface") + } + + fn display() -> &'static str { + "`@interface`" + } +} + +impl Parse<'_> for AtInterface { + fn parse(parser: Parser<'_>) -> Result { + parser.step(|cursor| { + if let Some(("@interface", rest)) = cursor.reserved() { + return Ok((AtInterface, rest)); + } + + Err(cursor.error("expected `@interface`")) + }) + } +} + +#[derive(PartialEq, Debug)] +enum FunctionType { + Input(Vec), + Output(Vec), +} + +impl Parse<'_> for FunctionType { + fn parse(parser: Parser<'_>) -> Result { + parser.parens(|parser| { + let mut lookahead = parser.lookahead1(); + + if lookahead.peek::() { + parser.parse::()?; + + let mut inputs = vec![]; + + while !parser.is_empty() { + inputs.push(parser.parse()?); + } + + Ok(FunctionType::Input(inputs)) + } else if lookahead.peek::() { + parser.parse::()?; + + let mut outputs = vec![]; + + while !parser.is_empty() { + outputs.push(parser.parse()?); + } + + Ok(FunctionType::Output(outputs)) + } else { + Err(lookahead.error()) + } + }) + } +} + +#[derive(PartialEq, Debug)] +enum Interface<'a> { + Export(Export<'a>), + #[allow(dead_code)] + Type(Type<'a>), + Import(Import<'a>), + Adapter(Adapter<'a>), + Forward(Forward<'a>), +} + +impl<'a> Parse<'a> for Interface<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parens(|parser| { + let mut lookahead = parser.lookahead1(); + + if lookahead.peek::() { + parser.parse::()?; + + let mut lookahead = parser.lookahead1(); + + if lookahead.peek::() { + Ok(Interface::Export(parser.parse()?)) + } else if lookahead.peek::() { + Ok(Interface::Import(parser.parse()?)) + } else if lookahead.peek::() { + Ok(Interface::Adapter(parser.parse()?)) + } else if lookahead.peek::() { + Ok(Interface::Forward(parser.parse()?)) + } else { + Err(lookahead.error()) + } + } else { + Err(lookahead.error()) + } + }) + } +} + +impl<'a> Parse<'a> for Export<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + let name = parser.parse()?; + + let mut input_types = vec![]; + let mut output_types = vec![]; + + while !parser.is_empty() { + let function_type = parser.parse::()?; + + match function_type { + FunctionType::Input(mut inputs) => input_types.append(&mut inputs), + FunctionType::Output(mut outputs) => output_types.append(&mut outputs), + } + } + + Ok(Export { + name, + input_types, + output_types, + }) + } +} + +impl<'a> Parse<'a> for Import<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + parser.parse::()?; + + let (namespace, name) = parser.parens(|parser| { + parser.parse::()?; + + Ok((parser.parse()?, parser.parse()?)) + })?; + let mut input_types = vec![]; + let mut output_types = vec![]; + + while !parser.is_empty() { + let function_type = parser.parse::()?; + + match function_type { + FunctionType::Input(mut inputs) => input_types.append(&mut inputs), + FunctionType::Output(mut outputs) => output_types.append(&mut outputs), + } + } + + Ok(Import { + namespace, + name, + input_types, + output_types, + }) + } +} + +impl<'a> Parse<'a> for Adapter<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + let (kind, namespace, name) = parser.parens(|parser| { + let mut lookahead = parser.lookahead1(); + + if lookahead.peek::() { + parser.parse::()?; + + Ok((AdapterKind::Import, parser.parse()?, parser.parse()?)) + } else if lookahead.peek::() { + parser.parse::()?; + + Ok((AdapterKind::Export, "", parser.parse()?)) + } else { + Err(lookahead.error()) + } + })?; + let mut input_types = vec![]; + let mut output_types = vec![]; + let mut instructions = vec![]; + + while !parser.is_empty() { + if parser.peek::() { + let function_type = parser.parse::()?; + + match function_type { + FunctionType::Input(mut inputs) => input_types.append(&mut inputs), + FunctionType::Output(mut outputs) => output_types.append(&mut outputs), + } + } else { + instructions.push(parser.parse()?); + } + } + + Ok(match kind { + AdapterKind::Import => Adapter::Import { + namespace, + name, + input_types, + output_types, + instructions, + }, + + AdapterKind::Export => Adapter::Export { + name, + input_types, + output_types, + instructions, + }, + + _ => unimplemented!("Adapter of kind “helper” is not implemented yet."), + }) + } +} + +impl<'a> Parse<'a> for Forward<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + + let name = parser.parens(|parser| { + parser.parse::()?; + + Ok(parser.parse()?) + })?; + + Ok(Forward { name }) + } +} + +impl<'a> Parse<'a> for Interfaces<'a> { + fn parse(parser: Parser<'a>) -> Result { + let mut interfaces: Interfaces = Default::default(); + + while !parser.is_empty() { + let interface = parser.parse::()?; + + match interface { + Interface::Export(export) => interfaces.exports.push(export), + Interface::Type(ty) => interfaces.types.push(ty), + Interface::Import(import) => interfaces.imports.push(import), + Interface::Adapter(adapter) => interfaces.adapters.push(adapter), + Interface::Forward(forward) => interfaces.forwards.push(forward), + } + } + + Ok(interfaces) + } +} + +/// Parse a WIT definition in its textual format, and produces an +/// [AST](crate::ast) with the [`Interfaces`](crate::ast::Interfaces) +/// structure upon succesful. +/// +/// # Examples +/// +/// ```rust +/// use wasmer_interface_types::{ +/// ast::*, +/// decoders::wat::{parse, Buffer}, +/// interpreter::Instruction, +/// }; +/// +/// # fn main() { +/// let input = Buffer::new( +/// r#"(@interface export "foo" +/// (param i32)) +/// +/// (@interface export "bar") +/// +/// (@interface func $ns_foo (import "ns" "foo") +/// (result i32)) +/// +/// (@interface func $ns_bar (import "ns" "bar")) +/// +/// (@interface adapt (import "ns" "foo") +/// (param i32) +/// arg.get 42) +/// +/// (@interface adapt (export "bar") +/// arg.get 42) +/// +/// (@interface forward (export "main"))"#, +/// ) +/// .unwrap(); +/// let output = Interfaces { +/// exports: vec![ +/// Export { +/// name: "foo", +/// input_types: vec![InterfaceType::I32], +/// output_types: vec![], +/// }, +/// Export { +/// name: "bar", +/// input_types: vec![], +/// output_types: vec![], +/// }, +/// ], +/// types: vec![], +/// imports: vec![ +/// Import { +/// namespace: "ns", +/// name: "foo", +/// input_types: vec![], +/// output_types: vec![InterfaceType::I32], +/// }, +/// Import { +/// namespace: "ns", +/// name: "bar", +/// input_types: vec![], +/// output_types: vec![], +/// }, +/// ], +/// adapters: vec![ +/// Adapter::Import { +/// namespace: "ns", +/// name: "foo", +/// input_types: vec![InterfaceType::I32], +/// output_types: vec![], +/// instructions: vec![Instruction::ArgumentGet { index: 42 }], +/// }, +/// Adapter::Export { +/// name: "bar", +/// input_types: vec![], +/// output_types: vec![], +/// instructions: vec![Instruction::ArgumentGet { index: 42 }], +/// }, +/// ], +/// forwards: vec![Forward { name: "main" }], +/// }; +/// +/// assert_eq!(parse(&input).unwrap(), output); +/// # } +/// ``` +pub fn parse<'input>(input: &'input Buffer) -> Result> { + parser::parse::(&input) +} + +#[cfg(test)] +mod tests { + use super::*; + use wast::parser; + + fn buffer(input: &str) -> Buffer { + Buffer::new(input).expect("Failed to build the parser buffer.") + } + + #[test] + fn test_interface_type() { + let inputs = vec![ + "int", "float", "any", "string", "seq", "i32", "i64", "f32", "f64", "anyref", + ]; + let outputs = vec![ + InterfaceType::Int, + InterfaceType::Float, + InterfaceType::Any, + InterfaceType::String, + InterfaceType::Seq, + InterfaceType::I32, + InterfaceType::I64, + InterfaceType::F32, + InterfaceType::F64, + InterfaceType::AnyRef, + ]; + + 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![ + "arg.get 7", + "call 7", + r#"call-export "foo""#, + "read-utf8", + r#"write-utf8 "foo""#, + "as-wasm int", + "as-interface anyref", + "table-ref-add", + "table-ref-get", + "call-method 7", + "make-record int", + "get-field int 7", + "const i32 7", + "fold-seq 7", + "add int", + r#"mem-to-seq int "foo""#, + r#"load int "foo""#, + "seq.new int", + "list.push", + "repeat-until 1 2", + ]; + let outputs = vec![ + Instruction::ArgumentGet { index: 7 }, + Instruction::Call { function_index: 7 }, + Instruction::CallExport { export_name: "foo" }, + Instruction::ReadUtf8, + Instruction::WriteUtf8 { + allocator_name: "foo", + }, + Instruction::AsWasm(InterfaceType::Int), + Instruction::AsInterface(InterfaceType::AnyRef), + Instruction::TableRefAdd, + Instruction::TableRefGet, + Instruction::CallMethod(7), + Instruction::MakeRecord(InterfaceType::Int), + Instruction::GetField(InterfaceType::Int, 7), + Instruction::Const(InterfaceType::I32, 7), + Instruction::FoldSeq(7), + Instruction::Add(InterfaceType::Int), + Instruction::MemToSeq(InterfaceType::Int, "foo"), + Instruction::Load(InterfaceType::Int, "foo"), + Instruction::SeqNew(InterfaceType::Int), + Instruction::ListPush, + Instruction::RepeatUntil(1, 2), + ]; + + 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_param_empty() { + let input = buffer("(param)"); + let output = FunctionType::Input(vec![]); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_param() { + let input = buffer("(param i32 string)"); + let output = FunctionType::Input(vec![InterfaceType::I32, InterfaceType::String]); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_result_empty() { + let input = buffer("(result)"); + let output = FunctionType::Output(vec![]); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_result() { + let input = buffer("(result i32 string)"); + let output = FunctionType::Output(vec![InterfaceType::I32, InterfaceType::String]); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_export_with_no_param_no_result() { + let input = buffer(r#"(@interface export "foo")"#); + let output = Interface::Export(Export { + name: "foo", + input_types: vec![], + output_types: vec![], + }); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_export_with_some_param_no_result() { + let input = buffer(r#"(@interface export "foo" (param i32))"#); + let output = Interface::Export(Export { + name: "foo", + input_types: vec![InterfaceType::I32], + output_types: vec![], + }); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_export_with_no_param_some_result() { + let input = buffer(r#"(@interface export "foo" (result i32))"#); + let output = Interface::Export(Export { + name: "foo", + input_types: vec![], + output_types: vec![InterfaceType::I32], + }); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_export_with_some_param_some_result() { + let input = buffer(r#"(@interface export "foo" (param string) (result i32 i32))"#); + let output = Interface::Export(Export { + name: "foo", + input_types: vec![InterfaceType::String], + output_types: vec![InterfaceType::I32, InterfaceType::I32], + }); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_export_escaped_name() { + let input = buffer(r#"(@interface export "fo\"o")"#); + let output = Interface::Export(Export { + name: r#"fo"o"#, + input_types: vec![], + output_types: vec![], + }); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_import_with_no_param_no_result() { + let input = buffer(r#"(@interface func $ns_foo (import "ns" "foo"))"#); + let output = Interface::Import(Import { + namespace: "ns", + name: "foo", + input_types: vec![], + output_types: vec![], + }); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_import_with_some_param_no_result() { + let input = buffer(r#"(@interface func $ns_foo (import "ns" "foo") (param i32))"#); + let output = Interface::Import(Import { + namespace: "ns", + name: "foo", + input_types: vec![InterfaceType::I32], + output_types: vec![], + }); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_import_with_no_param_some_result() { + let input = buffer(r#"(@interface func $ns_foo (import "ns" "foo") (result i32))"#); + let output = Interface::Import(Import { + namespace: "ns", + name: "foo", + input_types: vec![], + output_types: vec![InterfaceType::I32], + }); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_import_with_some_param_some_result() { + let input = buffer( + r#"(@interface func $ns_foo (import "ns" "foo") (param string) (result i32 i32))"#, + ); + let output = Interface::Import(Import { + namespace: "ns", + name: "foo", + input_types: vec![InterfaceType::String], + output_types: vec![InterfaceType::I32, InterfaceType::I32], + }); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_adapter_import() { + let input = + buffer(r#"(@interface adapt (import "ns" "foo") (param i32 i32) (result i32))"#); + let output = Interface::Adapter(Adapter::Import { + namespace: "ns", + name: "foo", + input_types: vec![InterfaceType::I32, InterfaceType::I32], + output_types: vec![InterfaceType::I32], + instructions: vec![], + }); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_adapter_export() { + let input = buffer(r#"(@interface adapt (export "foo") (param i32 i32) (result i32))"#); + let output = Interface::Adapter(Adapter::Export { + name: "foo", + input_types: vec![InterfaceType::I32, InterfaceType::I32], + output_types: vec![InterfaceType::I32], + instructions: vec![], + }); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + #[test] + fn test_forward() { + let input = buffer(r#"(@interface forward (export "foo"))"#); + let output = Interface::Forward(Forward { name: "foo" }); + + assert_eq!(parser::parse::(&input).unwrap(), output); + } + + #[test] + fn test_interfaces() { + let input = buffer( + r#"(@interface export "foo" + (param i32)) + +(@interface export "bar") + +(@interface func $ns_foo (import "ns" "foo") + (result i32)) + +(@interface func $ns_bar (import "ns" "bar")) + +(@interface adapt (import "ns" "foo") + (param i32) + arg.get 42) + +(@interface adapt (export "bar") + arg.get 42) + +(@interface forward (export "main"))"#, + ); + let output = Interfaces { + exports: vec![ + Export { + name: "foo", + input_types: vec![InterfaceType::I32], + output_types: vec![], + }, + Export { + name: "bar", + input_types: vec![], + output_types: vec![], + }, + ], + types: vec![], + imports: vec![ + Import { + namespace: "ns", + name: "foo", + input_types: vec![], + output_types: vec![InterfaceType::I32], + }, + Import { + namespace: "ns", + name: "bar", + input_types: vec![], + output_types: vec![], + }, + ], + adapters: vec![ + Adapter::Import { + namespace: "ns", + name: "foo", + input_types: vec![InterfaceType::I32], + output_types: vec![], + instructions: vec![Instruction::ArgumentGet { index: 42 }], + }, + Adapter::Export { + name: "bar", + input_types: vec![], + output_types: vec![], + instructions: vec![Instruction::ArgumentGet { index: 42 }], + }, + ], + forwards: vec![Forward { name: "main" }], + }; + + assert_eq!(parser::parse::(&input).unwrap(), output); + } +} diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 792c91e..e7ef7f1 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -43,8 +43,8 @@ pub(crate) type ExecutableInstruction