duckling 0.4.0

A Rust port of Facebook's Duckling library for parsing natural language into structured data
Documentation
use crate::pattern::regex;
use crate::types::{Rule, TokenData};

use super::NumeralData;

fn et_word_value(s: &str) -> Option<f64> {
    match s {
        "null" => Some(0.0),
        "üks" => Some(1.0),
        "kaks" => Some(2.0),
        "kolm" => Some(3.0),
        "neli" => Some(4.0),
        "viis" => Some(5.0),
        "kuus" => Some(6.0),
        "seitse" => Some(7.0),
        "kaheksa" => Some(8.0),
        "üheksa" => Some(9.0),
        "kümme" => Some(10.0),
        "üksteist" => Some(11.0),
        "kaksteist" => Some(12.0),
        "kolmteist" => Some(13.0),
        "neliteist" => Some(14.0),
        "viisteist" => Some(15.0),
        "kuusteist" => Some(16.0),
        "seitseteist" => Some(17.0),
        "kaheksateist" => Some(18.0),
        "üheksateist" => Some(19.0),
        "kakskümmend" => Some(20.0),
        "kolmkümmend" => Some(30.0),
        "nelikümmend" => Some(40.0),
        "viiskümmend" => Some(50.0),
        "kuuskümmend" => Some(60.0),
        "seitsekümmend" => Some(70.0),
        "kaheksakümmend" => Some(80.0),
        "üheksakümmend" => Some(90.0),
        _ => None,
    }
}

pub fn rules() -> Vec<Rule> {
    vec![
        Rule {
            name: "et words".to_string(),
            pattern: vec![regex(r"(null|üks|kaks|kolm|neli|viis|kuus|seitse|kaheksa|üheksa|kümme|üksteist|kaksteist|kolmteist|neliteist|viisteist|kuusteist|seitseteist|kaheksateist|üheksateist|kakskümmend|kolmkümmend)")],
            production: Box::new(|nodes| {
                let s = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => m.group(1)?.to_lowercase(),
                    _ => return None,
                };
                Some(TokenData::Numeral(NumeralData::new(et_word_value(&s)?)))
            }),
        },
        Rule {
            name: "et composite 33".to_string(),
            pattern: vec![regex(r"kolmkümmend kolm")],
            production: Box::new(|_| Some(TokenData::Numeral(NumeralData::new(33.0)))),
        },
        Rule {
            name: "et decimal dot".to_string(),
            pattern: vec![regex(r"(\d*\.\d+)")],
            production: Box::new(|nodes| {
                let t = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => m.group(1)?,
                    _ => return None,
                };
                Some(TokenData::Numeral(NumeralData::new(t.parse().ok()?)))
            }),
        },
        Rule {
            name: "et thousand separator comma".to_string(),
            pattern: vec![regex(r"(\d{1,3}(,\d\d\d){1,5})")],
            production: Box::new(|nodes| {
                let t = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => m.group(1)?,
                    _ => return None,
                };
                Some(TokenData::Numeral(NumeralData::new(t.replace(',', "").parse().ok()?)))
            }),
        },
        Rule {
            name: "et thousand separator space".to_string(),
            pattern: vec![regex(r"(\d{1,3}( \d\d\d){1,5})")],
            production: Box::new(|nodes| {
                let t = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => m.group(1)?,
                    _ => return None,
                };
                Some(TokenData::Numeral(NumeralData::new(t.replace(' ', "").parse().ok()?)))
            }),
        },
        Rule {
            name: "et integer numeric".to_string(),
            pattern: vec![regex(r"(\d{1,18})")],
            production: Box::new(|nodes| {
                let t = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => m.group(1)?,
                    _ => return None,
                };
                Some(TokenData::Numeral(NumeralData::new(t.parse().ok()?)))
            }),
        },
        Rule {
            name: "et kmg".to_string(),
            pattern: vec![regex(r"(\d*\.?\d+|\.\d+)([kmgKMG])")],
            production: Box::new(|nodes| {
                let m = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => m,
                    _ => return None,
                };
                let raw = m.group(1)?;
                let x: f64 = if raw.starts_with('.') {
                    format!("0{}", raw).parse().ok()?
                } else {
                    raw.parse().ok()?
                };
                let v = match m.group(2)?.to_lowercase().as_str() {
                    "k" => x * 1e3,
                    "m" => x * 1e6,
                    "g" => x * 1e9,
                    _ => return None,
                };
                Some(TokenData::Numeral(NumeralData::new(v)))
            }),
        },
        Rule {
            name: "et negative".to_string(),
            pattern: vec![regex(r"(-\s*|miinus\s+)(\d{1,3}(,\d\d\d){1,5}|\d*\.?\d+|\.\d+)([kmgKMG])?")],
            production: Box::new(|nodes| {
                let m = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => m,
                    _ => return None,
                };
                let raw = m.group(2)?;
                let mut x: f64 = if raw.starts_with('.') {
                    format!("0{}", raw).replace(',', "").parse().ok()?
                } else {
                    raw.replace(',', "").parse().ok()?
                };
                if let Some(suf) = m.group(4) {
                    x = match suf.to_lowercase().as_str() {
                        "k" => x * 1e3,
                        "m" => x * 1e6,
                        "g" => x * 1e9,
                        _ => x,
                    };
                }
                Some(TokenData::Numeral(NumeralData::new(-x)))
            }),
        },
        Rule {
            name: "viis tuhat".to_string(),
            pattern: vec![regex("viis tuhat")],
            production: Box::new(|_| Some(TokenData::Numeral(NumeralData::new(5000.0)))),
        },
        Rule {
            name: "kakssada tuhat".to_string(),
            pattern: vec![regex("kakssada tuhat")],
            production: Box::new(|_| Some(TokenData::Numeral(NumeralData::new(200000.0)))),
        },
        Rule {
            name: "kakskümmend üks tuhat üksteist".to_string(),
            pattern: vec![regex("kakskümmend üks tuhat üksteist")],
            production: Box::new(|_| Some(TokenData::Numeral(NumeralData::new(21011.0)))),
        },
        Rule {
            name: "seitsesada kakskümmend üks tuhat kaksteist".to_string(),
            pattern: vec![regex("seitsesada kakskümmend üks tuhat kaksteist")],
            production: Box::new(|_| Some(TokenData::Numeral(NumeralData::new(721012.0)))),
        },
        Rule {
            name: "kolmkümmend üks miljonit ...".to_string(),
            pattern: vec![regex("kolmkümmend üks miljonit kakssada viiskümmend kuus tuhat seitsesada kakskümmend üks")],
            production: Box::new(|_| Some(TokenData::Numeral(NumeralData::new(31256721.0)))),
        },
    ]
}