wain 0.1.7

WebAssembly interpreter written in Safe Rust with zero dependencies
#![forbid(unsafe_code)]
#![warn(clippy::dbg_macro)]

use std::env;
use std::fmt;
use std::fs;
use std::io::{self, Read};
use std::process::exit;

enum InputOption {
    Text(String),
    Binary(String),
    Stdin,
}

enum Input {
    Text(String),
    Binary(Vec<u8>),
}

impl InputOption {
    fn filename(&self) -> Option<&str> {
        match self {
            InputOption::Text(f) => Some(f),
            InputOption::Binary(f) => Some(f),
            InputOption::Stdin => None,
        }
    }

    fn read(&self) -> io::Result<Input> {
        match self {
            InputOption::Text(f) => Ok(Input::Text(fs::read_to_string(f)?)),
            InputOption::Stdin => {
                let mut stdin = vec![];
                io::stdin().read_to_end(&mut stdin)?;
                if stdin.starts_with(&[0x00, 0x61, 0x73, 0x6d]) {
                    Ok(Input::Binary(stdin))
                } else {
                    match String::from_utf8(stdin) {
                        Ok(s) => Ok(Input::Text(s)),
                        Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
                    }
                }
            }
            InputOption::Binary(f) => Ok(Input::Binary(fs::read(f)?)),
        }
    }
}

struct Options {
    file: InputOption,
    help: bool,
    validate_only: bool,
    version: bool,
}

fn parse_args() -> Result<Options, String> {
    let mut file = InputOption::Stdin;
    let mut help = false;
    let mut validate_only = false;
    let mut version = false;

    for arg in env::args().skip(1) {
        if arg == "--help" || arg == "-h" {
            help = true;
            break;
        }

        if arg == "--version" || arg == "-v" {
            version = true;
            break;
        }

        if arg == "--validate-only" {
            validate_only = true;
            continue;
        }

        if let Some(f) = file.filename() {
            return Err(format!(
                "Only one file can be specified for now. But '{}' and '{}' are specified. See --help",
                f, arg
            ));
        }

        if arg.ends_with(".wasm") {
            file = InputOption::Binary(arg);
            continue;
        }

        if arg.ends_with(".wat") {
            file = InputOption::Text(arg);
            continue;
        }

        return Err(format!(
            "File '{}' ends with neither '.wasm' nor '.wat'. See --help",
            arg
        ));
    }

    Ok(Options {
        file,
        help,
        version,
        validate_only,
    })
}

fn help() -> ! {
    println!(
        "\
wain: a WebAssembly INterpreter written in Safe Rust with zero dependencies

USAGE:
    wain [OPTIONS] [{{file}}]

OPTIONS:
    --validate-only : Only validate the Wasm module
    --help | -h     : Show this help
    --version | -v  : Show version

ARGUMENTS:
    Currently only a single '.wat' file or '.wasm' file can be specified.
    If no file is given, STDIN will be interpreted as binary or text Wasm
    module. wain automatically detects binary-format or text-format from the
    input.

REPOSITORY:
    https://github.com/rhysd/wain
"
    );
    exit(0);
}

fn unwrap<T, E: fmt::Display>(phase: &'static str, result: Result<T, E>) -> T {
    match result {
        Ok(r) => r,
        Err(e) => {
            eprintln!("Error on {}: {}", phase, e);
            exit(1);
        }
    }
}

fn interpret<S: wain_ast::source::Source>(ast: wain_ast::Root<'_, S>, validate_only: bool) {
    unwrap("validation", wain_validate::validate(&ast));
    if !validate_only {
        unwrap("executing wasm module", wain_exec::execute(&ast.module));
    }
}

fn main() {
    let opts = unwrap("parsing command line", parse_args());

    if opts.help {
        help();
    }

    if opts.version {
        println!("{}", env!("CARGO_PKG_VERSION"));
        exit(0);
    }

    match unwrap("reading input", opts.file.read()) {
        #[cfg(feature = "binary")]
        Input::Binary(bin) => interpret(
            unwrap("parsing binary format", wain_syntax_binary::parse(&bin)),
            opts.validate_only,
        ),
        #[cfg(feature = "text")]
        Input::Text(text) => interpret(
            unwrap("parsing text format", wain_syntax_text::parse(&text)),
            opts.validate_only,
        ),
        #[cfg(not(all(feature = "binary", feature = "text")))]
        input => {
            let format = match input {
                Input::Text(_) => "text",
                Input::Binary(_) => "binary",
            };
            eprintln!("Executing {0} format is unsupported. Build with '{0}' feature", format,);
            exit(1);
        }
    }
}