| #[cfg(feature = "component-model")] |
| use crate::component::WastVal; |
| use crate::core::{WastArgCore, WastRetCore}; |
| use crate::kw; |
| use crate::parser::{self, Cursor, Parse, ParseBuffer, Parser, Peek, Result}; |
| use crate::token::{Id, Span}; |
| use crate::{Error, Wat}; |
| |
| /// A parsed representation of a `*.wast` file. |
| /// |
| /// WAST files are not officially specified but are used in the official test |
| /// suite to write official spec tests for wasm. This type represents a parsed |
| /// `*.wast` file which parses a list of directives in a file. |
| #[derive(Debug)] |
| pub struct Wast<'a> { |
| #[allow(missing_docs)] |
| pub directives: Vec<WastDirective<'a>>, |
| } |
| |
| impl<'a> Parse<'a> for Wast<'a> { |
| fn parse(parser: Parser<'a>) -> Result<Self> { |
| let mut directives = Vec::new(); |
| |
| parser.with_standard_annotations_registered(|parser| { |
| // If it looks like a directive token is in the stream then we parse a |
| // bunch of directives, otherwise assume this is an inline module. |
| if parser.peek2::<WastDirectiveToken>()? { |
| while !parser.is_empty() { |
| directives.push(parser.parens(|p| p.parse())?); |
| } |
| } else { |
| let module = parser.parse::<Wat>()?; |
| directives.push(WastDirective::Module(QuoteWat::Wat(module))); |
| } |
| Ok(Wast { directives }) |
| }) |
| } |
| } |
| |
| struct WastDirectiveToken; |
| |
| impl Peek for WastDirectiveToken { |
| fn peek(cursor: Cursor<'_>) -> Result<bool> { |
| let kw = match cursor.keyword()? { |
| Some((kw, _)) => kw, |
| None => return Ok(false), |
| }; |
| Ok(kw.starts_with("assert_") |
| || kw == "module" |
| || kw == "component" |
| || kw == "register" |
| || kw == "invoke") |
| } |
| |
| fn display() -> &'static str { |
| unimplemented!() |
| } |
| } |
| |
| /// The different kinds of directives found in a `*.wast` file. |
| /// |
| /// |
| /// Some more information about these various branches can be found at |
| /// <https://github.com/WebAssembly/spec/blob/main/interpreter/README.md#scripts>. |
| #[allow(missing_docs)] |
| #[derive(Debug)] |
| pub enum WastDirective<'a> { |
| /// The provided module is defined, validated, and then instantiated. |
| Module(QuoteWat<'a>), |
| |
| /// The provided module is defined and validated. |
| /// |
| /// This module is not instantiated automatically. |
| ModuleDefinition(QuoteWat<'a>), |
| |
| /// The named module is instantiated under the instance name provided. |
| ModuleInstance { |
| span: Span, |
| instance: Option<Id<'a>>, |
| module: Option<Id<'a>>, |
| }, |
| |
| /// Asserts the module cannot be decoded with the given error. |
| AssertMalformed { |
| span: Span, |
| module: QuoteWat<'a>, |
| message: &'a str, |
| }, |
| |
| /// Asserts the module cannot be validated with the given error. |
| AssertInvalid { |
| span: Span, |
| module: QuoteWat<'a>, |
| message: &'a str, |
| }, |
| |
| /// Registers the `module` instance with the given `name` to be available |
| /// for importing in future module instances. |
| Register { |
| span: Span, |
| name: &'a str, |
| module: Option<Id<'a>>, |
| }, |
| |
| /// Invokes the specified export. |
| Invoke(WastInvoke<'a>), |
| |
| /// The invocation provided should trap with the specified error. |
| AssertTrap { |
| span: Span, |
| exec: WastExecute<'a>, |
| message: &'a str, |
| }, |
| |
| /// The invocation provided should succeed with the specified results. |
| AssertReturn { |
| span: Span, |
| exec: WastExecute<'a>, |
| results: Vec<WastRet<'a>>, |
| }, |
| |
| /// The invocation provided should exhaust system resources (e.g. stack |
| /// overflow). |
| AssertExhaustion { |
| span: Span, |
| call: WastInvoke<'a>, |
| message: &'a str, |
| }, |
| |
| /// The provided module should fail to link when instantiation is attempted. |
| AssertUnlinkable { |
| span: Span, |
| module: Wat<'a>, |
| message: &'a str, |
| }, |
| |
| /// The invocation provided should throw an exception. |
| AssertException { span: Span, exec: WastExecute<'a> }, |
| |
| /// The invocation should fail to handle a suspension. |
| AssertSuspension { |
| span: Span, |
| exec: WastExecute<'a>, |
| message: &'a str, |
| }, |
| |
| /// Creates a new system thread which executes the given commands. |
| Thread(WastThread<'a>), |
| |
| /// Waits for the specified thread to exit. |
| Wait { span: Span, thread: Id<'a> }, |
| } |
| |
| impl WastDirective<'_> { |
| /// Returns the location in the source that this directive was defined at |
| pub fn span(&self) -> Span { |
| match self { |
| WastDirective::Module(QuoteWat::Wat(w)) |
| | WastDirective::ModuleDefinition(QuoteWat::Wat(w)) => w.span(), |
| WastDirective::Module(QuoteWat::QuoteModule(span, _)) |
| | WastDirective::ModuleDefinition(QuoteWat::QuoteModule(span, _)) => *span, |
| WastDirective::Module(QuoteWat::QuoteComponent(span, _)) |
| | WastDirective::ModuleDefinition(QuoteWat::QuoteComponent(span, _)) => *span, |
| WastDirective::ModuleInstance { span, .. } |
| | WastDirective::AssertMalformed { span, .. } |
| | WastDirective::Register { span, .. } |
| | WastDirective::AssertTrap { span, .. } |
| | WastDirective::AssertReturn { span, .. } |
| | WastDirective::AssertExhaustion { span, .. } |
| | WastDirective::AssertUnlinkable { span, .. } |
| | WastDirective::AssertInvalid { span, .. } |
| | WastDirective::AssertException { span, .. } |
| | WastDirective::AssertSuspension { span, .. } |
| | WastDirective::Wait { span, .. } => *span, |
| WastDirective::Invoke(i) => i.span, |
| WastDirective::Thread(t) => t.span, |
| } |
| } |
| } |
| |
| impl<'a> Parse<'a> for WastDirective<'a> { |
| fn parse(parser: Parser<'a>) -> Result<Self> { |
| let mut l = parser.lookahead1(); |
| if l.peek::<kw::module>()? || l.peek::<kw::component>()? { |
| parse_wast_module(parser) |
| } else if l.peek::<kw::assert_malformed>()? { |
| let span = parser.parse::<kw::assert_malformed>()?.0; |
| Ok(WastDirective::AssertMalformed { |
| span, |
| module: parser.parens(|p| p.parse())?, |
| message: parser.parse()?, |
| }) |
| } else if l.peek::<kw::assert_invalid>()? { |
| let span = parser.parse::<kw::assert_invalid>()?.0; |
| Ok(WastDirective::AssertInvalid { |
| span, |
| module: parser.parens(|p| p.parse())?, |
| message: parser.parse()?, |
| }) |
| } else if l.peek::<kw::register>()? { |
| let span = parser.parse::<kw::register>()?.0; |
| Ok(WastDirective::Register { |
| span, |
| name: parser.parse()?, |
| module: parser.parse()?, |
| }) |
| } else if l.peek::<kw::invoke>()? { |
| Ok(WastDirective::Invoke(parser.parse()?)) |
| } else if l.peek::<kw::assert_trap>()? { |
| let span = parser.parse::<kw::assert_trap>()?.0; |
| Ok(WastDirective::AssertTrap { |
| span, |
| exec: parser.parens(|p| p.parse())?, |
| message: parser.parse()?, |
| }) |
| } else if l.peek::<kw::assert_return>()? { |
| let span = parser.parse::<kw::assert_return>()?.0; |
| let exec = parser.parens(|p| p.parse())?; |
| let mut results = Vec::new(); |
| while !parser.is_empty() { |
| results.push(parser.parens(|p| p.parse())?); |
| } |
| Ok(WastDirective::AssertReturn { |
| span, |
| exec, |
| results, |
| }) |
| } else if l.peek::<kw::assert_exhaustion>()? { |
| let span = parser.parse::<kw::assert_exhaustion>()?.0; |
| Ok(WastDirective::AssertExhaustion { |
| span, |
| call: parser.parens(|p| p.parse())?, |
| message: parser.parse()?, |
| }) |
| } else if l.peek::<kw::assert_unlinkable>()? { |
| let span = parser.parse::<kw::assert_unlinkable>()?.0; |
| Ok(WastDirective::AssertUnlinkable { |
| span, |
| module: parser.parens(parse_wat)?, |
| message: parser.parse()?, |
| }) |
| } else if l.peek::<kw::assert_exception>()? { |
| let span = parser.parse::<kw::assert_exception>()?.0; |
| Ok(WastDirective::AssertException { |
| span, |
| exec: parser.parens(|p| p.parse())?, |
| }) |
| } else if l.peek::<kw::assert_suspension>()? { |
| let span = parser.parse::<kw::assert_suspension>()?.0; |
| Ok(WastDirective::AssertSuspension { |
| span, |
| exec: parser.parens(|p| p.parse())?, |
| message: parser.parse()?, |
| }) |
| } else if l.peek::<kw::thread>()? { |
| Ok(WastDirective::Thread(parser.parse()?)) |
| } else if l.peek::<kw::wait>()? { |
| let span = parser.parse::<kw::wait>()?.0; |
| Ok(WastDirective::Wait { |
| span, |
| thread: parser.parse()?, |
| }) |
| } else { |
| Err(l.error()) |
| } |
| } |
| } |
| |
| #[allow(missing_docs)] |
| #[derive(Debug)] |
| pub enum WastExecute<'a> { |
| Invoke(WastInvoke<'a>), |
| Wat(Wat<'a>), |
| Get { |
| span: Span, |
| module: Option<Id<'a>>, |
| global: &'a str, |
| }, |
| } |
| |
| impl<'a> WastExecute<'a> { |
| /// Returns the first span for this execute statement. |
| pub fn span(&self) -> Span { |
| match self { |
| WastExecute::Invoke(i) => i.span, |
| WastExecute::Wat(i) => i.span(), |
| WastExecute::Get { span, .. } => *span, |
| } |
| } |
| } |
| |
| impl<'a> Parse<'a> for WastExecute<'a> { |
| fn parse(parser: Parser<'a>) -> Result<Self> { |
| let mut l = parser.lookahead1(); |
| if l.peek::<kw::invoke>()? { |
| Ok(WastExecute::Invoke(parser.parse()?)) |
| } else if l.peek::<kw::module>()? || l.peek::<kw::component>()? { |
| Ok(WastExecute::Wat(parse_wat(parser)?)) |
| } else if l.peek::<kw::get>()? { |
| let span = parser.parse::<kw::get>()?.0; |
| Ok(WastExecute::Get { |
| span, |
| module: parser.parse()?, |
| global: parser.parse()?, |
| }) |
| } else { |
| Err(l.error()) |
| } |
| } |
| } |
| |
| fn parse_wat(parser: Parser) -> Result<Wat> { |
| // Note that this doesn't use `Parse for Wat` since the `parser` provided |
| // has already peeled back the first layer of parentheses while `Parse for |
| // Wat` expects to be the top layer which means it also tries to peel off |
| // the parens. Instead we can skip the sugar that `Wat` has for simply a |
| // list of fields (no `(module ...)` container) and just parse the `Module` |
| // itself. |
| if parser.peek::<kw::component>()? { |
| Ok(Wat::Component(parser.parse()?)) |
| } else { |
| Ok(Wat::Module(parser.parse()?)) |
| } |
| } |
| |
| #[allow(missing_docs)] |
| #[derive(Debug)] |
| pub struct WastInvoke<'a> { |
| pub span: Span, |
| pub module: Option<Id<'a>>, |
| pub name: &'a str, |
| pub args: Vec<WastArg<'a>>, |
| } |
| |
| impl<'a> Parse<'a> for WastInvoke<'a> { |
| fn parse(parser: Parser<'a>) -> Result<Self> { |
| let span = parser.parse::<kw::invoke>()?.0; |
| let module = parser.parse()?; |
| let name = parser.parse()?; |
| let mut args = Vec::new(); |
| while !parser.is_empty() { |
| args.push(parser.parens(|p| p.parse())?); |
| } |
| Ok(WastInvoke { |
| span, |
| module, |
| name, |
| args, |
| }) |
| } |
| } |
| |
| fn parse_wast_module<'a>(parser: Parser<'a>) -> Result<WastDirective<'a>> { |
| if parser.peek2::<kw::quote>()? { |
| QuoteWat::parse(parser).map(WastDirective::Module) |
| } else if parser.peek2::<kw::definition>()? { |
| fn parse_module(span: Span, parser: Parser<'_>) -> Result<Wat<'_>> { |
| Ok(Wat::Module( |
| crate::core::Module::parse_without_module_keyword(span, parser)?, |
| )) |
| } |
| fn parse_component(_span: Span, parser: Parser<'_>) -> Result<Wat<'_>> { |
| #[cfg(feature = "component-model")] |
| return Ok(Wat::Component( |
| crate::component::Component::parse_without_component_keyword(_span, parser)?, |
| )); |
| #[cfg(not(feature = "component-model"))] |
| return Err(parser.error("component model support disabled at compile time")); |
| } |
| let (span, ctor) = if parser.peek::<kw::component>()? { |
| ( |
| parser.parse::<kw::component>()?.0, |
| parse_component as fn(_, _) -> _, |
| ) |
| } else { |
| ( |
| parser.parse::<kw::module>()?.0, |
| parse_module as fn(_, _) -> _, |
| ) |
| }; |
| parser.parse::<kw::definition>()?; |
| Ok(WastDirective::ModuleDefinition(QuoteWat::Wat(ctor( |
| span, parser, |
| )?))) |
| } else if parser.peek2::<kw::instance>()? { |
| let span = if parser.peek::<kw::component>()? { |
| parser.parse::<kw::component>()?.0 |
| } else { |
| parser.parse::<kw::module>()?.0 |
| }; |
| parser.parse::<kw::instance>()?; |
| Ok(WastDirective::ModuleInstance { |
| span, |
| instance: parser.parse()?, |
| module: parser.parse()?, |
| }) |
| } else { |
| QuoteWat::parse(parser).map(WastDirective::Module) |
| } |
| } |
| |
| #[allow(missing_docs)] |
| #[derive(Debug)] |
| pub enum QuoteWat<'a> { |
| Wat(Wat<'a>), |
| QuoteModule(Span, Vec<(Span, &'a [u8])>), |
| QuoteComponent(Span, Vec<(Span, &'a [u8])>), |
| } |
| |
| impl<'a> QuoteWat<'a> { |
| /// Encodes this module to bytes, either by encoding the module directly or |
| /// parsing the contents and then encoding it. |
| pub fn encode(&mut self) -> Result<Vec<u8>, Error> { |
| match self.to_test()? { |
| QuoteWatTest::Binary(bytes) => Ok(bytes), |
| QuoteWatTest::Text(text) => { |
| let text = std::str::from_utf8(&text).map_err(|_| { |
| let span = self.span(); |
| Error::new(span, "malformed UTF-8 encoding".to_string()) |
| })?; |
| let buf = ParseBuffer::new(&text)?; |
| let mut wat = parser::parse::<Wat<'_>>(&buf)?; |
| wat.encode() |
| } |
| } |
| } |
| |
| /// Converts this to either a `QuoteWatTest::Binary` or |
| /// `QuoteWatTest::Text` depending on what it is internally. |
| pub fn to_test(&mut self) -> Result<QuoteWatTest, Error> { |
| let (source, prefix) = match self { |
| QuoteWat::Wat(m) => return m.encode().map(QuoteWatTest::Binary), |
| QuoteWat::QuoteModule(_, source) => (source, None), |
| QuoteWat::QuoteComponent(_, source) => (source, Some("(component")), |
| }; |
| let mut ret = Vec::new(); |
| for (_, src) in source { |
| ret.extend_from_slice(src); |
| ret.push(b' '); |
| } |
| if let Some(prefix) = prefix { |
| ret.splice(0..0, prefix.as_bytes().iter().copied()); |
| ret.push(b')'); |
| } |
| Ok(QuoteWatTest::Text(ret)) |
| } |
| |
| /// Returns the identifier, if registered, for this module. |
| pub fn name(&self) -> Option<Id<'a>> { |
| match self { |
| QuoteWat::Wat(Wat::Module(m)) => m.id, |
| QuoteWat::Wat(Wat::Component(m)) => m.id, |
| QuoteWat::QuoteModule(..) | QuoteWat::QuoteComponent(..) => None, |
| } |
| } |
| |
| /// Returns the defining span of this module. |
| pub fn span(&self) -> Span { |
| match self { |
| QuoteWat::Wat(w) => w.span(), |
| QuoteWat::QuoteModule(span, _) => *span, |
| QuoteWat::QuoteComponent(span, _) => *span, |
| } |
| } |
| } |
| |
| impl<'a> Parse<'a> for QuoteWat<'a> { |
| fn parse(parser: Parser<'a>) -> Result<Self> { |
| if parser.peek2::<kw::quote>()? { |
| let ctor = if parser.peek::<kw::component>()? { |
| parser.parse::<kw::component>()?; |
| QuoteWat::QuoteComponent |
| } else { |
| parser.parse::<kw::module>()?; |
| QuoteWat::QuoteModule |
| }; |
| let span = parser.parse::<kw::quote>()?.0; |
| let mut src = Vec::new(); |
| while !parser.is_empty() { |
| let span = parser.cur_span(); |
| let string = parser.parse()?; |
| src.push((span, string)); |
| } |
| Ok(ctor(span, src)) |
| } else { |
| Ok(QuoteWat::Wat(parse_wat(parser)?)) |
| } |
| } |
| } |
| |
| /// Returned from [`QuoteWat::to_test`]. |
| #[allow(missing_docs)] |
| #[derive(Debug)] |
| pub enum QuoteWatTest { |
| Binary(Vec<u8>), |
| Text(Vec<u8>), |
| } |
| |
| #[derive(Debug)] |
| #[allow(missing_docs)] |
| pub enum WastArg<'a> { |
| Core(WastArgCore<'a>), |
| // TODO: technically this isn't cargo-compliant since it means that this |
| // isn't and additive feature by defining this conditionally. That being |
| // said this seems unlikely to break many in practice so this isn't a shared |
| // type, so fixing this is left to a future commit. |
| #[cfg(feature = "component-model")] |
| Component(WastVal<'a>), |
| } |
| |
| impl<'a> Parse<'a> for WastArg<'a> { |
| fn parse(parser: Parser<'a>) -> Result<Self> { |
| #[cfg(feature = "component-model")] |
| if parser.peek::<WastArgCore<'_>>()? { |
| Ok(WastArg::Core(parser.parse()?)) |
| } else { |
| Ok(WastArg::Component(parser.parse()?)) |
| } |
| |
| #[cfg(not(feature = "component-model"))] |
| Ok(WastArg::Core(parser.parse()?)) |
| } |
| } |
| |
| #[derive(Debug)] |
| #[allow(missing_docs)] |
| pub enum WastRet<'a> { |
| Core(WastRetCore<'a>), |
| #[cfg(feature = "component-model")] |
| Component(WastVal<'a>), |
| } |
| |
| impl<'a> Parse<'a> for WastRet<'a> { |
| fn parse(parser: Parser<'a>) -> Result<Self> { |
| #[cfg(feature = "component-model")] |
| if parser.peek::<WastRetCore<'_>>()? { |
| Ok(WastRet::Core(parser.parse()?)) |
| } else { |
| Ok(WastRet::Component(parser.parse()?)) |
| } |
| |
| #[cfg(not(feature = "component-model"))] |
| Ok(WastRet::Core(parser.parse()?)) |
| } |
| } |
| |
| #[derive(Debug)] |
| #[allow(missing_docs)] |
| pub struct WastThread<'a> { |
| pub span: Span, |
| pub name: Id<'a>, |
| pub shared_module: Option<Id<'a>>, |
| pub directives: Vec<WastDirective<'a>>, |
| } |
| |
| impl<'a> Parse<'a> for WastThread<'a> { |
| fn parse(parser: Parser<'a>) -> Result<Self> { |
| parser.depth_check()?; |
| let span = parser.parse::<kw::thread>()?.0; |
| let name = parser.parse()?; |
| |
| let shared_module = if parser.peek2::<kw::shared>()? { |
| let name = parser.parens(|p| { |
| p.parse::<kw::shared>()?; |
| p.parens(|p| { |
| p.parse::<kw::module>()?; |
| p.parse() |
| }) |
| })?; |
| Some(name) |
| } else { |
| None |
| }; |
| let mut directives = Vec::new(); |
| while !parser.is_empty() { |
| directives.push(parser.parens(|p| p.parse())?); |
| } |
| Ok(WastThread { |
| span, |
| name, |
| shared_module, |
| directives, |
| }) |
| } |
| } |