From 20b888bfa0aa4ae6fb5a7efb5ccdd7f35de67c37 Mon Sep 17 00:00:00 2001 From: Rerumu Date: Sun, 12 Jun 2022 19:55:42 -0400 Subject: [PATCH] Compartmentalize tests --- dev-test/tests/luajit_translate.rs | 169 +++++++++++++ dev-test/tests/luau_translate.rs | 176 +++++++++++++ dev-test/tests/spec_translate.rs | 389 ----------------------------- dev-test/tests/target.rs | 192 ++++++++++++++ 4 files changed, 537 insertions(+), 389 deletions(-) create mode 100644 dev-test/tests/luajit_translate.rs create mode 100644 dev-test/tests/luau_translate.rs delete mode 100644 dev-test/tests/spec_translate.rs create mode 100644 dev-test/tests/target.rs diff --git a/dev-test/tests/luajit_translate.rs b/dev-test/tests/luajit_translate.rs new file mode 100644 index 0000000..181f399 --- /dev/null +++ b/dev-test/tests/luajit_translate.rs @@ -0,0 +1,169 @@ +use std::{ + io::{Result, Write}, + path::PathBuf, +}; + +use wast::{ + core::{Expression, Instruction}, + token::{Float32, Float64}, + AssertExpression, WastExecute, WastInvoke, +}; + +use target::{Target, TypedModule}; + +mod target; + +static ASSERTION: &str = include_str!("assertion.lua"); + +struct LuaJIT; + +impl LuaJIT { + fn write_expression(data: &Expression, w: &mut dyn Write) -> Result<()> { + let data = &data.instrs; + + assert_eq!(data.len(), 1, "Only one instruction supported"); + + match &data[0] { + Instruction::I32Const(v) => write!(w, "{v}"), + Instruction::I64Const(v) => write!(w, "{v}LL"), + Instruction::F32Const(v) => write!(w, "{}", f32::from_bits(v.bits)), + Instruction::F64Const(v) => write!(w, "{}", f64::from_bits(v.bits)), + _ => panic!("Unsupported instruction"), + } + } + + write_assert_number!(write_assert_maybe_f32, Float32, f32); + write_assert_number!(write_assert_maybe_f64, Float64, f64); + + fn write_simple_expression(data: &AssertExpression, w: &mut dyn Write) -> Result<()> { + match data { + AssertExpression::I32(v) => write!(w, "{v}"), + AssertExpression::I64(v) => write!(w, "{v}LL"), + AssertExpression::F32(v) => Self::write_assert_maybe_f32(v, w), + AssertExpression::F64(v) => Self::write_assert_maybe_f64(v, w), + _ => panic!("Unsupported expression"), + } + } + + fn write_call_of(handler: &str, data: &WastInvoke, w: &mut dyn Write) -> Result<()> { + let name = TypedModule::resolve_id(data.module); + let func = data.name; + + write!(w, "{handler}(")?; + write!(w, r#"loaded["{name}"].func_list["{func}"]"#)?; + + data.args.iter().try_for_each(|v| { + write!(w, ", ")?; + Self::write_expression(v, w) + })?; + + write!(w, ")") + } +} + +impl Target for LuaJIT { + fn executable() -> String { + std::env::var("LUAJIT_PATH").unwrap_or_else(|_| "luajit".to_string()) + } + + fn write_register(post: &str, pre: &str, w: &mut dyn Write) -> Result<()> { + writeln!(w, r#"linked["{post}"] = loaded["{pre}"]"#) + } + + fn write_invoke(data: &WastInvoke, w: &mut dyn Write) -> Result<()> { + Self::write_call_of("raw_invoke", data, w)?; + writeln!(w) + } + + fn write_assert_trap(data: &WastExecute, w: &mut dyn Write) -> Result<()> { + match data { + WastExecute::Invoke(data) => { + Self::write_call_of("assert_trap", data, w)?; + writeln!(w) + } + WastExecute::Get { module, global } => { + let name = TypedModule::resolve_id(*module); + + write!(w, "assert_neq(")?; + write!(w, r#"loaded["{name}"].global_list["{global}"].value"#)?; + writeln!(w, ", nil)") + } + WastExecute::Wat(_) => panic!("Wat not supported"), + } + } + + fn write_assert_return( + data: &WastExecute, + result: &[AssertExpression], + w: &mut dyn Write, + ) -> Result<()> { + match data { + WastExecute::Invoke(data) => { + write!(w, "assert_return(")?; + write!(w, "{{")?; + Self::write_call_of("raw_invoke", data, w)?; + write!(w, "}}, {{")?; + + for v in result { + Self::write_simple_expression(v, w)?; + write!(w, ", ")?; + } + + writeln!(w, "}})") + } + WastExecute::Get { module, global } => { + let name = TypedModule::resolve_id(*module); + + write!(w, "assert_eq(")?; + write!(w, r#"loaded["{name}"].global_list["{global}"].value"#)?; + write!(w, ", ")?; + Self::write_simple_expression(&result[0], w)?; + writeln!(w, ")") + } + WastExecute::Wat(_) => panic!("Wat not supported"), + } + } + + fn write_assert_exhaustion(data: &WastInvoke, w: &mut dyn Write) -> Result<()> { + Self::write_call_of("assert_exhaustion", data, w)?; + writeln!(w) + } + + fn write_runtime(w: &mut dyn Write) -> Result<()> { + let runtime = codegen_luajit::RUNTIME; + + writeln!(w, "{ASSERTION}")?; + writeln!(w, "local rt = (function() {runtime} end)()") + } + + fn write_module(typed: &TypedModule, w: &mut dyn Write) -> Result<()> { + write!(w, r#"loaded["{}"] = (function() "#, typed.name())?; + codegen_luajit::from_module_typed(typed.module(), typed.type_info(), w)?; + writeln!(w, "end)()(nil)") + } +} + +static DO_NOT_RUN: [&str; 8] = [ + "binary-leb128.wast", + "conversions.wast", + "float_exprs.wast", + "float_literals.wast", + "float_memory.wast", + "float_misc.wast", + "names.wast", + "skip-stack-guard-page.wast", +]; + +#[test_generator::test_resources("dev-test/spec/*.wast")] +fn translate_file(path: PathBuf) { + let path = path.strip_prefix("dev-test/").unwrap(); + let name = path.file_name().unwrap().to_str().unwrap(); + + if DO_NOT_RUN.contains(&name) { + return; + } + + let source = std::fs::read_to_string(path).unwrap(); + + LuaJIT::test(name, &source).unwrap(); +} diff --git a/dev-test/tests/luau_translate.rs b/dev-test/tests/luau_translate.rs new file mode 100644 index 0000000..e1c254b --- /dev/null +++ b/dev-test/tests/luau_translate.rs @@ -0,0 +1,176 @@ +use std::{ + io::{Result, Write}, + path::PathBuf, +}; + +use wast::{ + core::{Expression, Instruction}, + token::{Float32, Float64}, + AssertExpression, WastExecute, WastInvoke, +}; + +use target::{Target, TypedModule}; + +mod target; + +static ASSERTION: &str = include_str!("assertion.lua"); + +struct Luau; + +impl Luau { + fn write_i64(data: i64, w: &mut dyn Write) -> Result<()> { + let data_1 = (data & 0xFFFFFFFF) as u32; + let data_2 = (data >> 32 & 0xFFFFFFFF) as u32; + + write!(w, "rt.num.from_u32({data_1}, {data_2})") + } + + fn write_expression(data: &Expression, w: &mut dyn Write) -> Result<()> { + let data = &data.instrs; + + assert_eq!(data.len(), 1, "Only one instruction supported"); + + match &data[0] { + Instruction::I32Const(v) => write!(w, "{v}"), + Instruction::I64Const(v) => Self::write_i64(*v, w), + Instruction::F32Const(v) => write!(w, "{}", f32::from_bits(v.bits)), + Instruction::F64Const(v) => write!(w, "{}", f64::from_bits(v.bits)), + _ => panic!("Unsupported instruction"), + } + } + + write_assert_number!(write_assert_maybe_f32, Float32, f32); + write_assert_number!(write_assert_maybe_f64, Float64, f64); + + fn write_simple_expression(data: &AssertExpression, w: &mut dyn Write) -> Result<()> { + match data { + AssertExpression::I32(v) => write!(w, "{v}"), + AssertExpression::I64(v) => Self::write_i64(*v, w), + AssertExpression::F32(v) => Self::write_assert_maybe_f32(v, w), + AssertExpression::F64(v) => Self::write_assert_maybe_f64(v, w), + _ => panic!("Unsupported expression"), + } + } + + fn write_call_of(handler: &str, data: &WastInvoke, w: &mut dyn Write) -> Result<()> { + let name = TypedModule::resolve_id(data.module); + let func = data.name; + + write!(w, "{handler}(")?; + write!(w, r#"loaded["{name}"].func_list["{func}"]"#)?; + + data.args.iter().try_for_each(|v| { + write!(w, ", ")?; + Self::write_expression(v, w) + })?; + + write!(w, ")") + } +} + +impl Target for Luau { + fn executable() -> String { + std::env::var("LUAU_PATH").unwrap_or_else(|_| "luau".to_string()) + } + + fn write_register(post: &str, pre: &str, w: &mut dyn Write) -> Result<()> { + writeln!(w, r#"linked["{post}"] = loaded["{pre}"]"#) + } + + fn write_invoke(data: &WastInvoke, w: &mut dyn Write) -> Result<()> { + Self::write_call_of("raw_invoke", data, w)?; + writeln!(w) + } + + fn write_assert_trap(data: &WastExecute, w: &mut dyn Write) -> Result<()> { + match data { + WastExecute::Invoke(data) => { + Self::write_call_of("assert_trap", data, w)?; + writeln!(w) + } + WastExecute::Get { module, global } => { + let name = TypedModule::resolve_id(*module); + + write!(w, "assert_neq(")?; + write!(w, r#"loaded["{name}"].global_list["{global}"].value"#)?; + writeln!(w, ", nil)") + } + WastExecute::Wat(_) => panic!("Wat not supported"), + } + } + + fn write_assert_return( + data: &WastExecute, + result: &[AssertExpression], + w: &mut dyn Write, + ) -> Result<()> { + match data { + WastExecute::Invoke(data) => { + write!(w, "assert_return(")?; + write!(w, "{{")?; + Self::write_call_of("raw_invoke", data, w)?; + write!(w, "}}, {{")?; + + for v in result { + Self::write_simple_expression(v, w)?; + write!(w, ", ")?; + } + + writeln!(w, "}})") + } + WastExecute::Get { module, global } => { + let name = TypedModule::resolve_id(*module); + + write!(w, "assert_eq(")?; + write!(w, r#"loaded["{name}"].global_list["{global}"].value"#)?; + write!(w, ", ")?; + Self::write_simple_expression(&result[0], w)?; + writeln!(w, ")") + } + WastExecute::Wat(_) => panic!("Wat not supported"), + } + } + + fn write_assert_exhaustion(data: &WastInvoke, w: &mut dyn Write) -> Result<()> { + Self::write_call_of("assert_exhaustion", data, w)?; + writeln!(w) + } + + fn write_runtime(w: &mut dyn Write) -> Result<()> { + let runtime = codegen_luau::RUNTIME; + + writeln!(w, "{ASSERTION}")?; + writeln!(w, "local rt = (function() {runtime} end)()") + } + + fn write_module(typed: &TypedModule, w: &mut dyn Write) -> Result<()> { + write!(w, r#"loaded["{}"] = (function() "#, typed.name())?; + codegen_luau::from_module_typed(typed.module(), typed.type_info(), w)?; + writeln!(w, "end)()(nil)") + } +} + +static DO_NOT_RUN: [&str; 8] = [ + "binary-leb128.wast", + "conversions.wast", + "float_exprs.wast", + "float_literals.wast", + "float_memory.wast", + "float_misc.wast", + "names.wast", + "skip-stack-guard-page.wast", +]; + +#[test_generator::test_resources("dev-test/spec/*.wast")] +fn translate_file(path: PathBuf) { + let path = path.strip_prefix("dev-test/").unwrap(); + let name = path.file_name().unwrap().to_str().unwrap(); + + if DO_NOT_RUN.contains(&name) { + return; + } + + let source = std::fs::read_to_string(path).unwrap(); + + Luau::test(name, &source).unwrap(); +} diff --git a/dev-test/tests/spec_translate.rs b/dev-test/tests/spec_translate.rs deleted file mode 100644 index f749e0b..0000000 --- a/dev-test/tests/spec_translate.rs +++ /dev/null @@ -1,389 +0,0 @@ -#![cfg(test)] - -use std::{ - io::{Result as IResult, Write}, - marker::PhantomData, - path::{Path, PathBuf}, - process::Command, -}; - -use parity_wasm::elements::Module as BinModule; -use wast::{ - core::{Expression, Instruction, Module as AstModule}, - parser::ParseBuffer, - token::{Float32, Float64, Id}, - AssertExpression, NanPattern, QuoteWat, Wast, WastDirective, WastExecute, WastInvoke, Wat, -}; - -use wasm_ast::builder::TypeInfo; - -static ASSERTION: &str = include_str!("assertion.lua"); - -macro_rules! write_assert_number { - ($name:ident, $generic:ty, $reader:ty) => { - fn $name(data: &NanPattern<$generic>, w: &mut dyn Write) -> IResult<()> { - match data { - NanPattern::CanonicalNan => write!(w, "LUA_NAN_CANONICAL"), - NanPattern::ArithmeticNan => write!(w, "LUA_NAN_ARITHMETIC"), - NanPattern::Value(data) => { - let number = <$reader>::from_bits(data.bits); - let sign = if number.is_sign_negative() { "-" } else { "" }; - - if number.is_infinite() { - write!(w, "{sign}LUA_INFINITY") - } else if number.is_nan() { - write!(w, "{sign}LUA_NAN_DEFAULT") - } else { - write!(w, "{number}") - } - } - } - } - }; -} - -struct TypedModule<'a> { - name: &'a str, - module: &'a BinModule, - type_info: TypeInfo<'a>, -} - -impl<'a> TypedModule<'a> { - fn resolve_id(id: Option) -> &str { - id.map_or("temp", |v| v.name()) - } - - fn from_id(id: Option>, module: &'a BinModule) -> Self { - Self { - module, - name: Self::resolve_id(id), - type_info: TypeInfo::from_module(module), - } - } -} - -trait Target: Sized { - fn executable() -> String; - - fn write_register(post: &str, pre: &str, w: &mut dyn Write) -> IResult<()>; - - fn write_invoke(data: &WastInvoke, w: &mut dyn Write) -> IResult<()>; - - fn write_assert_trap(data: &WastExecute, w: &mut dyn Write) -> IResult<()>; - - fn write_assert_return( - data: &WastExecute, - result: &[AssertExpression], - w: &mut dyn Write, - ) -> IResult<()>; - - fn write_assert_exhaustion(data: &WastInvoke, w: &mut dyn Write) -> IResult<()>; - - fn write_runtime(w: &mut dyn Write) -> IResult<()>; - - fn write_module(typed: &TypedModule, w: &mut dyn Write) -> IResult<()>; -} - -struct LuaJIT; - -impl LuaJIT { - fn write_expression(data: &Expression, w: &mut dyn Write) -> IResult<()> { - let data = &data.instrs; - - assert_eq!(data.len(), 1, "Only one instruction supported"); - - match &data[0] { - Instruction::I32Const(v) => write!(w, "{v}"), - Instruction::I64Const(v) => write!(w, "{v}LL"), - Instruction::F32Const(v) => write!(w, "{}", f32::from_bits(v.bits)), - Instruction::F64Const(v) => write!(w, "{}", f64::from_bits(v.bits)), - _ => panic!("Unsupported instruction"), - } - } - - write_assert_number!(write_assert_maybe_f32, Float32, f32); - write_assert_number!(write_assert_maybe_f64, Float64, f64); - - fn write_simple_expression(data: &AssertExpression, w: &mut dyn Write) -> IResult<()> { - match data { - AssertExpression::I32(v) => write!(w, "{v}"), - AssertExpression::I64(v) => write!(w, "{v}LL"), - AssertExpression::F32(v) => Self::write_assert_maybe_f32(v, w), - AssertExpression::F64(v) => Self::write_assert_maybe_f64(v, w), - _ => panic!("Unsupported expression"), - } - } - - fn write_call_of(handler: &str, data: &WastInvoke, w: &mut dyn Write) -> IResult<()> { - let name = TypedModule::resolve_id(data.module); - let func = data.name; - - write!(w, "{handler}(")?; - write!(w, r#"loaded["{name}"].func_list["{func}"]"#)?; - - data.args.iter().try_for_each(|v| { - write!(w, ", ")?; - Self::write_expression(v, w) - })?; - - write!(w, ")") - } -} - -impl Target for LuaJIT { - fn executable() -> String { - std::env::var("LUAJIT_PATH").unwrap_or_else(|_| "luajit".to_string()) - } - - fn write_register(post: &str, pre: &str, w: &mut dyn Write) -> IResult<()> { - writeln!(w, r#"linked["{post}"] = loaded["{pre}"]"#) - } - - fn write_invoke(data: &WastInvoke, w: &mut dyn Write) -> IResult<()> { - Self::write_call_of("raw_invoke", data, w)?; - writeln!(w) - } - - fn write_assert_trap(data: &WastExecute, w: &mut dyn Write) -> IResult<()> { - match data { - WastExecute::Invoke(data) => { - Self::write_call_of("assert_trap", data, w)?; - writeln!(w) - } - WastExecute::Get { module, global } => { - let name = TypedModule::resolve_id(*module); - - write!(w, "assert_neq(")?; - write!(w, r#"loaded["{name}"].global_list["{global}"].value"#)?; - writeln!(w, ", nil)") - } - WastExecute::Wat(_) => panic!("Wat not supported"), - } - } - - fn write_assert_return( - data: &WastExecute, - result: &[AssertExpression], - w: &mut dyn Write, - ) -> IResult<()> { - match data { - WastExecute::Invoke(data) => { - write!(w, "assert_return(")?; - write!(w, "{{")?; - Self::write_call_of("raw_invoke", data, w)?; - write!(w, "}}, {{")?; - - for v in result { - Self::write_simple_expression(v, w)?; - write!(w, ", ")?; - } - - writeln!(w, "}})") - } - WastExecute::Get { module, global } => { - let name = TypedModule::resolve_id(*module); - - write!(w, "assert_eq(")?; - write!(w, r#"loaded["{name}"].global_list["{global}"].value"#)?; - write!(w, ", ")?; - Self::write_simple_expression(&result[0], w)?; - writeln!(w, ")") - } - WastExecute::Wat(_) => panic!("Wat not supported"), - } - } - - fn write_assert_exhaustion(data: &WastInvoke, w: &mut dyn Write) -> IResult<()> { - Self::write_call_of("assert_exhaustion", data, w)?; - writeln!(w) - } - - fn write_runtime(w: &mut dyn Write) -> IResult<()> { - let runtime = codegen_luajit::RUNTIME; - - writeln!(w, "{ASSERTION}")?; - writeln!(w, "local rt = (function() {runtime} end)()") - } - - fn write_module(typed: &TypedModule, w: &mut dyn Write) -> IResult<()> { - write!(w, r#"loaded["{}"] = (function() "#, typed.name)?; - codegen_luajit::from_module_typed(typed.module, &typed.type_info, w)?; - writeln!(w, "end)()(nil)") - } -} - -struct Luau; - -impl Target for Luau { - fn executable() -> String { - std::env::var("LUAU_PATH").unwrap_or_else(|_| "luajit".to_string()) - } - - fn write_register(post: &str, pre: &str, w: &mut dyn Write) -> IResult<()> { - writeln!(w, r#"linked["{post}"] = loaded["{pre}"]"#) - } - - fn write_invoke(data: &WastInvoke, w: &mut dyn Write) -> IResult<()> { - todo!(); - } - - fn write_assert_trap(data: &WastExecute, w: &mut dyn Write) -> IResult<()> { - todo!(); - } - - fn write_assert_return( - data: &WastExecute, - result: &[AssertExpression], - w: &mut dyn Write, - ) -> IResult<()> { - todo!(); - } - - fn write_assert_exhaustion(data: &WastInvoke, w: &mut dyn Write) -> IResult<()> { - todo!(); - } - - fn write_runtime(w: &mut dyn Write) -> IResult<()> { - let runtime = codegen_luau::RUNTIME; - - writeln!(w, "{ASSERTION}")?; - writeln!(w, "local rt = (function() {runtime} end)()") - } - - fn write_module(typed: &TypedModule, w: &mut dyn Write) -> IResult<()> { - write!(w, r#"loaded["{}"] = (function() "#, typed.name)?; - codegen_luau::from_module_typed(typed.module, &typed.type_info, w)?; - writeln!(w, "end)()(nil)") - } -} - -fn try_into_ast_module(data: QuoteWat) -> Option { - if let QuoteWat::Wat(Wat::Module(data)) = data { - Some(data) - } else { - None - } -} - -// Only proceed with tests that observe any state. -fn parse_and_validate<'a>(buffer: &'a ParseBuffer) -> Option> { - let parsed: Wast = wast::parser::parse(buffer).unwrap(); - let observer = parsed.directives.iter().any(|v| { - matches!( - v, - WastDirective::AssertTrap { .. } - | WastDirective::AssertReturn { .. } - | WastDirective::AssertExhaustion { .. } - ) - }); - - observer.then(|| parsed) -} - -struct Tester { - _marker: PhantomData, -} - -impl Tester { - fn test(name: &str, source: &str) -> IResult<()> { - if let Some(data) = Self::run_generation(source)? { - let temp = PathBuf::from(env!("CARGO_TARGET_TMPDIR")) - .join(name) - .with_extension("wast.lua"); - - std::fs::write(&temp, &data)?; - Self::run_command(&temp)?; - } - - Ok(()) - } - - fn write_variant(variant: WastDirective, w: &mut dyn Write) -> IResult<()> { - match variant { - WastDirective::Wat(data) => { - let mut ast = try_into_ast_module(data).expect("Must be a module"); - let bytes = ast.encode().unwrap(); - let temp = parity_wasm::deserialize_buffer(&bytes).unwrap(); - let typed = TypedModule::from_id(ast.id, &temp); - - T::write_module(&typed, w)?; - } - WastDirective::Register { name, module, .. } => { - let pre = TypedModule::resolve_id(module); - - T::write_register(name, pre, w)?; - } - WastDirective::Invoke(data) => { - T::write_invoke(&data, w)?; - } - WastDirective::AssertTrap { exec, .. } => { - T::write_assert_trap(&exec, w)?; - } - WastDirective::AssertReturn { exec, results, .. } => { - T::write_assert_return(&exec, &results, w)?; - } - WastDirective::AssertExhaustion { call, .. } => { - T::write_assert_exhaustion(&call, w)?; - } - _ => {} - } - - Ok(()) - } - - fn run_command(file: &Path) -> IResult<()> { - let result = Command::new(T::executable()).arg(file).output()?; - - if result.status.success() { - Ok(()) - } else { - let data = String::from_utf8_lossy(&result.stderr); - - panic!("{}", data); - } - } - - fn run_generation(source: &str) -> IResult>> { - let lexed = ParseBuffer::new(source).expect("Failed to tokenize"); - let parsed = match parse_and_validate(&lexed) { - Some(v) => v, - None => return Ok(None), - }; - - let mut data = Vec::new(); - - T::write_runtime(&mut data)?; - - for variant in parsed.directives { - Self::write_variant(variant, &mut data)?; - } - - Ok(Some(data)) - } -} - -static DO_NOT_RUN: [&str; 8] = [ - "binary-leb128.wast", - "conversions.wast", - "float_exprs.wast", - "float_literals.wast", - "float_memory.wast", - "float_misc.wast", - "names.wast", - "skip-stack-guard-page.wast", -]; - -#[test_generator::test_resources("dev-test/spec/*.wast")] -fn translate_file(path: PathBuf) { - let path = path.strip_prefix("dev-test/").unwrap(); - let name = path.file_name().unwrap().to_str().unwrap(); - - if DO_NOT_RUN.contains(&name) { - return; - } - - let source = std::fs::read_to_string(path).unwrap(); - - Tester::::test(name, &source).unwrap(); - // Tester::::test(name, &source).unwrap(); -} diff --git a/dev-test/tests/target.rs b/dev-test/tests/target.rs new file mode 100644 index 0000000..2a6438c --- /dev/null +++ b/dev-test/tests/target.rs @@ -0,0 +1,192 @@ +use std::{ + io::{Result, Write}, + path::{Path, PathBuf}, + process::Command, +}; + +use parity_wasm::elements::Module as BinModule; +use wast::{ + core::Module as AstModule, parser::ParseBuffer, token::Id, AssertExpression, QuoteWat, Wast, + WastDirective, WastExecute, WastInvoke, Wat, +}; + +use wasm_ast::builder::TypeInfo; + +#[macro_export] +macro_rules! write_assert_number { + ($name:ident, $generic:ty, $reader:ty) => { + fn $name(data: &wast::NanPattern<$generic>, w: &mut dyn Write) -> Result<()> { + use wast::NanPattern; + + match data { + NanPattern::CanonicalNan => write!(w, "LUA_NAN_CANONICAL"), + NanPattern::ArithmeticNan => write!(w, "LUA_NAN_ARITHMETIC"), + NanPattern::Value(data) => { + let number = <$reader>::from_bits(data.bits); + let sign = if number.is_sign_negative() { "-" } else { "" }; + + if number.is_infinite() { + write!(w, "{sign}LUA_INFINITY") + } else if number.is_nan() { + write!(w, "{sign}LUA_NAN_DEFAULT") + } else { + write!(w, "{number}") + } + } + } + } + }; +} + +pub struct TypedModule<'a> { + name: &'a str, + module: &'a BinModule, + type_info: TypeInfo<'a>, +} + +impl<'a> TypedModule<'a> { + pub fn resolve_id(id: Option) -> &str { + id.map_or("temp", |v| v.name()) + } + + pub fn name(&self) -> &str { + self.name + } + + pub fn module(&self) -> &BinModule { + self.module + } + + pub fn type_info(&self) -> &TypeInfo<'a> { + &self.type_info + } + + fn from_id(id: Option>, module: &'a BinModule) -> Self { + Self { + module, + name: Self::resolve_id(id), + type_info: TypeInfo::from_module(module), + } + } +} + +fn try_into_ast_module(data: QuoteWat) -> Option { + if let QuoteWat::Wat(Wat::Module(data)) = data { + Some(data) + } else { + None + } +} + +// Only proceed with tests that observe any state. +fn parse_and_validate<'a>(buffer: &'a ParseBuffer) -> Option> { + let parsed: Wast = wast::parser::parse(buffer).unwrap(); + let observer = parsed.directives.iter().any(|v| { + matches!( + v, + WastDirective::AssertTrap { .. } + | WastDirective::AssertReturn { .. } + | WastDirective::AssertExhaustion { .. } + ) + }); + + observer.then(|| parsed) +} + +pub trait Target: Sized { + fn executable() -> String; + + fn write_register(post: &str, pre: &str, w: &mut dyn Write) -> Result<()>; + + fn write_invoke(data: &WastInvoke, w: &mut dyn Write) -> Result<()>; + + fn write_assert_trap(data: &WastExecute, w: &mut dyn Write) -> Result<()>; + + fn write_assert_return( + data: &WastExecute, + result: &[AssertExpression], + w: &mut dyn Write, + ) -> Result<()>; + + fn write_assert_exhaustion(data: &WastInvoke, w: &mut dyn Write) -> Result<()>; + + fn write_runtime(w: &mut dyn Write) -> Result<()>; + + fn write_module(typed: &TypedModule, w: &mut dyn Write) -> Result<()>; + + fn write_variant(variant: WastDirective, w: &mut dyn Write) -> Result<()> { + match variant { + WastDirective::Wat(data) => { + let mut ast = try_into_ast_module(data).expect("Must be a module"); + let bytes = ast.encode().unwrap(); + let temp = parity_wasm::deserialize_buffer(&bytes).unwrap(); + let typed = TypedModule::from_id(ast.id, &temp); + + Self::write_module(&typed, w)?; + } + WastDirective::Register { name, module, .. } => { + let pre = TypedModule::resolve_id(module); + + Self::write_register(name, pre, w)?; + } + WastDirective::Invoke(data) => { + Self::write_invoke(&data, w)?; + } + WastDirective::AssertTrap { exec, .. } => { + Self::write_assert_trap(&exec, w)?; + } + WastDirective::AssertReturn { exec, results, .. } => { + Self::write_assert_return(&exec, &results, w)?; + } + WastDirective::AssertExhaustion { call, .. } => { + Self::write_assert_exhaustion(&call, w)?; + } + _ => {} + } + + Ok(()) + } + + fn run_command(file: &Path) -> Result<()> { + let result = Command::new(Self::executable()).arg(file).output()?; + + if result.status.success() { + Ok(()) + } else { + let data = String::from_utf8_lossy(&result.stderr); + + panic!("{}", data); + } + } + + fn run_generation(source: &str) -> Result>> { + let lexed = ParseBuffer::new(source).expect("Failed to tokenize"); + let parsed = match parse_and_validate(&lexed) { + Some(v) => v, + None => return Ok(None), + }; + + let mut data = Vec::new(); + + Self::write_runtime(&mut data)?; + + for variant in parsed.directives { + Self::write_variant(variant, &mut data)?; + } + + Ok(Some(data)) + } + + fn test(name: &str, source: &str) -> Result<()> { + if let Some(data) = Self::run_generation(source)? { + let temp = PathBuf::from(env!("CARGO_TARGET_TMPDIR")) + .join(name) + .with_extension("wast.lua"); + + std::fs::write(&temp, &data)?; + Self::run_command(&temp)?; + } + + Ok(()) + } +}