ksl 0.1.30

KSL core library and interpreter
Documentation
//! # ksl::builtin::regex
//!
//! Built-in function `ReMatch`, `ReCapture`, `Fstr` and `Chn`.

use regex::escape;

use crate::{Environment, FALSE_SYMBOL, TRUE_SYMBOL, eval::apply::eval_apply, value::Value};

thread_local! {
    static REGEX_CACHE: std::cell::RefCell<rustc_hash::FxHashMap<std::sync::Arc<str>, regex::Regex>>
        = std::cell::RefCell::new(rustc_hash::FxHashMap::default());
}

pub(crate) fn try_regex_cache(pattern: &std::sync::Arc<str>) -> Result<regex::Regex, std::sync::Arc<str>> {
    REGEX_CACHE.with(|cache| {
        let mut cache = cache.borrow_mut();
        if let Some(re) = cache.get(pattern) {
            return Ok(re.clone());
        }

        match regex::RegexBuilder::new(pattern).build() {
            Ok(re) => {
                cache.insert(pattern.clone(), re.clone());
                Ok(re)
            }
            Err(e) => Err(std::sync::Arc::from(format!(
                concat!(
                    "Error[ksl::builtin::regex::try_regex_cache]: ",
                    "Invalid regex rules `{}` with `{}`.",
                ),
                pattern, e
            ))),
        }
    })
}

pub(crate) fn re_match(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
    if let [r, s] = args {
        match (eval_apply(r, env.clone())?, eval_apply(s, env)?) {
            (Value::String(pattern), Value::String(value)) => {
                let pat = try_regex_cache(&pattern)?;
                Ok(if pat.is_match(value.as_ref()) {
                    TRUE_SYMBOL.clone()
                } else {
                    FALSE_SYMBOL.clone()
                })
            }
            (Value::String(_), e) | (e, _) => Err(std::sync::Arc::from(format!(
                concat!("Error[ksl::builtin::ReMatch]: ", "Unexpected value: `{}`."),
                e
            ))),
        }
    } else {
        Err(std::sync::Arc::from(format!(
            concat!(
                "Error[ksl::builtin::ReMatch]: ",
                "Expected 2 parameters, but {} were passed."
            ),
            args.len()
        )))
    }
}

pub(crate) fn re_capture(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
    if let [r, s, atms] = args {
        match (
            eval_apply(r, env.clone())?,
            eval_apply(s, env.clone())?,
            eval_apply(atms, env)?,
        ) {
            (Value::String(pattern), Value::String(value), Value::List(atoms)) => {
                let pat = try_regex_cache(&pattern)?;
                match pat.captures(value.as_ref()) {
                    Some(caps) => Ok(Value::List(
                        atoms
                            .iter()
                            .map(|atom| match atom {
                                Value::Atom(a) => Ok(match caps.name(a.as_ref()) {
                                    Some(val) => Value::String(std::sync::Arc::from(val.as_str())),
                                    None => Value::Unit,
                                }),
                                e => Err(std::sync::Arc::from(format!(
                                    concat!(
                                        "Error[ksl::builtin::ReCapture]: ",
                                        "Unexpected value: `{}`."
                                    ),
                                    e
                                ))),
                            })
                            .collect::<Result<std::sync::Arc<[Value]>, std::sync::Arc<str>>>()?,
                    )),
                    None => Err(std::sync::Arc::from(concat!(
                        "Error[ksl::builtin::ReCapture]: ",
                        "Failed to capture values."
                    ))),
                }
            }
            (Value::String(_), Value::String(_), e) | (Value::String(_), e, _) | (e, _, _) => {
                Err(std::sync::Arc::from(format!(
                    concat!(
                        "Error[ksl::builtin::ReCapture]: ",
                        "Unexpected value: `{}`."
                    ),
                    e
                )))
            }
        }
    } else {
        Err(std::sync::Arc::from(format!(
            concat!(
                "Error[ksl::builtin::ReCapture]: ",
                "Expected 3 parameters, but {} were passed."
            ),
            args.len()
        )))
    }
}

pub(crate) fn fstr(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
    if let [pattern, obj] = args {
        match (eval_apply(pattern, env.clone())?, eval_apply(obj, env)?) {
            (Value::String(pats), Value::Object(tag, valt)) if tag.as_ref() == "Args" => {
                let key_re = try_regex_cache(&std::sync::Arc::from(format!(
                    "@({})",
                    valt.keys()
                        .map(|k| escape(k.as_ref()))
                        .collect::<Vec<String>>()
                        .join("|")
                )))?;

                Ok(Value::String(std::sync::Arc::from(key_re.replace_all(
                    pats.as_ref(),
                    |cap: &regex::Captures| {
                        let name = &cap[1];
                        match valt.get(name) {
                            Some(v) => match v.as_ref() {
                                Value::String(s) => s.as_ref().to_owned(),
                                v => v.to_string(),
                            },
                            None => name.to_string(),
                        }
                    },
                ))))
            }
            (Value::String(_), e) => Err(std::sync::Arc::from(format!(
                concat!(
                    "Error[ksl::builtin::Fstr]: ",
                    "Expected a `Pattern` object as arguments, but got: `{}`."
                ),
                e
            ))),
            (e, _) => Err(std::sync::Arc::from(format!(
                concat!(
                    "Error[ksl::builtin::Fstr]: ",
                    "Expected a string as pattern, but got: `{}`."
                ),
                e
            ))),
        }
    } else {
        Err(std::sync::Arc::from(format!(
            concat!(
                "Error[ksl::builtin::Fstr]: ",
                "Expected 2 parameter, but {} were passed."
            ),
            args.len()
        )))
    }
}

pub(crate) fn chn(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
    if let [e] = args {
        match eval_apply(e, env)? {
            Value::String(s) => {
                if s.chars().count() == 1
                    && let Some(ch) = s.chars().next()
                {
                    Ok(Value::Number(u32::from(ch) as f64))
                } else {
                    Err(std::sync::Arc::from(format!(
                        concat!(
                            "Error[ksl::builtin::Chn]: ",
                            "Invalid single character: `{}`."
                        ),
                        s
                    )))
                }
            }
            Value::Number(n) => {
                let code = n.trunc() as u32;
                if let Some(ch) = char::from_u32(code) {
                    Ok(Value::String(std::sync::Arc::from(String::from(ch))))
                } else {
                    Err(std::sync::Arc::from(format!(
                        concat!("Error[ksl::builtin::Chn]: ", "Invalid unicode: `{}`."),
                        n
                    )))
                }
            }
            e => Err(std::sync::Arc::from(format!(
                concat!("Error[ksl::builtin::Chn]: ", "Unexpected value: `{}`."),
                e
            ))),
        }
    } else {
        Err(std::sync::Arc::from(format!(
            concat!(
                "Error[ksl::builtin::Chn]: ",
                "Expected 1 parameter, but {} were passed."
            ),
            args.len()
        )))
    }
}