1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
//! A crate to parse the textual format for WebAssembly interface types.
//!
//! This crate is a work-in-progress and should expect to have a good deal of
//! change as the official proposal evolves. The main purpose of this crate is
//! to parse a textual file into a binary representation, and the parsing
//! includes parsing of all of the WebAssembly core types/syntax as well.

#![deny(missing_docs)]

use anyhow::{bail, Context};
use std::borrow::Cow;
use std::path::Path;
use std::str;
use wast::parser::ParseBuffer;

mod ast;
mod binary;
mod resolve;
pub use ast::*;

/// Parses a `file` on the filesystem as a textual representation of WebAssembly
/// Interface Types, returning the binary representation of the module.
///
/// Note that the `file` could either be a valid `*.wat` or `*.wasm`
/// file. In the `*.wasm` case the bytes are passed through unmodified.
///
/// # Errors
///
/// For information about errors, see the [`parse_bytes`] documentation.
pub fn parse_file(file: impl AsRef<Path>) -> anyhow::Result<Vec<u8>> {
    _parse_file(file.as_ref())
}

fn _parse_file(file: &Path) -> anyhow::Result<Vec<u8>> {
    let contents = std::fs::read(file).context(format!("failed to read `{}`", file.display()))?;
    match parse_bytes(&contents) {
        Ok(bytes) => Ok(bytes.into_owned()),
        Err(mut e) => {
            if let Some(e) = e.downcast_mut::<wast::Error>() {
                e.set_path(file);
            }
            Err(e)
        }
    }
}

/// Parses in-memory bytes as either the text format or a binary WebAssembly
/// module.
///
/// This function will attempt to interpret the given bytes as one of two
/// options:
///
/// * A utf-8 string which is a `*.wat` file to be parsed.
/// * A binary WebAssembly file starting with `b"\0asm"`
///
/// If the input is a string then it will be parsed as `*.wat`, and then after
/// parsing it will be encoded back into a WebAssembly binary module. If the
/// input is a binary that starts with `b"\0asm"` it will be returned verbatim.
/// Everything that doesn't start with `b"\0asm"` will be parsed as a utf-8
/// `*.wat` file, returning errors as appropriate.
///
/// # Errors
///
/// In addition to all of the errors that can be returned from [`parse_str`],
/// this function will also return an error if the input does not start with
/// `b"\0asm"` and is invalid utf-8. (failed to even try to call [`parse_str`]).
pub fn parse_bytes(bytes: &[u8]) -> anyhow::Result<Cow<'_, [u8]>> {
    if bytes.starts_with(b"\0asm") {
        return Ok(bytes.into());
    }
    let result = match str::from_utf8(bytes) {
        Ok(s) => _parse_str(s)?,
        Err(_) => bail!("input bytes aren't valid utf-8"),
    };
    Ok(result.into())
}

/// Parses an in-memory string as the text format, returning the file as a
/// binary WebAssembly file.
///
/// This function is intended to be a stable convenience function for parsing a
/// `*.wat` file into a WebAssembly binary. This is a high-level operation which
/// does not expose any parsing internals, for that you'll want to use the
/// [`Module`] type and the `wast` crate.
///
/// # Errors
///
/// This function can fail for a number of reasons, including (but not limited
/// to):
///
/// * The `wat` input may fail to lex, such as having invalid tokens or syntax
/// * The `wat` input may fail to parse, such as having incorrect syntactical
///   structure
/// * The `wat` input may contain names that could not be resolved
///
pub fn parse_str(wat: impl AsRef<str>) -> Result<Vec<u8>, wast::Error> {
    _parse_str(wat.as_ref())
}

fn _parse_str(wat: &str) -> Result<Vec<u8>, wast::Error> {
    let adjust = |mut err: wast::Error| {
        err.set_text(wat);
        err
    };
    let buf = ParseBuffer::new(&wat).map_err(adjust)?;
    let mut ast = wast::parser::parse::<Wat>(&buf).map_err(adjust)?;
    ast.module.encode().map_err(adjust)
}