scheme4r 0.2.3

Scheme interpreter for rust
Documentation
use super::*;

pub(super) fn install(env: &mut Environment) {
    define_builtin(env, "number?", is_number);
    define_builtin(env, "+", add);
    define_builtin(env, "-", subtract);
    define_builtin(env, "*", multiply);
    define_builtin(env, "/", divide);
    define_builtin(env, "abs", abs);
    define_builtin(env, "zero?", is_zero);
    define_builtin(env, "positive?", is_positive);
    define_builtin(env, "negative?", is_negative);
    define_builtin(env, "odd?", is_odd);
    define_builtin(env, "even?", is_even);
    define_builtin(env, "max", max);
    define_builtin(env, "min", min);
    define_builtin(env, "quotient", quotient);
    define_builtin(env, "remainder", remainder);
    define_builtin(env, "modulo", modulo);
    define_builtin(env, "gcd", gcd);
    define_builtin(env, "lcm", lcm);
    define_builtin(env, "=", numeric_eq);
    define_builtin(env, "<", numeric_lt);
    define_builtin(env, ">", numeric_gt);
    define_builtin(env, "<=", numeric_lte);
    define_builtin(env, ">=", numeric_gte);
    define_builtin(env, "number->string", number_to_string);
    define_builtin(env, "string->number", string_to_number);
}

fn is_number(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("number?", args, 1)?;
    Ok(Value::Boolean(matches!(args[0], Value::Number(_))))
}

fn add(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    let mut sum = 0_i64;
    for value in args {
        sum += expect_number("+", value)?;
    }
    Ok(Value::Number(sum))
}

fn subtract(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    let (first, rest) = args
        .split_first()
        .ok_or_else(|| SchemeError::arity("'-' expects at least 1 argument"))?;
    let mut total = expect_number("-", first)?;
    if rest.is_empty() {
        return Ok(Value::Number(-total));
    }
    for value in rest {
        total -= expect_number("-", value)?;
    }
    Ok(Value::Number(total))
}

fn multiply(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    let mut product = 1_i64;
    for value in args {
        product *= expect_number("*", value)?;
    }
    Ok(Value::Number(product))
}

fn divide(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    if args.len() < 2 {
        return Err(SchemeError::arity(
            "'/' in the minimal kernel expects at least 2 arguments",
        ));
    }
    let mut total = expect_number("/", &args[0])?;
    for value in &args[1..] {
        let divisor = expect_number("/", value)?;
        if divisor == 0 {
            return Err(SchemeError::runtime("division by zero"));
        }
        total /= divisor;
    }
    Ok(Value::Number(total))
}

fn abs(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("abs", args, 1)?;
    let number = expect_number("abs", &args[0])?;
    Ok(Value::Number(checked_abs("abs", number)?))
}

fn is_zero(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("zero?", args, 1)?;
    Ok(Value::Boolean(expect_number("zero?", &args[0])? == 0))
}

fn is_positive(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("positive?", args, 1)?;
    Ok(Value::Boolean(expect_number("positive?", &args[0])? > 0))
}

fn is_negative(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("negative?", args, 1)?;
    Ok(Value::Boolean(expect_number("negative?", &args[0])? < 0))
}

fn is_odd(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("odd?", args, 1)?;
    Ok(Value::Boolean(expect_number("odd?", &args[0])? % 2 != 0))
}

fn is_even(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("even?", args, 1)?;
    Ok(Value::Boolean(expect_number("even?", &args[0])? % 2 == 0))
}

fn max(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    let (first, rest) = args
        .split_first()
        .ok_or_else(|| SchemeError::arity("'max' expects at least 1 argument"))?;
    let mut current = expect_number("max", first)?;
    for value in rest {
        current = current.max(expect_number("max", value)?);
    }
    Ok(Value::Number(current))
}

fn min(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    let (first, rest) = args
        .split_first()
        .ok_or_else(|| SchemeError::arity("'min' expects at least 1 argument"))?;
    let mut current = expect_number("min", first)?;
    for value in rest {
        current = current.min(expect_number("min", value)?);
    }
    Ok(Value::Number(current))
}

fn quotient(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("quotient", args, 2)?;
    let dividend = expect_number("quotient", &args[0])?;
    let divisor = expect_number("quotient", &args[1])?;
    if divisor == 0 {
        return Err(SchemeError::runtime("division by zero"));
    }
    Ok(Value::Number(dividend / divisor))
}

fn remainder(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("remainder", args, 2)?;
    let dividend = expect_number("remainder", &args[0])?;
    let divisor = expect_number("remainder", &args[1])?;
    if divisor == 0 {
        return Err(SchemeError::runtime("division by zero"));
    }
    Ok(Value::Number(dividend % divisor))
}

fn modulo(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("modulo", args, 2)?;
    let dividend = expect_number("modulo", &args[0])?;
    let divisor = expect_number("modulo", &args[1])?;
    if divisor == 0 {
        return Err(SchemeError::runtime("division by zero"));
    }

    let remainder = dividend % divisor;
    let modulo =
        if remainder == 0 || (remainder > 0 && divisor > 0) || (remainder < 0 && divisor < 0) {
            remainder
        } else {
            remainder + divisor
        };
    Ok(Value::Number(modulo))
}

fn gcd(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    let mut current = 0_i64;
    for value in args {
        let number = expect_number("gcd", value)?;
        current = gcd_pair(current, number)?;
    }
    Ok(Value::Number(current))
}

fn lcm(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    let mut current = 1_i64;
    for value in args {
        let number = expect_number("lcm", value)?;
        current = lcm_pair(current, number)?;
    }
    Ok(Value::Number(current))
}

fn numeric_eq(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    Ok(Value::Boolean(compare_chain("=", args, |left, right| {
        left == right
    })?))
}

fn numeric_lt(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    Ok(Value::Boolean(compare_chain("<", args, |left, right| {
        left < right
    })?))
}

fn numeric_gt(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    Ok(Value::Boolean(compare_chain(">", args, |left, right| {
        left > right
    })?))
}

fn numeric_lte(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    Ok(Value::Boolean(compare_chain("<=", args, |left, right| {
        left <= right
    })?))
}

fn numeric_gte(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    Ok(Value::Boolean(compare_chain(">=", args, |left, right| {
        left >= right
    })?))
}

fn number_to_string(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("number->string", args, 1)?;
    let number = expect_number("number->string", &args[0])?;
    Ok(Value::string(number.to_string()))
}

fn string_to_number(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("string->number", args, 1)?;
    let text = expect_string("string->number", &args[0])?;
    match text.parse::<i64>() {
        Ok(number) => Ok(Value::Number(number)),
        Err(_) => Ok(Value::Boolean(false)),
    }
}

fn checked_abs(name: &str, value: i64) -> Result<i64, SchemeError> {
    value
        .checked_abs()
        .ok_or_else(|| SchemeError::runtime(format!("'{name}' integer magnitude overflow")))
}

fn gcd_pair(left: i64, right: i64) -> Result<i64, SchemeError> {
    let mut a = checked_abs("gcd", left)?;
    let mut b = checked_abs("gcd", right)?;
    while b != 0 {
        let remainder = a % b;
        a = b;
        b = remainder;
    }
    Ok(a)
}

fn lcm_pair(left: i64, right: i64) -> Result<i64, SchemeError> {
    if left == 0 || right == 0 {
        return Ok(0);
    }

    let gcd = gcd_pair(left, right)?;
    let quotient = left / gcd;
    let product = quotient
        .checked_mul(right)
        .ok_or_else(|| SchemeError::runtime("'lcm' integer overflow"))?;
    checked_abs("lcm", product)
}