cog-task 1.2.0

A general-purpose low-latency application to run cognitive tasks
Documentation
use crate::resource::{LoopbackError, LoopbackResult, VarMap};
use eyre::{eyre, Result};
use num_traits::ToPrimitive;
use savage_core::expression::{Expression, Integer, Rational, RationalRepresentation};
use serde_cbor::Value;
use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex};
use std::thread;

static mut SAVAGE_EXPR: Option<Arc<Mutex<BTreeMap<usize, Expression>>>> = None;

#[derive(Clone)]
pub struct Evaluator(usize);

impl Evaluator {
    pub fn new(_init: &str, expr: &str, _vars: &mut VarMap) -> Result<Self> {
        let expression = expr
            .parse::<Expression>()
            .map_err(|e| eyre!("Failed to parse `savage` expression ({e:#?})."))?;

        let index = unsafe {
            if SAVAGE_EXPR.is_none() {
                SAVAGE_EXPR = Some(Arc::new(Mutex::new(BTreeMap::new())));
            }

            let mut global = SAVAGE_EXPR.as_ref().unwrap().lock().unwrap();
            let index = global.len();
            global.insert(index, expression);
            index
        };

        Ok(Self(index))
    }

    pub fn eval(&self, vars: &VarMap) -> Result<Value> {
        let expression = unsafe {
            if let Some(expr) = SAVAGE_EXPR.as_ref().unwrap().lock().unwrap().get(&self.0) {
                expr.clone()
            } else {
                return Err(eyre!("`savage::Expression` is missing."));
            }
        };

        let mut hash_vars = HashMap::new();
        for (s, v) in vars.iter() {
            let v = match v {
                Value::Null => Expression::Integer(Integer::from(0)),
                Value::Bool(v) => Expression::Boolean(*v),
                Value::Integer(v) => Expression::Integer(Integer::from(*v)),
                Value::Float(v) => Expression::Rational(
                    Rational::from_float(*v)
                        .ok_or_else(|| eyre!("Failed to convert float ({v}) to ratio."))?,
                    RationalRepresentation::Decimal,
                ),
                _ => Err(eyre!(
                    "Failed to convert variable ({s}) to savage Expression."
                ))?,
            };

            hash_vars.insert(s.clone(), v);
        }

        let result = expression
            .evaluate(hash_vars)
            .map_err(|e| eyre!("Failed to evaluate mathematical expression ({e:#?})."))?;

        match result {
            Expression::Integer(x) => {
                Ok(Value::Integer(x.to_i128().ok_or_else(|| {
                    eyre!("Failed to convert result to i128.")
                })?))
            }
            Expression::Rational(x, _) => Ok(Value::Float(
                x.numer()
                    .to_f64()
                    .ok_or_else(|| eyre!("Failed to convert numerator of result to f64."))?
                    / x.denom()
                        .to_f64()
                        .ok_or_else(|| eyre!("Failed to convert denominator of result to f64."))?,
            )),
            Expression::Complex(_, _) => Err(eyre!("Complex results are currently not supported.")),
            _ => Err(eyre!("`savage::Expression` is missing.")),
        }
    }

    pub fn eval_lazy(
        &self,
        vars: &VarMap,
        loopback: LoopbackResult,
        error: LoopbackError,
    ) -> Result<()> {
        let index = self.0;
        let vars = vars.clone();

        thread::spawn(move || {
            let mut hash_vars = HashMap::new();
            for (s, v) in vars.iter() {
                let v = match v {
                    Value::Null => Expression::Integer(Integer::from(0)),
                    Value::Bool(v) => Expression::Boolean(*v),
                    Value::Integer(v) => Expression::Integer(Integer::from(*v)),
                    Value::Float(v) => {
                        if let Some(v) = Rational::from_float(*v) {
                            Expression::Rational(v, RationalRepresentation::Decimal)
                        } else {
                            error(eyre!("Failed to convert float ({v}) to ratio."));
                            return;
                        }
                    }
                    _ => {
                        error(eyre!(
                            "Failed to convert variable ({s}) to savage Expression."
                        ));
                        return;
                    }
                };

                hash_vars.insert(s.clone(), v);
            }

            let result = unsafe {
                if let Some(expr) = SAVAGE_EXPR.as_ref().unwrap().lock().unwrap().get(&index) {
                    expr.evaluate(hash_vars)
                } else {
                    error(eyre!("Global `savage::Expression` map is missing."));
                    return;
                }
            };

            match result {
                Ok(Expression::Integer(x)) => {
                    let r = x
                        .to_i128()
                        .ok_or_else(|| eyre!("Failed to convert result to i128."));

                    match r {
                        Ok(r) => loopback(Value::Integer(r)),
                        Err(e) => error(e),
                    }
                }
                Ok(Expression::Rational(x, _)) => {
                    let num = x
                        .numer()
                        .to_f64()
                        .ok_or_else(|| eyre!("Failed to convert numerator of result to f64."));
                    let den = x
                        .denom()
                        .to_f64()
                        .ok_or_else(|| eyre!("Failed to convert denominator of result to f64."));

                    match (num, den) {
                        (Ok(num), Ok(den)) => loopback(Value::Float(num / den)),
                        (Err(e), _) => error(e),
                        (_, Err(e)) => error(e),
                    }
                }
                Ok(Expression::Complex(_, _)) => {
                    error(eyre!("Complex results are currently not supported."))
                }
                Err(e) => error(eyre!(
                    "Failed to evaluate mathematical expression ({e:#?})."
                )),
                _ => error(eyre!("`savage::Expression` is missing.")),
            }
        });

        Ok(())
    }
}

impl Drop for Evaluator {
    fn drop(&mut self) {
        unsafe {
            SAVAGE_EXPR
                .as_ref()
                .unwrap()
                .lock()
                .unwrap()
                .remove(&self.0);
        }
    }
}