include!(concat!(env!("OUT_DIR"), "/lexicon_data.rs"));
pub use logicaffeine_lexicon::{
Aspect, Case, Definiteness, Feature, Gender, Number, Sort, Time, VerbClass,
AdjectiveMetadata, MorphologicalRule, NounMetadata, VerbEntry, VerbMetadata,
};
pub fn get_canonical_verb(lemma: &str) -> Option<(&'static str, bool)> {
lookup_canonical(lemma).map(|m| (m.lemma, m.polarity == Polarity::Negative))
}
pub trait LexiconTrait {
fn lookup_verb(&self, word: &str) -> Option<VerbMetadata>;
fn lookup_noun(&self, word: &str) -> Option<NounMetadata>;
fn lookup_adjective(&self, word: &str) -> Option<AdjectiveMetadata>;
}
pub struct StaticLexicon;
impl LexiconTrait for StaticLexicon {
fn lookup_verb(&self, word: &str) -> Option<VerbMetadata> {
lookup_verb_db(word)
}
fn lookup_noun(&self, word: &str) -> Option<NounMetadata> {
lookup_noun_db(word)
}
fn lookup_adjective(&self, word: &str) -> Option<AdjectiveMetadata> {
lookup_adjective_db(word)
}
}
pub struct Lexicon {}
impl Lexicon {
pub fn new() -> Self {
Lexicon {}
}
pub fn lookup_verb(&self, word: &str) -> Option<VerbEntry> {
let lower = word.to_lowercase();
if let Some(entry) = lookup_irregular_verb(&lower) {
return Some(entry);
}
if lower.ends_with("ing") {
let stem = self.strip_ing(&lower);
let lemma = Self::capitalize(&stem);
let class = self.lookup_verb_class(&lemma.to_lowercase());
return Some(VerbEntry {
lemma,
time: Time::None,
aspect: Aspect::Progressive,
class,
});
}
if lower.ends_with("ed") {
let stem = self.strip_ed(&lower);
if !is_base_verb(&stem) {
return None;
}
let lemma = Self::capitalize(&stem);
let class = self.lookup_verb_class(&lemma.to_lowercase());
return Some(VerbEntry {
lemma,
time: Time::Past,
aspect: Aspect::Simple,
class,
});
}
let is_third_person = if lower.ends_with("es") && lower.len() > 2 {
true
} else if lower.ends_with("s") && !lower.ends_with("ss") && lower.len() > 2 {
true
} else {
false
};
if is_third_person {
if is_stemming_exception(&lower) {
return None;
}
let stem = Self::third_person_stem(&lower)?;
let lemma = Self::capitalize(&stem);
let class = self.lookup_verb_class(&lemma.to_lowercase());
return Some(VerbEntry {
lemma,
time: Time::Present,
aspect: Aspect::Simple,
class,
});
}
if is_base_verb(&lower) {
let lemma = Self::capitalize(&lower);
let class = self.lookup_verb_class(&lower);
return Some(VerbEntry {
lemma,
time: Time::Present,
aspect: Aspect::Simple,
class,
});
}
None
}
fn lookup_verb_class(&self, lemma: &str) -> VerbClass {
lookup_verb_class(lemma)
}
fn strip_ing(&self, word: &str) -> String {
let base = &word[..word.len() - 3];
if base.len() >= 2 {
let chars: Vec<char> = base.chars().collect();
let last = chars[chars.len() - 1];
let second_last = chars[chars.len() - 2];
if last == second_last && !"aeiou".contains(last) {
return base[..base.len() - 1].to_string();
}
}
if needs_e_ing(base) {
return format!("{}e", base);
}
base.to_string()
}
fn strip_ed(&self, word: &str) -> String {
let base = &word[..word.len() - 2];
if base.ends_with("i") {
let with_e = format!("{}e", base);
if is_base_verb(&with_e) {
return with_e;
}
return format!("{}y", &base[..base.len() - 1]);
}
if base.len() >= 2 {
let chars: Vec<char> = base.chars().collect();
let last = chars[chars.len() - 1];
let second_last = chars[chars.len() - 2];
if last == second_last && !"aeiou".contains(last) {
if is_base_verb(base) {
return base.to_string();
}
return base[..base.len() - 1].to_string();
}
if (last == 'l' || last == 'r') && !"aeiou".contains(second_last) {
if chars.len() >= 3 && "aeiou".contains(chars[chars.len() - 3]) {
return format!("{}e", base);
}
}
}
if needs_e_ed(base) {
return format!("{}e", base);
}
let with_e = format!("{}e", base);
if is_base_verb(&with_e) {
return with_e;
}
base.to_string()
}
fn third_person_of(stem: &str) -> String {
if stem.ends_with('s')
|| stem.ends_with('x')
|| stem.ends_with('z')
|| stem.ends_with("ch")
|| stem.ends_with("sh")
|| stem.ends_with('o')
{
format!("{stem}es")
} else if stem.ends_with('y')
&& !stem.ends_with("ay")
&& !stem.ends_with("ey")
&& !stem.ends_with("oy")
&& !stem.ends_with("uy")
{
format!("{}ies", &stem[..stem.len() - 1])
} else {
format!("{stem}s")
}
}
fn third_person_stem(word: &str) -> Option<String> {
let mut candidates = vec![word[..word.len() - 1].to_string()];
if word.ends_with("es") {
candidates.push(word[..word.len() - 2].to_string());
}
if word.ends_with("ies") {
candidates.push(format!("{}y", &word[..word.len() - 3]));
}
candidates
.into_iter()
.find(|stem| is_base_verb(stem) && Self::third_person_of(stem) == word)
}
fn capitalize(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
}
}
impl Default for Lexicon {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WordAnalysis {
Noun(NounMetadata),
DerivedNoun {
lemma: String,
number: Number,
},
}
pub fn analyze_word(word: &str) -> Option<WordAnalysis> {
let lower = word.to_lowercase();
if let Some(meta) = lookup_noun_db(&lower) {
return Some(WordAnalysis::Noun(meta));
}
if lower.ends_with('s') && lower.len() > 2 {
let stem = &lower[..lower.len() - 1];
if let Some(meta) = lookup_noun_db(stem) {
return Some(WordAnalysis::Noun(NounMetadata {
lemma: meta.lemma,
number: Number::Plural,
features: meta.features,
}));
}
if lower.ends_with("es") && lower.len() > 3 {
let stem_es = &lower[..lower.len() - 2];
if let Some(meta) = lookup_noun_db(stem_es) {
return Some(WordAnalysis::Noun(NounMetadata {
lemma: meta.lemma,
number: Number::Plural,
features: meta.features,
}));
}
}
if lower.ends_with("ies") && lower.len() > 4 {
let stem_ies = format!("{}y", &lower[..lower.len() - 3]);
if let Some(meta) = lookup_noun_db(&stem_ies) {
return Some(WordAnalysis::Noun(NounMetadata {
lemma: meta.lemma,
number: Number::Plural,
features: meta.features,
}));
}
}
}
for rule in get_morphological_rules() {
let (is_plural, check_word) = if lower.ends_with('s') && !rule.suffix.ends_with('s') {
(true, &lower[..lower.len() - 1])
} else {
(false, lower.as_str())
};
if check_word.ends_with(rule.suffix) {
return Some(WordAnalysis::DerivedNoun {
lemma: check_word.to_string(),
number: if is_plural { Number::Plural } else { Number::Singular },
});
}
}
None
}
pub fn is_derivable_noun(word: &str) -> bool {
analyze_word(word).is_some()
}
pub fn is_proper_name(word: &str) -> bool {
let lower = word.to_lowercase();
if let Some(meta) = lookup_noun_db(&lower) {
return meta.features.contains(&Feature::Proper);
}
false
}
pub fn get_canonical_noun(word: &str) -> Option<&'static str> {
match analyze_word(word) {
Some(WordAnalysis::Noun(meta)) => Some(meta.lemma),
Some(WordAnalysis::DerivedNoun { .. }) => {
None
}
_ => None,
}
}