use serde::Serialize;
use crate::conlang::xbar::{self, SyntaxNode};
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct BindingReport {
pub antecedent: String,
pub anaphor: String,
pub anaphor_type: String,
pub c_commands: bool,
pub principle: &'static str,
pub coreference: &'static str,
pub note: String,
}
fn word_of(role: &str, subject: &str, object: Option<&str>, indirect: Option<&str>) -> Option<String> {
match role.to_lowercase().as_str() {
"subject" | "subj" => Some(subject.to_string()),
"object" | "obj" => object.map(str::to_string),
"indirect" | "iobj" | "recipient" => indirect.map(str::to_string),
_ => None,
}
}
pub fn analyze(
word_order: &str,
verb: &str,
subject: &str,
object: Option<&str>,
indirect: Option<&str>,
antecedent_role: &str,
anaphor_role: &str,
anaphor_type: &str,
) -> Option<BindingReport> {
let antecedent = word_of(antecedent_role, subject, object, indirect)?;
let anaphor = word_of(anaphor_role, subject, object, indirect)?;
let tree = xbar::build(word_order, verb, subject, object, indirect);
let cc = c_commands(&tree, &antecedent, &anaphor);
let (principle, coreference, note): (&'static str, &'static str, String) =
match anaphor_type.to_lowercase().as_str() {
"reflexive" | "anaphor" => {
if cc {
("A", "required", format!("a reflexive must be bound; `{antecedent}` c-commands it and binds it"))
} else {
("A", "forbidden", format!("a reflexive must be bound, but `{antecedent}` does not c-command it — unbound (ungrammatical)"))
}
}
"pronoun" => {
if cc {
("B", "forbidden", format!("a pronoun must be free in its clause, but `{antecedent}` c-commands it"))
} else {
("B", "permitted", format!("`{antecedent}` does not c-command it, so the pronoun is free to corefer"))
}
}
_ => {
if cc {
("C", "forbidden", format!("a name must be free; `{antecedent}` c-commands it (a Principle C violation)"))
} else {
("C", "permitted", format!("`{antecedent}` does not c-command it, so coreference is free"))
}
}
};
Some(BindingReport {
antecedent,
anaphor,
anaphor_type: anaphor_type.to_lowercase(),
c_commands: cc,
principle,
coreference,
note,
})
}
fn c_commands(root: &SyntaxNode, a: &str, b: &str) -> bool {
match parent_and_np(root, a) {
Some((parent, np)) => contains_head(parent, b) && !contains_head(np, b),
None => false,
}
}
fn parent_and_np<'a>(node: &'a SyntaxNode, word: &str) -> Option<(&'a SyntaxNode, &'a SyntaxNode)> {
for child in &node.children {
if child.label == "NP" && child.children.first().and_then(|n| n.word.as_deref()) == Some(word) {
return Some((node, child));
}
if let Some(found) = parent_and_np(child, word) {
return Some(found);
}
}
None
}
fn contains_head(node: &SyntaxNode, word: &str) -> bool {
if node.label == "N" && node.word.as_deref() == Some(word) {
return true;
}
node.children.iter().any(|c| contains_head(c, word))
}
#[cfg(test)]
mod tests {
use super::*;
fn run(anaphor_type: &str) -> BindingReport {
analyze("svo", "sees", "she", Some("x"), None, "subject", "object", anaphor_type).unwrap()
}
#[test]
fn subject_c_commands_the_object() {
let r = run("reflexive");
assert!(r.c_commands, "subject should c-command object");
}
#[test]
fn a_reflexive_object_may_bind_to_the_subject() {
let r = run("reflexive");
assert_eq!(r.principle, "A");
assert_eq!(r.coreference, "required");
}
#[test]
fn a_pronoun_object_may_not_corefer_with_the_subject() {
let r = run("pronoun");
assert_eq!(r.principle, "B");
assert_eq!(r.coreference, "forbidden");
}
#[test]
fn a_name_object_is_a_principle_c_violation() {
let r = run("name");
assert_eq!(r.principle, "C");
assert_eq!(r.coreference, "forbidden");
}
#[test]
fn the_object_does_not_c_command_the_subject() {
let r = analyze("svo", "sees", "she", Some("x"), None, "object", "subject", "pronoun").unwrap();
assert!(!r.c_commands, "object should not c-command subject");
assert_eq!(r.coreference, "permitted");
}
}