scheme4r 0.2.3

Scheme interpreter for rust
Documentation
use super::*;

pub(super) fn install(env: &mut Environment) {
    define_builtin(env, "bytevector?", is_bytevector);
    define_builtin(env, "bytevector", bytevector);
    define_builtin(env, "make-bytevector", make_bytevector);
    define_builtin(env, "bytevector-length", bytevector_length);
    define_builtin(env, "bytevector-u8-ref", bytevector_u8_ref);
    define_builtin(env, "bytevector-u8-set!", bytevector_u8_set);
    define_builtin(env, "bytevector-copy", bytevector_copy);
    define_builtin(env, "bytevector-copy!", bytevector_copy_in_place);
    define_builtin(env, "bytevector-append", bytevector_append);
    define_builtin(env, "utf8->string", utf8_to_string);
    define_builtin(env, "string->utf8", string_to_utf8);
}

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

fn bytevector(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    let mut bytes = Vec::with_capacity(args.len());
    for value in args {
        bytes.push(expect_byte("bytevector", value)?);
    }
    Ok(Value::bytevector(bytes))
}

fn make_bytevector(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    if args.is_empty() || args.len() > 2 {
        return Err(SchemeError::arity(
            "'make-bytevector' expects 1 or 2 arguments",
        ));
    }
    let len = expect_index("make-bytevector", &args[0])?;
    let fill = match args.get(1) {
        Some(value) => expect_byte("make-bytevector", value)?,
        None => 0,
    };
    Ok(Value::bytevector(vec![fill; len]))
}

fn bytevector_length(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("bytevector-length", args, 1)?;
    let bytes = expect_bytevector("bytevector-length", &args[0])?;
    let len = bytes.borrow().len() as i64;
    Ok(Value::Number(len))
}

fn bytevector_u8_ref(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("bytevector-u8-ref", args, 2)?;
    let bytes = expect_bytevector("bytevector-u8-ref", &args[0])?;
    let index = expect_index("bytevector-u8-ref", &args[1])?;
    let value = bytes
        .borrow()
        .get(index)
        .copied()
        .map(|value| Value::Number(i64::from(value)))
        .ok_or_else(|| SchemeError::runtime("'bytevector-u8-ref' index out of range"))?;
    Ok(value)
}

fn bytevector_u8_set(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    expect_arity("bytevector-u8-set!", args, 3)?;
    let bytes = expect_bytevector("bytevector-u8-set!", &args[0])?;
    let index = expect_index("bytevector-u8-set!", &args[1])?;
    let value = expect_byte("bytevector-u8-set!", &args[2])?;
    let mut bytes = bytes.borrow_mut();
    let slot = bytes
        .get_mut(index)
        .ok_or_else(|| SchemeError::runtime("'bytevector-u8-set!' index out of range"))?;
    *slot = value;
    Ok(Value::Unspecified)
}

fn bytevector_copy(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    if args.is_empty() || args.len() > 3 {
        return Err(SchemeError::arity(
            "'bytevector-copy' expects 1 to 3 arguments",
        ));
    }

    let bytes = expect_bytevector("bytevector-copy", &args[0])?;
    let copied = {
        let bytes_ref = bytes.borrow();
        let (start, end) =
            parse_range("bytevector-copy", bytes_ref.len(), args.get(1), args.get(2))?;
        bytes_ref[start..end].to_vec()
    };
    Ok(Value::bytevector(copied))
}

fn bytevector_copy_in_place(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    if args.len() < 3 || args.len() > 5 {
        return Err(SchemeError::arity(
            "'bytevector-copy!' expects 3 to 5 arguments",
        ));
    }

    let target = expect_bytevector("bytevector-copy!", &args[0])?;
    let at = expect_index("bytevector-copy!", &args[1])?;
    let source = expect_bytevector("bytevector-copy!", &args[2])?;
    let copied = {
        let source_ref = source.borrow();
        let (start, end) = parse_range(
            "bytevector-copy!",
            source_ref.len(),
            args.get(3),
            args.get(4),
        )?;
        source_ref[start..end].to_vec()
    };

    let mut target_ref = target.borrow_mut();
    if at > target_ref.len() || copied.len() > target_ref.len().saturating_sub(at) {
        return Err(SchemeError::runtime(
            "'bytevector-copy!' range out of bounds",
        ));
    }

    for (offset, value) in copied.into_iter().enumerate() {
        target_ref[at + offset] = value;
    }
    Ok(Value::Unspecified)
}

fn bytevector_append(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    let mut bytes = Vec::new();
    for value in args {
        let bytevector = expect_bytevector("bytevector-append", value)?;
        bytes.extend(bytevector.borrow().iter().copied());
    }
    Ok(Value::bytevector(bytes))
}

fn utf8_to_string(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    if args.is_empty() || args.len() > 3 {
        return Err(SchemeError::arity(
            "'utf8->string' expects 1 to 3 arguments",
        ));
    }

    let bytes = expect_bytevector("utf8->string", &args[0])?;
    let text = {
        let bytes_ref = bytes.borrow();
        let (start, end) = parse_range("utf8->string", bytes_ref.len(), args.get(1), args.get(2))?;
        String::from_utf8(bytes_ref[start..end].to_vec())
            .map_err(|_| SchemeError::runtime("'utf8->string' expected valid UTF-8 bytes"))?
    };
    Ok(Value::string(text))
}

fn string_to_utf8(_: &Engine, args: &[Value]) -> Result<Value, SchemeError> {
    if args.is_empty() || args.len() > 3 {
        return Err(SchemeError::arity(
            "'string->utf8' expects 1 to 3 arguments",
        ));
    }

    let text = expect_string("string->utf8", &args[0])?;
    let chars = text.chars().collect::<Vec<_>>();
    let (start, end) = parse_range("string->utf8", chars.len(), args.get(1), args.get(2))?;
    let slice = chars[start..end].iter().collect::<String>();
    Ok(Value::bytevector(slice.into_bytes()))
}