use crate::jbo_syntax::{
Abstractor, BridiTail, COI, EitherSelbriSumti, Fragment, FragmentOrStatement, Free, GekSentence, Lerfu, Mex, Numeral,
Operator, Paragraph, RelClause, Selbri, Sentence, Statement, Statement1, Subsentence, Sumti, SumtiAtom, SumtiQualifier,
Tagged, Term, Text,
};
use crate::camxes::peg::grammar::Peg;
use crate::camxes::peg::parsing::Span;
use crate::camxes::peg::semantic::SemanticNode;
use crate::camxes::peg::{downcast_ref, parse_with_semantics, single_root, span_slice, ReducerTable};
use std::sync::OnceLock;
type Jeksbs = (crate::jbo_syntax::Connective, crate::jbo_syntax::Selbri3);
#[derive(Clone)]
enum Selbri4Item {
Connective(crate::jbo_syntax::Connective),
Selbri3(crate::jbo_syntax::Selbri3),
Jeksbs(Jeksbs),
}
type Dtu = crate::jbo_syntax::DecoratedAbsTagUnit<crate::jbo_syntax::Selbri, crate::jbo_syntax::Sumti>;
static TEXT_PEG: OnceLock<Peg> = OnceLock::new();
static FREE_PEG: OnceLock<Peg> = OnceLock::new();
static JOIK_EK_PEG: OnceLock<Peg> = OnceLock::new();
static LA_CLAUSE_PEG: OnceLock<Peg> = OnceLock::new();
static MAI_CLAUSE_PEG: OnceLock<Peg> = OnceLock::new();
static ZOI_CLAUSE_PEG: OnceLock<Peg> = OnceLock::new();
static SEI_CLAUSE_PEG: OnceLock<Peg> = OnceLock::new();
static INDICATORS_PEG: OnceLock<Peg> = OnceLock::new();
static SI_CLAUSE_PEG: OnceLock<Peg> = OnceLock::new();
static REDUCER_TABLE: OnceLock<ReducerTable> = OnceLock::new();
fn build_peg(start: &str) -> Result<Peg, usize> {
let (_, grammar) = crate::camxes::LOJBAN_GRAMMAR;
Peg::new(start, grammar).map_err(|_| 0usize)
}
fn with_text_peg<R>(f: impl FnOnce(&Peg) -> Result<R, usize>) -> Result<R, usize> {
let peg = TEXT_PEG.get_or_init(|| build_peg("text").expect("text PEG build failed"));
f(peg)
}
fn with_free_peg<R>(f: impl FnOnce(&Peg) -> Result<R, usize>) -> Result<R, usize> {
let peg = FREE_PEG.get_or_init(|| build_peg("free").expect("free PEG build failed"));
f(peg)
}
fn with_joik_ek_peg<R>(f: impl FnOnce(&Peg) -> Result<R, usize>) -> Result<R, usize> {
let peg = JOIK_EK_PEG.get_or_init(|| build_peg("joik_ek").expect("joik_ek PEG build failed"));
f(peg)
}
fn with_la_clause_peg<R>(f: impl FnOnce(&Peg) -> Result<R, usize>) -> Result<R, usize> {
let peg = LA_CLAUSE_PEG.get_or_init(|| build_peg("LA_clause").expect("LA_clause PEG build failed"));
f(peg)
}
fn with_mai_clause_peg<R>(f: impl FnOnce(&Peg) -> Result<R, usize>) -> Result<R, usize> {
let peg = MAI_CLAUSE_PEG.get_or_init(|| build_peg("MAI_clause").expect("MAI_clause PEG build failed"));
f(peg)
}
fn with_zoi_clause_peg<R>(f: impl FnOnce(&Peg) -> Result<R, usize>) -> Result<R, usize> {
let peg = ZOI_CLAUSE_PEG.get_or_init(|| build_peg("ZOI_clause").expect("ZOI_clause PEG build failed"));
f(peg)
}
fn with_sei_clause_peg<R>(f: impl FnOnce(&Peg) -> Result<R, usize>) -> Result<R, usize> {
let peg = SEI_CLAUSE_PEG.get_or_init(|| build_peg("SEI_clause").expect("SEI_clause PEG build failed"));
f(peg)
}
fn with_indicators_peg<R>(f: impl FnOnce(&Peg) -> Result<R, usize>) -> Result<R, usize> {
let peg = INDICATORS_PEG.get_or_init(|| build_peg("indicators").expect("indicators PEG build failed"));
f(peg)
}
fn with_si_clause_peg<R>(f: impl FnOnce(&Peg) -> Result<R, usize>) -> Result<R, usize> {
let peg = SI_CLAUSE_PEG.get_or_init(|| build_peg("si_clause").expect("si_clause PEG build failed"));
f(peg)
}
fn reducer_table() -> &'static ReducerTable {
REDUCER_TABLE.get_or_init(build_reducers)
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct SumtiTailParts {
inner_sumti: Option<Box<Sumti>>,
inner_quant: Option<Mex>,
selbri_or_sumti: EitherSelbriSumti,
rels: Vec<RelClause>,
inner_rels: Vec<RelClause>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct LiClauseParts {
mex: Mex,
is_meho: bool,
kau_depth: Option<i32>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct TermsGikTerms {
ts1: Vec<Term>,
b: bool,
ts2: Vec<Term>,
}
fn attach_rels_to_term(term: Term, extra_rels: Vec<RelClause>) -> Term {
if extra_rels.is_empty() {
return term;
}
match term {
Term::Sumti(tagged, Sumti::QAtom { frees, quant, rels, atom }) => {
let mut combined_rels = rels;
combined_rels.extend(extra_rels);
Term::Sumti(tagged, Sumti::QAtom { frees, quant, rels: combined_rels, atom })
}
Term::Sumti(tagged, Sumti::QSelbri { quant, rels, selbri }) => {
let mut combined_rels = rels;
combined_rels.extend(extra_rels);
Term::Sumti(tagged, Sumti::QSelbri { quant, rels: combined_rels, selbri })
}
Term::Sumti(tagged, Sumti::ConnectedSumti(fore, con, s1, s2, rels)) => {
let mut combined_rels = rels;
combined_rels.extend(extra_rels);
Term::Sumti(tagged, Sumti::ConnectedSumti(fore, con, s1, s2, combined_rels))
}
other => other,
}
}
fn attach_rels_to_sumti(sumti: Sumti, rels: Vec<RelClause>) -> Sumti {
match attach_rels_to_term(Term::Sumti(Tagged::Untagged, sumti), rels) {
Term::Sumti(_, sumti) => sumti,
_ => unreachable!(),
}
}
fn litnum(numerals: &[Numeral]) -> Option<i32> {
let mut n = 0;
for numeral in numerals {
let digit = match numeral {
Numeral::PA(s) => match s.as_str() {
"no" => 0,
"pa" => 1,
"re" => 2,
"ci" => 3,
"vo" => 4,
"mu" => 5,
"xa" => 6,
"ze" => 7,
"bi" => 8,
"so" => 9,
_ => return None,
},
_ => return None,
};
n = n * 10 + digit;
}
Some(n)
}
fn attach_quant_to_term(term: Term, outer_quant: Option<Mex>) -> Term {
let Some(q) = outer_quant else {
return term;
};
match term {
Term::Sumti(tagged, Sumti::QAtom { frees, quant, rels, atom }) => Term::Sumti(
tagged,
Sumti::QAtom { frees, quant: quant.or(Some(q)), rels, atom },
),
Term::Sumti(tagged, Sumti::QSelbri { rels, selbri, .. }) => Term::Sumti(
tagged,
Sumti::QSelbri { quant: q, rels, selbri },
),
other => other,
}
}
fn attach_tag_to_connective(
mut con: crate::jbo_syntax::Connective,
tag: Option<crate::jbo_syntax::Tag>,
) -> crate::jbo_syntax::Connective {
let Some(tag) = tag else {
return con;
};
match &mut con {
crate::jbo_syntax::Connective::JboConnLog(mtag, _) => *mtag = Some(Box::new(tag)),
crate::jbo_syntax::Connective::JboConnJoik(mtag, _) => *mtag = Some(Box::new(tag)),
}
con
}
fn first_child_tag(children: &[SemanticNode]) -> Option<crate::jbo_syntax::Tag> {
children.iter().find_map(|child| {
child
.value_ref()
.and_then(|v| downcast_ref::<crate::jbo_syntax::Tag>(v).cloned())
})
}
fn joik_string_from_words(words: &[&str]) -> Option<String> {
let interval_idx = words
.iter()
.position(|word| matches!(*word, "bi'oi" | "bi'i" | "bi'o" | "mi'i"));
if let Some(idx) = interval_idx {
let has_se = idx > 0 && words[idx - 1] == "se";
let start = if has_se { idx - 1 } else { idx };
let left_gaho = if start > 0 && matches!(words[start - 1], "ga'o" | "ke'i") {
words[start - 1]
} else {
""
};
let has_nai = words.get(idx + 1).is_some_and(|word| *word == "nai");
let end = if has_nai { idx + 2 } else { idx + 1 };
let right_gaho = if words.get(end).is_some_and(|word| matches!(*word, "ga'o" | "ke'i")) {
words[end]
} else {
""
};
let mut joik = String::new();
joik.push_str(left_gaho);
if has_se {
joik.push_str("se");
}
joik.push_str(words[idx]);
if has_nai {
joik.push_str("nai");
}
joik.push_str(right_gaho);
return Some(joik);
}
let joi = words
.iter()
.find(|word| matches!(**word, "fa'u" | "pi'u" | "joi" | "jo'u" | "jo'e" | "ju'e" | "ce'o" | "ce" | "ku'a"))?;
let mut joik = String::new();
if words.contains(&"se") {
joik.push_str("se");
}
joik.push_str(joi);
if words.contains(&"nai") {
joik.push_str("nai");
}
Some(joik)
}
fn reduce_connected_sumti(children: &[SemanticNode], input: &str) -> Option<Term> {
let mut terms: Vec<Term> = Vec::new();
let mut conns: Vec<crate::jbo_syntax::Connective> = Vec::new();
let mut rels: Vec<RelClause> = Vec::new();
let mut pending_conn: Option<crate::jbo_syntax::Connective> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(t) = downcast_ref::<Term>(v) {
terms.push(t.clone());
} else if let Some(sumti) = downcast_ref::<Sumti>(v) {
terms.push(Term::Sumti(Tagged::Untagged, sumti.clone()));
} else if let Some(con) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
if let Some(depth) = child_free_kau_depth(child, input) {
if let Some(term) = terms.pop() {
terms.push(with_sumti_kau(term, Some(depth)));
}
}
if let Some(con) = pending_conn.take() {
conns.push(con);
}
pending_conn = Some(con.clone());
} else if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
if let Some(con) = pending_conn.take() {
pending_conn = Some(attach_tag_to_connective(con, Some(tag.clone())));
}
} else if let Some(rs) = downcast_ref::<Vec<RelClause>>(v) {
rels.extend(rs.clone());
}
}
}
if let Some(con) = pending_conn.take() {
conns.push(con);
}
if terms.len() <= 1 || conns.is_empty() {
return terms.pop().map(|t| attach_rels_to_term(t, rels));
}
let mut term_iter = terms.into_iter();
let first = match term_iter.next()? {
Term::Sumti(_, sumti) => sumti,
other => return Some(attach_rels_to_term(other, rels)),
};
let mut connected = first;
for (con, term) in conns.into_iter().zip(term_iter) {
let Term::Sumti(_, rhs) = term else {
return Some(attach_rels_to_term(Term::Sumti(Tagged::Untagged, connected), rels));
};
connected = Sumti::ConnectedSumti(false, con, Box::new(connected), Box::new(rhs), Vec::new());
}
Some(attach_rels_to_term(Term::Sumti(Tagged::Untagged, connected), rels))
}
fn collect_tail_terms(node: &SemanticNode, out: &mut Vec<Term>) {
if let Some(v) = node.value_ref() {
if let Some(term) = downcast_ref::<Term>(v) {
out.push(term.clone());
return;
}
if let Some(terms) = downcast_ref::<Vec<Term>>(v) {
out.extend(terms.clone());
return;
}
}
if let SemanticNode::NonTerminal { name, children, .. } = node {
if name == "subsentence" || name == "gek_sentence" {
return;
}
for child in children {
collect_tail_terms(child, out);
}
}
}
fn pa_to_int(s: &str) -> Option<i32> {
match s {
"no" => Some(0),
"pa" => Some(1),
"re" => Some(2),
"ci" => Some(3),
"vo" => Some(4),
"mu" => Some(5),
"xa" => Some(6),
"ze" => Some(7),
"bi" => Some(8),
"so" => Some(9),
_ => None,
}
}
fn pa_words_to_int(words: &[&str]) -> Option<i32> {
let mut n = 0;
let mut saw_digit = false;
for word in words {
let digit = pa_to_int(word)?;
n = n * 10 + digit;
saw_digit = true;
}
saw_digit.then_some(n)
}
fn xi_lit_from_words(words: &[&str], pos: usize) -> Option<i32> {
if words.get(pos + 1) == Some(&"xi") {
let end = words[pos + 2..]
.iter()
.position(|word| pa_to_int(word).is_none())
.map(|i| pos + 2 + i)
.unwrap_or(words.len());
pa_words_to_int(words.get(pos + 2..end)?)
} else {
None
}
}
fn pa_sequence_prefix(s: &str) -> Option<i32> {
let mut n = 0;
let mut rest = s;
let mut saw_digit = false;
while !rest.is_empty() {
let (digit, tail) = ["no", "pa", "re", "ci", "vo", "mu", "xa", "ze", "bi", "so"]
.into_iter()
.find_map(|word| rest.strip_prefix(word).map(|tail| (pa_to_int(word).unwrap(), tail)))?;
n = n * 10 + digit;
rest = tail;
saw_digit = true;
}
saw_digit.then_some(n)
}
fn split_koh_a_xi_lit(word: &str) -> (&str, Option<i32>) {
if let Some((base, suffix)) = word.split_once("xi") {
if !base.is_empty() {
if let Some(n) = pa_sequence_prefix(suffix) {
return (base, Some(n));
}
}
}
(word, None)
}
fn koh_a_sumti_atom_from_words(words: &[&str]) -> Option<SumtiAtom> {
let (text, compact_xi_lit) = split_koh_a_xi_lit(words.first()?);
let xi_lit = compact_xi_lit.or_else(|| xi_lit_from_words(words, 0));
if let Some(kau_pos) = words.iter().position(|word| *word == "kau") {
return Some(SumtiAtom::SumtiQ(Some(kau_depth_from_words(words, kau_pos))));
}
Some(match text {
"zo'e" => SumtiAtom::Zohe,
"ma" => SumtiAtom::SumtiQ(None),
"ra" | "ru" => SumtiAtom::Ra(text.to_string()),
"ke'a" => SumtiAtom::RelVar(xi_lit.unwrap_or(1)),
"ce'u" => SumtiAtom::LambdaVar(xi_lit, xi_lit_from_words(words, 2)),
"ri" => SumtiAtom::Ri(xi_lit.unwrap_or(1)),
"vo'a" => SumtiAtom::MainBridiSumbasti(xi_lit.unwrap_or(1)),
"vo'e" => SumtiAtom::MainBridiSumbasti(xi_lit.unwrap_or(2)),
"vo'i" => SumtiAtom::MainBridiSumbasti(xi_lit.unwrap_or(3)),
"vo'o" => SumtiAtom::MainBridiSumbasti(xi_lit.unwrap_or(4)),
"vo'u" => SumtiAtom::MainBridiSumbasti(xi_lit.unwrap_or(5)),
"ko'a" => SumtiAtom::Assignable(xi_lit.unwrap_or(1)),
"ko'e" => SumtiAtom::Assignable(xi_lit.unwrap_or(2)),
"ko'i" => SumtiAtom::Assignable(xi_lit.unwrap_or(3)),
"ko'o" => SumtiAtom::Assignable(xi_lit.unwrap_or(4)),
"ko'u" => SumtiAtom::Assignable(xi_lit.unwrap_or(5)),
"fo'a" => SumtiAtom::Assignable(xi_lit.unwrap_or(6)),
"fo'e" => SumtiAtom::Assignable(xi_lit.unwrap_or(7)),
"fo'i" => SumtiAtom::Assignable(xi_lit.unwrap_or(8)),
"fo'o" => SumtiAtom::Assignable(xi_lit.unwrap_or(9)),
"fo'u" => SumtiAtom::Assignable(xi_lit.unwrap_or(10)),
"da" => SumtiAtom::Variable(xi_lit.unwrap_or(1)),
"de" => SumtiAtom::Variable(xi_lit.unwrap_or(2)),
"di" => SumtiAtom::Variable(xi_lit.unwrap_or(3)),
_ => SumtiAtom::NonAnaphoricProsumti(text.to_string()),
})
}
fn qatom_term(atom: SumtiAtom) -> Term {
Term::Sumti(
Tagged::Untagged,
Sumti::QAtom {
frees: Vec::new(),
quant: None,
rels: Vec::new(),
atom: Box::new(atom),
},
)
}
fn is_haskell_bahe(word: &str) -> bool {
matches!(word.trim_matches('.'), "ba'e" | "za'e")
}
fn words_without_bahe(text: &str) -> Vec<String> {
text.split_whitespace()
.map(|word| word.trim_matches('.'))
.filter(|word| !word.is_empty() && !is_haskell_bahe(word))
.map(String::from)
.collect()
}
fn first_non_bahe_word(text: &str) -> String {
words_without_bahe(text).into_iter().next().unwrap_or_default()
}
fn kau_depth_from_words(words: &[&str], pos: usize) -> i32 {
xi_lit_from_words(words, pos).unwrap_or(1)
}
fn truthq_from_text(text: &str) -> Option<Free> {
let words: Vec<&str> = text
.split_whitespace()
.map(|w| w.trim_matches('.'))
.filter(|w| !w.is_empty())
.collect();
let xu_pos = words.iter().position(|w| *w == "xu")?;
let kau_pos = words.iter().skip(xu_pos + 1).position(|w| *w == "kau").map(|p| p + xu_pos + 1);
let kau_depth = kau_pos.map(|pos| kau_depth_from_words(&words, pos));
Some(Free::TruthQ(kau_depth))
}
fn sumti_kau_depth_from_text(text: &str) -> Option<i32> {
let words: Vec<&str> = text
.split_whitespace()
.map(|w| w.trim_matches('.'))
.filter(|w| !w.is_empty())
.collect();
let pos = words.iter().position(|w| *w == "kau")?;
Some(kau_depth_from_words(&words, pos))
}
fn direct_sumti_kau_depth(children: &[SemanticNode], input: &str) -> Option<i32> {
let mut sumti6_end = None;
let mut next_start = None;
for child in children {
if let SemanticNode::NonTerminal { name, span, .. } = child {
if name == "sumti_6" {
sumti6_end = Some(span.1);
} else if sumti6_end.is_some() {
next_start = Some(span.0);
break;
}
}
}
let start = sumti6_end?;
let end = next_start.unwrap_or_else(|| children.iter().map(|child| match child {
SemanticNode::NonTerminal { span, .. } | SemanticNode::Terminal { span } => span.1,
}).max().unwrap_or(start));
sumti_kau_depth_from_text(&input[start..end])
}
fn quantifier_free_kau_depth(children: &[SemanticNode], input: &str) -> Option<i32> {
children.iter().find_map(|child| {
if let SemanticNode::NonTerminal { name, span, .. } = child {
if name == "quantifier" {
let words: Vec<&str> = span_slice(input, *span)
.split_whitespace()
.map(|w| w.trim_matches('.'))
.filter(|w| !w.is_empty())
.collect();
if words.first() == Some(&"xo") {
let pos = words.iter().position(|w| *w == "kau")?;
return Some(kau_depth_from_words(&words, pos));
}
}
}
None
})
}
fn quantifier_free_kau_depth_in_sumti5(node: &SemanticNode, input: &str) -> Option<i32> {
let SemanticNode::NonTerminal { name, children, .. } = node else {
return None;
};
if name == "sumti_5" {
return quantifier_free_kau_depth(children, input);
}
if matches!(name.as_str(), "terms" | "terms_1" | "terms_2" | "term" | "term_1" | "sumti" | "sumti_1" | "sumti_2" | "sumti_3" | "sumti_4") {
return children.iter().find_map(|child| quantifier_free_kau_depth_in_sumti5(child, input));
}
None
}
fn direct_free_kau_depth(children: &[SemanticNode], input: &str) -> Option<i32> {
children.iter().find_map(|child| {
if let SemanticNode::NonTerminal { name, span, .. } = child {
if name == "free" {
return sumti_kau_depth_from_text(span_slice(input, *span));
}
}
None
})
}
fn child_free_kau_depth(node: &SemanticNode, input: &str) -> Option<i32> {
match node {
SemanticNode::NonTerminal { name, span, children, .. } => {
if name == "free" {
sumti_kau_depth_from_text(span_slice(input, *span))
} else {
children.iter().find_map(|child| child_free_kau_depth(child, input))
}
}
SemanticNode::Terminal { .. } => None,
}
}
fn with_sumti_kau(term: Term, depth: Option<i32>) -> Term {
let Some(depth) = depth else {
return term;
};
match term {
Term::Sumti(tagged, Sumti::QAtom { frees, quant, rels, atom }) => {
Term::Sumti(tagged, Sumti::QAtom {
frees,
quant,
rels,
atom: if matches!(atom.as_ref(), SumtiAtom::SumtiQ(_)) {
atom
} else {
Box::new(SumtiAtom::SumtiQ(Some(depth)))
},
})
}
other => other,
}
}
fn with_sumti_frees(term: Term, extra_frees: &[Free]) -> Term {
if extra_frees.is_empty() {
return term;
}
match term {
Term::Sumti(tagged, Sumti::QAtom { mut frees, quant, rels, atom }) => {
frees.extend(extra_frees.iter().cloned());
Term::Sumti(tagged, Sumti::QAtom { frees, quant, rels, atom })
}
other => other,
}
}
fn append_tags(left: crate::jbo_syntax::Tag, right: crate::jbo_syntax::Tag) -> crate::jbo_syntax::Tag {
match (left, right) {
(crate::jbo_syntax::Tag::DecoratedTagUnits(mut left), crate::jbo_syntax::Tag::DecoratedTagUnits(right)) => {
left.extend(right);
crate::jbo_syntax::Tag::DecoratedTagUnits(left)
}
(left, right) => crate::jbo_syntax::Tag::ConnectedTag(
crate::jbo_syntax::Connective::JboConnJoik(None, "jo'u".to_string()),
Box::new(left),
Box::new(right),
),
}
}
fn merge_bare_tag_into_tagged_sumti(left: &Term, right: &Term, left_text: &str, between: &str) -> Option<Term> {
if left_text
.split_whitespace()
.chain(between.split_whitespace())
.any(|word| matches!(word.trim_matches('.'), "ku" | "nu'i" | "nu'u"))
{
return None;
}
let Term::BareTag(left_tag) = left else {
return None;
};
let Term::Sumti(Tagged::Tagged(right_tag), sumti) = right else {
return None;
};
Some(Term::Sumti(
Tagged::Tagged(append_tags(left_tag.clone(), right_tag.clone())),
sumti.clone(),
))
}
fn collect_terms_with_grouped_tags(children: &[SemanticNode], input: &str) -> Vec<Term> {
let mut items: Vec<(Term, Span)> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(term) = downcast_ref::<Term>(v) {
items.push((term.clone(), child.span()));
} else if let Some(terms_vec) = downcast_ref::<Vec<Term>>(v) {
for term in terms_vec {
items.push((term.clone(), child.span()));
}
}
}
}
let mut result: Vec<(Term, Span)> = Vec::new();
for (term, span) in items {
if let Some((last_term, last_span)) = result.last_mut() {
let left_text = span_slice(input, *last_span);
let between = if last_span.1 <= span.0 && span.0 <= input.len() {
&input[last_span.1..span.0]
} else {
""
};
if let Some(merged) = merge_bare_tag_into_tagged_sumti(last_term, &term, left_text, between) {
*last_term = merged;
last_span.1 = span.1;
continue;
}
}
result.push((term, span));
}
result.into_iter().map(|(term, _)| term).collect()
}
fn xo_kau_depth(mex: &Mex, span: Span, input: &str) -> Option<i32> {
match mex {
Mex::MexNumeralString(ns)
if ns.len() == 1 && matches!(&ns[0], Numeral::PA(cmavo) if cmavo == "xo") =>
{
let words: Vec<&str> = span_slice(input, span)
.split_whitespace()
.map(|w| w.trim_matches('.'))
.filter(|w| !w.is_empty())
.collect();
let pos = words.iter().position(|w| *w == "kau")?;
Some(kau_depth_from_words(&words, pos))
}
_ => None,
}
}
fn selbri_bridi_question(depth: i32) -> Selbri {
Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(
crate::jbo_syntax::Selbri3::TanruHead(
Vec::new(),
Box::new(crate::jbo_syntax::TanruUnit::TUBridiQ(Some(depth))),
Vec::new(),
),
))
}
fn collect_frees(node: &SemanticNode, out: &mut Vec<Free>) {
if let Some(v) = node.value_ref() {
if let Some(free) = downcast_ref::<Free>(v) {
out.push(free.clone());
return;
}
if let Some(frees) = downcast_ref::<Vec<Free>>(v) {
out.extend(frees.clone());
return;
}
}
if let SemanticNode::NonTerminal { name, children, .. } = node {
if matches!(name.as_str(), "free" | "indicators" | "INDICATOR" | "text_part_2") {
for child in children {
collect_frees(child, out);
}
}
}
}
fn collect_frees_before_span(node: &SemanticNode, before: usize, out: &mut Vec<Free>) {
if let SemanticNode::NonTerminal { span: Span(_, end), .. } = node {
if *end > before {
return;
}
}
collect_frees(node, out);
}
fn collect_frees_after_span(node: &SemanticNode, after: usize, out: &mut Vec<Free>) {
if let SemanticNode::NonTerminal { span: Span(start, _), .. } = node {
if *start < after {
return;
}
}
collect_frees(node, out);
}
fn collect_terms_deep(node: &SemanticNode, out: &mut Vec<Term>) {
if let Some(v) = node.value_ref() {
if let Some(term) = downcast_ref::<Term>(v) {
out.push(term.clone());
return;
}
if let Some(terms) = downcast_ref::<Vec<Term>>(v) {
out.extend(terms.clone());
return;
}
}
if let SemanticNode::NonTerminal { name, children, .. } = node {
if name == "sumti" || name == "selbri" || name == "bridi_tail" || name == "free" {
return;
}
for child in children {
collect_terms_deep(child, out);
}
}
}
fn statement_from_sentence(sentence: &Sentence) -> Statement {
Statement {
frees: Vec::new(),
prenex: Vec::new(),
body: Statement1::StatementSentence {
frees: Vec::new(),
sentence: sentence.clone(),
},
}
}
fn statement1_from_sentence(sentence: &Sentence) -> Statement1 {
Statement1::StatementSentence {
frees: Vec::new(),
sentence: sentence.clone(),
}
}
fn collect_statement_items(node: &SemanticNode, out: &mut Vec<Statement>) {
if let Some(v) = node.value_ref() {
if let Some(stmt) = downcast_ref::<Statement>(v) {
out.push(stmt.clone());
return;
}
if let Some(sentence) = downcast_ref::<Sentence>(v) {
out.push(statement_from_sentence(sentence));
return;
}
}
if let SemanticNode::NonTerminal { children, .. } = node {
for child in children {
collect_statement_items(child, out);
}
}
}
fn collect_paragraph_items(node: &SemanticNode, out: &mut Vec<Paragraph>) {
if let Some(v) = node.value_ref() {
if let Some(paras) = downcast_ref::<Vec<Paragraph>>(v) {
out.extend(paras.clone());
return;
}
if let Some(para) = downcast_ref::<Paragraph>(v) {
out.push(para.clone());
return;
}
if downcast_ref::<Text>(v).is_some() {
return;
}
}
if let SemanticNode::NonTerminal { children, .. } = node {
for child in children {
collect_paragraph_items(child, out);
}
}
}
fn collect_selbri4_items(node: &SemanticNode, items: &mut Vec<Selbri4Item>) {
if let Some(v) = node.value_ref() {
if let Some(pair) = downcast_ref::<Jeksbs>(v) {
items.push(Selbri4Item::Jeksbs(pair.clone()));
return;
}
if let Some(con) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
items.push(Selbri4Item::Connective(con.clone()));
return;
}
if let Some(crate::jbo_syntax::Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(sb3))) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
items.push(Selbri4Item::Selbri3(sb3.clone()));
return;
}
}
if let SemanticNode::NonTerminal { name, children, .. } = node {
if name == "selbri_5" {
return;
}
for child in children {
collect_selbri4_items(child, items);
}
}
}
#[derive(Clone)]
enum RpItem {
Mex(Mex),
Operator(Operator),
}
#[derive(Clone)]
struct RpItems(Vec<RpItem>);
enum MexSeqItem {
Mex(Mex),
Operator(Operator),
}
fn mex_seq_item(node: &SemanticNode) -> Option<MexSeqItem> {
if let Some(v) = node.value_ref() {
if let Some(m) = downcast_ref::<Mex>(v) {
return Some(MexSeqItem::Mex(m.clone()));
}
if let Some(op) = downcast_ref::<Operator>(v) {
return Some(MexSeqItem::Operator(op.clone()));
}
}
if let SemanticNode::NonTerminal { children, .. } = node {
for child in children {
if let Some(item) = mex_seq_item(child) {
return Some(item);
}
}
}
None
}
fn build_reducers() -> ReducerTable {
let mut reducers = ReducerTable::new();
reducers.on("text", |_name, _span, children, _input| {
let mut text_frees: Vec<Free> = Vec::new();
let mut text_paras: Vec<Paragraph> = Vec::new();
let mut top_level_joik_jek: Option<crate::jbo_syntax::Connective> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(frees) = downcast_ref::<Vec<Free>>(v) {
text_frees.extend(frees.clone());
}
}
if let SemanticNode::NonTerminal { name, value, .. } = child {
if name == "joik_jek" {
if let Some(value) = value {
if let Some(con) = downcast_ref::<crate::jbo_syntax::Connective>(value) {
top_level_joik_jek = Some(con.clone());
}
}
}
}
collect_paragraph_items(child, &mut text_paras);
}
if text_paras.is_empty() {
if let Some(con) = top_level_joik_jek {
text_paras.push(vec![FragmentOrStatement::Fragment(Fragment::FragCon(con))]);
}
}
Some(Text {
text_frees,
vaguely_negated_text: false,
text_paras,
})
});
reducers.on("free", |_name, span, children, input| {
let text = span_slice(input, span);
let words = text
.split_whitespace()
.map(|word| word.trim_matches('.'))
.filter(|word| !word.is_empty())
.collect::<Vec<_>>();
if matches!(words.first(), Some(&"ba'e" | &"za'e" | &"y")) {
return Some(Free::NullFree);
}
if let Some(truthq) = truthq_from_text(text) {
return Some(truthq);
}
if words.first() == Some(&"sei") || words.first() == Some(&"ti'o") {
let mut selbri: Option<Selbri> = None;
let mut terms = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(s) = downcast_ref::<Selbri>(v) {
selbri = Some(s.clone());
}
}
if let SemanticNode::NonTerminal { name, .. } = child {
if name == "terms" {
collect_terms_deep(child, &mut terms);
}
}
}
let bridi_tail = BridiTail::BridiTail3(selbri?, terms);
return Some(if words.first() == Some(&"ti'o") {
Free::MexPrecedence(bridi_tail)
} else {
Free::Discursive(bridi_tail)
});
}
if words.first() == Some(&"soi") {
let mut sumtis = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(Term::Sumti(_, sumti)) = downcast_ref::<Term>(v) {
sumtis.push(sumti.clone());
} else if let Some(sumti) = downcast_ref::<Sumti>(v) {
sumtis.push(sumti.clone());
}
}
}
return sumtis.first().cloned().map(|s| Free::SOI(s, sumtis.get(1).cloned()));
}
if children.iter().any(|child| matches!(child, SemanticNode::NonTerminal { name, .. } if name == "MAI_clause")) {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(m) = downcast_ref::<Mex>(v) {
return Some(Free::MAI(m.clone()));
}
if let Some(ls) = downcast_ref::<Vec<Lerfu>>(v) {
return Some(Free::MAI(Mex::MexLerfuString(ls.clone())));
}
}
}
}
if children.iter().any(|child| matches!(child, SemanticNode::NonTerminal { name, .. } if name == "TO_clause")) {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(t) = downcast_ref::<Text>(v) {
return Some(Free::Bracketed(Box::new(t.clone())));
}
}
}
}
if children.iter().any(|child| matches!(child, SemanticNode::NonTerminal { name, .. } if name == "xi_clause")) {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(m) = downcast_ref::<Mex>(v) {
return Some(Free::XI(m.clone()));
}
}
}
}
if children.iter().any(|child| matches!(child, SemanticNode::NonTerminal { name, .. } if name == "vocative")) {
let cois = children
.iter()
.find_map(|child| child.value_ref().and_then(|v| downcast_ref::<Vec<COI>>(v).cloned()))
.unwrap_or_default();
let coi_words = cois.iter().map(|coi| coi.coi_coi.as_str()).collect::<Vec<_>>();
let mut rels = Vec::new();
let mut selbri: Option<Selbri> = None;
let mut sumti: Option<Sumti> = None;
let mut names = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(rs) = downcast_ref::<Vec<RelClause>>(v) {
rels.extend(rs.clone());
} else if let Some(s) = downcast_ref::<Selbri>(v) {
selbri = Some(s.clone());
} else if let Some(Term::Sumti(_, s)) = downcast_ref::<Term>(v) {
sumti = Some(s.clone());
} else if let Some(s) = downcast_ref::<Sumti>(v) {
sumti = Some(s.clone());
} else if let Some(name) = downcast_ref::<String>(v) {
if name.as_str() != "doi" && !coi_words.contains(&name.as_str()) {
names.push(name.trim_end_matches('.').to_string());
}
}
}
}
if let Some(s) = sumti {
return Some(Free::Vocative(cois, Some(s)));
}
if let Some(sb) = selbri {
return Some(Free::Vocative(cois, Some(Sumti::QAtom {
frees: Vec::new(),
quant: None,
rels,
atom: Box::new(SumtiAtom::Description(
"le".to_string(),
None,
None,
EitherSelbriSumti::Selbri(Box::new(sb)),
Vec::new(),
Vec::new(),
)),
})));
}
if !names.is_empty() {
return Some(Free::Vocative(cois, Some(Sumti::QAtom {
frees: Vec::new(),
quant: None,
rels,
atom: Box::new(SumtiAtom::Name("la".to_string(), Vec::new(), names.join(" "))),
})));
}
return Some(Free::Vocative(cois, None));
}
for child in children {
if let Some(v) = child.value_ref() {
if let Some(free) = downcast_ref::<Free>(v) {
return Some(free.clone());
}
}
}
None
});
reducers.on("INDICATOR_2", |_name, span, _children, input| {
truthq_from_text(span_slice(input, span)).or_else(|| {
let words = span_slice(input, span)
.split_whitespace()
.map(|word| word.trim_matches('.'))
.filter(|word| !word.is_empty())
.collect::<Vec<_>>();
words.first().map(|cmavo| Free::Indicator {
indicator_nai: words.contains(&"nai"),
indicator_cmavo: (*cmavo).to_string(),
})
})
});
reducers.on("FUhO_clause", |_name, span, _children, input| {
Some(Free::Indicator {
indicator_nai: false,
indicator_cmavo: span_slice(input, span).split_whitespace().next().unwrap_or("fu'o").to_string(),
})
});
reducers.on("indicators", |_name, _span, children, _input| {
let mut frees = Vec::new();
for child in children {
collect_frees(child, &mut frees);
}
if frees.is_empty() { None } else { Some(frees) }
});
reducers.on("text_part_2", |_name, _span, children, _input| {
let mut frees = Vec::new();
for child in children {
collect_frees(child, &mut frees);
}
Some(frees)
});
reducers.on("text_1", |_name, _span, children, _input| {
let mut paras = Vec::new();
for child in children {
collect_paragraph_items(child, &mut paras);
}
if paras.is_empty() { None } else { Some(paras) }
});
reducers.on("paragraphs", |_name, _span, children, _input| {
let mut paras: Vec<Paragraph> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(para) = downcast_ref::<Paragraph>(v) {
paras.push(para.clone());
} else if let Some(more_paras) = downcast_ref::<Vec<Paragraph>>(v) {
paras.extend(more_paras.clone());
}
}
}
if paras.is_empty() {
None
} else {
Some(paras)
}
});
reducers.on("paragraph", |_name, _span, children, _input| {
let mut items: Vec<FragmentOrStatement> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(stmt) = downcast_ref::<Statement>(v) {
items.push(FragmentOrStatement::Statement(stmt.clone()));
} else if let Some(frag) = downcast_ref::<Fragment>(v) {
items.push(FragmentOrStatement::Fragment(frag.clone()));
}
}
}
if items.is_empty() {
None
} else {
Some(items)
}
});
reducers.on("sentence", |_name, _span, children, _input| {
let mut terms: Vec<Term> = Vec::new();
let mut tail: Option<BridiTail> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(term) = downcast_ref::<Term>(v) {
terms.push(term.clone());
} else if let Some(terms_vec) = downcast_ref::<Vec<Term>>(v) {
terms.extend(terms_vec.clone());
} else if let Some(bt) = downcast_ref::<BridiTail>(v) {
tail = Some(bt.clone());
} else if let Some(gs) = downcast_ref::<GekSentence>(v) {
tail = Some(BridiTail::GekSentence(Box::new(gs.clone())));
}
}
}
let tail = tail.unwrap_or_else(|| {
BridiTail::BridiTail3(
crate::jbo_syntax::Selbri::Selbri2(
crate::jbo_syntax::Selbri2::Selbri3(
crate::jbo_syntax::Selbri3::TanruHead(
Vec::new(),
Box::new(crate::jbo_syntax::TanruUnit::TUBrivla(
"broda".to_string(),
)),
Vec::new(),
),
),
),
Vec::new(),
)
});
Some(Sentence {
terms,
tail: Box::new(tail),
})
});
reducers.on("gek_sentence", |_name, _span, children, _input| {
let mut conn: Option<crate::jbo_syntax::Connective> = None;
let mut subsentences: Vec<Subsentence> = Vec::new();
let mut tail_terms: Vec<Term> = Vec::new();
let mut inner: Option<GekSentence> = None;
let mut gik_truth = true;
let mut negated = false;
for child in children {
if let Some(v) = child.value_ref() {
if conn.is_none() {
if let Some(c) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
conn = Some(c.clone());
continue;
}
}
if matches!(downcast_ref::<Term>(v), Some(Term::Negation)) {
negated = true;
continue;
}
if let Some(b) = downcast_ref::<bool>(v) {
gik_truth = *b;
continue;
}
if let Some(ss) = downcast_ref::<Subsentence>(v) {
subsentences.push(ss.clone());
continue;
}
if inner.is_none() {
if let Some(gs) = downcast_ref::<GekSentence>(v) {
inner = Some(gs.clone());
continue;
}
}
}
if let SemanticNode::NonTerminal { name, .. } = child {
if name == "tail_terms" {
collect_tail_terms(child, &mut tail_terms);
}
}
}
if let (Some(mut c), Some(left), Some(right)) = (conn, subsentences.first(), subsentences.get(1)) {
if let crate::jbo_syntax::Connective::JboConnLog(_, lcon) = &mut c {
lcon.b2 = gik_truth;
}
let gs = GekSentence::ConnectedGS(c, Box::new(left.clone()), Box::new(right.clone()), tail_terms);
return Some(if negated {
GekSentence::NegatedGS(Box::new(gs))
} else {
gs
});
}
inner.map(|gs| {
if negated {
GekSentence::NegatedGS(Box::new(gs))
} else {
gs
}
})
});
reducers.on("bridi_tail", |_name, _span, children, _input| {
let mut tails: Vec<BridiTail> = Vec::new();
let mut conn: Option<crate::jbo_syntax::Connective> = None;
let mut tail_terms: Vec<Term> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(bt) = downcast_ref::<BridiTail>(v) {
tails.push(bt.clone());
} else if let Some(c) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
conn = Some(c.clone());
} else if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
if let Some(c) = conn.take() {
conn = Some(attach_tag_to_connective(c, Some(tag.clone())));
}
} else if let Some(s) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
tails.push(BridiTail::BridiTail3(s.clone(), Vec::new()));
} else if let Some(tu) = downcast_ref::<crate::jbo_syntax::TanruUnit>(v) {
tails.push(BridiTail::BridiTail3(
crate::jbo_syntax::Selbri::Selbri2(
crate::jbo_syntax::Selbri2::Selbri3(
crate::jbo_syntax::Selbri3::TanruHead(
Vec::new(),
Box::new(tu.clone()),
Vec::new(),
),
),
),
Vec::new(),
));
} else if let Some(term) = downcast_ref::<Term>(v) {
tail_terms.push(term.clone());
} else if let Some(terms_vec) = downcast_ref::<Vec<Term>>(v) {
tail_terms.extend(terms_vec.clone());
}
}
}
match (tails.first().cloned(), conn, tails.get(1).cloned()) {
(Some(left), Some(c), Some(right)) => Some(BridiTail::ConnectedBT(c, Box::new(left), Box::new(right), tail_terms)),
(Some(BridiTail::BridiTail3(selbri, mut existing_terms)), _, _) if !tail_terms.is_empty() => {
existing_terms.extend(tail_terms);
Some(BridiTail::BridiTail3(selbri, existing_terms))
}
(Some(left), _, _) => Some(left),
_ => None,
}
});
for i in 1..=2 {
let rule_name = format!("bridi_tail_{}", i);
reducers.on(&rule_name, |_name, _span, children, _input| {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(bt) = downcast_ref::<BridiTail>(v) {
return Some(bt.clone());
}
}
}
None
});
}
reducers.on("bridi_tail_3", |_name, _span, children, _input| {
let mut selbri: Option<crate::jbo_syntax::Selbri> = None;
let mut tail_terms: Vec<Term> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(s) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
selbri = Some(s.clone());
} else if let Some(term) = downcast_ref::<Term>(v) {
tail_terms.push(term.clone());
} else if let Some(terms_vec) = downcast_ref::<Vec<Term>>(v) {
tail_terms.extend(terms_vec.clone());
}
}
}
selbri.map(|s| BridiTail::BridiTail3(s, tail_terms))
});
reducers.on("subsentence", |_name, _span, children, _input| {
let mut prenex = Vec::new();
let mut subsentence: Option<crate::jbo_syntax::Subsentence> = None;
let mut sentence: Option<Sentence> = None;
let mut statement: Option<Statement> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(terms) = downcast_ref::<Vec<Term>>(v) {
prenex.extend(terms.clone());
} else if let Some(ss) = downcast_ref::<crate::jbo_syntax::Subsentence>(v) {
subsentence = Some(ss.clone());
} else if let Some(s) = downcast_ref::<Sentence>(v) {
sentence = Some(s.clone());
} else if let Some(stmt) = downcast_ref::<Statement>(v) {
statement = Some(stmt.clone());
}
}
}
if let Some(mut ss) = subsentence {
if !prenex.is_empty() {
prenex.extend(ss.prenex);
ss.prenex = prenex;
}
return Some(ss);
}
if let Some(sentence) = sentence {
return Some(crate::jbo_syntax::Subsentence {
frees: Vec::new(),
prenex,
sentence: Box::new(sentence),
});
}
if let Some(stmt) = statement {
if let Statement1::StatementSentence { sentence, .. } = &stmt.body {
let mut stmt_prenex = prenex;
stmt_prenex.extend(stmt.prenex.clone());
return Some(crate::jbo_syntax::Subsentence {
frees: stmt.frees.clone(),
prenex: stmt_prenex,
sentence: Box::new(sentence.clone()),
});
}
}
None
});
reducers.on("relative_clause_1", |_name, span, children, input| {
let text = span_slice(input, span).trim();
let mut subsentence: Option<crate::jbo_syntax::Subsentence> = None;
let mut term: Option<Term> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(ss) = downcast_ref::<crate::jbo_syntax::Subsentence>(v) {
subsentence = Some(ss.clone());
} else if let Some(t) = downcast_ref::<Term>(v) {
term = Some(t.clone());
}
}
}
if let Some(ss) = subsentence {
let first = text.split_whitespace().next().unwrap_or("");
return match first {
"poi" => Some(RelClause::Restrictive(ss)),
"noi" => Some(RelClause::Incidental(ss)),
"voi" => Some(RelClause::Descriptive(ss)),
_ => Some(RelClause::Descriptive(ss)),
};
}
if let Some(t) = term {
let first = text.split_whitespace().next().unwrap_or("");
return match first {
"pe" | "po" | "po'e" | "po'u" => Some(RelClause::RestrictiveGOI(first.to_string(), t)),
"ne" | "no'u" => Some(RelClause::IncidentalGOI(first.to_string(), t)),
"goi" => Some(RelClause::Assignment(t)),
_ => None,
};
}
None
});
reducers.on("relative_clause", |_name, _span, children, _input| {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(rel) = downcast_ref::<RelClause>(v) {
return Some(rel.clone());
}
}
}
None
});
reducers.on("relative_clauses", |_name, _span, children, _input| {
let mut rels = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(rel) = downcast_ref::<RelClause>(v) {
rels.push(rel.clone());
} else if let Some(more) = downcast_ref::<Vec<RelClause>>(v) {
rels.extend(more.clone());
}
}
}
if rels.is_empty() { None } else { Some(rels) }
});
for rule in &["sumti_1", "sumti_2", "sumti_3"] {
reducers.on(*rule, move |_name, _span, children, input| reduce_connected_sumti(children, input));
}
reducers.on("sumti_4", move |_name, _span, children, _input| {
let mut con: Option<crate::jbo_syntax::Connective> = None;
let mut right_truth = true;
let mut sumtis = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(con_value) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
con = Some(con_value.clone());
} else if let Some(gik_truth) = downcast_ref::<bool>(v) {
right_truth = *gik_truth;
} else if let Some(sumti) = downcast_ref::<Sumti>(v) {
sumtis.push(sumti.clone());
} else if let Some(Term::Sumti(_, sumti)) = downcast_ref::<Term>(v) {
sumtis.push(sumti.clone());
}
}
}
if let (Some(mut con), [s1, s2]) = (con, sumtis.as_slice()) {
if let crate::jbo_syntax::Connective::JboConnLog(_, lcon) = &mut con {
lcon.b2 = right_truth;
}
return Some(Term::Sumti(
Tagged::Untagged,
Sumti::ConnectedSumti(true, con, Box::new(s1.clone()), Box::new(s2.clone()), Vec::new()),
));
}
reduce_connected_sumti(children, _input)
});
reducers.on("sumti_5", move |_name, _span, children, input| {
let mut quant: Option<Mex> = None;
let mut rels: Vec<RelClause> = Vec::new();
let kau_depth = direct_sumti_kau_depth(children, input);
for child in children {
if let Some(v) = child.value_ref() {
if let Some(m) = downcast_ref::<Mex>(v) {
if quant.is_none() {
quant = Some(m.clone());
}
} else if let Some(rs) = downcast_ref::<Vec<RelClause>>(v) {
rels.extend(rs.clone());
}
}
}
if let Some(term) = reduce_connected_sumti(children, input) {
return Some(with_sumti_kau(attach_quant_to_term(term, quant), kau_depth));
}
if let Some(q) = quant {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(selbri) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
return Some(Term::Sumti(
Tagged::Untagged,
Sumti::QSelbri {
quant: q,
rels,
selbri: selbri.clone(),
},
));
}
}
}
}
None
});
reducers.on("li_clause", |_name, span, children, input| {
for child in children {
if let SemanticNode::NonTerminal { name, value: Some(v), .. } = child {
if name == "mex" {
if let Some(m) = downcast_ref::<Mex>(v) {
return Some(LiClauseParts {
mex: m.clone(),
is_meho: span_slice(input, span).split_whitespace().next() == Some("me'o"),
kau_depth: xo_kau_depth(m, span, input),
});
}
}
}
}
None
});
reducers.on("sumti_6", |_name, span, children, input| {
let mut gadri: Option<String> = None;
let mut name_marker: Option<String> = None;
let mut name_parts: Vec<String> = Vec::new();
let mut tail_parts: Option<SumtiTailParts> = None;
let mut selbri: Option<crate::jbo_syntax::Selbri> = None;
let mut mex_li: Option<Mex> = None;
let mut mex_is_meho = false;
let mut mex_kau_depth: Option<i32> = None;
let mut qualifier: Option<SumtiQualifier> = None;
let mut qualifier_rels: Vec<RelClause> = Vec::new();
let mut sumti_frees: Vec<Free> = Vec::new();
for child in children {
if let SemanticNode::NonTerminal { name, .. } = child {
if name == "free" || name == "indicators" {
collect_frees(child, &mut sumti_frees);
}
}
}
let span_text = span_slice(input, span);
let mut span_words = span_text.split_whitespace();
if span_words.next() == Some("zo") {
let word = span_words
.next()
.map(|word| word.trim_matches('.').to_string())
.unwrap_or_default();
return Some(Term::Sumti(
Tagged::Untagged,
Sumti::QAtom {
frees: Vec::new(),
quant: None,
rels: Vec::new(),
atom: Box::new(SumtiAtom::Word(word)),
},
));
}
let span_tokens = span_text.split_whitespace().collect::<Vec<_>>();
if (span_tokens.first() == Some(&"zoi") || span_tokens.first() == Some(&"la'o"))
&& span_tokens.len() >= 3 {
let quoted = span_tokens[2..span_tokens.len().saturating_sub(1)].join(" ");
return Some(Term::Sumti(
Tagged::Untagged,
Sumti::QAtom {
frees: Vec::new(),
quant: None,
rels: Vec::new(),
atom: Box::new(SumtiAtom::NonJboQuote(quoted)),
},
));
}
if span_tokens.first() == Some(&"lo'u") {
let words = span_tokens
.iter()
.skip(1)
.take_while(|word| **word != "le'u")
.map(|word| (*word).to_string())
.collect::<Vec<_>>();
return Some(Term::Sumti(
Tagged::Untagged,
Sumti::QAtom {
frees: Vec::new(),
quant: None,
rels: Vec::new(),
atom: Box::new(SumtiAtom::ErrorQuote(words)),
},
));
}
let koh_a_span = children.iter().find_map(|child| {
if let SemanticNode::NonTerminal { name, span, .. } = child {
if name == "KOhA_clause" {
return Some(*span);
}
}
None
});
if let Some(koh_a_span) = koh_a_span {
let mut words = span_slice(input, koh_a_span)
.split_whitespace()
.map(|word| word.trim_matches('.'))
.filter(|word| !word.is_empty() && !is_haskell_bahe(word))
.collect::<Vec<_>>();
for child in children {
if let SemanticNode::NonTerminal { name, span, .. } = child {
if name == "free" {
let free_words = span_slice(input, *span)
.split_whitespace()
.map(|word| word.trim_matches('.'))
.filter(|word| !word.is_empty());
words.extend(free_words);
}
}
}
if let Some(atom) = koh_a_sumti_atom_from_words(&words) {
return Some(with_sumti_frees(qatom_term(atom), &sumti_frees));
}
}
if let Some(quoted_text) = children.iter().find_map(|child| {
child.value_ref().and_then(|v| downcast_ref::<Text>(v).cloned())
}) {
return Some(Term::Sumti(
Tagged::Untagged,
Sumti::QAtom {
frees: sumti_frees,
quant: None,
rels: Vec::new(),
atom: Box::new(SumtiAtom::Quote(Box::new(quoted_text))),
},
));
}
for child in children {
if let SemanticNode::NonTerminal { name, value: Some(v), .. } = child {
if name == "li_clause" {
if let Some(parts) = downcast_ref::<LiClauseParts>(v) {
mex_li = Some(parts.mex.clone());
mex_is_meho = parts.is_meho;
mex_kau_depth = parts.kau_depth;
continue;
}
}
}
if let Some(v) = child.value_ref() {
if let Some(m) = downcast_ref::<Mex>(v) {
if mex_li.is_none() {
mex_li = Some(m.clone());
}
continue;
}
if let Some(s) = downcast_ref::<String>(v) {
if matches!(s.as_str(), "tu'a" | "la'e" | "lu'a" | "lu'e" | "lu'i" | "lu'o" | "vu'i" | "zo'ei" | "du'au" | "lau'e" | "tau'e" | "ce'u" | "mo'a") {
if qualifier.is_none() {
qualifier = Some(SumtiQualifier::LAhE(s.clone()));
continue;
}
} else if s == "bo" {
continue;
} else if matches!(s.as_str(), "na'e" | "to'e" | "no'e" | "je'a") {
if qualifier.is_none() {
qualifier = Some(SumtiQualifier::NAhE_BO(s.clone()));
continue;
}
} else if s.starts_with("la") || s.starts_with("lai") {
if name_marker.is_none() {
name_marker = Some(s.clone());
continue;
}
} else if s.starts_with("le") || s.starts_with("lo") {
if gadri.is_none() {
gadri = Some(s.clone());
continue;
}
} else if name_marker.is_some() {
name_parts.push(s.clone());
continue;
} else if gadri.is_none() {
gadri = Some(s.clone());
continue;
}
}
if let Some(parts) = downcast_ref::<SumtiTailParts>(v) {
tail_parts = Some(parts.clone());
}
if let Some(rs) = downcast_ref::<Vec<RelClause>>(v) {
qualifier_rels.extend(rs.clone());
continue;
}
if let Some(s) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
selbri = Some(s.clone());
}
if let Some(term) = downcast_ref::<Term>(v) {
if let Some(qual) = qualifier.clone() {
if let Term::Sumti(tag, sumti) = term {
return Some(Term::Sumti(
tag.clone(),
Sumti::QAtom {
frees: Vec::new(),
quant: None,
rels: Vec::new(),
atom: Box::new(SumtiAtom::QualifiedSumti(
qual,
qualifier_rels.clone(),
Box::new(sumti.clone()),
)),
},
));
}
}
let term = with_sumti_frees(term.clone(), &sumti_frees);
return Some(with_sumti_kau(term, direct_free_kau_depth(children, input)));
}
}
}
if mex_li.is_none() {
let words: Vec<&str> = span_slice(input, span).split_whitespace().collect();
if words.first() == Some(&"li") && words.len() >= 2 {
mex_li = Some(Mex::MexNumeralString(vec![Numeral::PA(words[1].to_string())]));
}
}
if let Some(m) = mex_li {
let atom = if !mex_is_meho {
mex_kau_depth
.map(|depth| SumtiAtom::SumtiQ(Some(depth)))
.unwrap_or(SumtiAtom::MexLi(m))
} else {
SumtiAtom::MexMex(m)
};
return Some(Term::Sumti(
Tagged::Untagged,
Sumti::QAtom {
frees: Vec::new(),
quant: None,
rels: Vec::new(),
atom: Box::new(atom),
},
));
}
if let (Some(g), Some(parts)) = (gadri.clone().or_else(|| name_marker.clone()), tail_parts) {
return Some(Term::Sumti(
Tagged::Untagged,
Sumti::QAtom {
frees: Vec::new(),
quant: None,
rels: Vec::new(),
atom: Box::new(SumtiAtom::Description(
g,
parts.inner_sumti,
parts.inner_quant,
parts.selbri_or_sumti,
parts.rels,
parts.inner_rels,
)),
},
));
}
if let (Some(g), Some(s)) = (gadri.or_else(|| name_marker.clone()), selbri) {
return Some(Term::Sumti(
Tagged::Untagged,
Sumti::QAtom {
frees: Vec::new(),
quant: None,
rels: Vec::new(),
atom: Box::new(SumtiAtom::Description(
g,
None,
None,
EitherSelbriSumti::Selbri(Box::new(s)),
Vec::new(),
Vec::new(),
)),
},
));
}
if let Some(marker) = name_marker.clone() {
let names = if name_parts.is_empty() {
span_slice(input, span)
.split_whitespace()
.skip(1)
.map(|word| word.trim_end_matches('.').to_string())
.collect::<Vec<_>>()
} else {
name_parts.clone()
};
if !names.is_empty() {
return Some(with_sumti_kau(Term::Sumti(
Tagged::Untagged,
Sumti::QAtom {
frees: Vec::new(),
quant: None,
rels: Vec::new(),
atom: Box::new(crate::jbo_syntax::SumtiAtom::Name(
marker,
Vec::new(),
names.join(" "),
)),
},
), direct_free_kau_depth(children, input)));
}
}
for child in children {
if let Some(v) = child.value_ref() {
if let Some(lerfu) = downcast_ref::<Vec<Lerfu>>(v) {
return Some(Term::Sumti(
Tagged::Untagged,
Sumti::QAtom {
frees: Vec::new(),
quant: None,
rels: Vec::new(),
atom: Box::new(SumtiAtom::LerfuString(lerfu.clone())),
},
));
}
}
}
None
});
for rule in &["term_1", "nonabs_term"] {
reducers.on(*rule, |_name, _span, children, _input| {
fn find_tag(node: &SemanticNode) -> Option<crate::jbo_syntax::Tag> {
if let Some(v) = node.value_ref() {
if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
return Some(tag.clone());
}
if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
return Some(crate::jbo_syntax::Tag::DecoratedTagUnits(vec![
crate::jbo_syntax::DecoratedAbsTagUnit {
tag_nahe: None,
tag_se: None,
tag_nai: false,
tag_unit: tu.clone(),
},
]));
}
if let Some(Term::BareTag(tag)) = downcast_ref::<Term>(v) {
return Some(tag.clone());
}
}
if let SemanticNode::NonTerminal { name, children, .. } = node {
if *name == "sumti" {
return None;
}
for child in children {
if let Some(tag) = find_tag(child) {
return Some(tag);
}
}
}
None
}
let mut fa: Option<Option<i32>> = None;
let mut tag: Option<crate::jbo_syntax::Tag> = None;
let mut sumti_term: Option<Term> = None;
let mut sumti_value: Option<Sumti> = None;
for child in children {
if tag.is_none() {
tag = find_tag(child);
}
if let Some(v) = child.value_ref() {
if let Some(Term::BareFA(n)) = downcast_ref::<Term>(v) {
fa = Some(*n);
} else if let Some(Term::Sumti(_, _)) = downcast_ref::<Term>(v) {
sumti_term = downcast_ref::<Term>(v).cloned();
} else if let Some(sumti) = downcast_ref::<Sumti>(v) {
sumti_value = Some(sumti.clone());
}
}
}
if let Some(n) = fa {
let tagged = match n {
Some(place) => Tagged::FATagged(place),
None => Tagged::FAITagged,
};
if let Some(Term::Sumti(_, sumti)) = sumti_term.clone() {
return Some(Term::Sumti(tagged, sumti));
}
if let Some(sumti) = sumti_value.clone() {
return Some(Term::Sumti(tagged, sumti));
}
}
if let Some(tag) = tag {
if let Some(Term::Sumti(_, sumti)) = sumti_term.clone() {
return Some(Term::Sumti(Tagged::Tagged(tag), sumti));
}
if let Some(sumti) = sumti_value.clone() {
return Some(Term::Sumti(Tagged::Tagged(tag), sumti));
}
}
for child in children {
if let Some(v) = child.value_ref() {
if let Some(term) = downcast_ref::<Term>(v) {
return Some(term.clone());
}
}
}
for child in children {
if let Some(v) = child.value_ref() {
if let Some(sumti) = downcast_ref::<Sumti>(v) {
return Some(Term::Sumti(Tagged::Untagged, sumti.clone()));
}
}
}
None
});
}
reducers.on("terms_1", |_name, _span, children, _input| {
let mut terms: Vec<Term> = Vec::new();
let mut conns: Vec<crate::jbo_syntax::Connective> = Vec::new();
let mut pending_conn: Option<crate::jbo_syntax::Connective> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(term) = downcast_ref::<Term>(v) {
terms.push(term.clone());
} else if let Some(terms_vec) = downcast_ref::<Vec<Term>>(v) {
terms.extend(terms_vec.clone());
} else if let Some(con) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
if let Some(con) = pending_conn.take() {
conns.push(con);
}
pending_conn = Some(con.clone());
}
}
}
if let Some(con) = pending_conn.take() {
conns.push(con);
}
let mut current = terms.first()?.clone();
for (idx, term) in terms.into_iter().skip(1).enumerate() {
let conn = conns.get(idx)?.clone();
current = Term::ConnectedTerms(false, conn, Box::new(current), Box::new(term));
}
Some(vec![current])
});
reducers.on("terms_2", |_name, _span, children, _input| {
let mut terms: Vec<Term> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(term) = downcast_ref::<Term>(v) {
terms.push(term.clone());
} else if let Some(terms_vec) = downcast_ref::<Vec<Term>>(v) {
terms.extend(terms_vec.clone());
}
}
}
if terms.len() <= 1 {
return Some(terms);
}
Some(vec![Term::Termset(terms)])
});
reducers.on("selbri_3", |_name, _span, children, _input| {
let mut sb3_list: Vec<crate::jbo_syntax::Selbri3> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(selbri) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
if let crate::jbo_syntax::Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(sb3)) = selbri {
sb3_list.push(sb3.clone());
}
} else if let Some(tu) = downcast_ref::<crate::jbo_syntax::TanruUnit>(v) {
sb3_list.push(crate::jbo_syntax::Selbri3::TanruHead(
Vec::new(),
Box::new(tu.clone()),
Vec::new(),
));
}
}
}
let mut iter = sb3_list.into_iter();
let mut result = iter.next()?;
for sb3 in iter {
result = crate::jbo_syntax::Selbri3::SBTanru(
Box::new(result),
Box::new(sb3),
);
}
Some(crate::jbo_syntax::Selbri::Selbri2(
crate::jbo_syntax::Selbri2::Selbri3(result)
))
});
let tanru_unit_to_selbri3 = |tu: &crate::jbo_syntax::TanruUnit| {
crate::jbo_syntax::Selbri3::TanruHead(
Vec::new(),
Box::new(tu.clone()),
Vec::new(),
)
};
reducers.on("selbri_1", |_name, span, children, input| {
let negated = span_slice(input, span).split_whitespace().next() == Some("na");
for child in children {
if let Some(v) = child.value_ref() {
let selbri = if let Some(selbri) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
Some(selbri.clone())
} else { downcast_ref::<crate::jbo_syntax::TanruUnit>(v).map(|tu| crate::jbo_syntax::Selbri::Selbri2(
crate::jbo_syntax::Selbri2::Selbri3(
crate::jbo_syntax::Selbri3::TanruHead(
Vec::new(),
Box::new(tu.clone()),
Vec::new(),
),
),
)) };
if let Some(selbri) = selbri {
return Some(if negated {
crate::jbo_syntax::Selbri::Negated(Box::new(selbri))
} else {
selbri
});
}
}
}
None
});
reducers.on("selbri_2", |_name, _span, children, _input| {
let mut parts: Vec<crate::jbo_syntax::Selbri2> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(selbri) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
if let crate::jbo_syntax::Selbri::Selbri2(sb2) = selbri {
parts.push(sb2.clone());
}
} else if let Some(tu) = downcast_ref::<crate::jbo_syntax::TanruUnit>(v) {
parts.push(crate::jbo_syntax::Selbri2::Selbri3(
crate::jbo_syntax::Selbri3::TanruHead(
Vec::new(),
Box::new(tu.clone()),
Vec::new(),
),
));
}
}
}
match parts.as_slice() {
[crate::jbo_syntax::Selbri2::Selbri3(sb3), right] => Some(crate::jbo_syntax::Selbri::Selbri2(
crate::jbo_syntax::Selbri2::SBInverted(sb3.clone(), Box::new(right.clone())),
)),
[only] => Some(crate::jbo_syntax::Selbri::Selbri2(only.clone())),
_ => None,
}
});
reducers.on("selbri_4", |_name, _span, children, _input| {
let mut items = Vec::new();
for child in children {
collect_selbri4_items(child, &mut items);
}
let mut result: Option<crate::jbo_syntax::Selbri3> = None;
let mut pending_con: Option<crate::jbo_syntax::Connective> = None;
for item in items {
match item {
Selbri4Item::Jeksbs((con, right)) => {
if let Some(left) = result.take() {
result = Some(crate::jbo_syntax::Selbri3::ConnectedSB(
false,
con,
Box::new(crate::jbo_syntax::Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(left))),
Box::new(right),
));
}
}
Selbri4Item::Connective(con) => pending_con = Some(con),
Selbri4Item::Selbri3(sb3) => {
if let Some(left) = result.take() {
if let Some(con) = pending_con.take() {
result = Some(crate::jbo_syntax::Selbri3::ConnectedSB(
false,
con,
Box::new(crate::jbo_syntax::Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(left))),
Box::new(sb3),
));
} else {
result = Some(left);
}
} else {
result = Some(sb3);
}
}
}
}
result.map(|sb3| crate::jbo_syntax::Selbri::Selbri2(
crate::jbo_syntax::Selbri2::Selbri3(sb3),
))
});
reducers.on("selbri_6", move |_name, _span, children, _input| {
let mut units = Vec::new();
let mut con: Option<crate::jbo_syntax::Connective> = None;
let mut right_truth = true;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(tu) = downcast_ref::<crate::jbo_syntax::TanruUnit>(v) {
units.push(tanru_unit_to_selbri3(tu));
} else if let Some(selbri) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
if let crate::jbo_syntax::Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(sb3)) = selbri {
units.push(sb3.clone());
}
} else if let Some(con_value) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
con = Some(con_value.clone());
} else if let Some(gik_truth) = downcast_ref::<bool>(v) {
right_truth = *gik_truth;
}
}
}
if let (Some(mut con), [left, right]) = (con, units.as_slice()) {
if let crate::jbo_syntax::Connective::JboConnLog(_, lcon) = &mut con {
lcon.b2 = right_truth;
}
return Some(crate::jbo_syntax::Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(
crate::jbo_syntax::Selbri3::ConnectedSB(
true,
con,
Box::new(crate::jbo_syntax::Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(left.clone()))),
Box::new(right.clone()),
),
)));
}
let mut iter = units.into_iter().rev();
let mut result = iter.next()?;
for seltau in iter {
result = crate::jbo_syntax::Selbri3::SBTanru(Box::new(seltau), Box::new(result));
}
Some(crate::jbo_syntax::Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(result)))
});
reducers.on("selbri_5", |_name, _span, children, _input| {
let mut left: Option<crate::jbo_syntax::Selbri3> = None;
let mut con: Option<crate::jbo_syntax::Connective> = None;
let mut right: Option<crate::jbo_syntax::Selbri3> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(selbri) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
if let crate::jbo_syntax::Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(sb3)) = selbri {
if left.is_none() {
left = Some(sb3.clone());
} else {
right = Some(sb3.clone());
}
}
} else if let Some(con_value) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
con = Some(con_value.clone());
} else if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
if let Some(pending) = con.take() {
con = Some(attach_tag_to_connective(pending, Some(tag.clone())));
}
}
}
}
match (left, con, right) {
(Some(sb1), Some(con), Some(sb2)) => {
Some(crate::jbo_syntax::Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(
crate::jbo_syntax::Selbri3::ConnectedSB(
false,
con,
Box::new(crate::jbo_syntax::Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(sb1))),
Box::new(sb2),
),
)))
}
(Some(sb), _, _) => Some(crate::jbo_syntax::Selbri::Selbri2(
crate::jbo_syntax::Selbri2::Selbri3(sb),
)),
_ => None,
}
});
reducers.on("tanru_unit_1", |_name, _span, children, _input| {
let mut tu: Option<crate::jbo_syntax::TanruUnit> = None;
let mut terms: Vec<Term> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(unit) = downcast_ref::<crate::jbo_syntax::TanruUnit>(v) {
if tu.is_none() {
tu = Some(unit.clone());
}
} else if let Some(link_terms) = downcast_ref::<Vec<Term>>(v) {
terms.extend(link_terms.clone());
}
}
}
if terms.is_empty() {
tu
} else {
tu.map(|unit| crate::jbo_syntax::TanruUnit::TUSelbri3(
crate::jbo_syntax::Selbri3::TanruHead(Vec::new(), Box::new(unit), terms),
))
}
});
reducers.on("linkargs_1", |_name, _span, children, _input| {
let mut terms = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(term) = downcast_ref::<Term>(v) {
terms.push(term.clone());
} else if let Some(more_terms) = downcast_ref::<Vec<Term>>(v) {
terms.extend(more_terms.clone());
}
}
}
Some(terms)
});
reducers.on("links_1", |_name, _span, children, _input| {
let mut terms = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(term) = downcast_ref::<Term>(v) {
terms.push(term.clone());
} else if let Some(more_terms) = downcast_ref::<Vec<Term>>(v) {
terms.extend(more_terms.clone());
}
}
}
Some(terms)
});
for rule in &["linkargs", "links"] {
reducers.on(*rule, |_name, _span, children, _input| {
let mut terms = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(more_terms) = downcast_ref::<Vec<Term>>(v) {
terms.extend(more_terms.clone());
}
}
}
Some(terms)
});
}
reducers.on("tanru_unit_2", move |_name, span, children, input| {
let mut se: Option<i32> = None;
let mut frees: Vec<Free> = Vec::new();
let mut saw_me = false;
let mut saw_nuha = false;
let mut saw_jai = false;
let mut jai_tag: Option<crate::jbo_syntax::Tag> = None;
let mut nahe: Option<String> = None;
let mut nu: Option<String> = None;
let mut subsentence: Option<crate::jbo_syntax::Subsentence> = None;
let mut sumti: Option<Sumti> = None;
let mut operator: Option<crate::jbo_syntax::Operator> = None;
let mut number: Option<Mex> = None;
let mut moi: Option<String> = None;
let rule_words: Vec<&str> = span_slice(input, span).split_whitespace().collect();
let wrap_nahe = |tu: crate::jbo_syntax::TanruUnit, nahe: &Option<String>| {
if let Some(nahe) = nahe {
crate::jbo_syntax::TanruUnit::TUSelbri3(crate::jbo_syntax::Selbri3::ScalarNegatedSB(
nahe.clone(),
Box::new(tanru_unit_to_selbri3(&tu)),
))
} else {
tu
}
};
let wrap_frees = |tu: crate::jbo_syntax::TanruUnit, frees: &[Free]| {
if frees.is_empty() {
tu
} else {
crate::jbo_syntax::TanruUnit::TUSelbri3(crate::jbo_syntax::Selbri3::TanruHead(
frees.to_vec(),
Box::new(tu),
Vec::new(),
))
}
};
fn find_tag(node: &SemanticNode) -> Option<crate::jbo_syntax::Tag> {
if let SemanticNode::NonTerminal { name, children, .. } = node {
if *name == "tanru_unit_2" {
return None;
}
for child in children {
if let Some(tag) = find_tag(child) {
return Some(tag);
}
}
}
if let Some(v) = node.value_ref() {
if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
return Some(tag.clone());
}
if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
return Some(crate::jbo_syntax::Tag::DecoratedTagUnits(vec![
crate::jbo_syntax::DecoratedAbsTagUnit {
tag_nahe: None,
tag_se: None,
tag_nai: false,
tag_unit: tu.clone(),
},
]));
}
}
None
}
fn connect_abstractors(
con: crate::jbo_syntax::Connective,
left: Abstractor,
right: Abstractor,
) -> Abstractor {
match con {
crate::jbo_syntax::Connective::JboConnLog(_, lcon) if lcon.c == 'i' => {
Abstractor::JoiConnectedAbstractor("??".to_string(), Box::new(left), Box::new(right))
}
crate::jbo_syntax::Connective::JboConnLog(_, lcon) => {
Abstractor::LogConnectedAbstractor(lcon, Box::new(left), Box::new(right))
}
crate::jbo_syntax::Connective::JboConnJoik(_, joik) => {
Abstractor::JoiConnectedAbstractor(joik, Box::new(left), Box::new(right))
}
}
}
fn connective_from_joik_jek_text(text: &str) -> Option<crate::jbo_syntax::Connective> {
let words: Vec<&str> = text.split_whitespace().collect();
if let Some(joik) = joik_string_from_words(&words) {
return Some(crate::jbo_syntax::Connective::JboConnJoik(None, joik));
}
let conchar = if words.iter().any(|word| matches!(*word, "ja" | "ga" | "gu'a" | "gi'a")) {
'a'
} else if words.iter().any(|word| matches!(*word, "je" | "ge" | "gu'e" | "gi'e")) {
'e'
} else if words.iter().any(|word| matches!(*word, "jo" | "go" | "gu'o" | "gi'o")) {
'o'
} else if words.iter().any(|word| matches!(*word, "ju" | "gu" | "gu'u" | "gi'u")) {
'u'
} else if words.contains(&"je'i") {
'i'
} else {
return None;
};
Some(crate::jbo_syntax::Connective::JboConnLog(
None,
crate::jbo_syntax::LogJboConnective {
b1: !words.contains(&"na"),
c: if words.contains(&"se") && conchar == 'u' { 'U' } else { conchar },
b2: !words.contains(&"nai"),
},
))
}
fn build_abstractor(children: &[SemanticNode], input: &str) -> Option<Abstractor> {
let mut abstractors = Vec::new();
let mut connectives = Vec::new();
for child in children {
if let SemanticNode::NonTerminal { name, span, .. } = child {
if *name == "subsentence" {
break;
}
if *name == "NAI_clause" {
if let Some(last) = abstractors.pop() {
abstractors.push(Abstractor::NegatedAbstractor(Box::new(last)));
}
continue;
}
if *name == "joik_jek" {
if let Some(con) = child.value_ref()
.and_then(|v| downcast_ref::<crate::jbo_syntax::Connective>(v).cloned())
.or_else(|| connective_from_joik_jek_text(span_slice(input, *span)))
{
connectives.push(con);
}
continue;
}
}
if let Some(v) = child.value_ref() {
if let SemanticNode::NonTerminal { name, .. } = child {
if *name == "NU_clause" {
if let Some(s) = downcast_ref::<String>(v) {
abstractors.push(Abstractor::NU(s.clone()));
}
}
}
}
}
let mut current = abstractors.pop()?;
while let Some(left) = abstractors.pop() {
current = connect_abstractors(connectives.pop()?, left, current);
}
Some(current)
}
let subsentence_span = children.iter().find_map(|child| {
if let SemanticNode::NonTerminal { name, span, .. } = child {
if name == "subsentence" {
return Some(*span);
}
}
None
});
for child in children {
if let SemanticNode::NonTerminal { name, .. } = child {
if name == "free" || name == "indicators" {
if let Some(Span(_, end)) = subsentence_span {
if *name == "free" {
collect_frees_after_span(child, end, &mut frees);
} else {
collect_frees(child, &mut frees);
}
} else {
collect_frees(child, &mut frees);
}
}
}
}
for child in children {
if jai_tag.is_none() {
jai_tag = find_tag(child);
}
if let SemanticNode::NonTerminal { name, span, .. } = child {
if name == "SE_clause" && se.is_none() {
let se_word = span_slice(input, *span).split_whitespace().next();
se = match se_word {
Some("se") => xi_lit_from_words(&rule_words, 0).or(Some(2)),
Some("te") => xi_lit_from_words(&rule_words, 0).or(Some(3)),
Some("ve") => xi_lit_from_words(&rule_words, 0).or(Some(4)),
Some("xe") => xi_lit_from_words(&rule_words, 0).or(Some(5)),
_ => None,
};
if se.is_some() {
continue;
}
}
}
if let Some(v) = child.value_ref() {
if let Some(s) = downcast_ref::<String>(v) {
if s == "me" {
saw_me = true;
} else if s == "nua" || s == "nu'a" {
saw_nuha = true;
} else if s == "jai" {
saw_jai = true;
} else if matches!(s.as_str(), "na'e" | "to'e" | "no'e" | "je'a") {
nahe = Some(s.clone());
} else if matches!(s.as_str(), "mei" | "moi" | "si'e" | "cu'o" | "va'e") {
moi = Some(s.clone());
} else if matches!(s.as_str(), "ka" | "ni" | "nu" | "du'u" | "su'u" | "mu'e" | "zu'o" | "pu'u" | "za'i" | "li'i" | "si'o" | "jei" | "poi'i") {
nu = Some(s.clone());
}
}
if let Some(m) = downcast_ref::<Mex>(v) {
number = Some(m.clone());
}
if let Some(ls) = downcast_ref::<Vec<Lerfu>>(v) {
number = Some(Mex::MexLerfuString(ls.clone()));
}
if let Some(ss) = downcast_ref::<crate::jbo_syntax::Subsentence>(v) {
subsentence = Some(ss.clone());
}
if let Some(op) = downcast_ref::<crate::jbo_syntax::Operator>(v) {
operator = Some(op.clone());
}
if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
jai_tag = Some(tag.clone());
}
if let Some(Term::Sumti(_, s)) = downcast_ref::<Term>(v) {
sumti = Some(s.clone());
}
if let Some(crate::jbo_syntax::Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(sb3))) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
let mut tu = crate::jbo_syntax::TanruUnit::TUSelbri3(sb3.clone());
if saw_jai {
tu = crate::jbo_syntax::TanruUnit::TUJai(jai_tag.clone(), Box::new(tu));
}
let tu = wrap_frees(wrap_nahe(tu, &nahe), &frees);
return Some(if let Some(n) = se {
crate::jbo_syntax::TanruUnit::TUPermuted(n, Box::new(tu))
} else {
tu
});
}
if let Some(tu) = downcast_ref::<crate::jbo_syntax::TanruUnit>(v) {
let mut tu = tu.clone();
let mut frees = frees.clone();
if matches!(tu, crate::jbo_syntax::TanruUnit::TUBridiQ(None)) {
if let Some(depth) = direct_free_kau_depth(children, input) {
tu = crate::jbo_syntax::TanruUnit::TUBridiQ(Some(depth));
frees.retain(|free| !matches!(free, Free::Indicator { indicator_nai: false, indicator_cmavo } if indicator_cmavo == "kau"));
}
}
if saw_jai {
tu = crate::jbo_syntax::TanruUnit::TUJai(jai_tag.clone(), Box::new(tu));
}
let tu = wrap_frees(wrap_nahe(tu, &nahe), &frees);
return Some(if let Some(n) = se {
crate::jbo_syntax::TanruUnit::TUPermuted(n, Box::new(tu))
} else {
tu
});
}
}
}
if let Some(mut ss) = subsentence {
if let Some(Span(start, _)) = subsentence_span {
let mut subsentence_frees = Vec::new();
for child in children {
if let SemanticNode::NonTerminal { name, .. } = child {
if name == "free" {
collect_frees_before_span(child, start, &mut subsentence_frees);
}
}
}
if !subsentence_frees.is_empty() {
subsentence_frees.extend(ss.frees);
ss.frees = subsentence_frees;
}
}
if let Some(abs) = build_abstractor(children, input) {
return Some(wrap_frees(crate::jbo_syntax::TanruUnit::TUAbstraction(abs, ss), &frees));
}
if let Some(nu) = nu {
return Some(wrap_frees(crate::jbo_syntax::TanruUnit::TUAbstraction(Abstractor::NU(nu), ss), &frees));
}
}
if saw_nuha {
if let Some(op) = operator {
return Some(wrap_frees(crate::jbo_syntax::TanruUnit::TUOperator(op), &frees));
}
}
if let (Some(n), Some(m)) = (number, moi.clone()) {
let sumti = Sumti::QAtom {
frees: Vec::new(),
quant: None,
rels: Vec::new(),
atom: Box::new(SumtiAtom::MexLi(n)),
};
return Some(wrap_frees(wrap_nahe(crate::jbo_syntax::TanruUnit::TUMoi(Box::new(sumti), m), &nahe), &frees));
}
if saw_me {
if let Some(s) = sumti {
if let Some(m) = moi {
return Some(wrap_frees(wrap_nahe(crate::jbo_syntax::TanruUnit::TUMoi(Box::new(s), m), &nahe), &frees));
}
return Some(wrap_frees(wrap_nahe(crate::jbo_syntax::TanruUnit::TUMe(Box::new(s)), &nahe), &frees));
}
}
None
});
reducers.on("bridi_tail_3", |_name, _span, children, input| {
fn collect_terms(node: &SemanticNode, out: &mut Vec<Term>) {
if let Some(v) = node.value_ref() {
if let Some(term) = downcast_ref::<Term>(v) {
out.push(term.clone());
return;
}
if let Some(terms) = downcast_ref::<Vec<Term>>(v) {
out.extend(terms.clone());
return;
}
}
if let SemanticNode::NonTerminal { children, .. } = node {
for child in children {
collect_terms(child, out);
}
}
}
let mut selbri: Option<crate::jbo_syntax::Selbri> = None;
let mut tail_terms: Vec<Term> = Vec::new();
let mut bridi_question_depth: Option<i32> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(bt) = downcast_ref::<BridiTail>(v) {
return Some(bt.clone());
} else if let Some(gs) = downcast_ref::<GekSentence>(v) {
return Some(BridiTail::GekSentence(Box::new(gs.clone())));
} else if let Some(s) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
selbri = Some(s.clone());
continue;
}
}
if let SemanticNode::NonTerminal { name, children, .. } = child {
if name == "tail_terms" {
collect_terms(child, &mut tail_terms);
bridi_question_depth = children.iter().find_map(|tail_child| match tail_child {
SemanticNode::NonTerminal { name, children, .. } if name == "terms" => {
children.iter().find_map(|terms_child| quantifier_free_kau_depth_in_sumti5(terms_child, input))
}
_ => quantifier_free_kau_depth_in_sumti5(tail_child, input),
});
}
}
}
if let Some(s) = selbri {
let s = bridi_question_depth.map(selbri_bridi_question).unwrap_or(s);
return Some(BridiTail::BridiTail3(s, tail_terms));
}
None
});
reducers.on("bridi_tail_1", |_name, _span, children, _input| {
let mut tails: Vec<BridiTail> = Vec::new();
let mut conns: Vec<crate::jbo_syntax::Connective> = Vec::new();
let mut tail_terms: Vec<Vec<Term>> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(bt) = downcast_ref::<BridiTail>(v) {
tails.push(bt.clone());
continue;
} else if let Some(conn) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
conns.push(conn.clone());
continue;
} else if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
if let Some(conn) = conns.pop() {
conns.push(attach_tag_to_connective(conn, Some(tag.clone())));
}
continue;
} else if let Some(selbri) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
tails.push(BridiTail::BridiTail3(selbri.clone(), Vec::new()));
continue;
}
}
if let SemanticNode::NonTerminal { name, .. } = child {
if name == "tail_terms" {
let mut terms = Vec::new();
collect_tail_terms(child, &mut terms);
tail_terms.push(terms);
}
}
}
let mut current = tails.first()?.clone();
for (idx, right) in tails.into_iter().skip(1).enumerate() {
let conn = conns.get(idx)?.clone();
let terms = tail_terms.get(idx).cloned().unwrap_or_default();
current = BridiTail::ConnectedBT(conn, Box::new(current), Box::new(right), terms);
}
Some(current)
});
reducers.on("bridi_tail_2", |_name, _span, children, _input| {
let mut tails: Vec<BridiTail> = Vec::new();
let mut conn: Option<crate::jbo_syntax::Connective> = None;
let mut terms = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(bt) = downcast_ref::<BridiTail>(v) {
tails.push(bt.clone());
continue;
} else if let Some(c) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
conn = Some(c.clone());
continue;
} else if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
if let Some(c) = conn.take() {
conn = Some(attach_tag_to_connective(c, Some(tag.clone())));
}
continue;
} else if let Some(selbri) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
tails.push(BridiTail::BridiTail3(selbri.clone(), Vec::new()));
continue;
}
}
if let SemanticNode::NonTerminal { name, .. } = child {
if name == "tail_terms" {
collect_tail_terms(child, &mut terms);
}
}
}
match (tails.first().cloned(), conn, tails.get(1).cloned()) {
(Some(left), Some(c), Some(right)) => Some(BridiTail::ConnectedBT(c, Box::new(left), Box::new(right), terms)),
(Some(left), _, _) => Some(left),
_ => None,
}
});
for rule in &["statement_1", "statement_2"] {
let rule_name = *rule;
reducers.on(rule_name, move |_name, _span, children, _input| {
let mut statements = Vec::new();
let mut conns = Vec::new();
let mut pending_conn: Option<crate::jbo_syntax::Connective> = None;
for child in children {
if rule_name == "statement_2" {
if let SemanticNode::NonTerminal { name, .. } = child {
if *name == "I_clause" {
if let Some(conn) = pending_conn.take() {
conns.push(conn);
}
pending_conn = Some(crate::jbo_syntax::Connective::JboConnLog(
None,
crate::jbo_syntax::LogJboConnective { b1: true, c: 'e', b2: true },
));
}
}
}
if let Some(v) = child.value_ref() {
if let Some(conn) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
if rule_name == "statement_2" && pending_conn.is_some() {
pending_conn = Some(conn.clone());
} else {
if let Some(conn) = pending_conn.take() {
conns.push(conn);
}
pending_conn = Some(conn.clone());
}
} else if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
if let Some(conn) = &mut pending_conn {
match conn {
crate::jbo_syntax::Connective::JboConnLog(mtag, _) => *mtag = Some(Box::new(tag.clone())),
crate::jbo_syntax::Connective::JboConnJoik(mtag, _) => *mtag = Some(Box::new(tag.clone())),
}
}
}
}
collect_statement_items(child, &mut statements);
}
if let Some(conn) = pending_conn.take() {
conns.push(conn);
}
let mut iter = statements.into_iter();
let first = iter.next()?;
let mut frees = first.frees;
let mut prenex = first.prenex;
let mut result = first.body;
for (conn, rhs) in conns.into_iter().zip(iter) {
frees.extend(rhs.frees);
prenex.extend(rhs.prenex);
result = Statement1::ConnectedStatement(conn, Box::new(result), Box::new(rhs.body));
}
Some(Statement {
frees,
prenex,
body: result,
})
});
}
reducers.on("statement_3", |_name, _span, children, _input| {
let mut tag: Option<crate::jbo_syntax::Tag> = None;
let mut paras: Vec<Paragraph> = Vec::new();
let mut frees: Vec<Free> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(t) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
tag = Some(t.clone());
}
}
if let SemanticNode::NonTerminal { name, .. } = child {
if name == "text_1" {
collect_paragraph_items(child, &mut paras);
} else if name == "free" || name == "indicators" {
collect_frees(child, &mut frees);
}
}
}
if !paras.is_empty() {
return Some(Statement {
frees: Vec::new(),
prenex: Vec::new(),
body: Statement1::StatementParas { tag, paras },
});
}
for child in children {
if let Some(v) = child.value_ref() {
if let Some(stmt) = downcast_ref::<Statement>(v) {
return Some(stmt.clone());
} else if let Some(sentence) = downcast_ref::<Sentence>(v) {
return Some(Statement {
frees: Vec::new(),
prenex: Vec::new(),
body: Statement1::StatementSentence {
frees,
sentence: sentence.clone(),
},
});
}
}
}
None
});
reducers.on("tanru_unit", move |_name, span, children, input| {
let mut units: Vec<crate::jbo_syntax::TanruUnit> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(tu) = downcast_ref::<crate::jbo_syntax::TanruUnit>(v) {
units.push(tu.clone());
}
}
}
let words: Vec<&str> = span_slice(input, span).split_whitespace().collect();
if words.contains(&"cei") && units.len() >= 2 {
let left = tanru_unit_to_selbri3(&units[0]);
let right = tanru_unit_to_selbri3(&units[1]);
return Some(crate::jbo_syntax::TanruUnit::TUSelbri3(
crate::jbo_syntax::Selbri3::BridiBinding(Box::new(left), Box::new(right)),
));
}
if words.contains(&"jai") {
units.into_iter().last()
} else {
units.into_iter().next()
}
});
reducers.on("selbri", |_name, _span, children, _input| {
fn find_tag(node: &SemanticNode) -> Option<crate::jbo_syntax::Tag> {
if let Some(v) = node.value_ref() {
if downcast_ref::<crate::jbo_syntax::Sumti>(v).is_some()
|| matches!(downcast_ref::<crate::jbo_syntax::Term>(v), Some(crate::jbo_syntax::Term::Sumti(_, _)))
{
return None;
}
if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
return Some(tag.clone());
}
if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
return Some(crate::jbo_syntax::Tag::DecoratedTagUnits(vec![
crate::jbo_syntax::DecoratedAbsTagUnit {
tag_nahe: None,
tag_se: None,
tag_nai: false,
tag_unit: tu.clone(),
},
]));
}
}
if let SemanticNode::NonTerminal { name, children, .. } = node {
if *name == "sumti" {
return None;
}
for child in children {
if let Some(tag) = find_tag(child) {
return Some(tag);
}
}
}
None
}
let mut tag: Option<crate::jbo_syntax::Tag> = None;
let mut selbri: Option<crate::jbo_syntax::Selbri> = None;
for child in children {
if tag.is_none() {
if let Some(v) = child.value_ref() {
if let Some(t) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
tag = Some(t.clone());
} else if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
tag = Some(crate::jbo_syntax::Tag::DecoratedTagUnits(vec![
crate::jbo_syntax::DecoratedAbsTagUnit {
tag_nahe: None,
tag_se: None,
tag_nai: false,
tag_unit: tu.clone(),
},
]));
} else if downcast_ref::<crate::jbo_syntax::Selbri>(v).is_none() {
tag = find_tag(child);
}
} else {
tag = find_tag(child);
}
}
if let Some(v) = child.value_ref() {
if selbri.is_none() {
if let Some(sb) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
selbri = Some(sb.clone());
continue;
} else if let Some(tu) = downcast_ref::<crate::jbo_syntax::TanruUnit>(v) {
selbri = Some(crate::jbo_syntax::Selbri::Selbri2(
crate::jbo_syntax::Selbri2::Selbri3(
crate::jbo_syntax::Selbri3::TanruHead(
Vec::new(),
Box::new(tu.clone()),
Vec::new(),
),
),
));
continue;
}
}
}
}
if let Some(t) = tag {
if let Some(sb) = selbri {
return Some(crate::jbo_syntax::Selbri::TaggedSelbri(Box::new(t), Box::new(sb)));
}
}
selbri
});
reducers.on("sumti", |_name, _span, children, input| reduce_connected_sumti(children, input));
reducers.on("term", |_name, _span, children, _input| {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(term) = downcast_ref::<Term>(v) {
return Some(term.clone());
} else if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
return Some(Term::BareTag(tag.clone()));
}
}
}
None
});
reducers.on("tail_terms", |_name, _span, children, _input| {
let mut terms = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(term) = downcast_ref::<Term>(v) {
terms.push(term.clone());
} else if let Some(terms_vec) = downcast_ref::<Vec<Term>>(v) {
terms.extend(terms_vec.clone());
}
}
}
if terms.is_empty() {
None
} else {
Some(terms)
}
});
reducers.on("terms", |_name, _span, children, input| {
let terms = collect_terms_with_grouped_tags(children, input);
if terms.is_empty() {
None
} else {
Some(terms)
}
});
reducers.on("prenex", |_name, _span, children, _input| {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(terms) = downcast_ref::<Vec<Term>>(v) {
return Some(terms.clone());
}
}
}
None
});
reducers.on("statement", |_name, _span, children, _input| {
let mut prenex = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(terms) = downcast_ref::<Vec<Term>>(v) {
prenex.extend(terms.clone());
}
}
}
for child in children {
if let Some(v) = child.value_ref() {
if let Some(stmt) = downcast_ref::<Statement>(v) {
let mut stmt = stmt.clone();
if !prenex.is_empty() {
prenex.extend(stmt.prenex);
stmt.prenex = prenex.clone();
}
return Some(stmt);
} else if let Some(s) = downcast_ref::<Sentence>(v) {
return Some(Statement {
frees: Vec::new(),
prenex: prenex.clone(),
body: Statement1::StatementSentence {
frees: Vec::new(),
sentence: s.clone(),
},
});
}
}
}
Some(Statement {
frees: Vec::new(),
prenex: Vec::new(),
body: Statement1::StatementSentence {
frees: Vec::new(),
sentence: Sentence {
terms: Vec::new(),
tail: Box::new(BridiTail::BridiTail3(
crate::jbo_syntax::Selbri::Selbri2(
crate::jbo_syntax::Selbri2::Selbri3(
crate::jbo_syntax::Selbri3::TanruHead(
Vec::new(),
Box::new(crate::jbo_syntax::TanruUnit::TUBrivla(
"broda".to_string(),
)),
Vec::new(),
),
),
),
Vec::new(),
)),
},
},
})
});
reducers.on("fragment", |_name, span, children, input| {
fn collect_terms(node: &SemanticNode, out: &mut Vec<Term>) {
if let Some(v) = node.value_ref() {
if let Some(term) = downcast_ref::<Term>(v) {
out.push(term.clone());
return;
}
if let Some(terms) = downcast_ref::<Vec<Term>>(v) {
out.extend(terms.clone());
return;
}
}
if let SemanticNode::NonTerminal { children, .. } = node {
for child in children {
collect_terms(child, out);
}
}
}
for child in children {
if let SemanticNode::NonTerminal { name, value, .. } = child {
if name == "terms" {
if let Some(val) = value {
if let Some(terms) = downcast_ref::<Vec<Term>>(val) {
return Some(Fragment::FragTerms(terms.clone()));
}
} else {
return Some(Fragment::FragTerms(Vec::new()));
}
}
}
}
let text = span_slice(input, span);
let words: Vec<&str> = text.split_whitespace().collect();
if let Some(con) = gihek_fragment_from_words(&words) {
return Some(Fragment::FragCon(con));
}
for child in children {
if let SemanticNode::NonTerminal { name, value, .. } = child {
if name == "quantifier" {
if let Some(val) = value {
if let Some(mex) = downcast_ref::<Mex>(val) {
return Some(Fragment::FragQuantifier(mex.clone()));
}
}
}
}
}
let mut terms = Vec::new();
for child in children {
collect_terms(child, &mut terms);
}
if !terms.is_empty() {
return Some(Fragment::FragTerms(terms));
}
Some(Fragment::FragLaName(text.to_string()))
});
reducers.on("KOhA_clause", |_name, span, _children, input| {
let words: Vec<&str> = span_slice(input, span)
.split_whitespace()
.map(|word| word.trim_matches('.'))
.filter(|word| !word.is_empty() && !is_haskell_bahe(word))
.collect();
koh_a_sumti_atom_from_words(&words).map(qatom_term)
});
reducers.on("BRIVLA_clause", |_name, span, _children, input| {
let text = span_slice(input, span).trim();
let words = words_without_bahe(text);
let zei_words: Vec<String> = words.iter().filter(|word| word.as_str() != "zei").cloned().collect();
if zei_words.len() > 1 && words.iter().any(|word| word == "zei") {
Some(crate::jbo_syntax::TanruUnit::TUZei(zei_words))
} else {
Some(crate::jbo_syntax::TanruUnit::TUBrivla(zei_words.first().cloned().unwrap_or_default()))
}
});
reducers.on("GOhA_clause", |_name, span, _children, input| {
let text = span_slice(input, span)
.split_whitespace()
.next()
.unwrap_or("")
.trim_matches('.')
.to_string();
if text == "mo" {
Some(crate::jbo_syntax::TanruUnit::TUBridiQ(None))
} else {
Some(crate::jbo_syntax::TanruUnit::TUGOhA(text, 1))
}
});
fn gihek_fragment_from_words(words: &[&str]) -> Option<crate::jbo_syntax::Connective> {
let compact = words.join("");
if !["gi'a", "gi'e", "gi'i", "gi'o", "gi'u"].iter().any(|giha| compact.contains(giha)) {
return None;
}
log_connective_from_words(words)
}
fn log_connective_from_words(words: &[&str]) -> Option<crate::jbo_syntax::Connective> {
let compact = words.join("");
let left = !words.contains(&"na") && !compact.starts_with("na");
let right = !words.contains(&"nai") && !compact.ends_with("nai");
let se = words.contains(&"se");
let conchar = if words.iter().any(|word| matches!(*word, "ja" | "ga" | "gu'a" | "gi'a")) || compact.contains("gi'a") {
'a'
} else if words.iter().any(|word| matches!(*word, "je" | "ge" | "gu'e" | "gi'e")) || compact.contains("gi'e") {
'e'
} else if words.iter().any(|word| matches!(*word, "jo" | "go" | "gu'o" | "gi'o")) || compact.contains("gi'o") {
'o'
} else if words.iter().any(|word| matches!(*word, "ju" | "gu" | "gu'u" | "gi'u")) || compact.contains("gi'u") {
'u'
} else if words.iter().any(|word| matches!(*word, "ji" | "je'i" | "gi'i" | "ji'a'a" | "je'u'a")) || compact.contains("gi'i") {
'i'
} else {
return None;
};
let conchar = if se && conchar == 'u' { 'U' } else { conchar };
let lcon = crate::jbo_syntax::LogJboConnective {
b1: left,
c: conchar,
b2: right,
};
if conchar == 'i' {
Some(crate::jbo_syntax::Connective::JboConnJoik(None, "??".to_string()))
} else {
Some(crate::jbo_syntax::Connective::JboConnLog(None, lcon))
}
}
reducers.on("jek", |_name, span, children, input| {
let words: Vec<&str> = span_slice(input, span).split_whitespace().collect();
log_connective_from_words(&words).map(|con| attach_tag_to_connective(con, first_child_tag(children)))
});
reducers.on("guhek", |_name, span, children, input| {
let words: Vec<&str> = span_slice(input, span).split_whitespace().collect();
log_connective_from_words(&words).map(|con| attach_tag_to_connective(con, first_child_tag(children)))
});
reducers.on("gek", |_name, span, children, input| {
let words: Vec<&str> = span_slice(input, span).split_whitespace().collect();
let se = words.contains(&"se");
let conchar = if words.iter().any(|word| matches!(*word, "ga" | "gu'a")) {
'a'
} else if words.iter().any(|word| matches!(*word, "ge" | "gu'e")) {
'e'
} else if words.iter().any(|word| matches!(*word, "go" | "gu'o")) {
'o'
} else if words.iter().any(|word| matches!(*word, "gu" | "gu'u")) {
'u'
} else if words.iter().any(|word| matches!(*word, "ge'i" | "gu'i")) {
'i'
} else {
if let Some(con) = children.iter().find_map(|child| {
child
.value_ref()
.and_then(|v| downcast_ref::<crate::jbo_syntax::Connective>(v).cloned())
}) {
return Some(attach_tag_to_connective(con, first_child_tag(children)));
}
return first_child_tag(children).map(|tag| {
crate::jbo_syntax::Connective::JboConnLog(
Some(Box::new(tag)),
crate::jbo_syntax::LogJboConnective { b1: true, c: 'e', b2: true },
)
});
};
let conchar = if se && conchar == 'u' { 'U' } else { conchar };
let con = if conchar == 'i' {
crate::jbo_syntax::Connective::JboConnJoik(None, "??".to_string())
} else {
crate::jbo_syntax::Connective::JboConnLog(
None,
crate::jbo_syntax::LogJboConnective {
b1: !words.contains(&"nai"),
c: conchar,
b2: true,
},
)
};
Some(attach_tag_to_connective(con, first_child_tag(children)))
});
reducers.on("gihek", |_name, span, children, input| {
let words: Vec<&str> = span_slice(input, span).split_whitespace().collect();
log_connective_from_words(&words).map(|con| attach_tag_to_connective(con, first_child_tag(children)))
});
reducers.on("gik", |_name, span, _children, input| {
let words: Vec<&str> = span_slice(input, span).split_whitespace().collect();
Some(!words.contains(&"nai"))
});
reducers.on("ek", |_name, span, children, input| {
let words: Vec<&str> = span_slice(input, span).split_whitespace().collect();
let conchar = if words.iter().any(|word| matches!(*word, "a" | "ga")) {
'a'
} else if words.iter().any(|word| matches!(*word, "e" | "ge")) {
'e'
} else if words.iter().any(|word| matches!(*word, "o" | "go")) {
'o'
} else if words.iter().any(|word| matches!(*word, "u" | "gu")) {
'u'
} else if words.contains(&"ji") {
'i'
} else {
return None;
};
let tag = first_child_tag(children).map(Box::new);
let conchar = if words.contains(&"se") && conchar == 'u' { 'U' } else { conchar };
let con = if conchar == 'i' {
crate::jbo_syntax::Connective::JboConnJoik(tag, "??".to_string())
} else {
crate::jbo_syntax::Connective::JboConnLog(
tag,
crate::jbo_syntax::LogJboConnective {
b1: !words.contains(&"na"),
c: conchar,
b2: !words.contains(&"nai"),
},
)
};
Some(con)
});
reducers.on("joik", |_name, span, children, input| {
let words: Vec<&str> = span_slice(input, span).split_whitespace().collect();
let joik = joik_string_from_words(&words)?;
Some(attach_tag_to_connective(
crate::jbo_syntax::Connective::JboConnJoik(None, joik),
first_child_tag(children),
))
});
for rule in ["joik_ek", "joik_ek_1", "joik_jek"] {
reducers.on(rule, |_name, _span, children, _input| {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(con) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
return Some(con.clone());
}
}
}
None
});
}
reducers.on("_jeksbs", |_name, _span, children, _input| {
let mut con: Option<crate::jbo_syntax::Connective> = None;
let mut right: Option<crate::jbo_syntax::Selbri3> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(con_value) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
con = Some(con_value.clone());
} else if let Some(crate::jbo_syntax::Selbri::Selbri2(crate::jbo_syntax::Selbri2::Selbri3(sb3))) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
right = Some(sb3.clone());
}
}
}
Some((con?, right?))
});
reducers.on("LE_clause", |_name, span, _children, input| {
Some(first_non_bahe_word(span_slice(input, span)))
});
reducers.on("LA_clause", |_name, span, _children, input| {
Some(first_non_bahe_word(span_slice(input, span)))
});
reducers.on("CMEVLA_clause", |_name, span, _children, input| {
Some(first_non_bahe_word(span_slice(input, span)))
});
reducers.on("ME_clause", |_name, span, _children, input| {
let text = span_slice(input, span).trim().to_string();
Some(text)
});
reducers.on("JAI_clause", |_name, _span, _children, _input| {
Some("jai".to_string())
});
reducers.on("LAhE_clause", |_name, span, _children, input| {
let text = span_slice(input, span).trim().to_string();
Some(text)
});
reducers.on("NAhE_clause", |_name, span, _children, input| {
let text = span_slice(input, span).trim().to_string();
Some(text)
});
reducers.on("NA_clause", |_name, _span, _children, _input| {
Some(Term::Negation)
});
reducers.on("BO_clause", |_name, span, _children, input| {
let text = span_slice(input, span).trim().to_string();
Some(text)
});
reducers.on("FA_clause", |_name, span, _children, input| {
let text = span_slice(input, span).trim();
let words: Vec<&str> = text.split_whitespace().collect();
let place = match words.as_slice() {
["fa", ..] => Some(1),
["fe", ..] => Some(2),
["fi", ..] => Some(3),
["fo", ..] => Some(4),
["fu", ..] => Some(5),
["fai", ..] => None,
_ => return None,
};
Some(Term::BareFA(place))
});
for rule in ["BAhE_clause", "Y_clause"] {
reducers.on(rule, |_name, _span, _children, _input| Some(Free::NullFree));
}
for rule in ["SEI_clause", "SOI_clause", "TO_clause", "XI_clause", "MAI_clause", "DOI_clause"] {
reducers.on(rule, |_name, span, _children, input| {
Some(first_non_bahe_word(span_slice(input, span)))
});
}
reducers.on("COI_clause", |_name, span, _children, input| {
Some(first_non_bahe_word(span_slice(input, span)))
});
reducers.on("vocative", |_name, _span, children, _input| {
let mut cois = Vec::new();
let mut pending: Option<String> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(s) = downcast_ref::<String>(v) {
if s == "nai" {
if let Some(coi) = pending.take() {
cois.push(COI { coi_coi: coi, coi_nai: true });
}
} else if s == "doi" {
continue;
} else {
if let Some(coi) = pending.replace(s.clone()) {
cois.push(COI { coi_coi: coi, coi_nai: false });
}
}
}
}
}
if let Some(coi) = pending {
cois.push(COI { coi_coi: coi, coi_nai: false });
}
Some(cois)
});
reducers.on("xi_clause", |_name, _span, children, _input| {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(m) = downcast_ref::<Mex>(v) {
return Some(m.clone());
}
if let Some(ls) = downcast_ref::<Vec<Lerfu>>(v) {
return Some(Mex::MexLerfuString(ls.clone()));
}
}
}
None
});
reducers.on("BY_clause", |_name, span, children, input| {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(lerfu) = downcast_ref::<Lerfu>(v) {
return Some(lerfu.clone());
}
}
}
let text = span_slice(input, span).trim().to_string();
let lerfu = match text.as_str() {
"y'y" => Lerfu::LerfuChar('h'),
"y" => Lerfu::LerfuChar('y'),
s if s.ends_with('y') && s.chars().count() == 2 => {
Lerfu::LerfuChar(s.chars().next().unwrap())
}
_ => Lerfu::LerfuShift(text),
};
Some(lerfu)
});
reducers.on("bu_clause", |_name, span, _children, input| {
let words: Vec<&str> = span_slice(input, span).split_whitespace().collect();
words.first().map(|word| {
if word.chars().count() == 1 && matches!(*word, "a" | "e" | "i" | "o" | "u") {
Lerfu::LerfuChar(word.chars().next().unwrap())
} else {
Lerfu::LerfuValsi((*word).to_string())
}
})
});
reducers.on("lerfu_vowel_bu", |_name, span, _children, input| {
span_slice(input, span)
.chars()
.find(|c| matches!(*c, 'a' | 'e' | 'i' | 'o' | 'u'))
.map(Lerfu::LerfuChar)
});
reducers.on("lerfu_valsi_bu", |_name, span, _children, input| {
span_slice(input, span)
.split_whitespace()
.next()
.map(|word| Lerfu::LerfuValsi(word.to_string()))
});
for rule in ["LAU_clause", "TEI_clause", "FOI_clause"] {
reducers.on(rule, |_name, span, _children, input| {
Some(span_slice(input, span).trim().to_string())
});
}
reducers.on("lerfu_word", |_name, _span, children, _input| {
let mut cmavo = Vec::new();
let mut lerfu = Vec::new();
let mut lerfu_string: Option<Vec<Lerfu>> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(l) = downcast_ref::<Lerfu>(v) {
lerfu.push(l.clone());
} else if let Some(ls) = downcast_ref::<Vec<Lerfu>>(v) {
lerfu_string = Some(ls.clone());
} else if let Some(s) = downcast_ref::<String>(v) {
cmavo.push(s.clone());
}
}
}
if cmavo.first().is_some_and(|s| s == "tei") {
if let Some(ls) = lerfu_string {
return Some(Lerfu::LerfuComposite(ls));
}
}
if let (Some(lau), Some(l)) = (cmavo.first(), lerfu.first()) {
return Some(Lerfu::LerfuShifted(lau.clone(), Box::new(l.clone())));
}
lerfu.into_iter().next()
});
reducers.on("lerfu_string", |_name, _span, children, _input| {
let mut lerfu = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(l) = downcast_ref::<Lerfu>(v) {
lerfu.push(l.clone());
} else if let Some(Numeral::PA(pa)) = downcast_ref::<Numeral>(v) {
let l = match pa.as_str() {
"no" => Lerfu::LerfuChar('0'),
"pa" => Lerfu::LerfuChar('1'),
"re" => Lerfu::LerfuChar('2'),
"ci" => Lerfu::LerfuChar('3'),
"vo" => Lerfu::LerfuChar('4'),
"mu" => Lerfu::LerfuChar('5'),
"xa" => Lerfu::LerfuChar('6'),
"ze" => Lerfu::LerfuChar('7'),
"bi" => Lerfu::LerfuChar('8'),
"so" => Lerfu::LerfuChar('9'),
_ => Lerfu::LerfuPA(pa.clone()),
};
lerfu.push(l);
}
}
}
if lerfu.is_empty() { None } else { Some(lerfu) }
});
reducers.on("PA_clause", |_name, span, _children, input| {
let text = span_slice(input, span).trim();
let pa = text.strip_suffix(" boi").unwrap_or(text).to_string();
Some(Numeral::PA(pa))
});
reducers.on("MOI_clause", |_name, span, _children, input| {
let text = span_slice(input, span).trim().to_string();
Some(text)
});
reducers.on("number", |_name, _span, children, _input| {
fn collect_numerals(node: &SemanticNode, numerals: &mut Vec<Numeral>) {
if let Some(v) = node.value_ref() {
if let Some(n) = downcast_ref::<Numeral>(v) {
numerals.push(n.clone());
return;
}
}
if let SemanticNode::NonTerminal { children, .. } = node {
for child in children {
collect_numerals(child, numerals);
}
}
}
let mut numerals = Vec::new();
for child in children {
collect_numerals(child, &mut numerals);
}
if numerals.is_empty() {
None
} else if let Some(n) = litnum(&numerals) {
Some(Mex::MexInt(n))
} else {
Some(Mex::MexNumeralString(numerals))
}
});
for rule in &["operand", "mex_2", "mex"] {
reducers.on(*rule, |_name, _span, children, _input| {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(m) = downcast_ref::<Mex>(v) {
return Some(m.clone());
}
}
}
None
});
}
for rule in &["operand_0", "operand_1", "operand_2"] {
reducers.on(*rule, |_name, _span, children, _input| {
let mut mexes = Vec::new();
let mut conns = Vec::new();
let mut pending_conn: Option<crate::jbo_syntax::Connective> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(con) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
if let Some(con) = pending_conn.take() {
conns.push(con);
}
pending_conn = Some(con.clone());
continue;
} else if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
if let Some(con) = pending_conn.take() {
pending_conn = Some(attach_tag_to_connective(con, Some(tag.clone())));
}
continue;
} else if let Some(m) = downcast_ref::<Mex>(v) {
mexes.push(m.clone());
}
}
}
if let Some(con) = pending_conn.take() {
conns.push(con);
}
let mut iter = mexes.into_iter();
let mut current = iter.next()?;
for (con, rhs) in conns.into_iter().zip(iter) {
current = Mex::ConnectedMex(false, con, Box::new(current), Box::new(rhs));
}
Some(current)
});
}
reducers.on("operand_3", |_name, _span, children, _input| {
let mut con: Option<crate::jbo_syntax::Connective> = None;
let mut mexes = Vec::new();
let mut gik_truth: Option<bool> = None;
let mut saw_mohe = false;
let mut saw_nihe = false;
let mut saw_array = false;
let mut qualifier: Option<SumtiQualifier> = None;
let mut mohe_sumti: Option<Sumti> = None;
let mut nihe_selbri: Option<Selbri> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(c) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
con = Some(c.clone());
} else if let Some(m) = downcast_ref::<Mex>(v) {
mexes.push(m.clone());
} else if let Some(ls) = downcast_ref::<Vec<Lerfu>>(v) {
mexes.push(Mex::MexLerfuString(ls.clone()));
} else if let Some(b) = downcast_ref::<bool>(v) {
gik_truth = Some(*b);
} else if let Some(s) = downcast_ref::<String>(v) {
if s == "mo'e" {
saw_mohe = true;
} else if s == "ni'e" {
saw_nihe = true;
} else if s == "jo'i" {
saw_array = true;
} else if matches!(s.as_str(), "tu'a" | "la'e" | "lu'a" | "lu'e" | "lu'i" | "lu'o" | "vu'i" | "zo'ei" | "du'au" | "lau'e" | "tau'e" | "ce'u" | "mo'a") {
if qualifier.is_none() {
qualifier = Some(SumtiQualifier::LAhE(s.clone()));
}
} else if matches!(s.as_str(), "na'e" | "to'e" | "no'e" | "je'a")
&& qualifier.is_none() {
qualifier = Some(SumtiQualifier::NAhE_BO(s.clone()));
}
} else if let Some(term) = downcast_ref::<Term>(v) {
if let Term::Sumti(_, sumti) = term {
mohe_sumti = Some(sumti.clone());
}
} else if let Some(sb) = downcast_ref::<Selbri>(v) {
nihe_selbri = Some(sb.clone());
}
}
}
if saw_mohe {
if let Some(sumti) = mohe_sumti {
return Some(Mex::MexSumti(Box::new(sumti)));
}
}
if saw_nihe {
if let Some(selbri) = nihe_selbri {
return Some(Mex::MexSelbri(Box::new(selbri)));
}
}
let mex = if saw_array {
Mex::MexArray(mexes)
} else if let (Some(mut con), Some(m1), Some(b), Some(m2)) =
(con, mexes.first().cloned(), gik_truth, mexes.get(1).cloned())
{
if let crate::jbo_syntax::Connective::JboConnLog(_, lcon) = &mut con {
lcon.b2 = b;
}
Mex::ConnectedMex(true, con, Box::new(m1), Box::new(m2))
} else {
mexes.into_iter().next()?
};
if let Some(qualifier) = qualifier {
Some(Mex::QualifiedMex(qualifier, Box::new(mex)))
} else {
Some(mex)
}
});
reducers.on("rp_expression_tail", |_name, _span, children, _input| {
let mut items = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(rp) = downcast_ref::<RpItems>(v) {
items.extend(rp.0.clone());
continue;
}
if let Some(op) = downcast_ref::<Operator>(v) {
items.push(RpItem::Operator(op.clone()));
}
}
}
Some(RpItems(items))
});
reducers.on("rp_expression", |_name, _span, children, _input| {
let mut items = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(m) = downcast_ref::<Mex>(v) {
items.push(RpItem::Mex(m.clone()));
continue;
}
if let Some(rp) = downcast_ref::<RpItems>(v) {
items.extend(rp.0.clone());
}
}
}
Some(RpItems(items))
});
reducers.on("rp_clause", |_name, _span, children, _input| {
let mut items = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(rp) = downcast_ref::<RpItems>(v) {
items.extend(rp.0.clone());
}
}
}
let mut stack: Vec<Mex> = Vec::new();
for item in items {
match item {
RpItem::Mex(m) => stack.push(m),
RpItem::Operator(op) => {
let rhs = stack.pop()?;
let lhs = stack.pop()?;
stack.push(Mex::Operation(Box::new(op), vec![lhs, rhs]));
}
}
}
if stack.len() == 1 {
stack.pop()
} else {
None
}
});
reducers.on("mex_0", |_name, _span, children, _input| {
let mut mexes = Vec::new();
let mut ops = Vec::new();
let mut conns = Vec::new();
let mut pending_conn: Option<crate::jbo_syntax::Connective> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(con) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
if let Some(con) = pending_conn.take() {
conns.push(con);
}
pending_conn = Some(con.clone());
continue;
} else if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
if let Some(con) = pending_conn.take() {
pending_conn = Some(attach_tag_to_connective(con, Some(tag.clone())));
}
continue;
}
}
match mex_seq_item(child) {
Some(MexSeqItem::Mex(m)) => mexes.push(m),
Some(MexSeqItem::Operator(op)) => ops.push(op),
None => {}
}
}
if let Some(con) = pending_conn.take() {
conns.push(con);
}
if !conns.is_empty() {
let mut iter = mexes.into_iter();
let mut current = iter.next()?;
for (con, rhs) in conns.into_iter().zip(iter) {
current = Mex::ConnectedMex(false, con, Box::new(current), Box::new(rhs));
}
return Some(current);
}
let mut current = mexes.first()?.clone();
for (op, rhs) in ops.into_iter().zip(mexes.into_iter().skip(1)) {
current = Mex::Operation(Box::new(op), vec![current, rhs]);
}
Some(current)
});
reducers.on("mex_1", |_name, _span, children, _input| {
let mut mexes = Vec::new();
let mut ops = Vec::new();
let mut con: Option<crate::jbo_syntax::Connective> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(con_value) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
con = Some(con_value.clone());
continue;
} else if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
if let Some(pending) = con.take() {
con = Some(attach_tag_to_connective(pending, Some(tag.clone())));
}
continue;
}
}
match mex_seq_item(child) {
Some(MexSeqItem::Mex(m)) => mexes.push(m),
Some(MexSeqItem::Operator(op)) => ops.push(op),
None => {}
}
}
let first = mexes.first()?.clone();
let rhs = mexes.get(1).cloned();
if let (Some(con), Some(rhs)) = (con, rhs.clone()) {
Some(Mex::ConnectedMex(false, con, Box::new(first), Box::new(rhs)))
} else if let (Some(op), Some(rhs)) = (ops.into_iter().next(), rhs) {
Some(Mex::Operation(Box::new(op), vec![first, rhs]))
} else {
Some(first)
}
});
reducers.on("mex_forethought", |_name, _span, children, _input| {
let mut operator: Option<Operator> = None;
let mut operands: Vec<Mex> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if operator.is_none() {
if let Some(op) = downcast_ref::<Operator>(v) {
operator = Some(op.clone());
continue;
}
}
}
if let SemanticNode::NonTerminal { name, children, .. } = child {
if name == "fore_operands" {
for operand_child in children {
if let Some(v) = operand_child.value_ref() {
if let Some(m) = downcast_ref::<Mex>(v) {
operands.push(m.clone());
}
}
}
}
}
}
operator.map(|op| Mex::Operation(Box::new(op), operands))
});
reducers.on("VUhU_clause", |_name, span, _children, input| {
let text = match span_slice(input, span).trim() {
"sui" => "su'i".to_string(),
other => other.to_string(),
};
Some(Operator::OpVUhU(text))
});
reducers.on("NUhA_clause", |_name, span, _children, input| {
Some(span_slice(input, span).trim().to_string())
});
reducers.on("MOhE_clause", |_name, span, _children, input| {
Some(span_slice(input, span).trim().to_string())
});
reducers.on("NIhE_clause", |_name, span, _children, input| {
Some(span_slice(input, span).trim().to_string())
});
reducers.on("JOhI_clause", |_name, span, _children, input| {
Some(span_slice(input, span).trim().to_string())
});
reducers.on("MAhO_clause", |_name, span, _children, input| {
Some(span_slice(input, span).trim().to_string())
});
reducers.on("NAhU_clause", |_name, span, _children, input| {
Some(span_slice(input, span).trim().to_string())
});
reducers.on("NU_clause", |_name, span, _children, input| {
Some(span_slice(input, span).trim().to_string())
});
reducers.on("mex_operator", |_name, span, children, _input| {
let mut se: Option<i32> = None;
let mut nahe: Option<String> = None;
let mut saw_maho = false;
let mut saw_nahu = false;
let rule_words: Vec<&str> = span_slice(_input, span).split_whitespace().collect();
let wrap_operator = |op: Operator, se: Option<i32>, nahe: Option<String>| {
let op = if let Some(nahe) = nahe {
Operator::OpScalarNegated(nahe, Box::new(op))
} else {
op
};
if let Some(se) = se {
Operator::OpPermuted(se, Box::new(op))
} else {
op
}
};
for child in children {
if let SemanticNode::NonTerminal { name, span, .. } = child {
if name == "SE_clause" && se.is_none() {
let se_word = span_slice(_input, *span).split_whitespace().next();
se = match se_word {
Some("se") => xi_lit_from_words(&rule_words, 0).or(Some(2)),
Some("te") => xi_lit_from_words(&rule_words, 0).or(Some(3)),
Some("ve") => xi_lit_from_words(&rule_words, 0).or(Some(4)),
Some("xe") => xi_lit_from_words(&rule_words, 0).or(Some(5)),
_ => None,
};
if se.is_some() {
continue;
}
}
}
if let Some(v) = child.value_ref() {
if let Some(op) = downcast_ref::<Operator>(v) {
return Some(wrap_operator(op.clone(), se, nahe));
}
if let Some(s) = downcast_ref::<String>(v) {
if s == "ma'o" {
saw_maho = true;
} else if s == "na'u" {
saw_nahu = true;
} else if matches!(s.as_str(), "na'e" | "to'e" | "no'e" | "je'a") {
nahe = Some(s.clone());
}
}
if saw_maho {
if let Some(m) = downcast_ref::<Mex>(v) {
return Some(wrap_operator(Operator::OpMex(Box::new(m.clone())), se, nahe));
}
}
if saw_nahu {
if let Some(sb) = downcast_ref::<Selbri>(v) {
return Some(wrap_operator(Operator::OpSelbri(Box::new(sb.clone())), se, nahe));
}
}
}
}
None
});
for rule in &["operator_2", "operator"] {
reducers.on(*rule, |_name, _span, children, _input| {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(op) = downcast_ref::<Operator>(v) {
return Some(op.clone());
}
}
}
None
});
}
for rule in &["operator_0", "operator_1"] {
reducers.on(*rule, |_name, _span, children, _input| {
let mut ops = Vec::new();
let mut conns = Vec::new();
let mut pending_conn: Option<crate::jbo_syntax::Connective> = None;
let mut gik_truth: Option<bool> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(con) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
if let Some(con) = pending_conn.take() {
conns.push(con);
}
pending_conn = Some(con.clone());
continue;
} else if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
if let Some(con) = pending_conn.take() {
pending_conn = Some(attach_tag_to_connective(con, Some(tag.clone())));
}
continue;
} else if let Some(b) = downcast_ref::<bool>(v) {
gik_truth = Some(*b);
continue;
} else if let Some(op) = downcast_ref::<Operator>(v) {
ops.push(op.clone());
}
}
}
if let Some(mut con) = pending_conn.take() {
if let Some(b) = gik_truth {
if let crate::jbo_syntax::Connective::JboConnLog(_, lcon) = &mut con {
lcon.b2 = b;
}
}
conns.push(con);
}
let mut iter = ops.into_iter();
let mut current = iter.next()?;
for (con, rhs) in conns.into_iter().zip(iter) {
current = Operator::ConnectedOperator(false, con, Box::new(current), Box::new(rhs));
}
Some(current)
});
}
reducers.on("quantifier", |_name, _span, children, _input| {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(m) = downcast_ref::<Mex>(v) {
return Some(m.clone());
}
}
}
None
});
reducers.on("sumti_tail_1", |_name, _span, children, _input| {
let mut inner_quant: Option<Mex> = None;
let mut selbri: Option<crate::jbo_syntax::Selbri> = None;
let mut sumti: Option<Sumti> = None;
let mut rels: Vec<RelClause> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(m) = downcast_ref::<Mex>(v) {
inner_quant = Some(m.clone());
} else if let Some(rs) = downcast_ref::<Vec<RelClause>>(v) {
rels.extend(rs.clone());
} else if let Some(s) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
selbri = Some(s.clone());
} else if let Some(tu) = downcast_ref::<crate::jbo_syntax::TanruUnit>(v) {
selbri = Some(crate::jbo_syntax::Selbri::Selbri2(
crate::jbo_syntax::Selbri2::Selbri3(
crate::jbo_syntax::Selbri3::TanruHead(
Vec::new(),
Box::new(tu.clone()),
Vec::new(),
),
),
));
} else if let Some(Term::Sumti(_, s)) = downcast_ref::<Term>(v) {
sumti = Some(s.clone());
}
}
}
if let Some(s) = selbri {
return Some(SumtiTailParts {
inner_sumti: None,
inner_quant,
selbri_or_sumti: EitherSelbriSumti::Selbri(Box::new(s)),
rels: Vec::new(),
inner_rels: rels,
});
}
if let Some(s) = sumti {
return Some(SumtiTailParts {
inner_sumti: None,
inner_quant,
selbri_or_sumti: EitherSelbriSumti::Sumti(Box::new(s)),
rels: Vec::new(),
inner_rels: rels,
});
}
None
});
reducers.on("sumti_tail", |_name, _span, children, _input| {
let mut inner_sumti: Option<Box<Sumti>> = None;
let mut rels: Vec<RelClause> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(parts) = downcast_ref::<SumtiTailParts>(v) {
let mut parts = parts.clone();
if parts.inner_sumti.is_none() {
if let Some(s) = inner_sumti {
parts.inner_sumti = Some(Box::new(attach_rels_to_sumti(*s, rels)));
} else if !rels.is_empty() {
parts.rels.extend(rels);
}
} else if !rels.is_empty() {
parts.rels.extend(rels);
}
return Some(parts);
}
if let Some(rs) = downcast_ref::<Vec<RelClause>>(v) {
rels.extend(rs.clone());
}
if let Some(Term::Sumti(_, s)) = downcast_ref::<Term>(v) {
inner_sumti = Some(Box::new(s.clone()));
}
if let Some(s) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
return Some(SumtiTailParts {
inner_sumti,
inner_quant: None,
selbri_or_sumti: EitherSelbriSumti::Selbri(Box::new(s.clone())),
rels,
inner_rels: Vec::new(),
});
}
}
}
None
});
reducers.on("PU_clause", |_name, span, _children, input| {
let text = &input[span.0..span.1];
let cmavo = text.trim().to_string();
Some(crate::jbo_syntax::TagUnit::TenseCmavo(cmavo))
});
reducers.on("ZI_clause", |_name, span, _children, input| {
let text = &input[span.0..span.1];
let cmavo = text.trim().to_string();
Some(crate::jbo_syntax::TagUnit::TenseCmavo(cmavo))
});
reducers.on("VA_clause", |_name, span, _children, input| {
let text = &input[span.0..span.1];
let cmavo = text.trim().to_string();
Some(crate::jbo_syntax::TagUnit::TenseCmavo(cmavo))
});
reducers.on("VIhA_clause", |_name, span, _children, input| {
let text = &input[span.0..span.1];
let cmavo = text.trim().to_string();
Some(crate::jbo_syntax::TagUnit::TenseCmavo(cmavo))
});
reducers.on("FAhA_clause", |_name, span, _children, input| {
let text = &input[span.0..span.1];
let cmavo = text.trim().to_string();
Some(crate::jbo_syntax::TagUnit::FAhA {
faha_has_mohi: false,
faha_cmavo: cmavo,
})
});
reducers.on("BAI_clause", |_name, span, _children, input| {
let text = &input[span.0..span.1];
let cmavo = text.trim().to_string();
Some(crate::jbo_syntax::TagUnit::BAI(cmavo))
});
reducers.on("KI_clause", |_name, _span, _children, _input| {
Some(crate::jbo_syntax::TagUnit::KI)
});
reducers.on("CAhA_clause", |_name, span, _children, input| {
let cmavo = span_slice(input, span).trim().to_string();
Some(crate::jbo_syntax::TagUnit::CAhA(cmavo))
});
reducers.on("CUhE_clause", |_name, span, _children, input| {
let cmavo = span_slice(input, span).trim().to_string();
Some(crate::jbo_syntax::TagUnit::CUhE(cmavo))
});
reducers.on("ZEhA_clause", |_name, span, _children, input| {
let cmavo = span_slice(input, span).trim().to_string();
Some(crate::jbo_syntax::TagUnit::TenseCmavo(cmavo))
});
reducers.on("VEhA_clause", |_name, span, _children, input| {
let cmavo = span_slice(input, span).trim().to_string();
Some(crate::jbo_syntax::TagUnit::TenseCmavo(cmavo))
});
reducers.on("MOhI_clause", |_name, _span, _children, _input| {
Some(crate::jbo_syntax::TagUnit::FAhA {
faha_has_mohi: true,
faha_cmavo: String::new(),
})
});
reducers.on("TAhE_clause", |_name, span, _children, input| {
let cmavo = span_slice(input, span).trim().to_string();
Some(crate::jbo_syntax::TagUnit::TAhE_ZAhO {
tahe_zohe_is_space: false,
tahe_zaho_cmavo: cmavo,
})
});
reducers.on("ZAhO_clause", |_name, span, _children, input| {
let cmavo = span_slice(input, span).trim().to_string();
Some(crate::jbo_syntax::TagUnit::TAhE_ZAhO {
tahe_zohe_is_space: false,
tahe_zaho_cmavo: cmavo,
})
});
reducers.on("ROI_clause", |_name, span, _children, input| {
Some(span_slice(input, span).trim().to_string())
});
reducers.on("interval_property", |_name, _span, children, _input| {
let mut number = None;
let mut roi = None;
let mut unit = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(m) = downcast_ref::<Mex>(v) {
number = Some(m.clone());
} else if let Some(s) = downcast_ref::<String>(v) {
roi = Some(s.clone());
} else if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
unit = Some(tu.clone());
}
}
}
if let (Some(q), Some(r)) = (number, roi) {
Some(vec![crate::jbo_syntax::TagUnit::ROI {
roiroi: r,
roi_is_space: false,
roi_quantifier: q,
}])
} else {
unit.map(|tu| vec![tu])
}
});
reducers.on("time_offset", |_name, _span, children, _input| {
let units: Vec<_> = children
.iter()
.filter_map(|child| child.value_ref())
.filter_map(|v| downcast_ref::<crate::jbo_syntax::TagUnit>(v).cloned())
.collect();
if units.is_empty() { None } else { Some(units) }
});
reducers.on("time", |_name, _span, children, _input| {
let mut units = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(tus) = downcast_ref::<Vec<crate::jbo_syntax::TagUnit>>(v) {
units.extend(tus.clone());
} else if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
units.push(tu.clone());
}
}
}
if units.is_empty() { None } else { Some(units) }
});
reducers.on("space_offset", |_name, _span, children, _input| {
let mut faha = None;
let mut va = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
match tu {
crate::jbo_syntax::TagUnit::FAhA { .. } => faha = Some(tu.clone()),
crate::jbo_syntax::TagUnit::TenseCmavo(_) => va = Some(tu.clone()),
_ => {}
}
}
}
}
let units: Vec<_> = faha.into_iter().chain(va).collect();
if units.is_empty() { None } else { Some(units) }
});
reducers.on("space_interval", |_name, _span, children, _input| {
let mut units = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(tus) = downcast_ref::<Vec<crate::jbo_syntax::TagUnit>>(v) {
units.extend(tus.clone());
} else if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
units.push(tu.clone());
}
}
}
if units.is_empty() { None } else { Some(units) }
});
reducers.on("space_int_props", |_name, _span, children, _input| {
let mut units = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(tus) = downcast_ref::<Vec<crate::jbo_syntax::TagUnit>>(v) {
units.extend(tus.clone().into_iter().map(|tu| match tu {
crate::jbo_syntax::TagUnit::ROI { roiroi, roi_quantifier, .. } => crate::jbo_syntax::TagUnit::ROI {
roiroi,
roi_is_space: true,
roi_quantifier,
},
crate::jbo_syntax::TagUnit::TAhE_ZAhO { tahe_zaho_cmavo, .. } => crate::jbo_syntax::TagUnit::TAhE_ZAhO {
tahe_zohe_is_space: true,
tahe_zaho_cmavo,
},
other => other,
}));
}
}
}
if units.is_empty() { None } else { Some(units) }
});
reducers.on("space", |_name, _span, children, _input| {
let mut units = Vec::new();
let mut mohi = false;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(tus) = downcast_ref::<Vec<crate::jbo_syntax::TagUnit>>(v) {
for tu in tus {
if mohi {
if let crate::jbo_syntax::TagUnit::FAhA { faha_cmavo, .. } = tu {
units.push(crate::jbo_syntax::TagUnit::FAhA {
faha_has_mohi: true,
faha_cmavo: faha_cmavo.clone(),
});
mohi = false;
} else {
units.push(tu.clone());
}
} else {
units.push(tu.clone());
}
}
} else if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
match tu {
crate::jbo_syntax::TagUnit::FAhA { faha_has_mohi: true, faha_cmavo } if faha_cmavo.is_empty() => mohi = true,
_ if mohi => {
if let crate::jbo_syntax::TagUnit::FAhA { faha_cmavo, .. } = tu {
units.push(crate::jbo_syntax::TagUnit::FAhA {
faha_has_mohi: true,
faha_cmavo: faha_cmavo.clone(),
});
mohi = false;
} else {
units.push(tu.clone());
}
}
_ => units.push(tu.clone()),
}
}
}
}
if units.is_empty() { None } else { Some(units) }
});
reducers.on("simple_tense_modal", |_name, span, children, input| {
fn contains_rule(node: &SemanticNode, rule: &str) -> bool {
match node {
SemanticNode::NonTerminal { name, children, .. } => {
name == rule || children.iter().any(|child| contains_rule(child, rule))
}
SemanticNode::Terminal { .. } => false,
}
}
let mut nahe = None;
let mut se = None;
let mut nai = false;
let mut ki = false;
let rule_words: Vec<&str> = span_slice(input, span).split_whitespace().collect();
let mut tag_units = Vec::new();
let mut nai_indices = Vec::new();
for child in children {
if let SemanticNode::NonTerminal { name, span, .. } = child {
match name.as_str() {
"NAhE_clause" => nahe = Some(span_slice(input, *span).trim().to_string()),
"SE_clause" if se.is_none() => {
let se_word = span_slice(input, *span).split_whitespace().next();
se = match se_word {
Some("se") => xi_lit_from_words(&rule_words, 0).or(Some(2)),
Some("te") => xi_lit_from_words(&rule_words, 0).or(Some(3)),
Some("ve") => xi_lit_from_words(&rule_words, 0).or(Some(4)),
Some("xe") => xi_lit_from_words(&rule_words, 0).or(Some(5)),
_ => None,
};
}
"NAI_clause" => nai = true,
"KI_clause" => ki = true,
"time" | "space" | "time_offset" | "space_offset" | "space_interval" | "space_int_props" | "interval_property"
if contains_rule(child, "NAI_clause") => {
nai_indices.push(tag_units.len());
}
_ => {}
}
}
if let Some(v) = child.value_ref() {
if let Some(tus) = downcast_ref::<Vec<crate::jbo_syntax::TagUnit>>(v) {
tag_units.extend(tus.clone());
} else if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
if matches!(tu, crate::jbo_syntax::TagUnit::KI) {
ki = true;
} else {
tag_units.push(tu.clone());
}
}
}
}
if ki {
tag_units.push(crate::jbo_syntax::TagUnit::KI);
}
let units: Vec<Dtu> = tag_units
.into_iter()
.enumerate()
.map(|(i, tu)| crate::jbo_syntax::DecoratedAbsTagUnit {
tag_nahe: if i == 0 { nahe.clone() } else { None },
tag_se: if i == 0 { se } else { None },
tag_nai: (i == 0 && nai) || nai_indices.contains(&i),
tag_unit: tu,
})
.collect();
if units.is_empty() { None } else { Some(units) }
});
reducers.on("tense_modal", |_name, _span, children, _input| {
let mut saw_fiho = false;
for child in children {
if let SemanticNode::NonTerminal { name, .. } = child {
if name == "FIhO_clause" {
saw_fiho = true;
}
}
if let Some(v) = child.value_ref() {
if let Some(dtus) = downcast_ref::<Vec<Dtu>>(v) {
return Some(dtus.clone());
}
if saw_fiho {
if let Some(sb) = downcast_ref::<crate::jbo_syntax::Selbri>(v) {
return Some(vec![crate::jbo_syntax::DecoratedAbsTagUnit {
tag_nahe: None,
tag_se: None,
tag_nai: false,
tag_unit: crate::jbo_syntax::TagUnit::FIhO(Box::new(sb.clone())),
}]);
}
}
if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
return Some(vec![crate::jbo_syntax::DecoratedAbsTagUnit {
tag_nahe: None,
tag_se: None,
tag_nai: false,
tag_unit: tu.clone(),
}]);
}
}
}
None
});
reducers.on("tag", |_name, _span, children, _input| {
fn flush_units(
tag: &mut Option<crate::jbo_syntax::Tag>,
con: &mut Option<crate::jbo_syntax::Connective>,
units: &mut Vec<Dtu>,
) {
if units.is_empty() {
return;
}
let next = crate::jbo_syntax::Tag::DecoratedTagUnits(std::mem::take(units));
if let (Some(left), Some(conn)) = (tag.take(), con.take()) {
*tag = Some(crate::jbo_syntax::Tag::ConnectedTag(conn, Box::new(left), Box::new(next)));
} else if let Some(crate::jbo_syntax::Tag::DecoratedTagUnits(left_units)) = tag {
if let crate::jbo_syntax::Tag::DecoratedTagUnits(mut next_units) = next {
left_units.append(&mut next_units);
}
} else {
*tag = Some(next);
}
}
let mut tag: Option<crate::jbo_syntax::Tag> = None;
let mut con: Option<crate::jbo_syntax::Connective> = None;
let mut units: Vec<Dtu> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(child_con) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
flush_units(&mut tag, &mut con, &mut units);
con = Some(child_con.clone());
} else if let Some(dtus) = downcast_ref::<Vec<Dtu>>(v) {
units.extend(dtus.clone());
} else if let Some(dtu) = downcast_ref::<Dtu>(v) {
units.push(dtu.clone());
} else if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
units.push(crate::jbo_syntax::DecoratedAbsTagUnit {
tag_nahe: None,
tag_se: None,
tag_nai: false,
tag_unit: tu.clone(),
});
}
}
}
flush_units(&mut tag, &mut con, &mut units);
tag
});
reducers.on("termset", |_name, _span, children, _input| {
let mut con: Option<crate::jbo_syntax::Connective> = None;
let mut b: Option<bool> = None;
let mut termsets: Vec<Vec<Term>> = Vec::new();
let mut tgtk: Option<TermsGikTerms> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(c) = downcast_ref::<crate::jbo_syntax::Connective>(v) {
con = Some(c.clone());
} else if let Some(gik_truth) = downcast_ref::<bool>(v) {
b = Some(*gik_truth);
} else if let Some(terms) = downcast_ref::<Vec<Term>>(v) {
termsets.push(terms.clone());
} else if let Some(parts) = downcast_ref::<TermsGikTerms>(v) {
tgtk = Some(parts.clone());
}
}
}
if let (Some(mut con), Some(parts)) = (con.clone(), tgtk) {
if let crate::jbo_syntax::Connective::JboConnLog(_, lcon) = &mut con {
lcon.b2 = parts.b;
}
return Some(Term::ConnectedTerms(
true,
con,
Box::new(Term::Termset(parts.ts1)),
Box::new(Term::Termset(parts.ts2)),
));
}
if let (Some(mut con), Some(ts1), Some(gik_truth), Some(ts2)) =
(con, termsets.first().cloned(), b, termsets.get(1).cloned())
{
if let crate::jbo_syntax::Connective::JboConnLog(_, lcon) = &mut con {
lcon.b2 = gik_truth;
}
return Some(Term::ConnectedTerms(
true,
con,
Box::new(Term::Termset(ts1)),
Box::new(Term::Termset(ts2)),
));
}
termsets.into_iter().next().map(Term::Termset)
});
reducers.on("termsGikTerms", |_name, _span, children, _input| {
let mut first_term: Option<Term> = None;
let mut last_term: Option<Term> = None;
let mut inner: Option<TermsGikTerms> = None;
let mut gik_truth: Option<bool> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(t) = downcast_ref::<Term>(v) {
if first_term.is_none() {
first_term = Some(t.clone());
} else {
last_term = Some(t.clone());
}
} else if let Some(parts) = downcast_ref::<TermsGikTerms>(v) {
inner = Some(parts.clone());
} else if let Some(b) = downcast_ref::<bool>(v) {
gik_truth = Some(*b);
}
}
}
let t1 = first_term?;
let t2 = last_term?;
let mut parts = inner.unwrap_or(TermsGikTerms {
ts1: Vec::new(),
b: gik_truth.unwrap_or(true),
ts2: Vec::new(),
});
parts.ts1.insert(0, t1);
parts.ts2.push(t2);
Some(parts)
});
reducers.on("term_1", |_name, span, children, _input| {
fn find_tag(node: &SemanticNode) -> Option<crate::jbo_syntax::Tag> {
if let Some(v) = node.value_ref() {
if let Some(tag) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
return Some(tag.clone());
}
if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
return Some(crate::jbo_syntax::Tag::DecoratedTagUnits(vec![
crate::jbo_syntax::DecoratedAbsTagUnit {
tag_nahe: None,
tag_se: None,
tag_nai: false,
tag_unit: tu.clone(),
},
]));
}
if let Some(crate::jbo_syntax::Term::BareTag(tag)) = downcast_ref::<crate::jbo_syntax::Term>(v) {
return Some(tag.clone());
}
}
if let SemanticNode::NonTerminal { name, children, .. } = node {
if *name == "sumti" {
return None;
}
for child in children {
if let Some(tag) = find_tag(child) {
return Some(tag);
}
}
}
None
}
let rule_words: Vec<&str> = span_slice(_input, span).split_whitespace().collect();
let mut fa: Option<Option<i32>> = None;
let mut tag: Option<crate::jbo_syntax::Tag> = None;
let mut sumti_term: Option<crate::jbo_syntax::Term> = None;
let mut sumti_value: Option<crate::jbo_syntax::Sumti> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(term @ crate::jbo_syntax::Term::Termset(_)) = downcast_ref::<crate::jbo_syntax::Term>(v) {
return Some(term.clone());
}
if let Some(term @ crate::jbo_syntax::Term::ConnectedTerms(_, _, _, _)) = downcast_ref::<crate::jbo_syntax::Term>(v) {
return Some(term.clone());
}
}
}
for child in children {
if tag.is_none() {
tag = find_tag(child);
}
if let Some(v) = child.value_ref() {
if let Some(crate::jbo_syntax::Term::BareFA(n)) = downcast_ref::<crate::jbo_syntax::Term>(v) {
fa = Some(n.and_then(|base| xi_lit_from_words(&rule_words, 0).or(Some(base))));
} else if let Some(crate::jbo_syntax::Term::Sumti(_, _)) = downcast_ref::<crate::jbo_syntax::Term>(v) {
sumti_term = downcast_ref::<crate::jbo_syntax::Term>(v).cloned();
} else if let Some(sumti) = downcast_ref::<crate::jbo_syntax::Sumti>(v) {
sumti_value = Some(sumti.clone());
}
}
}
if let Some(n) = fa {
let tagged = match n {
Some(place) => crate::jbo_syntax::Tagged::FATagged(place),
None => crate::jbo_syntax::Tagged::FAITagged,
};
if let Some(crate::jbo_syntax::Term::Sumti(_, sumti)) = sumti_term.clone() {
return Some(crate::jbo_syntax::Term::Sumti(tagged, sumti));
}
if let Some(sumti) = sumti_value.clone() {
return Some(crate::jbo_syntax::Term::Sumti(tagged, sumti));
}
}
if let Some(tag) = tag {
if let Some(crate::jbo_syntax::Term::Sumti(_, sumti)) = sumti_term.clone() {
return Some(crate::jbo_syntax::Term::Sumti(crate::jbo_syntax::Tagged::Tagged(tag), sumti));
}
if let Some(sumti) = sumti_value.clone() {
return Some(crate::jbo_syntax::Term::Sumti(crate::jbo_syntax::Tagged::Tagged(tag), sumti));
}
return Some(crate::jbo_syntax::Term::BareTag(tag));
}
for child in children {
if let Some(v) = child.value_ref() {
if let Some(term) = downcast_ref::<crate::jbo_syntax::Term>(v) {
if !matches!(term, crate::jbo_syntax::Term::BareFA(_)) {
return Some(term.clone());
}
}
}
}
for child in children {
if let Some(v) = child.value_ref() {
if let Some(sumti) = downcast_ref::<crate::jbo_syntax::Sumti>(v) {
return Some(crate::jbo_syntax::Term::Sumti(crate::jbo_syntax::Tagged::Untagged, sumti.clone()));
}
}
}
None
});
reducers.on("term", |_name, _span, children, _input| {
for child in children {
if let Some(v) = child.value_ref() {
if let Some(term) = downcast_ref::<crate::jbo_syntax::Term>(v) {
return Some(term.clone());
}
}
}
None
});
reducers.on("stag", |_name, _span, children, _input| {
let mut tag_units: Vec<crate::jbo_syntax::DecoratedAbsTagUnit<crate::jbo_syntax::Selbri, crate::jbo_syntax::Sumti>> = Vec::new();
for child in children {
if let Some(v) = child.value_ref() {
if let Some(dtus) = downcast_ref::<Vec<Dtu>>(v) {
tag_units.extend(dtus.clone());
} else if let Some(dtu) = downcast_ref::<Dtu>(v) {
tag_units.push(dtu.clone());
} else if let Some(tu) = downcast_ref::<crate::jbo_syntax::TagUnit>(v) {
tag_units.push(crate::jbo_syntax::DecoratedAbsTagUnit {
tag_nahe: None,
tag_se: None,
tag_nai: false,
tag_unit: tu.clone(),
});
}
}
}
if tag_units.is_empty() {
None
} else {
Some(crate::jbo_syntax::Tag::DecoratedTagUnits(tag_units))
}
});
reducers.on("term_with_tag", |_name, _span, children, _input| {
let mut tag: Option<crate::jbo_syntax::Tag> = None;
let mut sumti: Option<crate::jbo_syntax::Sumti> = None;
for child in children {
if let Some(v) = child.value_ref() {
if let Some(t) = downcast_ref::<crate::jbo_syntax::Tag>(v) {
tag = Some(t.clone());
} else if let Some(s) = downcast_ref::<crate::jbo_syntax::Sumti>(v) {
sumti = Some(s.clone());
}
}
}
if let Some(t) = tag {
if sumti.is_none() {
return Some(crate::jbo_syntax::Term::BareTag(t));
}
}
None
});
reducers
}
fn parse_full(peg: &Peg, input: &str) -> Result<usize, usize> {
let result = peg.parse(input);
let error_pos = result.2;
let consumed = result.1;
match result.3.as_ref() {
Ok(_) => {
let remaining = &input[consumed..];
let remaining_trimmed = remaining.trim();
if remaining_trimmed.is_empty() || remaining_trimmed == "%%%END%%%" {
Ok(consumed)
} else {
let parsed_prefix = input[..consumed].trim();
let pos = if consumed > 0
&& !remaining_trimmed.starts_with("%%%END%%")
&& matches!(parsed_prefix, "a" | "e" | "o" | "u" | "ja" | "je" | "jo" | "ju")
{
0
} else {
let leftover_pos = partial_parse_error_position(consumed, remaining);
let end_marker = " %%%END%%%";
let end_marker_pos = if input.ends_with(end_marker) {
input.len() - end_marker.len()
} else {
input.len()
};
if error_pos > leftover_pos && error_pos < end_marker_pos {
match sei_tail_indicator_pos(&input[consumed..]) {
Ok(Some(pos)) => consumed + pos,
_ if input[consumed..].trim_start().starts_with("sei ") => {
let adjusted_error_pos = partial_parse_error_position(error_pos, &input[error_pos..]);
if adjusted_error_pos <= end_marker_pos + 1 {
adjusted_error_pos
} else {
error_pos
}
}
_ => leftover_pos,
}
} else {
leftover_pos
}
};
Err(pos)
}
}
Err(_e) => {
let end_marker = " %%%END%%%";
let end_marker_pos = if input.ends_with(end_marker) {
input.len() - end_marker.len()
} else {
input.len()
};
let pos = if error_pos < end_marker_pos {
error_pos
} else {
consumed
};
Err(pos)
}
}
}
fn partial_parse_error_position(consumed: usize, remaining: &str) -> usize {
let trimmed = remaining.trim_start();
let skipped = remaining.len() - trimmed.len();
if consumed == 0 {
return skipped;
}
let mut parts = trimmed.split_whitespace();
let offset = match (parts.next(), parts.next()) {
(Some("bo"), _) => 0,
(Some("zei"), _) => 0,
(Some("i"), Some(next)) if is_i_blocking_connector(next) => 0,
(Some("na"), Some("ku")) => "na ku ".len(),
(Some("pe'e"), Some(next)) if next != "%%%END%%%" => "pe'e ".len(),
(Some(word), Some("%%%END%%%")) => word.len() + 1,
(Some(word), Some(next)) if next != "%%%END%%%" && (consumed == 0 || word.len() <= 3) => {
word.len() + 1
}
_ => 0,
};
consumed + skipped + offset
}
fn is_i_blocking_connector(word: &str) -> bool {
matches!(word, "ja" | "je" | "jo" | "ju")
|| matches!(word, "je'a" | "je'anai" | "na" | "nai")
|| word.starts_with("jo'i")
|| word.starts_with("joi")
|| word.starts_with("jo'u")
|| word.starts_with("fa'u")
|| word.starts_with("ce'o")
|| word.starts_with("ce")
|| word.starts_with("pi'u")
|| word.starts_with("bi'i")
|| word.starts_with("bi'o")
}
fn parse_full_or_free_error(peg: &Peg, free_peg: &Peg, input: &str) -> Result<usize, usize> {
match parse_full(peg, input) {
Ok(consumed) => Ok(consumed),
Err(pos) => {
let (_, tail) = split_at_safe_boundary(input, pos);
let free_result = free_peg.parse(tail);
match free_result.3.as_ref() {
Err(e) => Err(pos + e.position),
Ok(_) if free_result.1 == 0 => Err(pos),
Ok(_) => Err(pos),
}
}
}
}
fn parse_full_for_nudge(peg: &Peg, free_peg: &Peg, input: &str) -> Result<usize, usize> {
let result = peg.parse(input);
let pos = match result.3.as_ref() {
Ok(_) => {
let consumed = result.1;
let remaining = &input[consumed..];
let remaining_trimmed = remaining.trim();
if remaining_trimmed.is_empty() || remaining_trimmed == "%%%END%%%" {
return Ok(consumed);
}
if consumed > 0 {
let skipped = remaining.len() - remaining.trim_start().len();
let connector_pos = consumed + skipped;
if let Ok(Some(pos)) = joik_ek_tail_free_pos(&input[connector_pos..]) {
return Err(connector_pos + pos);
}
if let Ok(Some(pos)) = find_joik_ek_tail_free_pos(&input[consumed..]) {
return Err(consumed + pos);
}
if let Ok(Some(pos)) = zoi_clause_error_pos(&input[consumed..]) {
return Err(consumed + pos);
}
if !la_clause_followed_by_connector_before_si(&input[consumed..]) {
if let Ok(Some(pos)) = si_clause_error_pos(&input[consumed..]) {
return Err(consumed + pos);
}
}
}
parse_full(peg, input).err().unwrap_or_else(|| partial_parse_error_position(consumed, remaining))
}
Err(_) => parse_full(peg, input).err().unwrap_or(result.2),
};
let (_, tail) = split_at_safe_boundary(input, pos);
let free_result = free_peg.parse(tail);
if let Err(e) = free_result.3.as_ref() {
if e.position > 0 {
return Err(pos + e.position);
}
}
let trimmed = input.trim_start();
let skipped = input.len() - trimmed.len();
if trimmed.starts_with("sei ") || trimmed.starts_with("ti'o ") {
if let Some(first_space) = trimmed.find(char::is_whitespace) {
let tail_start = skipped + first_space;
let tail = input[tail_start..].trim_start();
let inner_skipped = input[tail_start..].len() - tail.len();
let free_result = free_peg.parse(tail);
if free_result.3.is_ok() && free_result.1 > 0 {
return Err(tail_start + inner_skipped);
}
}
}
Err(pos)
}
fn joik_ek_tail_free_pos(input: &str) -> Result<Option<usize>, usize> {
with_joik_ek_peg(|joik_ek_peg| {
with_free_peg(|free_peg| {
let result = joik_ek_peg.parse(input);
if result.3.is_err() || result.1 == 0 {
return Ok(None);
}
let after_connector = &input[result.1..];
let skipped = after_connector.len() - after_connector.trim_start().len();
let free_pos = result.1 + skipped;
let (_, tail) = split_at_safe_boundary(input, free_pos);
let free_result = free_peg.parse(tail);
if free_result.3.is_ok() && free_result.1 > 0 {
if let Some(pos) = mai_clause_pos(&input[free_pos..])? {
Ok(Some(free_pos + pos))
} else {
Ok(Some(free_pos))
}
} else {
Ok(None)
}
})
})
}
fn mai_clause_pos(input: &str) -> Result<Option<usize>, usize> {
with_mai_clause_peg(|mai_clause_peg| {
let mut offset = 0usize;
for word in input.split_whitespace() {
let start = input[offset..].find(word).ok_or(0usize)? + offset;
let result = mai_clause_peg.parse(&input[start..]);
if result.3.is_ok() && result.1 > 0 {
return Ok(Some(start));
}
offset = start + word.len();
}
Ok(None)
})
}
fn next_word_with_offset(input: &str, pred: impl Fn(&str) -> bool) -> Option<(usize, &str)> {
let mut offset = 0usize;
for word in input.split_whitespace() {
let start = input[offset..].find(word)? + offset;
let normalized = word.trim_matches('.');
if pred(normalized) {
return Some((start, normalized));
}
offset = start + word.len();
}
None
}
fn find_joik_ek_tail_free_pos(input: &str) -> Result<Option<usize>, usize> {
let mut offset = 0usize;
let mut tail = input;
while let Some((word_start, word)) = next_word_with_offset(tail, |word| matches!(word, "a" | "e" | "o" | "u" | "ji")) {
if let Some(pos) = joik_ek_tail_free_pos(&tail[word_start..])? {
return Ok(Some(offset + word_start + pos));
}
let next_start = word_start + word.len();
offset += next_start;
tail = &tail[next_start..];
}
Ok(None)
}
fn sei_tail_indicator_pos(input: &str) -> Result<Option<usize>, usize> {
with_sei_clause_peg(|sei_clause_peg| {
let mut offset = 0usize;
let mut tail = input;
while let Some((word_start, word)) = next_word_with_offset(tail, |word| matches!(word, "sei" | "ti'o")) {
let result = sei_clause_peg.parse(&tail[word_start..]);
if result.3.is_err() || result.1 == 0 {
let next_start = word_start + word.len();
offset += next_start;
tail = &tail[next_start..];
continue;
}
let after_sei = &tail[word_start + result.1..];
let skipped = after_sei.len() - after_sei.trim_start().len();
let free_pos = word_start + result.1 + skipped;
let (_, free_tail) = split_at_safe_boundary(tail, free_pos);
let free_result = with_free_peg(|free_peg| Ok(free_peg.parse(free_tail)))?;
if free_result.3.is_ok() && free_result.1 > 0 {
let indicators_result = with_indicators_peg(|indicators_peg| Ok(indicators_peg.parse(free_tail)))?;
if indicators_result.3.is_ok() && indicators_result.1 == free_result.1 {
return Ok(Some(offset + free_pos));
}
}
let next_start = word_start + word.len();
offset += next_start;
tail = &tail[next_start..];
}
Ok(None)
})
}
fn zoi_clause_error_pos(input: &str) -> Result<Option<usize>, usize> {
with_zoi_clause_peg(|zoi_clause_peg| {
let Some((zoi_pos, _)) = next_word_with_offset(input, |word| matches!(word, "zoi" | "la'o")) else {
return Ok(None);
};
let result = zoi_clause_peg.parse(&input[zoi_pos..]);
if result.3.is_ok() || input[zoi_pos..].split_whitespace().any(|word| word.trim_matches('.') == "si") {
Ok(None)
} else {
Ok(Some(zoi_pos + result.2))
}
})
}
fn la_clause_followed_by_connector_before_si(input: &str) -> bool {
let Ok(Some(after_la)) = with_la_clause_peg(|la_clause_peg| {
let result = la_clause_peg.parse(input);
if result.3.is_ok() && result.1 > 0 {
Ok(Some(result.1))
} else {
Ok(None)
}
}) else {
return false;
};
let tail = &input[after_la..];
let skipped = tail.len() - tail.trim_start().len();
let tail = &tail[skipped..];
let Ok(Some(conn_len)) = with_joik_ek_peg(|joik_ek_peg| {
let result = joik_ek_peg.parse(tail);
if result.3.is_ok() && result.1 > 0 {
Ok(Some(result.1))
} else {
Ok(None)
}
}) else {
return false;
};
tail[conn_len..]
.split_whitespace()
.any(|word| word.trim_matches('.') == "si")
}
fn si_clause_error_pos(input: &str) -> Result<Option<usize>, usize> {
with_si_clause_peg(|si_clause_peg| {
let mut offset = 0usize;
let mut tail = input;
while let Some((i, word)) = tail.split_whitespace().enumerate().find(|(_, word)| word.trim_matches('.') == "si") {
let before = tail
.split_whitespace()
.take(i)
.collect::<Vec<_>>()
.join(" ");
let local_pos = if before.is_empty() { 0 } else { before.len() + 1 };
let result = si_clause_peg.parse(&tail[local_pos..]);
if result.3.is_err() || result.1 == 0 {
return Ok(Some(offset + local_pos));
}
let next_start = local_pos + word.len();
offset += next_start;
tail = &tail[next_start..];
}
Ok(None)
})
}
fn split_at_safe_boundary(s: &str, pos: usize) -> (&str, &str) {
let p = if pos > s.len() {
s.len()
} else if s.is_char_boundary(pos) {
pos
} else {
let mut i = pos;
while i > 0 && !s.is_char_boundary(i) {
i -= 1;
}
i
};
s.split_at(p)
}
fn nudge_free_once(head: &str, tail: &str, free_len: usize) -> String {
let ws: Vec<&str> = head.split_whitespace().collect();
let (head_keep, head_move) = if ws.is_empty() {
(&[][..], &[][..])
} else {
ws.split_at(ws.len() - 1)
};
let moved_free = &tail[..free_len.min(tail.len())];
let tail_rest = &tail[free_len.min(tail.len())..];
let mut out = String::new();
if !head_keep.is_empty() {
out.push_str(&head_keep.join(" "));
out.push(' ');
}
out.push_str(moved_free);
if !head_move.is_empty() {
out.push(' ');
out.push_str(&head_move.join(" "));
}
if !tail_rest.is_empty() {
out.push(' ');
out.push_str(tail_rest);
}
out
}
fn nudge_frees(input: &str, text_peg: &Peg, free_peg: &Peg) -> Result<String, usize> {
nudge_frees_inner(input, text_peg, free_peg, false).map(|(nudged, _)| nudged)
}
fn nudge_frees_inner(
input: &str,
text_peg: &Peg,
free_peg: &Peg,
in_free: bool,
) -> Result<(String, usize), usize> {
let trimmed = input.trim();
if trimmed.is_empty() || trimmed == "%%%END%%%" {
return match parse_full(text_peg, input) {
Ok(consumed) => Ok((input.to_string(), consumed)),
Err(pos) => Err(pos),
};
}
let mut current = input.to_string();
for _ in 0..64 {
let parsed = if in_free {
let result = text_peg.parse(¤t);
match result.3.as_ref() {
Ok(_) if result.1 > 0 => Ok(result.1),
Err(e) => Err(e.position),
_ => parse_full_for_nudge(text_peg, free_peg, ¤t),
}
} else {
parse_full_for_nudge(text_peg, free_peg, ¤t)
};
match parsed {
Ok(consumed) => return Ok((current, consumed)),
Err(pos) => {
let (head, tail) = split_at_safe_boundary(¤t, pos);
if in_free && pos == 0 {
return Err(0);
}
if tail.is_empty() {
return Err(pos);
}
let free_result = nudge_frees_inner(tail, free_peg, free_peg, true);
let free_len = match free_result {
Ok((_, consumed)) if consumed > 0 => consumed,
Err(n) => return Err(pos + n),
_ => return Err(pos),
};
let trimmed_tail = tail.trim_start();
let free_text = &trimmed_tail[..free_len.min(trimmed_tail.len())];
if free_text.split_whitespace().any(|word| matches!(word.trim_matches('.'), "mai" | "mo'o")) {
return Err(pos);
}
if free_text.split_whitespace().next().is_some_and(|word| matches!(word.trim_matches('.'), "sei" | "ti'o")) {
return Err(pos);
}
current = nudge_free_once(head, tail, free_len);
}
}
}
Err(0)
}
pub fn parse_text(input: &str) -> Result<Text, usize> {
let clean_input = input
.trim_end_matches(" %%%END%%%")
.trim_end_matches("%%%END%%%");
let with_end = format!("{} %%%END%%%", clean_input);
let reducers = reducer_table();
with_text_peg(|text_peg| {
with_free_peg(|free_peg| {
let nudged = nudge_frees(&with_end, text_peg, free_peg)?;
parse_full_or_free_error(text_peg, free_peg, &nudged)?;
let forest = parse_with_semantics(text_peg, &nudged, reducers)
.map_err(|e| e.position)?;
let root = single_root(forest).ok_or(0usize)?;
let value = root.value_ref().ok_or(0usize)?;
let text = downcast_ref::<Text>(value).ok_or(0usize)?;
Ok(text.clone())
})
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_peg_builds() {
let text_peg = build_peg("text");
assert!(text_peg.is_ok(), "Should build text PEG");
let free_peg = build_peg("free");
assert!(free_peg.is_ok(), "Should build free PEG");
}
#[test]
fn test_parser_on_simple_input() {
let text_peg = build_peg("text").expect("text peg");
let input = "mi klama le zarci %%%END%%%";
let result = text_peg.parse(input);
log::debug!("Parse result for {:?}:", input);
log::debug!(" Position: {}", result.1);
log::debug!(" Error position: {}", result.2);
log::debug!(" Success: {}", result.3.is_ok());
if let Err(e) = result.3.as_ref() {
log::debug!(" Error at position: {}", e.position);
}
}
#[test]
fn test_semantic_extraction() {
let parsed = parse_text("mi klama le zarci");
assert!(parsed.is_ok(), "Should parse: {:?}", parsed);
let text = parsed.unwrap();
log::debug!("Parsed text:");
log::debug!(" text_paras.len(): {}", text.text_paras.len());
for (i, para) in text.text_paras.iter().enumerate() {
log::debug!(" paragraph {}: {} items", i, para.len());
}
let reducers = build_reducers();
let text_peg = build_peg("text").expect("text peg");
let input = "mi klama le zarci %%%END%%%";
let forest = parse_with_semantics(&text_peg, input, &reducers).expect("parse");
log::debug!("\nParse forest has {} roots", forest.len());
for (i, node) in forest.iter().enumerate() {
log::debug!("Root {}: {:?}", i, node);
}
}
#[test]
fn parses_simple_sentence() {
let parsed = parse_text("mi klama le zarci");
if let Err(pos) = &parsed {
log::debug!("Parse failed at position: {}", pos);
}
assert!(parsed.is_ok(), "Parser should succeed: {:?}", parsed);
}
#[test]
fn handles_empty_input() {
let parsed = parse_text("");
if let Err(pos) = &parsed {
log::debug!("Parse failed at position: {}", pos);
}
assert!(parsed.is_ok(), "Should handle empty input: {:?}", parsed);
}
#[test]
fn nudge_keeps_valid_input_unchanged() {
let text = build_peg("text").expect("text peg");
let free = build_peg("free").expect("free peg");
let input = "mi klama le zarci %%%END%%%";
let result = nudge_frees(input, &text, &free);
if let Err(pos) = &result {
log::debug!("nudge_frees failed at position: {}", pos);
}
assert!(result.is_ok(), "nudge_frees should succeed: {:?}", result);
}
#[test]
fn strips_existing_end_marker() {
let parsed = parse_text("mi klama %%%END%%%");
if let Err(pos) = &parsed {
log::debug!("Parse failed at position: {}", pos);
}
assert!(parsed.is_ok(), "Should strip existing %%%END%%% marker");
}
}