pub(crate) type Tagged = (String, String);
pub(crate) fn apply_all(sentence: &mut [Tagged], index: usize) {
rule1(sentence, index);
rule2(sentence, index);
rule3(sentence, index);
rule4(sentence, index);
rule5(sentence, index);
rule6(sentence, index);
rule7(sentence, index);
rule8(sentence, index);
}
fn starts_with(s: &str, prefix: &str) -> bool {
if prefix.is_empty() {
return false;
}
s.find(prefix) == Some(0)
}
fn ends_with(s: &str, suffix: &str) -> bool {
if suffix.is_empty() {
return false;
}
let s_len = utf16_len(s);
let suffix_len = utf16_len(suffix);
if suffix_len > s_len {
return false;
}
match utf16_find(s, suffix) {
Some(idx) => idx == s_len - suffix_len,
None => false,
}
}
fn utf16_len(s: &str) -> usize {
s.chars().map(char::len_utf16).sum()
}
fn utf16_find(haystack: &str, needle: &str) -> Option<usize> {
let byte_idx = haystack.find(needle)?;
Some(utf16_len(&haystack[..byte_idx]))
}
fn rule1(sentence: &mut [Tagged], index: usize) {
if index > 0 && sentence[index - 1].1 == "DT" {
let tag = sentence[index].1.as_str();
if tag == "VBD" || tag == "VBP" || tag == "VB" {
sentence[index].1 = "NN".to_string();
}
}
}
fn rule2(sentence: &mut [Tagged], index: usize) {
if !starts_with(&sentence[index].1, "N") {
return;
}
let (word, tag) = &mut sentence[index];
if word.contains('.') {
if has_two_contiguous_letters(word) {
*tag = "URL".to_string();
} else {
*tag = "CD".to_string();
}
}
if parses_as_float(word) {
*tag = "CD".to_string();
}
}
fn has_two_contiguous_letters(word: &str) -> bool {
let bytes = word.as_bytes();
bytes
.windows(2)
.any(|w| w[0].is_ascii_alphabetic() && w[1].is_ascii_alphabetic())
}
fn rule3(sentence: &mut [Tagged], index: usize) {
if starts_with(&sentence[index].1, "N") && ends_with(&sentence[index].0, "ed") {
sentence[index].1 = "VBN".to_string();
}
}
fn rule4(sentence: &mut [Tagged], index: usize) {
if ends_with(&sentence[index].0, "ly") {
sentence[index].1 = "RB".to_string();
}
}
fn rule5(sentence: &mut [Tagged], index: usize) {
if starts_with(&sentence[index].1, "NN") && ends_with(&sentence[index].0, "al") {
sentence[index].1 = "JJ".to_string();
}
}
fn rule6(sentence: &mut [Tagged], index: usize) {
if index > 0
&& starts_with(&sentence[index].1, "NN")
&& sentence[index - 1].0.eq_ignore_ascii_case("would")
{
sentence[index].1 = "VB".to_string();
}
}
fn rule7(sentence: &mut [Tagged], index: usize) {
if sentence[index].1 == "NN" && ends_with(&sentence[index].0, "s") {
sentence[index].1 = "NNS".to_string();
}
}
fn rule8(sentence: &mut [Tagged], index: usize) {
if starts_with(&sentence[index].1, "NN") && ends_with(&sentence[index].0, "ing") {
sentence[index].1 = "VBG".to_string();
}
}
fn parses_as_float(s: &str) -> bool {
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | 0x0b | 0x0c) {
i += 1;
}
if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
i += 1;
}
if s[i..].starts_with("Infinity") {
return true;
}
let mut saw_digit = false;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
saw_digit = true;
}
if i < bytes.len() && bytes[i] == b'.' {
i += 1;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
saw_digit = true;
}
}
if !saw_digit {
return false;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ends_with_uses_first_occurrence() {
assert!(!ends_with("boss", "s"));
assert!(!ends_with("class", "s"));
assert!(ends_with("dogs", "s"));
assert!(ends_with("passed", "ed"));
assert!(!ends_with("word", ""));
}
#[test]
fn starts_with_basic() {
assert!(starts_with("NNP", "N"));
assert!(starts_with("NNS", "NN"));
assert!(!starts_with("VBD", "N"));
assert!(!starts_with("anything", ""));
}
#[test]
fn parse_float_matches_js() {
assert!(parses_as_float("5.2"));
assert!(parses_as_float("5.2abc"));
assert!(parses_as_float("5abc"));
assert!(parses_as_float("3.14"));
assert!(parses_as_float(".5"));
assert!(parses_as_float("5."));
assert!(parses_as_float("-5.2"));
assert!(parses_as_float("+5.2"));
assert!(parses_as_float("1e3"));
assert!(parses_as_float(" 5.2"));
assert!(parses_as_float("Infinity"));
assert!(parses_as_float("-Infinity"));
assert!(parses_as_float("0x10"));
assert!(!parses_as_float("v1.2"));
assert!(!parses_as_float("v1.2.3"));
assert!(!parses_as_float("."));
assert!(!parses_as_float(".e5"));
}
#[test]
fn two_contiguous_letters() {
assert!(has_two_contiguous_letters("google.com"));
assert!(!has_two_contiguous_letters("v1.2.3"));
assert!(!has_two_contiguous_letters("5.42"));
}
}