use crate::error::{Error, Result};
use super::*;
pub(crate) fn print_interlinear(rendered: &crate::conlang::syntax::RenderedClause) {
let widths: Vec<usize> = rendered
.words
.iter()
.map(|(w, g)| w.chars().count().max(g.chars().count()) + 2)
.collect();
let mut line1 = String::from(" ");
let mut line2 = String::from(" ");
for (i, (w, g)) in rendered.words.iter().enumerate() {
line1.push_str(&format!("{:<width$}", w, width = widths[i]));
line2.push_str(&format!("{:<width$}", g, width = widths[i]));
}
println!("{line1}");
println!("{line2}");
println!(" '{}'", rendered.literal);
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn relative(
project: &Path,
language: &str,
head: &str,
role: &str,
verb: &str,
with: Option<&str>,
relativizer: Option<&str>,
noun_paradigm: &str,
verb_paradigm: &str,
) -> Result<()> {
use crate::conlang::syntax::{self, RelativeClause};
let head_is_subject = match role.to_ascii_lowercase().as_str() {
"subject" | "subj" | "s" => true,
"object" | "obj" | "o" => false,
other => {
return Err(Error::Config(format!(
"unknown --role `{other}` (expected subject or object)"
)))
}
};
let (store, hierarchy, lang_book) = open_lang_book(project, language)?;
let phon = load_phonology(&store, &hierarchy, &lang_book)?.ok_or_else(|| {
Error::Config(format!("language `{language}` has no phoneme block"))
})?;
let morph = load_morphology(&store, &hierarchy, &lang_book)?.unwrap_or_default();
let (grammar_spec, _) = load_grammar_spec(&store, &hierarchy, &lang_book)?;
let rc = RelativeClause {
head_is_subject,
verb: parse_word(verb),
other: with.map(parse_word),
relativizer: relativizer.map(parse_word),
noun_paradigm: noun_paradigm.to_string(),
verb_paradigm: verb_paradigm.to_string(),
};
let head_word = parse_word(head);
let rendered = syntax::relative_np(&phon, &morph, &grammar_spec.grammar, &head_word, &rc);
let strategy = grammar_spec
.grammar
.get("relative_clause")
.map(String::as_str)
.unwrap_or("postnominal");
println!("{} ({strategy})", rendered.surface);
print_interlinear(&rendered);
Ok(())
}
pub(crate) fn coordinate(
project: &Path,
language: &str,
clauses: &[String],
nps: &[String],
conjunction: Option<&str>,
noun_paradigm: &str,
verb_paradigm: &str,
) -> Result<()> {
use crate::conlang::syntax::{self, Clause, NounPhrase};
let (store, hierarchy, lang_book) = open_lang_book(project, language)?;
let phon = load_phonology(&store, &hierarchy, &lang_book)?.ok_or_else(|| {
Error::Config(format!("language `{language}` has no phoneme block"))
})?;
let morph = load_morphology(&store, &hierarchy, &lang_book)?.unwrap_or_default();
let (grammar_spec, _) = load_grammar_spec(&store, &hierarchy, &lang_book)?;
let conj = conjunction.map(parse_word);
let rendered = if nps.len() >= 2 {
let parts: Vec<_> = nps
.iter()
.map(|n| syntax::bare_np(&parse_word(n)))
.collect();
syntax::coordinate(&parts, conj.as_ref())
} else if clauses.len() >= 2 {
let mut parts = Vec::new();
for spec in clauses {
let toks: Vec<&str> = spec.split_whitespace().collect();
if toks.len() < 2 {
return Err(Error::Config(format!(
"--clause `{spec}` needs at least a subject and a verb"
)));
}
let np = |t: &str| NounPhrase {
head: parse_word(t),
number: "sg".into(),
adjective: None,
};
let c = Clause {
subject: Some(np(toks[0])),
verb: Some(parse_word(toks[1])),
verb_person: "3".into(),
object: toks.get(2).map(|t| np(t)),
noun_paradigm: noun_paradigm.to_string(),
verb_paradigm: verb_paradigm.to_string(),
..Default::default()
};
parts.push(syntax::assemble(&phon, &morph, &grammar_spec.grammar, &c));
}
syntax::coordinate(&parts, conj.as_ref())
} else {
return Err(Error::Config(
"give at least two --np nouns or two --clause clauses to coordinate".into(),
));
};
println!("{}", rendered.surface);
print_interlinear(&rendered);
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn complement(
project: &Path,
language: &str,
subject: Option<&str>,
subject_person: &str,
subject_number: &str,
verb: &str,
complementizer: Option<&str>,
comp_subject: Option<&str>,
comp_verb: &str,
comp_object: Option<&str>,
noun_paradigm: &str,
verb_paradigm: &str,
) -> Result<()> {
use crate::conlang::syntax::{self, Clause, ComplementSentence, NounPhrase};
let (store, hierarchy, lang_book) = open_lang_book(project, language)?;
let phon = load_phonology(&store, &hierarchy, &lang_book)?.ok_or_else(|| {
Error::Config(format!("language `{language}` has no phoneme block"))
})?;
let morph = load_morphology(&store, &hierarchy, &lang_book)?.unwrap_or_default();
let (grammar_spec, _) = load_grammar_spec(&store, &hierarchy, &lang_book)?;
let np = |w: &str| NounPhrase {
head: parse_word(w),
number: "sg".into(),
adjective: None,
};
let complement_clause = Clause {
subject: comp_subject.map(np),
verb: Some(parse_word(comp_verb)),
verb_person: "3".into(),
object: comp_object.map(np),
noun_paradigm: noun_paradigm.to_string(),
verb_paradigm: verb_paradigm.to_string(),
..Default::default()
};
let cs = ComplementSentence {
matrix_subject: subject.map(parse_word),
matrix_verb: parse_word(verb),
matrix_person: subject_person.to_string(),
matrix_number: subject_number.to_string(),
complementizer: complementizer.map(parse_word),
complement: complement_clause,
noun_paradigm: noun_paradigm.to_string(),
verb_paradigm: verb_paradigm.to_string(),
};
let rendered = syntax::complement_sentence(&phon, &morph, &grammar_spec.grammar, &cs);
let order = grammar_spec.grammar.get("word_order").map(String::as_str).unwrap_or("svo");
println!("{} ({} order)", rendered.surface, order.to_uppercase());
print_interlinear(&rendered);
Ok(())
}
pub(crate) fn agree(
project: &Path,
language: &str,
word: &str,
pos: &str,
features: &str,
gloss: Option<&str>,
) -> Result<()> {
use std::collections::BTreeMap;
let (store, hierarchy, lang_book) = open_lang_book(project, language)?;
let phonology = load_phonology(&store, &hierarchy, &lang_book)?.ok_or_else(|| {
Error::Config(format!("language `{language}` has no phoneme block"))
})?;
let morph = load_morphology(&store, &hierarchy, &lang_book)?.ok_or_else(|| {
Error::Config(format!(
"language `{language}` has no morphology yet — add a `morphemes` / `paradigms` / \
`agreement` HJSON paragraph under its `Grammar` chapter"
))
})?;
let rule = morph.agreement_for(pos).ok_or_else(|| {
Error::Config(format!(
"language `{language}` has no agreement rule for `{pos}` (dependents: {})",
morph.agreement.iter().map(|a| a.dependent.as_str()).collect::<Vec<_>>().join(", ")
))
})?;
let head_features: BTreeMap<String, String> = features
.split(',')
.filter_map(|kv| kv.split_once('='))
.map(|(k, v)| (k.trim().to_string(), v.trim().to_string()))
.collect();
let root_gloss = gloss.unwrap_or(word);
let result = crate::conlang::morphology::agreement::agree(
&phonology, &morph, rule, word, root_gloss, &head_features,
)
.ok_or_else(|| {
Error::Config(format!(
"no form of `{word}` agrees with those features — check the `{}` paradigm has a \
matching cell, and that --features uses the rule's features ({})",
rule.paradigm,
rule.features.join(", ")
))
})?;
let matched = result
.matched
.iter()
.map(|(k, v)| format!("{k}={v}"))
.collect::<Vec<_>>()
.join(" ");
let head = if rule.head.is_empty() { "head".to_string() } else { rule.head.clone() };
println!("{word} ({pos}) agreeing with its {head} [{matched}]:");
println!(" {} — {}", result.form, result.gloss);
Ok(())
}