use narsese::{
conversion::string::impl_enum::format_instances::FORMAT_ASCII as FORMAT_ASCII_ENUM, lexical::*,
};
use std::{cmp::Ordering, collections::HashMap};
fn get_identifier(term: &Term) -> &str {
match term {
Atom { prefix, .. } => prefix,
Compound { connecter, .. } => connecter,
Set { left_bracket, .. } => left_bracket,
Statement { copula, .. } => copula,
}
}
fn is_variable_atom_prefix(prefix: &str) -> bool {
prefix == FORMAT_ASCII_ENUM.atom.prefix_variable_independent
|| prefix == FORMAT_ASCII_ENUM.atom.prefix_variable_dependent
|| prefix == FORMAT_ASCII_ENUM.atom.prefix_variable_query
}
type VariableNameMap = HashMap<String, String>;
#[allow(unused)]
fn rename_variables_in_term(term: &mut Term) -> (bool, VariableNameMap) {
let mut map = VariableNameMap::new();
rename_variables_in_term_with_map(term, &mut map);
(!map.is_empty(), map)
}
fn rename_variables_in_term_with_map(term: &mut Term, map: &mut VariableNameMap) -> bool {
find_variables_renaming(term, map);
let modified = map.iter().any(|(k, v)| k != v);
if modified {
apply_name_substitute(term, map);
}
modified
}
fn find_variables_renaming(term: &Term, map: &mut VariableNameMap) {
match term {
Atom { prefix, name } if is_variable_atom_prefix(prefix) => {
let new_name = match map.get(name) {
Some(n) => n.clone(),
None => (map.len() + 1).to_string(), };
map.insert(name.clone(), new_name.clone());
}
Compound { terms, .. } | Set { terms, .. } => terms
.iter()
.for_each(|term| find_variables_renaming(term, map)),
Statement {
subject, predicate, ..
} => [subject, predicate]
.into_iter()
.for_each(|term| find_variables_renaming(term, map)),
_ => (),
}
}
fn apply_name_substitute(term: &mut Term, map: &VariableNameMap) {
match term {
Atom { name, .. } => {
if let Some(new_name) = map.get(name) {
*name = new_name.clone()
}
}
Compound { terms, .. } | Set { terms, .. } => {
for term in terms {
apply_name_substitute(term, map)
}
}
Statement {
subject, predicate, ..
} => {
apply_name_substitute(subject, map);
apply_name_substitute(predicate, map);
}
}
}
fn is_communicative_term(identifier: &str) -> bool {
identifier == FORMAT_ASCII_ENUM.compound.brackets_set_extension.0
|| identifier == FORMAT_ASCII_ENUM.compound.brackets_set_intension.0
|| identifier == FORMAT_ASCII_ENUM.compound.connecter_intersection_extension
|| identifier == FORMAT_ASCII_ENUM.compound.connecter_intersection_intension
|| identifier == FORMAT_ASCII_ENUM.compound.connecter_conjunction
|| identifier == FORMAT_ASCII_ENUM.compound.connecter_disjunction
|| identifier == FORMAT_ASCII_ENUM.compound.connecter_conjunction_parallel
|| identifier == FORMAT_ASCII_ENUM.statement.copula_similarity
|| identifier == FORMAT_ASCII_ENUM.statement.copula_equivalence
|| identifier == FORMAT_ASCII_ENUM.statement.copula_equivalence_concurrent
}
fn term_comparator(term1: &Term, term2: &Term) -> Ordering {
fn term_comparator_zipped((term1, term2): (&Term, &Term)) -> Ordering {
term_comparator(term1, term2)
}
use Ordering::*;
match (term1, term2) {
(
Atom {
prefix: p1,
name: n1,
},
Atom {
prefix: p2,
name: n2,
},
) => match (is_variable_atom_prefix(p1), is_variable_atom_prefix(p2)) {
(true, true) => Equal,
(false, true) => Less,
(true, false) => Greater,
(false, false) => p1.cmp(p2).then(n1.cmp(n2)),
},
(
Compound {
connecter: c1,
terms: t1,
},
Compound {
connecter: c2,
terms: t2,
},
)
| (
Set {
left_bracket: c1,
terms: t1,
..
},
Set {
left_bracket: c2,
terms: t2,
..
},
) => c1.cmp(c2).then(
t1.iter()
.zip(t2.iter())
.map(term_comparator_zipped)
.fold(Equal, Ordering::then),
),
(
Statement {
copula: c1,
subject: s1,
predicate: p1,
},
Statement {
copula: c2,
subject: s2,
predicate: p2,
},
) => c1.cmp(c2).then(
([s1, p1].into_iter())
.zip([s2, p2])
.map(|(inner1, inner2)| term_comparator(inner1, inner2))
.fold(Equal, Ordering::then),
),
_ => term1.cmp(term2),
}
}
fn sort_communicative_terms(term: &mut Term) -> bool {
let mut modified = match term {
Compound { terms, .. } | Set { terms, .. } => {
let mut modified = false;
for term in terms {
modified = sort_communicative_terms(term) || modified;
}
modified
}
Statement {
subject, predicate, ..
} => {
let modified_subject = sort_communicative_terms(subject);
let modified_predicate = sort_communicative_terms(predicate);
modified_subject || modified_predicate
}
_ => false,
};
if is_communicative_term(get_identifier(term)) {
modified = sort_a_communicative_term(term) || modified;
}
modified
}
fn sort_a_communicative_term(term: &mut Term) -> bool {
match term {
Compound { terms, .. } | Set { terms, .. } => {
let mut ref_terms: Vec<&Term> = terms.iter().collect();
ref_terms.sort_by(|&t1, &t2| term_comparator(t1, t2));
let modified = terms.iter().zip(ref_terms).any(|(t1, t2)| t1 != t2);
if modified {
terms.sort_by(term_comparator);
}
modified
}
Statement {
subject, predicate, ..
} => {
match term_comparator(subject, predicate) {
Ordering::Greater => {
std::mem::swap(subject, predicate);
true }
_ => false,
}
}
_ => false,
}
}
const MAX_TRIES_FORMALIZE: usize = 0x100;
pub fn formalize_term(term: &mut Term) -> &mut Term {
let mut map = VariableNameMap::new();
let mut modified;
for _ in 0..MAX_TRIES_FORMALIZE {
modified = rename_variables_in_term_with_map(term, &mut map);
modified = sort_communicative_terms(term) || modified;
if !modified {
return term;
}
map.clear();
}
const N: usize = 0x10;
let mut stack = Vec::with_capacity(N);
for _ in 0..N {
use narsese::conversion::string::impl_lexical::format_instances::FORMAT_ASCII;
rename_variables_in_term_with_map(term, &mut map);
sort_communicative_terms(term);
stack.push(format!("modified: {:}", FORMAT_ASCII.format(term)));
map.clear();
}
panic!(
"异常:程序重复尝试了{MAX_TRIES_FORMALIZE}次,仍未稳定。\n堆栈:\n{}",
stack.join("\n")
);
}
pub fn semantical_equal_mut(term1: &mut Term, term2: &mut Term) -> bool {
*formalize_term(term1) == *formalize_term(term2)
}
#[cfg(test)]
mod tests {
use super::*;
use nar_dev_utils::macro_once;
use narsese::conversion::string::impl_lexical::format_instances::FORMAT_ASCII;
fn parse_term(s: &str) -> Term {
FORMAT_ASCII
.parse(s)
.expect("Narsese解析失败")
.try_into_term()
.unwrap()
}
macro_rules! term {
($s:expr) => {
parse_term($s)
};
}
fn fmt_term(term: &Term) -> String {
FORMAT_ASCII.format(term)
}
fn print_term(term: &Term) {
println!("{:}", fmt_term(term));
}
#[test]
fn rename_variables() {
fn t(s: &str) {
let term = parse_term(s);
print_term(&term);
let mut renamed = term.clone();
rename_variables_in_term(&mut renamed);
print_term(&renamed);
let mut r_renamed = renamed.clone();
rename_variables_in_term(&mut r_renamed);
print_term(&r_renamed);
assert_eq!(&renamed, &r_renamed)
}
t("<(&&, <$2 --> $1>, <$3 <-> $2>, S, #4) ==> <<A <-> $1> ==> <$3 --> {(/, R, _, $3), $2}>>>");
t("<(&&,<$the_one --> lock>,<$second --> key>) ==> <$the_one --> (/,open,$second,_)>>");
t("<(&&,<$the_one --> key>,<$second --> lock>) ==> <$second --> (/,open,$the_one,_)>>");
}
#[test]
fn sort() {
let term = term!("<[#1, B, $1, A, $2, B] <-> (&&, #1, A, {G,E,B}, [C], <F <=> D>)>");
print_term(&term);
let mut renamed = term.clone();
sort_communicative_terms(&mut renamed);
print_term(&renamed);
let mut r_renamed = renamed.clone();
sort_communicative_terms(&mut r_renamed);
print_term(&r_renamed);
assert_eq!(&renamed, &r_renamed);
fn sort_eq(term1: &mut Term, term2: &mut Term) -> bool {
sort_communicative_terms(term1);
sort_communicative_terms(term2);
term1 == term2
}
macro_once! {
macro test_ {
($($s1:literal $t1:tt $s2:literal)*) => {$(
test_!{ @INNER $s1 $t1 $s2 }
)*}
(@INNER $s1:literal == $s2:literal) => {
let mut t1 = term!($s1);
let mut t2 = term!($s2);
assert!(sort_eq(&mut t1, &mut t2), "{} != {}", fmt_term(&t1), fmt_term(&t2));
}
(@INNER $s1:literal != $s2:literal) => {
let mut t1 = term!($s1);
let mut t2 = term!($s2);
assert!(!sort_eq(&mut t1, &mut t2), "{} == {}", fmt_term(&t1), fmt_term(&t2));
}
}
"A" == "A"
"<A <-> B>" == "<B <-> A>"
"<A --> B>" != "<B --> A>"
"<(&&,<$1 --> lock>,<$2 --> key>) ==> <$1 --> (/,open,$2,_)>>" ==
"<(&&,<$2 --> key>,<$1 --> lock>) ==> <$1 --> (/,open,$2,_)>>"
}
}
#[test]
fn term_compare() {
macro_once! {
macro test_ {
($($s1:literal $t1:tt $s2:literal)*) => {$(
test_!{ @INNER $s1 ($t1) $s2 }
)*}
(@VALUE $s1:literal $t1:tt $s2:literal $ordering:expr) => {
let t1 = term!($s1);
let t2 = term!($s2);
let cmp_result = term_comparator(&t1, &t2);
assert_eq!(cmp_result, $ordering, "{} !{} {}", fmt_term(&t1), $t1, fmt_term(&t2));
}
(@INNER $s1:literal (>) $s2:literal) => {
test_!{ @VALUE $s1 ">" $s2 Ordering::Greater }
}
(@INNER $s1:literal (<) $s2:literal) => {
test_!{ @VALUE $s1 "<" $s2 Ordering::Less }
}
(@INNER $s1:literal (==) $s2:literal) => {
test_!{ @VALUE $s1 "==" $s2 Ordering::Equal }
}
}
"A" < "B"
"$1" == "$2"
"$3" == "$2"
"$3" == "$a"
"(&&, A)" > "#1"
"(&&, #2)" == "(&&, $2)"
"<$1 --> lock>" > "<$2 --> key>"
}
}
#[test]
fn formalize() {
fn t(s: &str) {
let mut term = parse_term(s);
print_term(&term);
formalize_term(&mut term);
print_term(&term);
let term_original = term.clone();
formalize_term(&mut term);
print_term(&term);
assert!(term == term_original)
}
t("<(&&, <$2 --> $1>, <$3 <-> $2>, S, #4) ==> <<A <-> $1> ==> <$3 --> {(/, R, _, $3), $2}>>>");
}
#[test]
fn semantical_eq() {
macro_once! {
macro test_ {
($($s1:literal $t1:tt $s2:literal)*) => {$(
test_!{ @INNER $s1 $t1 $s2 }
)*}
(@INNER $s1:literal == $s2:literal) => {
let mut t1 = term!($s1);
let mut t2 = term!($s2);
let eq = semantical_equal_mut(&mut t1, &mut t2);
assert!(eq, "{} != {}", fmt_term(&t1), fmt_term(&t2));
}
(@INNER $s1:literal != $s2:literal) => {
let mut t1 = term!($s1);
let mut t2 = term!($s2);
let eq = semantical_equal_mut(&mut t1, &mut t2);
assert!(!eq, "{} == {}", fmt_term(&t1), fmt_term(&t2));
}
}
"<(&&,<$1 --> lock>,<$2 --> key>) ==> <$1 --> (/,open,$2,_)>>"
== "<(&&,<$1 --> key>,<$2 --> lock>) ==> <$2 --> (/,open,$1,_)>>"
"(&&,<$1 --> lock>,<$2 --> key>)" == "(&&,<$1 --> key>,<$2 --> lock>)"
"(&&,<$1 --> 🔒>,<$2 --> 🔑>)" == "(&&,<$1 --> 🔑>,<$2 --> 🔒>)"
"(&&,<#1 --> 🔒>,<$2 --> 🔑>)" != "(&&,<#1 --> 🔑>,<$2 --> 🔒>)"
"$1" == "$2"
"$1" != "A"
"(/,open,$2,_)" == "(/,open,$1,_)"
"<$1 --> (/,open,$2,_)>" == "<$2 --> (/,open,$1,_)>"
"<$1 --> $2>" == "<$2 --> $1>"
"<$1 --> 2>" != "<$2 --> 1>"
"<1 --> $2>" != "<$2 --> 1>"
"<$1 --> #2>" != "<#2 --> $1>"
"<?1 --> #2>" != "<#2 --> ?1>"
"<$1 --> ?2>" != "<?2 --> $1>"
"<$1 --> #2>" == "<$2 --> #1>"
"<?1 --> #2>" == "<?2 --> #1>"
"<$1 --> ?2>" == "<$2 --> ?1>"
}
}
}