dntk 3.1.1

Command line's multi-platform interactive calculator, GNU bc wrapper.
Documentation
use std::collections::{BTreeMap, HashMap};
use std::sync::Arc;

use dashu::Decimal;
use rand::{rngs::SmallRng, SeedableRng};

#[derive(Clone, Debug)]
pub struct FunctionDef {
    pub params: Arc<[String]>,
    pub body: Arc<[String]>,
}

#[derive(Clone, Debug)]
pub enum StatementOutcome {
    None,
    Value(Decimal),
    Return(Decimal),
}

impl StatementOutcome {
    pub fn value(value: Decimal) -> Self {
        StatementOutcome::Value(value)
    }

    pub fn ret(value: Decimal) -> Self {
        StatementOutcome::Return(value)
    }
}

#[derive(Debug)]
pub struct Runtime {
    namespaces: Vec<BTreeMap<String, Decimal>>,
    functions: HashMap<String, FunctionDef>,
    scale: u32,
    obase: u32,
    rng: SmallRng,
}

impl Runtime {
    pub fn with_defaults(scale: u32) -> Self {
        let mut namespaces = vec![BTreeMap::new()];
        namespaces[0].insert("scale".to_string(), Decimal::from(scale));
        namespaces[0].insert("obase".to_string(), Decimal::from(10));
        Self {
            namespaces,
            functions: HashMap::new(),
            scale,
            obase: 10,
            rng: SmallRng::seed_from_u64(0x5eed_5eed_5eed_5eed),
        }
    }

    pub fn scale(&self) -> u32 {
        self.scale
    }

    pub fn set_scale(&mut self, scale: u32) {
        self.scale = scale;
        if let Some(scope) = self.namespaces.last_mut() {
            scope.insert("scale".to_string(), Decimal::from(scale));
        }
    }

    pub fn obase(&self) -> u32 {
        self.obase
    }

    pub fn set_obase(&mut self, obase: u32) {
        self.obase = obase;
        if let Some(scope) = self.namespaces.last_mut() {
            scope.insert("obase".to_string(), Decimal::from(obase));
        }
    }

    pub fn rng_mut(&mut self) -> &mut SmallRng {
        &mut self.rng
    }

    pub fn push_scope(&mut self, scope: BTreeMap<String, Decimal>) {
        self.namespaces.push(scope);
    }

    pub fn pop_scope(&mut self) {
        if self.namespaces.len() > 1 {
            self.namespaces.pop();
        }
    }

    pub fn current_scope_mut(&mut self) -> Option<&mut BTreeMap<String, Decimal>> {
        self.namespaces.last_mut()
    }

    pub fn find_scope_mut(&mut self, name: &str) -> Option<&mut BTreeMap<String, Decimal>> {
        self.namespaces
            .iter_mut()
            .rev()
            .find(|scope| scope.contains_key(name))
    }

    pub fn get_variable(&self, name: &str) -> Option<Decimal> {
        self.namespaces
            .iter()
            .rev()
            .find_map(|scope| scope.get(name).cloned())
    }

    pub fn define_function(&mut self, name: String, def: FunctionDef) {
        self.functions.insert(name, def);
    }

    pub fn get_function(&self, name: &str) -> Option<&FunctionDef> {
        self.functions.get(name)
    }

    pub fn reseed_rng(&mut self, seed: u64) {
        self.rng = SmallRng::seed_from_u64(seed);
    }
}