duckling 0.4.0

A Rust port of Facebook's Duckling library for parsing natural language into structured data
Documentation
use crate::dimensions::numeral::helpers::numeral_data;
use crate::dimensions::time_grain::Grain;
use crate::pattern::{dim, predicate, regex};
use crate::types::{DimensionKind, Rule, TokenData};

use super::DurationData;

fn is_natural(td: &TokenData) -> bool {
    matches!(td, TokenData::Numeral(d) if d.value > 0.0 && d.value.fract() == 0.0)
}

pub fn rules() -> Vec<Rule> {
    vec![
        Rule {
            name: "quarter of an hour".to_string(),
            pattern: vec![regex("(1/4\\s?sa(at)?|çeyrek saat)")],
            production: Box::new(|_| {
                Some(TokenData::Duration(DurationData::new(15, Grain::Minute)))
            }),
        },
        Rule {
            name: "half an hour".to_string(),
            pattern: vec![regex("(1/2\\s?sa(at)?|yarım saat)")],
            production: Box::new(|_| {
                Some(TokenData::Duration(DurationData::new(30, Grain::Minute)))
            }),
        },
        Rule {
            name: "three-quarters of an hour".to_string(),
            pattern: vec![regex("(3/4\\s?sa(at)?|üç çeyrek sa(at)?)")],
            production: Box::new(|_| {
                Some(TokenData::Duration(DurationData::new(45, Grain::Minute)))
            }),
        },
        Rule {
            name: "fortnight".to_string(),
            pattern: vec![regex("iki hafta")],
            production: Box::new(|_| Some(TokenData::Duration(DurationData::new(14, Grain::Day)))),
        },
        Rule {
            name: "<integer> + '\"'".to_string(),
            pattern: vec![predicate(is_natural), regex("(['\\\"])")],
            production: Box::new(|nodes| {
                let v = numeral_data(&nodes[0].token_data)?.value as i64;
                let q = match &nodes[1].token_data {
                    TokenData::RegexMatch(m) => m.group(1)?,
                    _ => return None,
                };
                match q {
                    "'" => Some(TokenData::Duration(DurationData::new(v, Grain::Minute))),
                    "\"" => Some(TokenData::Duration(DurationData::new(v, Grain::Second))),
                    _ => None,
                }
            }),
        },
        Rule {
            name: "number.number hours".to_string(),
            pattern: vec![regex("(\\d+)(\\.|,)(\\d+) *sa(at)?")],
            production: Box::new(|nodes| {
                let m = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => m,
                    _ => return None,
                };
                let hh: i64 = m.group(1)?.parse().ok()?;
                let frac = m.group(3)?;
                let num: i64 = frac.parse().ok()?;
                let den: i64 = 10_i64.pow(frac.len() as u32);
                let minutes = 60_i64
                    .checked_mul(hh)?
                    .checked_add(num.checked_mul(60)?.checked_div(den)?)?;
                Some(TokenData::Duration(DurationData::new(
                    minutes,
                    Grain::Minute,
                )))
            }),
        },
        Rule {
            name: "<integer> and an half hour".to_string(),
            pattern: vec![predicate(is_natural), regex("buçuk sa(at)?")],
            production: Box::new(|nodes| {
                let v = numeral_data(&nodes[0].token_data)?.value as i64;
                Some(TokenData::Duration(DurationData::new(
                    60_i64.checked_mul(v)?.checked_add(30)?,
                    Grain::Minute,
                )))
            }),
        },
        Rule {
            name: "<duration> about|exactly".to_string(),
            pattern: vec![
                regex("(yaklaşık|tam(\\solarak)?)"),
                dim(DimensionKind::Duration),
            ],
            production: Box::new(|nodes| Some(nodes[1].token_data.clone())),
        },
        Rule {
            name: "<duration> about|exactly 2".to_string(),
            pattern: vec![dim(DimensionKind::Duration), regex("gibi|civarında")],
            production: Box::new(|nodes| Some(nodes[0].token_data.clone())),
        },
    ]
}