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::{QuantityData, QuantityUnit};

fn zh_digit(ch: char) -> Option<f64> {
    match ch {
        '' => 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),
        _ => None,
    }
}

fn parse_zh_integer(s: &str) -> Option<f64> {
    if let Ok(v) = s.parse::<f64>() {
        return Some(v);
    }
    let mut total = 0.0f64;
    let mut current = 0.0f64;
    for ch in s.chars() {
        match ch {
            '' => {
                let lhs = if current == 0.0 { 1.0 } else { current };
                total += lhs * 10.0;
                current = 0.0;
            }
            '' => {
                let lhs = if current == 0.0 { 1.0 } else { current };
                total += lhs * 100.0;
                current = 0.0;
            }
            '' => {
                let lhs = if current == 0.0 { 1.0 } else { current };
                total += lhs * 1000.0;
                current = 0.0;
            }
            _ => current = zh_digit(ch)?,
        }
    }
    Some(total + current)
}

fn parse_zh_number(s: &str) -> Option<f64> {
    if let Ok(v) = s.parse::<f64>() {
        return Some(v);
    }
    if let Some((i, f)) = s.split_once('').or_else(|| s.split_once('')) {
        let int_v = parse_zh_integer(i)?;
        let mut frac = 0.0;
        let mut place = 0.1;
        for ch in f.chars() {
            let d = zh_digit(ch)?;
            frac += d * place;
            place /= 10.0;
        }
        return Some(int_v + frac);
    }
    parse_zh_integer(s)
}

pub fn rules() -> Vec<Rule> {
    vec![
        Rule {
            name: "zh quantity number + unit".to_string(),
            pattern: vec![regex("([0-9]+(?:\\.[0-9]+)?)\\s*(千克|公斤|斤|两|兩|克|毫克|kg|g|mg)")],
            production: Box::new(|nodes| {
                let (v, u) = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => (m.group(1)?.parse::<f64>().ok()?, m.group(2)?),
                    _ => return None,
                };
                let value = match u.to_lowercase().as_str() {
                    "千克" | "公斤" | "kg" => v * 1000.0,
                    "" => v * 500.0,
                    "" | "" => v * 50.0,
                    "毫克" | "mg" => v / 1000.0,
                    _ => v,
                };
                Some(TokenData::Quantity(QuantityData::new(value, QuantityUnit::Gram)))
            }),
        },
        Rule {
            name: "zh quantity zh-number + unit".to_string(),
            pattern: vec![regex("([零一二两兩三四五六七八九十百千]+)\\s*(斤|两|兩|克|毫克)")],
            production: Box::new(|nodes| {
                let (v, u) = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => (parse_zh_number(m.group(1)?)?, m.group(2)?),
                    _ => return None,
                };
                let value = match u {
                    "" => v * 500.0,
                    "" | "" => v * 50.0,
                    "毫克" => v / 1000.0,
                    _ => v,
                };
                Some(TokenData::Quantity(QuantityData::new(value, QuantityUnit::Gram)))
            }),
        },
        Rule {
            name: "zh quantity catty half".to_string(),
            pattern: vec![regex("([0-9]+|[零一二两兩三四五六七八九十百千]+)斤半")],
            production: Box::new(|nodes| {
                let x = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => parse_zh_number(m.group(1)?)?,
                    _ => return None,
                };
                Some(TokenData::Quantity(QuantityData::new(
                    x * 500.0 + 250.0,
                    QuantityUnit::Gram,
                )))
            }),
        },
        Rule {
            name: "zh quantity tael half".to_string(),
            pattern: vec![regex("([0-9]+|[零一二两兩三四五六七八九十百千]+)(两|兩)半")],
            production: Box::new(|nodes| {
                let x = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => parse_zh_number(m.group(1)?)?,
                    _ => return None,
                };
                Some(TokenData::Quantity(QuantityData::new(
                    x * 50.0 + 25.0,
                    QuantityUnit::Gram,
                )))
            }),
        },
        Rule {
            name: "zh quantity catty tael".to_string(),
            pattern: vec![regex("([0-9]+|[零一二两兩三四五六七八九十百千]+)斤([0-9]+|[零一二两兩三四五六七八九十百千]+)(两|兩)")],
            production: Box::new(|nodes| {
                let (x, y) = match &nodes[0].token_data {
                    TokenData::RegexMatch(m) => (parse_zh_number(m.group(1)?)?, parse_zh_number(m.group(2)?)?),
                    _ => return None,
                };
                Some(TokenData::Quantity(QuantityData::new(
                    x * 500.0 + y * 50.0,
                    QuantityUnit::Gram,
                )))
            }),
        },
    ]
}