use super::term_equal::*;
use crate::test_tools::OutputExpectation;
use anyhow::Result;
use nar_dev_utils::if_return;
use nar_dev_utils::macro_once;
use narsese::{
api::{FloatPrecision, NarseseValue},
conversion::{
inter_type::lexical_fold::TryFoldInto,
string::impl_enum::{format_instances::FORMAT_ASCII as FORMAT_ASCII_ENUM, NarseseFormat},
},
enum_narsese::{
Budget as EnumBudget, Punctuation as EnumPunctuation, Stamp as EnumStamp,
Truth as EnumTruth,
},
lexical::{Narsese, Sentence as LexicalSentence, Task as LexicalTask, Term},
};
use navm::output::{Operation, Output};
impl OutputExpectation {
pub fn matches(&self, output: &Output, precision_epoch: FloatPrecision) -> bool {
if let Some(expected) = &self.output_type {
if_return! { expected != output.type_name() => false }
}
match (&self.narsese, output.get_narsese()) {
(Some(..), None) => return false,
(Some(expected), Some(out)) => {
if_return! {
!is_expected_narsese_lexical(expected, out, precision_epoch)
=> false
}
}
_ => (),
}
match (&self.operation, output.get_operation()) {
(None, ..) => true,
(Some(_), None) => false,
(Some(expected), Some(out)) => is_expected_operation(expected, out),
}
}
}
pub fn is_expected_narsese_lexical(
expected: &Narsese,
out: &Narsese,
precision_epoch: FloatPrecision,
) -> bool {
_is_expected_narsese(expected.clone(), out.clone(), precision_epoch)
}
fn _is_expected_narsese(
mut expected: Narsese,
mut out: Narsese,
precision_epoch: FloatPrecision,
) -> bool {
fn get_term_mut(narsese: &mut Narsese) -> &mut Term {
use NarseseValue::*;
match narsese {
Term(term)
| Sentence(LexicalSentence { term, .. })
| Task(LexicalTask {
sentence: LexicalSentence { term, .. },
..
}) => term,
}
}
if_return! {
!semantical_equal_mut(get_term_mut(&mut expected), get_term_mut(&mut out))
=> false
};
let fold = PartialFoldResult::try_from;
match (fold(expected), fold(out)) {
(Ok(expected), Ok(out)) => expected.is_expected_out(&out, precision_epoch),
_ => false,
}
}
#[derive(Debug, Clone, Default)]
struct PartialFoldResult {
truth: Option<EnumTruth>,
stamp: Option<EnumStamp>,
budget: Option<EnumBudget>,
punctuation: Option<EnumPunctuation>,
}
impl PartialFoldResult {
fn is_expected_out(&self, out: &Self, precision_epoch: FloatPrecision) -> bool {
macro_once! {
macro both_and {
($( { $($code:tt)* } ) && *) => {
$(
both_and!(@SINGLE $($code)*)
)&&*
};
(@SINGLE @EMPTY_WILDCARD $exp_i:ident @ $exp:expr, $out_i:ident @ $out:expr => $($code:tt)*) => {
match ($exp.as_ref(), $out.as_ref()) {
(Some($exp_i), Some($out_i)) => {
$($code)*
},
(None, _) => true,
_ => false,
}
};
(@SINGLE $l_i:ident @ $l:expr, $r_i:ident @ $r:expr => $($code:tt)*) => {
match ($l.as_ref(), $r.as_ref()) {
(Some($l_i), Some($r_i)) => {
$($code)*
},
(None, None) => true,
_ => false,
}
};
}
{
expected @ self.punctuation,
out @ out.punctuation =>
expected == out } && {
expected @ self.stamp,
out @ out.stamp =>
expected == out } && {
@EMPTY_WILDCARD expected @ self.truth,
out @ out.truth =>
is_expected_truth(expected, out, precision_epoch) } && {
@EMPTY_WILDCARD expected @ self.budget,
out @ out.budget =>
is_expected_budget(expected, out, precision_epoch) }
}
}
}
impl TryFrom<Narsese> for PartialFoldResult {
type Error = ();
fn try_from(narsese: Narsese) -> Result<Self, Self::Error> {
const FORMAT: &NarseseFormat<&str> = &FORMAT_ASCII_ENUM;
macro_rules! some_try {
($v:expr) => {
Some(match $v {
Ok(v) => v,
Err(..) => return Err(()),
})
};
}
let value = match narsese {
NarseseValue::Term(..) => Self::default(),
NarseseValue::Sentence(LexicalSentence {
punctuation,
stamp,
truth,
..
}) => Self {
truth: some_try!(truth.try_fold_into(FORMAT)),
stamp: some_try!(FORMAT.parse(&stamp)),
budget: None,
punctuation: some_try!(FORMAT.parse(&punctuation)),
},
NarseseValue::Task(LexicalTask {
budget,
sentence:
LexicalSentence {
punctuation,
stamp,
truth,
..
},
}) => Self {
truth: some_try!(truth.try_fold_into(FORMAT)),
stamp: some_try!(FORMAT.parse(&stamp)),
budget: some_try!(budget.try_fold_into(FORMAT)),
punctuation: some_try!(FORMAT.parse(&punctuation)),
},
};
Ok(value)
}
}
fn is_expected_float(
expected: &FloatPrecision,
out: &FloatPrecision,
precision_epoch: FloatPrecision,
) -> bool {
if precision_epoch == 0.0 {
return expected == out;
}
(expected - out).abs() <= precision_epoch
}
#[inline]
fn is_expected_truth(
expected: &EnumTruth,
out: &EnumTruth,
precision_epoch: FloatPrecision,
) -> bool {
use EnumTruth::*;
match [expected, out] {
[Empty, ..] => true,
[Single(f_e), Single(f_o) | Double(f_o, ..)] => {
is_expected_float(f_e, f_o, precision_epoch)
}
[Double(f_e, c_e), Double(f_o, c_o)] => {
is_expected_float(f_e, f_o, precision_epoch)
&& is_expected_float(c_e, c_o, precision_epoch)
}
_ => false,
}
}
#[inline]
fn is_expected_budget(
expected: &EnumBudget,
out: &EnumBudget,
precision_epoch: FloatPrecision,
) -> bool {
use EnumBudget::*;
match [expected, out] {
[Empty, ..] => true,
[Single(p_e), Single(p_o) | Double(p_o, ..) | Triple(p_o, ..)] => {
is_expected_float(p_e, p_o, precision_epoch)
}
[Double(p_e, d_e), Double(p_o, d_o) | Triple(p_o, d_o, ..)] => {
is_expected_float(p_e, p_o, precision_epoch)
&& is_expected_float(d_e, d_o, precision_epoch)
}
[Triple(p_e, d_e, q_e), Triple(p_o, d_o, q_o)] => {
is_expected_float(p_e, p_o, precision_epoch)
&& is_expected_float(d_e, d_o, precision_epoch)
&& is_expected_float(q_e, q_o, precision_epoch)
}
_ => false,
}
}
pub fn is_expected_operation(expected: &Operation, out: &Operation) -> bool {
if_return! { expected.operator_name != out.operator_name => false }
match [expected.no_params(), out.no_params()] {
[true, ..] => true,
[false, true] => false,
[false, false] => expected.params == out.params,
}
}
#[cfg(test)]
mod tests {
use super::*;
use narsese::lexical_nse as nse;
use navm::operation;
#[test]
fn is_expected_narsese_lexical() {
fn test(expected: Narsese, out: Narsese, precision_epoch: FloatPrecision) {
assert!(
super::is_expected_narsese_lexical(&expected, &out, precision_epoch),
"正例断言失败!\nexpected: {expected:?}\nout: {out:?}\nprecision_epoch: {precision_epoch:?}"
);
}
fn test_negative(expected: Narsese, out: Narsese, precision_epoch: FloatPrecision) {
assert!(
!super::is_expected_narsese_lexical(&expected, &out, precision_epoch),
"反例断言失败!\nexpected: {expected:?}\nout: {out:?}\nprecision_epoch: {precision_epoch:?}"
);
}
macro_once! {
macro test {
( $($expected:literal $op:tt $config:tt => $out:literal $(,)?)*
) => {
$(
test!(@SPECIFIC $expected, $out, $op, $config);
)*
}
( @SPECIFIC
$expected:literal,
$out:literal,
==,
($epoch:expr)
) => {
test(nse!($expected), nse!($out), $epoch)
}
( @SPECIFIC
$expected:literal,
$out:literal,
!=,
($epoch:expr)
) => {
test_negative(nse!($expected), nse!($out), $epoch)
}
}
"A" ==(0.0)=> "A",
"A" !=(0.0)=> "B",
"A." ==(0.0)=> "A.",
"A." !=(0.0)=> "A?",
"A?" ==(0.0)=> "A?",
"A?" !=(0.0)=> "<A --> B>?",
"A! %1.0;0.9%" ==(0.0)=> "A! %1.0;0.9%"
"$0.5;0.5;0.5$ A@" ==(0.0)=> "$0.5;0.5;0.5$ A@",
"$0.5;0.5;0.5$ A. %1.0;0.9%" ==(0.0)=> "$0.5;0.5;0.5$ A. %1.0;0.9%",
"A." ==(0.0)=> "A. %1.0;0.9%",
"A!" ==(0.0)=> "A! %1.0;0.9%",
"A. %1.0;0.9%" !=(0.0)=> "A.",
"A! %1.0;0.9%" !=(0.0)=> "A!",
"A." ==(0.0)=> "$0.5;0.5;0.5$ A.",
"A!" ==(0.0)=> "$0.5;0.5;0.5$ A!",
"A." ==(0.0)=> "$0.5;0.5;0.5$ A. %1.0;0.9%",
"A!" ==(0.0)=> "$0.5;0.5;0.5$ A! %1.0;0.9%",
"$0.5;0.5;0.5$ A." !=(0.0)=> "A.",
"$0.5;0.5;0.5$ A!" !=(0.0)=> "A!",
"$0.5;0.5;0.5$ A. %1.0;0.9%" !=(0.0)=> "A.",
"$0.5;0.5;0.5$ A! %1.0;0.9%" !=(0.0)=> "A!",
"A. %0.5;0.9%" ==(0.00)=> "A. %0.5;0.9%",
"A. %0.5;0.9%" ==(0.10)=> "A. %0.55;0.95%", "A. %0.5;0.9%" ==(0.10)=> "A. %0.45;0.85%", "A. %0.5;0.9%" ==(0.10)=> "A. %0.55;0.85%", "A. %0.5;0.9%" !=(0.01)=> "A. %0.55;0.85%", "A. %0.5%" ==(0.1)=> "A. %0.55;0.85%", "A. %0.5%" !=(-0.1)=> "A. %0.5%", "A. %0;1%" ==(FloatPrecision::INFINITY)=> "A. %1;0%", "A. %0.5%" !=(FloatPrecision::NEG_INFINITY)=> "A. %0.5%", "$0.5;0.7;0.9$ A." ==(0.0)=> "$0.5;0.7;0.9$ A.",
"$0.5;0.9$ A." ==(0.051)=> "$0.55;0.85$ A.", "$0.5;0.9$ A." !=(0.051)=> "$0.55;0.84$ A.", "$0.5;0.9$ A." !=(0.051)=> "$0.55;0.96$ A.", "$0.5;0.7;0.9$ A." ==(0.051)=> "$0.55;0.7058;0.85$ A.", "$0.5;0.7;0.9$ A." !=(0.041)=> "$0.55;0.7058;0.85$ A.", "$0.5;0.7;0.9$ A." ==(0.041)=> "$0.54;0.7058;0.86$ A.", "$0.5;0.7;0.9$ A." !=(0.001)=> "$0.55;0.7058;0.85$ A.", "<(&&,<$1 --> lock>,<$2 --> key>) ==> <$1 --> (/,open,$2,_)>>. %1.00;0.45%"
==(0.0)=> "<(&&,<$1 --> key>,<$2 --> lock>) ==> <$2 --> (/,open,$1,_)>>. %1.00;0.45%"
"<animal --> robin>. %1.00;0.45%" ==(0.01)=> "$0.9944;0.7848;0.7238$ <animal --> robin>. %1.0000;0.4500%",
"<swimmer --> bird>. %1.00;0.47%" ==(0.01)=> "$0.8333;0.7200;0.7369$ <swimmer --> bird>. %1.0000;0.4737%",
}
}
#[test]
fn is_expected_operation() {
macro_once! {
macro test($(
[$($t_expected:tt)*] => [$($t_out:tt)*]
)*) {
$(
let expected = operation!($($t_expected)*);
let out = operation!($($t_out)*);
assert!(
super::is_expected_operation(&expected, &out),
"正例断言失败!\nexpected: {expected:?}, out: {out:?}"
);
)*
}
["left"] => ["left"]
["left" => "{SELF}"] => ["left" => "{SELF}"]
["left" => "{SELF}" "x"] => ["left" => "{SELF}" "x"]
}
macro_once! {
macro test($(
[$($t_expected:tt)*] != [$($t_out:tt)*]
)*) {
$(
let expected = operation!($($t_expected)*);
let out = operation!($($t_out)*);
assert!(
!super::is_expected_operation(&expected, &out),
"反例断言失败!\nexpected: {expected:?}, out: {out:?}"
);
)*
}
["left"] != ["right"]
["left" => "{SELF}"] != ["right" => "{SELF}"]
["left" => "{SELF}" "x"] != ["right" => "{SELF}" "x"]
["left" => "{SELF}"] != ["left" => "{SELF}" "x"]
["left" => "{SELF}" "x"] != ["left" => "[good]" "x"]
["left" => "{SELF}" "x"] != ["left" => "{OTHER}" "x"]
["left" => "{SELF}" "x"] != ["left" => "{SELF}" "y"]
}
}
}