rimu-stdlib 0.2.0

A data structure template system.
Documentation
use std::{cell::RefCell, rc::Rc};

use rimu_eval::call;
use rimu_meta::{Span, Spanned};
use rimu_value::{
    Environment, EvalError, Function, FunctionBody, NativeFunction, SerdeValue, SerdeValueObject,
    SpannedValue, Value,
};
use rust_decimal::prelude::ToPrimitive;

pub fn create_stdlib() -> SerdeValueObject {
    let mut lib = SerdeValueObject::new();
    lib.insert("length".into(), length().into());
    lib.insert("map".into(), map().into());
    lib.insert("range".into(), range().into());
    lib
}

fn empty_env() -> Rc<RefCell<Environment>> {
    Rc::new(RefCell::new(Environment::new()))
}

pub fn length() -> Function {
    let function = |span: Span, args: &[Spanned<Value>]| -> Result<SpannedValue, EvalError> {
        let (arg, arg_span) = &args[0].clone().take();
        let value = match arg {
            Value::List(list) => list.len().into(),
            Value::String(string) => string.len().into(),
            _ => {
                return Err(EvalError::TypeError {
                    span: arg_span.clone(),
                    expected: "list | string".into(),
                    got: arg.clone().into(),
                })
            }
        };
        Ok(Spanned::new(value, span))
    };
    Function {
        args: vec!["arg".into()],
        env: empty_env(),
        body: FunctionBody::Native(NativeFunction::new(function)),
    }
}
pub fn map() -> Function {
    let function = |span: Span, args: &[Spanned<Value>]| -> Result<SpannedValue, EvalError> {
        let (arg, arg_span) = &args[0].clone().take();
        match arg {
            Value::Object(object) => {
                let list_arg = object.get("list").map(|a| a.inner());
                let mapper_arg = object.get("each").map(|a| a.inner());
                match (list_arg, mapper_arg) {
                    (Some(Value::List(list)), Some(Value::Function(mapper))) => map_op(
                        span,
                        MapOptions {
                            list: list.clone(),
                            mapper: mapper.clone(),
                        },
                    ),
                    _ => Err(EvalError::TypeError {
                        span: arg_span.clone(),
                        expected: "{ list: list, each: (item) => next }".into(),
                        got: arg.clone().into(),
                    }),
                }
            }
            _ => Err(EvalError::TypeError {
                span: arg_span.clone(),
                expected: "object".into(),
                got: arg.clone().into(),
            }),
        }
    };

    Function {
        args: vec!["arg".into()],
        env: empty_env(),
        body: FunctionBody::Native(NativeFunction::new(function)),
    }
}

struct MapOptions {
    list: Vec<SpannedValue>,
    mapper: Function,
}

fn map_op(span: Span, options: MapOptions) -> Result<SpannedValue, EvalError> {
    let MapOptions { list, mapper } = options;
    let next_list = list
        .iter()
        .map(|item| call(span.clone(), mapper.clone(), &[item.clone()]))
        .collect::<Result<Vec<SpannedValue>, EvalError>>()?;
    Ok(Spanned::new(Value::List(next_list), span))
}

pub fn range() -> Function {
    let function = |span: Span, args: &[Spanned<Value>]| -> Result<SpannedValue, EvalError> {
        let (arg, arg_span) = &args[0].clone().take();
        match arg {
            Value::Object(object) => {
                let start = object.get("start");
                let end = object.get("end");
                let start = start.map(|a| a.clone().take());
                let end = end.map(|a| a.clone().take());
                match (start, end) {
                    (None, None) => Ok(Spanned::new(Value::List(vec![]), span)),
                    (None, Some((Value::Number(end), end_span))) => {
                        let end = end.to_usize().ok_or_else(|| EvalError::TypeError {
                            span: end_span,
                            expected: "zero or positive integer".into(),
                            got: SerdeValue::Number(end),
                        })?;
                        range_op(span, RangeOptions { start: None, end })
                    }
                    (
                        Some((Value::Number(start), start_span)),
                        Some((Value::Number(end), end_span)),
                    ) => {
                        let start = start.to_usize().ok_or_else(|| EvalError::TypeError {
                            span: start_span,
                            expected: "zero or positive integer".into(),
                            got: SerdeValue::Number(start),
                        })?;
                        let end = end.to_usize().ok_or_else(|| EvalError::TypeError {
                            span: end_span,
                            expected: "zero or positive integer".into(),
                            got: SerdeValue::Number(end),
                        })?;
                        range_op(
                            span,
                            RangeOptions {
                                start: Some(start),
                                end,
                            },
                        )
                    }
                    _ => Err(EvalError::TypeError {
                        span: arg_span.clone(),
                        expected: "{ start?: number, end: number }".into(),
                        got: arg.clone().into(),
                    }),
                }
            }
            _ => Err(EvalError::TypeError {
                span: arg_span.clone(),
                expected: "object".into(),
                got: arg.clone().into(),
            }),
        }
    };

    Function {
        args: vec!["arg".into()],
        env: empty_env(),
        body: FunctionBody::Native(NativeFunction::new(function)),
    }
}

struct RangeOptions {
    start: Option<usize>,
    end: usize,
}

fn range_op(span: Span, options: RangeOptions) -> Result<SpannedValue, EvalError> {
    let RangeOptions { start, end } = options;
    let start = start.unwrap_or(0);
    let list = (start..end).map(Into::into).collect();
    Ok(SerdeValue::List(list).with_span(span))
}