use std::env;
use std::error::Error;
use airlang_dev::init_logger;
use log::error;
use log::trace;
use crate::cfg::comp::BaseCompCfg;
use crate::cfg::prelude;
use crate::semantics::cfg::Cfg;
use crate::semantics::core::Eval;
use crate::semantics::func::DynFunc;
use crate::semantics::val::Val;
use crate::type_::Key;
const MAIN_DELIMITER: &str = "\n=====\n";
const SUB_DELIMITER: &str = "\n-----\n";
pub(crate) fn parse_test_file<'a, const N: usize>(
input: &'a str, file_name: &str,
) -> Vec<[&'a str; N]> {
let mut cases = Vec::with_capacity(100);
if input.is_empty() {
return cases;
}
let cases_str = input.split(MAIN_DELIMITER);
for case_str in cases_str {
let split_err = format!("file {file_name} case ({case_str}): invalid test case format");
let case: Vec<&str> = case_str.split(SUB_DELIMITER).collect();
let case: [&str; N] = case.try_into().expect(&split_err);
cases.push(case);
}
cases
}
fn test(input: &str, file_name: &str) -> Result<(), Box<dyn Error>> {
init_logger();
let mut cfg = BaseCompCfg::generate();
let ctx = prelude(&mut cfg);
test_interpret(cfg, ctx, input, file_name)
}
fn test_interpret(cfg: Cfg, ctx: Val, input: &str, file_name: &str) -> Result<(), Box<dyn Error>> {
let backup_cfg = cfg;
let backup_ctx = ctx;
for [title, i, o] in parse_test_file::<3>(input, file_name) {
let src: Val = i.parse().map_err(|e| {
eprintln!("file {file_name} case ({title}): input ({i}) parse failed\n{e}");
e
})?;
trace!("file {file_name} case ({title})");
let mut cfg = backup_cfg.clone();
let mut ctx = backup_ctx.clone();
let ret = Eval.call(&mut cfg, &mut ctx, src);
log_abort(&cfg);
let ret_expected = o.parse().map_err(|e| {
eprintln!("file {file_name} case ({title}): output ({o}) parse failed\n{e}");
e
})?;
let show_env = if let Ok(show) = env::var("AIR_TEST_SHOW_CFG_CTX") {
show == "1" || show == "on" || show == "true"
} else {
false
};
if show_env {
assert_eq!(
ret, ret_expected,
"file {file_name} case({title}) input({i}): expect({o}) != real({ret:#})\n\
current ctx:\n{:#}\ncurrent cfg:\n{:#}",
ctx, cfg
);
} else {
assert_eq!(
ret, ret_expected,
"file {file_name} case({title}) input({i}): expect({o}) != real({ret:#})",
);
}
}
Ok(())
}
fn log_abort(cfg: &Cfg) {
if !cfg.is_aborted() {
return;
}
let type_ = cfg.import(Key::from_str_unchecked(Cfg::ABORT_TYPE));
let msg = cfg.import(Key::from_str_unchecked(Cfg::ABORT_MSG));
match (type_, msg) {
(Some(type_), Some(msg)) => error!("aborted by {type_}: {msg}"),
(None, Some(msg)) => error!("aborted: {msg}"),
(Some(type_), None) => error!("aborted by {type_}"),
(None, None) => error!("aborted"),
}
}
#[test]
fn test_unit() -> Result<(), Box<dyn Error>> {
test(include_str!("test/unit.air"), "test/unit.air")
}
#[test]
fn test_bit() -> Result<(), Box<dyn Error>> {
test(include_str!("test/bit.air"), "test/bit.air")
}
#[test]
fn test_key() -> Result<(), Box<dyn Error>> {
test(include_str!("test/key.air"), "test/key.air")
}
#[test]
fn test_text() -> Result<(), Box<dyn Error>> {
test(include_str!("test/text.air"), "test/text.air")
}
#[test]
fn test_integer() -> Result<(), Box<dyn Error>> {
test(include_str!("test/integer.air"), "test/integer.air")
}
#[test]
fn test_decimal() -> Result<(), Box<dyn Error>> {
test(include_str!("test/decimal.air"), "test/decimal.air")
}
#[test]
fn test_byte() -> Result<(), Box<dyn Error>> {
test(include_str!("test/byte.air"), "test/byte.air")
}
#[test]
fn test_cell() -> Result<(), Box<dyn Error>> {
test(include_str!("test/cell.air"), "test/cell.air")
}
#[test]
fn test_pair() -> Result<(), Box<dyn Error>> {
test(include_str!("test/pair.air"), "test/pair.air")
}
#[test]
fn test_call() -> Result<(), Box<dyn Error>> {
test(include_str!("test/call.air"), "test/call.air")
}
#[test]
fn test_list() -> Result<(), Box<dyn Error>> {
test(include_str!("test/list.air"), "test/list.air")
}
#[test]
fn test_map() -> Result<(), Box<dyn Error>> {
test(include_str!("test/map.air"), "test/map.air")
}
#[test]
fn test_link() -> Result<(), Box<dyn Error>> {
test(include_str!("test/link.air"), "test/link.air")
}
#[test]
fn test_config() -> Result<(), Box<dyn Error>> {
test(include_str!("test/config.air"), "test/config.air")
}
#[test]
fn test_function() -> Result<(), Box<dyn Error>> {
test(include_str!("test/function.air"), "test/function.air")
}
#[test]
fn test_context() -> Result<(), Box<dyn Error>> {
test(include_str!("test/context.air"), "test/context.air")
}
#[test]
fn test_control() -> Result<(), Box<dyn Error>> {
test(include_str!("test/control.air"), "test/control.air")
}
#[test]
fn test_value() -> Result<(), Box<dyn Error>> {
test(include_str!("test/value.air"), "test/value.air")
}
#[test]
fn test_resource() -> Result<(), Box<dyn Error>> {
test(include_str!("test/resource.air"), "test/resource.air")
}
#[test]
fn test_error() -> Result<(), Box<dyn Error>> {
test(include_str!("test/error.air"), "test/error.air")
}
#[test]
fn test_language() -> Result<(), Box<dyn Error>> {
test(include_str!("test/language.air"), "test/language.air")
}
#[test]
fn test_core() -> Result<(), Box<dyn Error>> {
test(include_str!("test/core.air"), "test/core.air")
}
#[test]
fn test_doc() -> Result<(), Box<dyn Error>> {
test(include_str!("test/doc.air"), "test/doc.air")
}
#[test]
fn test_debug() -> Result<(), Box<dyn Error>> {
test(include_str!("test/debug.air"), "test/debug.air")
}
#[test]
fn test_val_size() {
let size = size_of::<Val>();
assert_eq!(size, 24);
}