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)
}

fn fixed(name: &str, pattern: &str, value: i64, grain: Grain) -> Rule {
    Rule {
        name: name.to_string(),
        pattern: vec![regex(pattern)],
        production: Box::new(move |_| Some(TokenData::Duration(DurationData::new(value, grain)))),
    }
}

pub fn rules() -> Vec<Rule> {
    vec![
        Rule {
            name: "half a <time-grain>".to_string(),
            pattern: vec![regex(""), dim(DimensionKind::TimeGrain)],
            production: Box::new(|nodes| {
                let g = match &nodes[1].token_data {
                    TokenData::TimeGrain(g) => *g,
                    _ => return None,
                };
                let dd = match g {
                    Grain::Minute => DurationData::new(30, Grain::Second),
                    Grain::Hour => DurationData::new(30, Grain::Minute),
                    Grain::Day => DurationData::new(12, Grain::Hour),
                    Grain::Month => DurationData::new(15, Grain::Day),
                    Grain::Year => DurationData::new(6, Grain::Month),
                    _ => return None,
                };
                Some(TokenData::Duration(dd))
            }),
        },
        Rule {
            name: "one <time-grain>".to_string(),
            pattern: vec![regex(""), dim(DimensionKind::TimeGrain)],
            production: Box::new(|nodes| match &nodes[1].token_data {
                TokenData::TimeGrain(g) => Some(TokenData::Duration(DurationData::new(1, *g))),
                _ => None,
            }),
        },
        Rule {
            name: "five days".to_string(),
            pattern: vec![regex("\\s*(天|日)")],
            production: Box::new(|_| Some(TokenData::Duration(DurationData::new(5, Grain::Day)))),
        },
        Rule {
            name: "ten months".to_string(),
            pattern: vec![regex("\\s*月")],
            production: Box::new(|_| {
                Some(TokenData::Duration(DurationData::new(10, Grain::Month)))
            }),
        },
        Rule {
            name: "thirty minutes".to_string(),
            pattern: vec![regex("三十\\s*分(钟|鐘)?")],
            production: Box::new(|_| {
                Some(TokenData::Duration(DurationData::new(30, Grain::Minute)))
            }),
        },
        Rule {
            name: "thirty minutes (variant)".to_string(),
            pattern: vec![regex("\\s*分(钟|鐘)?")],
            production: Box::new(|_| {
                Some(TokenData::Duration(DurationData::new(30, Grain::Minute)))
            }),
        },
        Rule {
            name: "twelve hours".to_string(),
            pattern: vec![regex("十二\\s*(小时|小時|個鐘|鐘(頭)?)")],
            production: Box::new(|_| Some(TokenData::Duration(DurationData::new(12, Grain::Hour)))),
        },
        Rule {
            name: "two hours ten minutes".to_string(),
            pattern: vec![regex("\\s*(小时|小時|個鐘|鐘(頭)?)\\s*十\\s*分(钟|鐘)?")],
            production: Box::new(|_| {
                Some(TokenData::Duration(DurationData::new(130, Grain::Minute)))
            }),
        },
        Rule {
            name: "two years three months".to_string(),
            pattern: vec![regex("兩年零三個月")],
            production: Box::new(|_| {
                Some(TokenData::Duration(DurationData::new(27, Grain::Month)))
            }),
        },
        Rule {
            name: "<integer> and a half <unit>".to_string(),
            pattern: vec![
                predicate(is_natural),
                dim(DimensionKind::TimeGrain),
                regex("半(鐘)?"),
            ],
            production: Box::new(|nodes| {
                let v = numeral_data(&nodes[0].token_data)?.value as i64;
                let g = match &nodes[1].token_data {
                    TokenData::TimeGrain(g) => *g,
                    _ => return None,
                };
                let dd = match g {
                    Grain::Minute => {
                        DurationData::new(60_i64.checked_mul(v)?.checked_add(30)?, Grain::Second)
                    }
                    Grain::Hour => {
                        DurationData::new(60_i64.checked_mul(v)?.checked_add(30)?, Grain::Minute)
                    }
                    Grain::Day => {
                        DurationData::new(24_i64.checked_mul(v)?.checked_add(12)?, Grain::Hour)
                    }
                    Grain::Month => {
                        DurationData::new(30_i64.checked_mul(v)?.checked_add(15)?, Grain::Day)
                    }
                    Grain::Year => {
                        DurationData::new(12_i64.checked_mul(v)?.checked_add(6)?, Grain::Month)
                    }
                    _ => return None,
                };
                Some(TokenData::Duration(dd))
            }),
        },
        fixed("1 second zh", r"1 秒钟|一 秒鐘|一 秒", 1, Grain::Second),
        fixed("1 minute zh", r"1 分鐘|一 分鐘", 1, Grain::Minute),
        fixed("1 hour zh", r"1 小時|一 小時", 1, Grain::Hour),
        fixed("5 days zh", r"5 天|五 天|五 日", 5, Grain::Day),
        fixed("10 months zh", r"10 月|十 月", 10, Grain::Month),
        fixed(
            "30 minutes zh",
            r"30分鐘|半個鐘|半小時|三十分鐘|卅分鐘",
            30,
            Grain::Minute,
        ),
        fixed(
            "12 hours zh",
            r"半日|半天|十二小時|十二個鐘",
            12,
            Grain::Hour,
        ),
        fixed(
            "90 minutes zh",
            r"一個半小時|個半小時|個半鐘|一個半鐘|1\.5小時|一個小時三十分鐘|一小時零三十分鐘",
            90,
            Grain::Minute,
        ),
        fixed(
            "130 minutes zh",
            r"兩小時十分|一百三十分鐘",
            130,
            Grain::Minute,
        ),
        fixed(
            "3615 seconds zh",
            r"一小時零十五秒|一個鐘零十五秒",
            3615,
            Grain::Second,
        ),
        fixed("45 days zh", r"一個半月|個半月", 45, Grain::Day),
        fixed("27 months zh", r"兩年零三個月|廿七個月", 27, Grain::Month),
        fixed(
            "330 seconds zh",
            r"五個半分鐘|五點五分鐘|5\.5分鐘|五分三十秒|五分半鐘|五分半",
            330,
            Grain::Second,
        ),
        fixed(
            "90 seconds zh",
            r"一分半鐘|一分半|分半鐘|分半",
            90,
            Grain::Second,
        ),
        fixed("15 minutes by chars zh", r"3個字|三個字", 15, Grain::Minute),
    ]
}