wp-lang 0.3.0

WPL language crate with AST, parser, evaluator, builtins, and generators.
Documentation
//! Utilities for Hong Kong Identity Card

use regex::Regex;
use std::collections::HashMap;
use std::sync::LazyLock;

static PREFIX_LETTERS: LazyLock<HashMap<&'static str, u32>> = LazyLock::new(|| {
    let mut map = HashMap::new();
    map.insert("A", 1);
    map.insert("B", 2);
    map.insert("C", 3);
    map.insert("R", 18);
    map.insert("U", 21);
    map.insert("Z", 26);
    map.insert("X", 24);
    map.insert("W", 23);
    map.insert("O", 15);
    map.insert("N", 14);
    map
});
static PATTERN: LazyLock<Regex> =
    LazyLock::new(|| Regex::new(r"^[A-Z]{1,2}[0-9]{6}\(?[0-9A]\)?$").unwrap());
static REMOVAL_PATTERN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[\(|\)]").unwrap());

/// Validates the number.
pub fn validate(number: &str) -> bool {
    if !PATTERN.is_match(number) {
        return false;
    }

    let number = REMOVAL_PATTERN
        .replace_all(number, "")
        .trim()
        .to_ascii_uppercase();

    let mut sum: u32;
    let mut card = &number[..];
    if number.len() == 9 {
        let first = match number.chars().nth(0) {
            Some(ch) => ch as u32,
            _ => return false,
        };
        let second = match number.chars().nth(1) {
            Some(ch) => ch as u32,
            _ => return false,
        };
        sum = (first - 55) * 9 + (second - 55) * 8;
        card = &number[1..9];
    } else {
        let first = match number.chars().nth(0) {
            Some(ch) => ch as u32,
            _ => return false,
        };
        sum = 522 + (first - 55) * 8;
    }

    let mid = &card[1..7];
    let end = &card[7..8];

    let mut flag = 7;
    for ch in mid.chars() {
        let i = match ch.to_digit(10) {
            Some(value) => value,
            _ => return false,
        };
        sum = sum + i * flag;
        flag -= 1;
    }

    if end == "A" {
        sum = sum + 10;
    } else {
        let i = match end.chars().nth(0) {
            Some(ch) => match ch.to_digit(10) {
                Some(value) => value,
                _ => return false,
            },
            _ => return false,
        };
        sum = sum + i;
    }
    sum % 11 == 0
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_validate() {
        assert_eq!(validate("G123456(A)"), true);
        assert_eq!(validate("G123456(a)"), false);
        assert_eq!(validate("G123456A"), true);
        assert_eq!(validate("L555555(0)"), true);
        assert_eq!(validate("AB987654(3)"), true);
        assert_eq!(validate("C123456(9)"), true);
        assert_eq!(validate("AY987654(A)"), false);
    }
}