use bock_interp::{BockString, BuiltinRegistry, RuntimeError, TypeTag, Value};
pub fn register(registry: &mut BuiltinRegistry) {
registry.register(TypeTag::Char, "compare", char_compare);
registry.register(TypeTag::Char, "equals", char_equals);
registry.register(TypeTag::Char, "hash_code", char_hash_code);
registry.register(TypeTag::Char, "display", char_display);
registry.register(TypeTag::Char, "to_upper", char_to_upper);
registry.register(TypeTag::Char, "to_lower", char_to_lower);
registry.register(TypeTag::Char, "is_alpha", char_is_alpha);
registry.register(TypeTag::Char, "is_digit", char_is_digit);
registry.register(TypeTag::Char, "is_whitespace", char_is_whitespace);
registry.register(TypeTag::Char, "to_int", char_to_int);
}
fn expect_char(args: &[Value], pos: usize, method: &str) -> Result<char, RuntimeError> {
match args.get(pos) {
Some(Value::Char(c)) => Ok(*c),
Some(other) => Err(RuntimeError::TypeError(format!(
"Char.{method} expects Char, got {other}"
))),
None => Err(RuntimeError::ArityMismatch {
expected: pos + 1,
got: args.len(),
}),
}
}
fn char_compare(args: &[Value]) -> Result<Value, RuntimeError> {
let a = expect_char(args, 0, "compare")?;
let b = expect_char(args, 1, "compare")?;
Ok(Value::Int(a.cmp(&b) as i64))
}
fn char_equals(args: &[Value]) -> Result<Value, RuntimeError> {
let a = expect_char(args, 0, "equals")?;
let b = expect_char(args, 1, "equals")?;
Ok(Value::Bool(a == b))
}
fn char_hash_code(args: &[Value]) -> Result<Value, RuntimeError> {
use std::hash::{Hash, Hasher};
let a = expect_char(args, 0, "hash_code")?;
let mut hasher = std::collections::hash_map::DefaultHasher::new();
a.hash(&mut hasher);
Ok(Value::Int(hasher.finish() as i64))
}
fn char_display(args: &[Value]) -> Result<Value, RuntimeError> {
let a = expect_char(args, 0, "display")?;
Ok(Value::String(BockString::new(a.to_string())))
}
fn char_to_upper(args: &[Value]) -> Result<Value, RuntimeError> {
let c = expect_char(args, 0, "to_upper")?;
let upper: String = c.to_uppercase().collect();
if upper.chars().count() == 1 {
Ok(Value::Char(
upper
.chars()
.next()
.expect("uppercase always yields at least one char"),
))
} else {
Ok(Value::String(BockString::new(upper)))
}
}
fn char_to_lower(args: &[Value]) -> Result<Value, RuntimeError> {
let c = expect_char(args, 0, "to_lower")?;
let lower: String = c.to_lowercase().collect();
if lower.chars().count() == 1 {
Ok(Value::Char(
lower
.chars()
.next()
.expect("lowercase always yields at least one char"),
))
} else {
Ok(Value::String(BockString::new(lower)))
}
}
fn char_is_alpha(args: &[Value]) -> Result<Value, RuntimeError> {
let c = expect_char(args, 0, "is_alpha")?;
Ok(Value::Bool(c.is_alphabetic()))
}
fn char_is_digit(args: &[Value]) -> Result<Value, RuntimeError> {
let c = expect_char(args, 0, "is_digit")?;
Ok(Value::Bool(c.is_ascii_digit()))
}
fn char_is_whitespace(args: &[Value]) -> Result<Value, RuntimeError> {
let c = expect_char(args, 0, "is_whitespace")?;
Ok(Value::Bool(c.is_whitespace()))
}
fn char_to_int(args: &[Value]) -> Result<Value, RuntimeError> {
let c = expect_char(args, 0, "to_int")?;
Ok(Value::Int(c as i64))
}
#[cfg(test)]
mod tests {
use super::*;
fn reg() -> BuiltinRegistry {
let mut r = BuiltinRegistry::new();
register(&mut r);
r
}
#[test]
fn compare_less() {
let r = reg();
let result = r.call(
TypeTag::Char,
"compare",
&[Value::Char('a'), Value::Char('z')],
);
assert_eq!(result.unwrap().unwrap(), Value::Int(-1));
}
#[test]
fn equals_true() {
let r = reg();
let result = r.call(
TypeTag::Char,
"equals",
&[Value::Char('x'), Value::Char('x')],
);
assert_eq!(result.unwrap().unwrap(), Value::Bool(true));
}
#[test]
fn display_char() {
let r = reg();
let result = r.call(TypeTag::Char, "display", &[Value::Char('A')]);
assert_eq!(
result.unwrap().unwrap(),
Value::String(BockString::new("A"))
);
}
#[test]
fn to_upper_ok() {
let r = reg();
let result = r.call(TypeTag::Char, "to_upper", &[Value::Char('a')]);
assert_eq!(result.unwrap().unwrap(), Value::Char('A'));
}
#[test]
fn to_lower_ok() {
let r = reg();
let result = r.call(TypeTag::Char, "to_lower", &[Value::Char('A')]);
assert_eq!(result.unwrap().unwrap(), Value::Char('a'));
}
#[test]
fn is_alpha_true() {
let r = reg();
let result = r.call(TypeTag::Char, "is_alpha", &[Value::Char('x')]);
assert_eq!(result.unwrap().unwrap(), Value::Bool(true));
}
#[test]
fn is_alpha_false() {
let r = reg();
let result = r.call(TypeTag::Char, "is_alpha", &[Value::Char('5')]);
assert_eq!(result.unwrap().unwrap(), Value::Bool(false));
}
#[test]
fn is_digit_true() {
let r = reg();
let result = r.call(TypeTag::Char, "is_digit", &[Value::Char('5')]);
assert_eq!(result.unwrap().unwrap(), Value::Bool(true));
}
#[test]
fn is_whitespace_true() {
let r = reg();
let result = r.call(TypeTag::Char, "is_whitespace", &[Value::Char(' ')]);
assert_eq!(result.unwrap().unwrap(), Value::Bool(true));
}
#[test]
fn to_int_ok() {
let r = reg();
let result = r.call(TypeTag::Char, "to_int", &[Value::Char('A')]);
assert_eq!(result.unwrap().unwrap(), Value::Int(65));
}
#[test]
fn hash_code_deterministic() {
let r = reg();
let h1 = r
.call(TypeTag::Char, "hash_code", &[Value::Char('x')])
.unwrap()
.unwrap();
let h2 = r
.call(TypeTag::Char, "hash_code", &[Value::Char('x')])
.unwrap()
.unwrap();
assert_eq!(h1, h2);
}
}