wast 2.0.0

Customizable Rust parsers for the WebAssembly Text formats WAT and WAST
Documentation
//! A crate for low-level parsing of the WebAssembly text formats: WAT and WAST.
//!
//! This crate is intended to be a low-level detail of the `wat` crate,
//! providing a low-level parsing API for parsing WebAssembly text format
//! structures. The API provided by this crate is very similar to
//! [`syn`](https://docs.rs/syn) and provides the ability to write customized
//! parsers which may be an extension to the core WebAssembly text format. For
//! more documentation see the [`parser`] module.
//!
//! # High-level Overview
//!
//! This crate provides a few major pieces of functionality
//!
//! * [`lexer`] - this is a raw lexer for the wasm text format. This is not
//!   customizable, but if you'd like to iterate over raw tokens this is the
//!   module for you. You likely won't use this much.
//!
//! * [`parser`] - this is the workhorse of this crate. The [`parser`] module
//!   provides the [`Parse`][] trait primarily and utilities
//!   around working with a [`Parser`](`parser::Parser`) to parse streams of
//!   tokens.
//!
//! * [`Module`] - this contains an Abstract Syntax Tree (AST) of the
//!   WebAssembly Text format (WAT) as well as the unofficial WAST format. This
//!   also has a [`Module::encode`] method to emit a module in its binary form.
//!
//! # Stability and WebAssembly Features
//!
//! This crate provides support for many in-progress WebAssembly features such
//! as reference types, multi-value, etc. Be sure to check out the documentation
//! of the [`wast` crate](https://docs.rs/wast) for policy information on crate
//! stability vs WebAssembly Features. The tl;dr; version is that this crate
//! will issue semver-non-breaking releases which will break the parsing of the
//! text format. This crate, unlike `wast`, is expected to have numerous Rust
//! public API changes, all of which will be accompanied with a semver-breaking
//! release.
//!
//! [`Parse`]: parser::Parse
//! [`LexError`]: lexer::LexError

#![deny(missing_docs)]

use std::fmt;
use std::path::{Path, PathBuf};

mod binary;
mod resolve;

mod ast;
pub use self::ast::*;

pub mod lexer;
pub mod parser;

/// A convenience error type to tie together all the detailed errors produced by
/// this crate.
///
/// This type can be created from a [`lexer::LexError`] or [`parser::Error`].
/// This also contains storage for file/text information so a nice error can be
/// rendered along the same lines of rustc's own error messages (minus the
/// color).
///
/// This type is typically suitable for use in public APIs for consumers of this
/// crate.
#[derive(Debug)]
pub struct Error {
    inner: Box<ErrorInner>,
}

#[derive(Debug)]
struct ErrorInner {
    text: Option<Text>,
    file: Option<PathBuf>,
    span: Span,
    kind: ErrorKind,
}

#[derive(Debug)]
struct Text {
    line: usize,
    col: usize,
    snippet: String,
}

#[derive(Debug)]
enum ErrorKind {
    Lex(lexer::LexError),
    Custom(String),
}

impl Error {
    fn lex(span: Span, content: &str, kind: lexer::LexError) -> Error {
        let mut ret = Error {
            inner: Box::new(ErrorInner {
                text: None,
                file: None,
                span,
                kind: ErrorKind::Lex(kind),
            }),
        };
        ret.set_text(content);
        return ret;
    }

    fn parse(span: Span, content: &str, message: String) -> Error {
        let mut ret = Error {
            inner: Box::new(ErrorInner {
                text: None,
                file: None,
                span,
                kind: ErrorKind::Custom(message),
            }),
        };
        ret.set_text(content);
        return ret;
    }

    fn new(span: Span, message: String) -> Error {
        Error {
            inner: Box::new(ErrorInner {
                text: None,
                file: None,
                span,
                kind: ErrorKind::Custom(message),
            }),
        }
    }

    /// To provide a more useful error this function can be used to extract
    /// relevant textual information about this error into the error itself.
    ///
    /// The `contents` here should be the full text of the original file being
    /// parsed, and this will extract a sub-slice as necessary to render in the
    /// `Display` implementation later on.
    pub fn set_text(&mut self, contents: &str) {
        if self.inner.text.is_some() {
            return;
        }
        self.inner.text = Some(Text::new(contents, self.inner.span));
    }

    /// To provide a more useful error this function can be used to set
    /// the file name that this error is associated with.
    ///
    /// The `path` here will be stored in this error and later rendered in the
    /// `Display` implementation.
    pub fn set_path(&mut self, path: &Path) {
        if self.inner.file.is_some() {
            return;
        }
        self.inner.file = Some(path.to_path_buf());
    }

    /// Returns the underlying `LexError`, if any, that describes this error.
    pub fn lex_error(&self) -> Option<&lexer::LexError> {
        match &self.inner.kind {
            ErrorKind::Lex(e) => Some(e),
            _ => None,
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let err = match &self.inner.kind {
            ErrorKind::Lex(e) => e as &dyn fmt::Display,
            ErrorKind::Custom(e) => e as &dyn fmt::Display,
        };
        let text = match &self.inner.text {
            Some(text) => text,
            None => {
                return write!(f, "{} at byte offset {}", err, self.inner.span.offset);
            }
        };
        let file = self
            .inner
            .file
            .as_ref()
            .and_then(|p| p.to_str())
            .unwrap_or("<anon>");
        write!(
            f,
            "\
{err}
     --> {file}:{line}:{col}
      |
 {line:4} | {text}
      | {marker:>0$}",
            text.col + 1,
            file = file,
            line = text.line + 1,
            col = text.col + 1,
            err = err,
            text = text.snippet,
            marker = "^",
        )
    }
}

impl std::error::Error for Error {}

impl Text {
    fn new(content: &str, span: Span) -> Text {
        let (line, col) = span.linecol_in(content);
        let snippet = content.lines().nth(line).unwrap_or("").to_string();
        Text { line, col, snippet }
    }
}