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 lex(s: &str) -> Option<f64> {
    match s {
        "אפס" => Some(0.0),
        "אחד" | "אחת" | "יחיד" => Some(1.0),
        "שתיים" | "שניים" | "זוג" => Some(2.0),
        "שלוש" | "שלושה" => Some(3.0),
        "ארבע" | "ארבעה" => Some(4.0),
        "חמש" | "חמישה" => Some(5.0),
        "שש" | "ששה" => Some(6.0),
        "שבע" | "שבעה" => Some(7.0),
        "שמונה" => Some(8.0),
        "תשע" | "תשעה" => Some(9.0),
        "עשר" | "עשרה" => Some(10.0),
        "עשרים" => Some(20.0),
        "שלושים" => Some(30.0),
        "ארבעים" => Some(40.0),
        "חמישים" => Some(50.0),
        "שישים" => Some(60.0),
        "שבעים" => Some(70.0),
        "שמונים" => Some(80.0),
        "תשעים" => Some(90.0),
        "ארבעה עשר" | "ארבע עשרה" => Some(14.0),
        "ששה עשר" | "שש עשרה" => Some(16.0),
        "שבעה עשר" | "שבע עשרה" => Some(17.0),
        "שמונה עשר" | "שמונה עשרה" => Some(18.0),
        "חצי" => Some(0.5),
        _ => None,
    }
}

pub fn rules() -> Vec<Rule> {
    vec![
        Rule {
            name: "hebrew lexical numerals".to_string(),
            pattern: vec![regex("(אפס|אחד|אחת|יחיד|שתיים|שניים|זוג|שלוש|שלושה|ארבע|ארבעה|חמש|חמישה|שש|ששה|שבע|שבעה|שמונה|תשע|תשעה|עשר|עשרה|עשרים|שלושים|ארבעים|חמישים|שישים|שבעים|שמונים|תשעים|ארבעה עשר|ארבע עשרה|ששה עשר|שש עשרה|שבעה עשר|שבע עשרה|שמונה עשר|שמונה עשרה|חצי)")],
            production: Box::new(|nodes| {
                let s = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => m.group(1)?,
                    _ => return None,
                };
                Some(TokenData::Numeral(NumeralData::new(lex(s)?)))
            }),
        },
        Rule {
            name: "composite tens".to_string(),
            pattern: vec![regex("(שלושים|עשרים|ארבעים|חמישים|שישים|שבעים|שמונים|תשעים)\\s+ו?(אחד|אחת|שתיים|שניים|שלוש|שלושה|ארבע|ארבעה|חמש|חמישה|שש|ששה|שבע|שבעה|שמונה|תשע|תשעה)")],
            production: Box::new(|nodes| {
                let m = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => m,
                    _ => return None,
                };
                let t = m.group(1)?;
                let u = m.group(2)?;
                Some(TokenData::Numeral(NumeralData::new(lex(t)? + lex(u)?)))
            }),
        },
        Rule {
            name: "dot decimal".to_string(),
            pattern: vec![regex("(\\d*\\.\\d+)")],
            production: Box::new(|nodes| {
                let m = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => m.group(1)?,
                    _ => return None,
                };
                Some(TokenData::Numeral(NumeralData::new(m.parse::<f64>().ok()?)))
            }),
        },
        Rule {
            name: "comma thousands".to_string(),
            pattern: vec![regex("(\\d{1,3}(,\\d\\d\\d){1,5})")],
            production: Box::new(|nodes| {
                let m = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => m.group(1)?,
                    _ => return None,
                };
                Some(TokenData::Numeral(NumeralData::new(
                    m.replace(',', "").parse::<f64>().ok()?,
                )))
            }),
        },
        Rule {
            name: "negative prefix".to_string(),
            pattern: vec![regex("(-|מינוס)\\s*(\\d{1,3}(,\\d\\d\\d){0,5})")],
            production: Box::new(|nodes| {
                let m = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => m.group(2)?,
                    _ => return None,
                };
                let v: f64 = m.replace(',', "").parse().ok()?;
                Some(TokenData::Numeral(NumeralData::new(-v)))
            }),
        },
    ]
}