use serde::Serialize;
use crate::conlang::xbar::{self, SyntaxNode};
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct MovementReport {
pub moved: String,
pub role: String,
pub landing: &'static str,
pub tree: SyntaxNode,
}
pub fn front(
word_order: &str,
verb: &str,
subject: &str,
object: Option<&str>,
indirect: Option<&str>,
role: &str,
) -> Option<MovementReport> {
let target = match role.to_lowercase().as_str() {
"subject" | "subj" => Some(subject),
"object" | "obj" => object,
"indirect" | "iobj" | "recipient" => indirect,
_ => None,
}?;
let mut tree = xbar::build(word_order, verb, subject, object, indirect);
let mut captured: Option<SyntaxNode> = None;
if !replace_with_trace(&mut tree, target, &mut captured) {
return None;
}
let mut moved_np = captured?;
moved_np.label = "NP\u{2081}".to_string(); if tree.label == "CP" {
tree.children.insert(0, moved_np);
}
Some(MovementReport {
moved: target.to_string(),
role: role.to_lowercase(),
landing: "Spec-CP",
tree,
})
}
fn replace_with_trace(node: &mut SyntaxNode, target: &str, captured: &mut Option<SyntaxNode>) -> bool {
for i in 0..node.children.len() {
let child = &node.children[i];
let is_target_np = child.label == "NP"
&& child.children.first().and_then(|n| n.word.as_deref()) == Some(target);
if is_target_np {
*captured = Some(node.children[i].clone());
node.children[i] = SyntaxNode {
label: "t\u{2081}".to_string(), word: None,
children: Vec::new(),
};
return true;
}
if replace_with_trace(&mut node.children[i], target, captured) {
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
fn find<'a>(n: &'a SyntaxNode, label: &str) -> Option<&'a SyntaxNode> {
if n.label == label {
return Some(n);
}
n.children.iter().find_map(|c| find(c, label))
}
#[test]
fn fronting_the_object_leaves_a_trace() {
let r = front("svo", "sees", "she", Some("bird"), None, "object").unwrap();
assert_eq!(r.moved, "bird");
assert_eq!(r.landing, "Spec-CP");
assert_eq!(r.tree.children[0].label, "NP\u{2081}");
assert_eq!(r.tree.children[0].children[0].word.as_deref(), Some("bird"));
assert!(find(&r.tree, "t\u{2081}").is_some());
let vp = find(&r.tree, "VP").unwrap();
assert!(find(vp, "t\u{2081}").is_some());
}
#[test]
fn fronting_the_subject_works_too() {
let r = front("svo", "sees", "she", Some("bird"), None, "subject").unwrap();
assert_eq!(r.moved, "she");
assert_eq!(r.tree.children[0].label, "NP\u{2081}");
}
#[test]
fn an_unfilled_role_yields_no_movement() {
assert!(front("svo", "sleeps", "she", None, None, "object").is_none());
assert!(front("svo", "sees", "she", Some("bird"), None, "wombat").is_none());
}
}