mod flip;
pub(crate) mod parse;
mod processor;
use std::collections::HashMap;
use crate::Ambiguity;
use crate::classify::Classification;
use crate::error::ParseError;
use crate::text::singularize;
use crate::triple::{Predicate, Term, Triple};
pub(crate) use processor::process;
pub(crate) struct GrammarRule {
pub subject: String,
pub predicate: String,
pub object: String,
}
#[derive(Default)]
pub(crate) struct SubjectEntry {
pub predicates: HashMap<String, PredicateObjects>,
}
#[derive(Default)]
pub(crate) struct ObjectEntry {
pub predicates: HashMap<String, PredicateSubjects>,
}
pub(crate) struct PredicateObjects {
pub objects: Vec<String>,
}
pub(crate) struct PredicateSubjects {
#[allow(dead_code)] pub subjects: Vec<String>,
}
pub(crate) struct Rules {
pub subjects: HashMap<String, SubjectEntry>,
#[allow(dead_code)] pub predicates: Vec<String>,
pub objects: HashMap<String, ObjectEntry>,
pub vocabulary: HashMap<String, String>,
pub entries: Vec<GrammarRule>,
}
impl Rules {
pub(crate) fn build(grammar: &[String], vocabulary: HashMap<String, String>) -> Rules {
let mut rules = Rules {
subjects: HashMap::new(),
predicates: Vec::new(),
objects: HashMap::new(),
vocabulary,
entries: Vec::new(),
};
for line in grammar {
let tokens: Vec<&str> = line.split(' ').collect();
let subject = singularize(tokens.first().copied().unwrap_or_default());
let predicate = singularize(tokens.get(1).copied().unwrap_or_default());
let object = singularize(tokens.get(2).copied().unwrap_or_default());
rules.entries.push(GrammarRule {
subject: subject.clone(),
predicate: predicate.clone(),
object: object.clone(),
});
rules
.subjects
.entry(subject.clone())
.or_default()
.predicates
.entry(predicate.clone())
.or_insert_with(|| PredicateObjects {
objects: vec![object.clone()],
});
if !rules.predicates.contains(&predicate) {
rules.predicates.push(predicate.clone());
}
rules
.objects
.entry(object)
.or_default()
.predicates
.entry(predicate)
.or_insert_with(|| PredicateSubjects {
subjects: vec![subject.clone()],
});
}
rules
}
}
pub(crate) fn build_triple(classification: &Classification, rules: &Rules) -> Triple {
let parts = parse::parts(classification, rules);
Triple {
subject: Term {
ty: parts
.subjects
.subjects
.first()
.or_else(|| parts.subjects.objects.first())
.cloned(),
value: first_value(&parts.subjects),
},
predicate: Predicate {
ty: None,
value: parts.predicate.as_ref().and_then(|(token, _tag)| {
if token.is_empty() {
None
} else {
parse::map_predicate(token, &rules.vocabulary)
}
}),
},
object: Term {
ty: parts
.objects
.objects
.first()
.or_else(|| parts.subjects.subjects.first())
.cloned(),
value: first_value(&parts.objects),
},
}
}
fn first_value(reduced: &parse::Reduced) -> Option<String> {
reduced
.after
.first()
.or_else(|| reduced.before.first())
.map(|(token, _tag)| token.clone())
}
pub(crate) fn infer_types(
mut triple: Triple,
rules: &Rules,
ambiguity: Ambiguity,
) -> Result<Triple, ParseError> {
let Some(predicate) = triple.predicate.value.clone() else {
return Ok(triple);
};
if triple.subject.ty.is_none() {
let candidates = ordered_dedup(
rules
.entries
.iter()
.filter(|e| e.predicate == predicate)
.map(|e| e.subject.clone()),
);
triple.subject.ty = pick(&predicate, candidates, ambiguity)?;
}
if triple.object.ty.is_none()
&& let Some(subject) = triple.subject.ty.clone()
{
let candidates = ordered_dedup(
rules
.entries
.iter()
.filter(|e| e.subject == subject && e.predicate == predicate)
.map(|e| e.object.clone()),
);
triple.object.ty = pick(&predicate, candidates, ambiguity)?;
}
Ok(triple)
}
fn ordered_dedup(iter: impl Iterator<Item = String>) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
for item in iter {
if !out.contains(&item) {
out.push(item);
}
}
out
}
fn pick(
predicate: &str,
mut candidates: Vec<String>,
ambiguity: Ambiguity,
) -> Result<Option<String>, ParseError> {
match candidates.len() {
0 => Ok(None),
1 => Ok(Some(candidates.remove(0))),
_ => match ambiguity {
Ambiguity::FirstMatch => Ok(Some(candidates.remove(0))),
Ambiguity::Error => Err(ParseError::AmbiguousType {
predicate: predicate.to_string(),
candidates,
}),
},
}
}