graphix-stdlib 0.3.1

A dataflow language for UIs and network programming, standard library
Documentation
use crate::{deftype, CachedArgs, CachedVals, EvalCached};
use anyhow::Result;
use arcstr::{literal, ArcStr};
use graphix_compiler::{errf, ExecCtx, Rt, UserEvent};
use netidx::subscriber::Value;
use netidx_value::ValArray;
use regex::Regex;

fn maybe_compile(s: &str, re: &mut Option<Regex>) -> Result<()> {
    let compile = match re {
        None => true,
        Some(re) => re.as_str() != s,
    };
    if compile {
        *re = Some(Regex::new(s)?)
    }
    Ok(())
}

static TAG: ArcStr = literal!("ReError");

#[derive(Debug, Default)]
struct IsMatchEv {
    re: Option<Regex>,
}

impl EvalCached for IsMatchEv {
    const NAME: &str = "re_is_match";
    deftype!("re", "fn(#pat:string, string) -> Result<bool, `ReError(string)>");

    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
        if let Some(Value::String(s)) = &from.0[0] {
            if let Err(e) = maybe_compile(s, &mut self.re) {
                return Some(errf!(TAG, "{e:?}"));
            }
        }
        if let Some(Value::String(s)) = &from.0[1] {
            if let Some(re) = self.re.as_ref() {
                return Some(Value::Bool(re.is_match(s)));
            }
        }
        None
    }
}

type IsMatch = CachedArgs<IsMatchEv>;

#[derive(Debug, Default)]
struct FindEv {
    re: Option<Regex>,
}

impl EvalCached for FindEv {
    const NAME: &str = "re_find";
    deftype!("re", "fn(#pat:string, string) -> Result<Array<string>, `ReError(string)>");

    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
        if let Some(Value::String(s)) = &from.0[0] {
            if let Err(e) = maybe_compile(s, &mut self.re) {
                return Some(errf!(TAG, "{e:?}"));
            }
        }
        if let Some(Value::String(s)) = &from.0[1] {
            if let Some(re) = self.re.as_ref() {
                let a = ValArray::from_iter(
                    re.find_iter(s).map(|s| Value::String(s.as_str().into())),
                );
                return Some(Value::Array(a));
            }
        }
        None
    }
}

type Find = CachedArgs<FindEv>;

#[derive(Debug, Default)]
struct CapturesEv {
    re: Option<Regex>,
}

impl EvalCached for CapturesEv {
    const NAME: &str = "re_captures";
    deftype!("re", "fn(#pat:string, string) -> Result<Array<Array<Option<string>>>, `ReError(string)>");

    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
        if let Some(Value::String(s)) = &from.0[0] {
            if let Err(e) = maybe_compile(s, &mut self.re) {
                return Some(errf!(TAG, "{e:?}"));
            }
        }
        if let Some(Value::String(s)) = &from.0[1] {
            if let Some(re) = self.re.as_ref() {
                let a = ValArray::from_iter(re.captures_iter(s).map(|c| {
                    let a = ValArray::from_iter(c.iter().map(|m| match m {
                        None => Value::Null,
                        Some(m) => Value::String(m.as_str().into()),
                    }));
                    Value::Array(a)
                }));
                return Some(Value::Array(a));
            }
        }
        None
    }
}

type Captures = CachedArgs<CapturesEv>;

#[derive(Debug, Default)]
struct SplitEv {
    re: Option<Regex>,
}

impl EvalCached for SplitEv {
    const NAME: &str = "re_split";
    deftype!("re", "fn(#pat:string, string) -> Result<Array<string>, `ReError(string)>");

    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
        if let Some(Value::String(s)) = &from.0[0] {
            if let Err(e) = maybe_compile(s, &mut self.re) {
                return Some(errf!(TAG, "{e:?}"));
            }
        }
        if let Some(Value::String(s)) = &from.0[1] {
            if let Some(re) = self.re.as_ref() {
                let a = ValArray::from_iter(re.split(s).map(|s| Value::String(s.into())));
                return Some(Value::Array(a));
            }
        }
        None
    }
}

type Split = CachedArgs<SplitEv>;

#[derive(Debug, Default)]
struct SplitNEv {
    re: Option<Regex>,
    lim: Option<usize>,
}

impl EvalCached for SplitNEv {
    const NAME: &str = "re_splitn";
    deftype!(
        "re",
        "fn(#pat:string, #limit:i64, string) -> Result<Array<string>, `ReError(string)>"
    );

    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
        if let Some(Value::String(s)) = &from.0[0] {
            if let Err(e) = maybe_compile(s, &mut self.re) {
                return Some(errf!(TAG, "{e:?}"));
            }
        }
        if let Some(Value::I64(lim)) = &from.0[1] {
            self.lim = Some(*lim as usize);
        }
        if let Some(Value::String(s)) = &from.0[2] {
            if let Some(lim) = self.lim {
                if let Some(re) = self.re.as_ref() {
                    let a = ValArray::from_iter(
                        re.splitn(s, lim).map(|s| Value::String(s.into())),
                    );
                    return Some(Value::Array(a));
                }
            }
        }
        None
    }
}

type SplitN = CachedArgs<SplitNEv>;

pub(super) fn register<R: Rt, E: UserEvent>(ctx: &mut ExecCtx<R, E>) -> Result<(ArcStr, ArcStr)> {
    ctx.register_builtin::<IsMatch>()?;
    ctx.register_builtin::<Find>()?;
    ctx.register_builtin::<Captures>()?;
    ctx.register_builtin::<Split>()?;
    ctx.register_builtin::<SplitN>()?;
    Ok((literal!(include_str!("re.gx")), literal!(include_str!("re.gxi"))))
}