use std::collections::HashMap;
use std::error;
use std::ffi::CString;
use std::fmt;
use std::io;
use std::str;
use std::vec;
use serde_json;
use super::{Error as WabtError, Script, WabtBuf, WabtWriteScriptResult};
mod json;
#[derive(Debug)]
pub enum Error {
IoError(io::Error),
WabtError(WabtError),
Other(String),
WithLineInfo {
line: u64,
error: Box<Error>,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::IoError(ref io_err) => write!(f, "IO error: {}", io_err),
Error::WabtError(ref wabt_err) => write!(f, "wabt error: {:?}", wabt_err),
Error::Other(ref message) => write!(f, "{}", message),
Error::WithLineInfo { line, ref error } => write!(f, "At line {}: {}", line, error),
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::IoError(ref e) => e.description(),
Error::WabtError(_) => "wabt error",
Error::Other(ref msg) => &msg,
Error::WithLineInfo { ref error, .. } => error.description(),
}
}
fn cause(&self) -> Option<&error::Error> {
match *self {
Error::IoError(ref io_err) => Some(io_err),
Error::WabtError(ref wabt_err) => Some(wabt_err),
Error::Other(_) => None,
Error::WithLineInfo { ref error, .. } => Some(error),
}
}
}
impl From<WabtError> for Error {
fn from(e: WabtError) -> Error {
Error::WabtError(e)
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Error {
Error::IoError(e)
}
}
pub trait FromBits<T> {
fn from_bits(other: T) -> Self;
}
impl<T> FromBits<T> for T {
fn from_bits(other: T) -> Self {
other
}
}
impl FromBits<u32> for f32 {
fn from_bits(other: u32) -> Self {
Self::from_bits(other)
}
}
impl FromBits<u64> for f64 {
fn from_bits(other: u64) -> Self {
Self::from_bits(other)
}
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub enum Value<F32 = f32, F64 = f64> {
I32(i32),
I64(i64),
F32(F32),
F64(F64),
}
impl<F32: FromBits<u32>, F64: FromBits<u64>> Value<F32, F64> {
fn decode_f32(val: u32) -> Self {
Value::F32(F32::from_bits(val))
}
fn decode_f64(val: u64) -> Self {
Value::F64(F64::from_bits(val))
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum Action<F32 = f32, F64 = f64> {
Invoke {
module: Option<String>,
field: String,
args: Vec<Value<F32, F64>>,
},
Get {
module: Option<String>,
field: String,
},
}
fn parse_value<F32: FromBits<u32>, F64: FromBits<u64>>(
test_val: &json::RuntimeValue,
) -> Result<Value<F32, F64>, Error> {
fn parse_val<P: str::FromStr>(str_val: &str, str_ty: &str) -> Result<P, Error> {
str_val
.parse()
.map_err(|_| Error::Other(format!("can't parse '{}' as '{}'", str_val, str_ty)))
}
let value = match test_val.value_type.as_ref() {
"i32" => {
let unsigned: u32 = parse_val(&test_val.value, &test_val.value_type)?;
Value::I32(unsigned as i32)
}
"i64" => {
let unsigned: u64 = parse_val(&test_val.value, &test_val.value_type)?;
Value::I64(unsigned as i64)
}
"f32" => {
let unsigned: u32 = parse_val(&test_val.value, &test_val.value_type)?;
Value::decode_f32(unsigned)
}
"f64" => {
let unsigned: u64 = parse_val(&test_val.value, &test_val.value_type)?;
Value::decode_f64(unsigned)
}
other_ty => {
return Err(Error::Other(format!("Unknown type '{}'", other_ty)));
}
};
Ok(value)
}
fn parse_value_list<F32: FromBits<u32>, F64: FromBits<u64>>(
test_vals: &[json::RuntimeValue],
) -> Result<Vec<Value<F32, F64>>, Error> {
test_vals.iter().map(parse_value).collect()
}
fn jstring_to_rstring(jstring: &str) -> String {
let jstring_chars: Vec<u8> = jstring.chars().map(|c| c as u8).collect();
String::from_utf8(jstring_chars).unwrap()
}
fn parse_action<F32: FromBits<u32>, F64: FromBits<u64>>(
test_action: &json::Action,
) -> Result<Action<F32, F64>, Error> {
let action = match *test_action {
json::Action::Invoke {
ref module,
ref field,
ref args,
} => Action::Invoke {
module: module.to_owned(),
field: jstring_to_rstring(field),
args: parse_value_list(args)?,
},
json::Action::Get {
ref module,
ref field,
} => Action::Get {
module: module.to_owned(),
field: jstring_to_rstring(field),
},
};
Ok(action)
}
fn wast2json(source: &[u8], test_filename: &str) -> Result<WabtWriteScriptResult, Error> {
let script = Script::parse(test_filename, source)?;
script.resolve_names()?;
script.validate()?;
let result = script.write_binaries(test_filename)?;
Ok(result)
}
#[derive(Clone, Debug)]
pub struct ModuleBinary {
module: Vec<u8>,
}
impl Eq for ModuleBinary {}
impl PartialEq for ModuleBinary {
fn eq(&self, rhs: &Self) -> bool {
self.module == rhs.module
}
}
impl ModuleBinary {
fn from_vec(module: Vec<u8>) -> ModuleBinary {
ModuleBinary { module }
}
pub fn into_vec(self) -> Vec<u8> {
self.module
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum CommandKind<F32 = f32, F64 = f64> {
Module {
module: ModuleBinary,
name: Option<String>,
},
AssertReturn {
action: Action<F32, F64>,
expected: Vec<Value<F32, F64>>,
},
AssertReturnCanonicalNan {
action: Action<F32, F64>,
},
AssertReturnArithmeticNan {
action: Action<F32, F64>,
},
AssertTrap {
action: Action<F32, F64>,
message: String,
},
AssertInvalid {
module: ModuleBinary,
message: String,
},
AssertMalformed {
module: ModuleBinary,
message: String,
},
AssertUninstantiable {
module: ModuleBinary,
message: String,
},
AssertExhaustion {
action: Action<F32, F64>,
},
AssertUnlinkable {
module: ModuleBinary,
message: String,
},
Register {
name: Option<String>,
as_name: String,
},
PerformAction(Action<F32, F64>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct Command<F32 = f32, F64 = f64> {
pub line: u64,
pub kind: CommandKind<F32, F64>,
}
pub struct ScriptParser<F32 = f32, F64 = f64> {
cmd_iter: vec::IntoIter<json::Command>,
modules: HashMap<CString, WabtBuf>,
_phantom: ::std::marker::PhantomData<(F32, F64)>,
}
impl<F32: FromBits<u32>, F64: FromBits<u64>> ScriptParser<F32, F64> {
pub fn from_source_and_name(source: &[u8], test_filename: &str) -> Result<Self, Error> {
if !test_filename.ends_with(".wast") {
return Err(Error::Other(format!(
"Provided {} should have .wast extension",
test_filename
)));
}
let results = wast2json(source, test_filename)?;
let results = results.take_all().expect("Failed to release");
let json_str = results.json_output_buffer.as_ref();
let spec: json::Spec =
serde_json::from_slice(json_str).expect("Failed to deserialize JSON buffer");
let json::Spec { commands, .. } = spec;
Ok(ScriptParser {
cmd_iter: commands.into_iter(),
modules: results.module_output_buffers,
_phantom: Default::default(),
})
}
pub fn from_str(source: &str) -> Result<Self, Error> {
ScriptParser::from_source_and_name(source.as_bytes(), "test.wast")
}
pub fn next(&mut self) -> Result<Option<Command<F32, F64>>, Error> {
let command = match self.cmd_iter.next() {
Some(cmd) => cmd,
None => return Ok(None),
};
let get_module = |filename: String, s: &Self| {
let filename = CString::new(filename).unwrap();
s.modules
.get(&filename)
.map(|module| ModuleBinary::from_vec(module.as_ref().to_owned()))
.expect("Module referenced in JSON does not exist.")
};
let (line, kind) = match command {
json::Command::Module {
line,
name,
filename,
} => (
line,
CommandKind::Module {
module: get_module(filename, self),
name,
},
),
json::Command::AssertReturn {
line,
action,
expected,
} => (
line,
CommandKind::AssertReturn {
action: parse_action(&action)?,
expected: parse_value_list(&expected)?,
},
),
json::Command::AssertReturnCanonicalNan { line, action } => (
line,
CommandKind::AssertReturnCanonicalNan {
action: parse_action(&action)?,
},
),
json::Command::AssertReturnArithmeticNan { line, action } => (
line,
CommandKind::AssertReturnArithmeticNan {
action: parse_action(&action)?,
},
),
json::Command::AssertExhaustion { line, action } => (
line,
CommandKind::AssertExhaustion {
action: parse_action(&action)?,
},
),
json::Command::AssertTrap { line, action, text } => (
line,
CommandKind::AssertTrap {
action: parse_action(&action)?,
message: text,
},
),
json::Command::AssertInvalid {
line,
filename,
text,
} => (
line,
CommandKind::AssertInvalid {
module: get_module(filename, self),
message: text,
},
),
json::Command::AssertMalformed {
line,
filename,
text,
} => (
line,
CommandKind::AssertMalformed {
module: get_module(filename, self),
message: text,
},
),
json::Command::AssertUnlinkable {
line,
filename,
text,
} => (
line,
CommandKind::AssertUnlinkable {
module: get_module(filename, self),
message: text,
},
),
json::Command::AssertUninstantiable {
line,
filename,
text,
} => (
line,
CommandKind::AssertUninstantiable {
module: get_module(filename, self),
message: text,
},
),
json::Command::Register {
line,
name,
as_name,
} => (line, CommandKind::Register { name, as_name }),
json::Command::Action { line, action } => {
(line, CommandKind::PerformAction(parse_action(&action)?))
}
};
Ok(Some(Command { line, kind }))
}
}