use crate::diagnostics::{Error as DiagnosticError, Result};
use crate::eval::value::{Value, PrimitiveProcedure, PrimitiveImpl, ThreadSafeEnvironment};
use crate::effects::Effect;
use crate::utils::intern_symbol;
use std::sync::Arc;
pub fn create_character_bindings(env: &Arc<ThreadSafeEnvironment>) {
bind_character_predicates(env);
bind_character_comparison(env);
bind_character_conversion(env);
bind_character_case_operations(env);
}
fn bind_character_predicates(env: &Arc<ThreadSafeEnvironment>) {
env.define("char?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_p),
effects: vec![Effect::Pure],
})));
env.define("char-alphabetic?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-alphabetic?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_alphabetic_p),
effects: vec![Effect::Pure],
})));
env.define("char-numeric?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-numeric?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_numeric_p),
effects: vec![Effect::Pure],
})));
env.define("char-whitespace?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-whitespace?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_whitespace_p),
effects: vec![Effect::Pure],
})));
env.define("char-upper-case?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-upper-case?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_upper_case_p),
effects: vec![Effect::Pure],
})));
env.define("char-lower-case?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-lower-case?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_lower_case_p),
effects: vec![Effect::Pure],
})));
env.define("char-title-case?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-title-case?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_title_case_p),
effects: vec![Effect::Pure],
})));
env.define("char-general-category".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-general-category".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_general_category),
effects: vec![Effect::Pure],
})));
}
fn bind_character_comparison(env: &Arc<ThreadSafeEnvironment>) {
env.define("char=?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char=?".to_string(),
arity_min: 2,
arity_max: None,
implementation: PrimitiveImpl::RustFn(primitive_char_equal),
effects: vec![Effect::Pure],
})));
env.define("char<?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char<?".to_string(),
arity_min: 2,
arity_max: None,
implementation: PrimitiveImpl::RustFn(primitive_char_less),
effects: vec![Effect::Pure],
})));
env.define("char>?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char>?".to_string(),
arity_min: 2,
arity_max: None,
implementation: PrimitiveImpl::RustFn(primitive_char_greater),
effects: vec![Effect::Pure],
})));
env.define("char<=?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char<=?".to_string(),
arity_min: 2,
arity_max: None,
implementation: PrimitiveImpl::RustFn(primitive_char_less_equal),
effects: vec![Effect::Pure],
})));
env.define("char>=?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char>=?".to_string(),
arity_min: 2,
arity_max: None,
implementation: PrimitiveImpl::RustFn(primitive_char_greater_equal),
effects: vec![Effect::Pure],
})));
env.define("char-ci=?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-ci=?".to_string(),
arity_min: 2,
arity_max: None,
implementation: PrimitiveImpl::RustFn(primitive_char_ci_equal),
effects: vec![Effect::Pure],
})));
env.define("char-ci<?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-ci<?".to_string(),
arity_min: 2,
arity_max: None,
implementation: PrimitiveImpl::RustFn(primitive_char_ci_less),
effects: vec![Effect::Pure],
})));
env.define("char-ci>?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-ci>?".to_string(),
arity_min: 2,
arity_max: None,
implementation: PrimitiveImpl::RustFn(primitive_char_ci_greater),
effects: vec![Effect::Pure],
})));
env.define("char-ci<=?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-ci<=?".to_string(),
arity_min: 2,
arity_max: None,
implementation: PrimitiveImpl::RustFn(primitive_char_ci_less_equal),
effects: vec![Effect::Pure],
})));
env.define("char-ci>=?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-ci>=?".to_string(),
arity_min: 2,
arity_max: None,
implementation: PrimitiveImpl::RustFn(primitive_char_ci_greater_equal),
effects: vec![Effect::Pure],
})));
}
fn bind_character_conversion(env: &Arc<ThreadSafeEnvironment>) {
env.define("char->integer".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char->integer".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_to_integer),
effects: vec![Effect::Pure],
})));
env.define("integer->char".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "integer->char".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_integer_to_char),
effects: vec![Effect::Pure],
})));
}
fn bind_character_case_operations(env: &Arc<ThreadSafeEnvironment>) {
env.define("char-upcase".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-upcase".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_upcase),
effects: vec![Effect::Pure],
})));
env.define("char-downcase".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-downcase".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_downcase),
effects: vec![Effect::Pure],
})));
env.define("char-foldcase".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-foldcase".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_foldcase),
effects: vec![Effect::Pure],
})));
env.define("char-titlecase".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-titlecase".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_titlecase),
effects: vec![Effect::Pure],
})));
}
fn primitive_char_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("char? expects 1 argument, got {}", args.len()),
None,
)));
}
let is_char = matches!(args[0], Value::Literal(crate::ast::Literal::Character(_)));
Ok(Value::boolean(is_char))
}
fn primitive_char_alphabetic_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("char-alphabetic? expects 1 argument, got {}", args.len()),
None,
)));
}
let ch = extract_character(&args[0], "char-alphabetic?")?;
Ok(Value::boolean(ch.is_alphabetic()))
}
fn primitive_char_numeric_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("char-numeric? expects 1 argument, got {}", args.len()),
None,
)));
}
let ch = extract_character(&args[0], "char-numeric?")?;
Ok(Value::boolean(ch.is_numeric()))
}
fn primitive_char_whitespace_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("char-whitespace? expects 1 argument, got {}", args.len()),
None,
)));
}
let ch = extract_character(&args[0], "char-whitespace?")?;
Ok(Value::boolean(ch.is_whitespace()))
}
fn primitive_char_upper_case_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("char-upper-case? expects 1 argument, got {}", args.len()),
None,
)));
}
let ch = extract_character(&args[0], "char-upper-case?")?;
Ok(Value::boolean(ch.is_uppercase()))
}
fn primitive_char_lower_case_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("char-lower-case? expects 1 argument, got {}", args.len()),
None,
)));
}
let ch = extract_character(&args[0], "char-lower-case?")?;
Ok(Value::boolean(ch.is_lowercase()))
}
fn primitive_char_title_case_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("char-title-case? expects 1 argument, got {}", args.len()),
None,
)));
}
let ch = extract_character(&args[0], "char-title-case?")?;
Ok(Value::boolean(ch.is_uppercase() && ch.is_alphabetic()))
}
fn primitive_char_general_category(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("char-general-category expects 1 argument, got {}", args.len()),
None,
)));
}
let ch = extract_character(&args[0], "char-general-category")?;
let category = if ch.is_alphabetic() {
if ch.is_uppercase() {
"Lu" } else if ch.is_lowercase() {
"Ll" } else {
"Lo" }
} else if ch.is_numeric() {
"Nd" } else if ch.is_whitespace() {
"Zs" } else if ch.is_control() {
"Cc" } else if ch.is_ascii_punctuation() {
"Po" } else {
"Cn" };
Ok(Value::symbol(intern_symbol(category)))
}
fn primitive_char_equal(args: &[Value]) -> Result<Value> {
if args.len() < 2 {
return Err(Box::new(DiagnosticError::runtime_error(
"char=? requires at least 2 arguments".to_string(),
None,
)));
}
let first = extract_character(&args[0], "char=?")?;
for arg in &args[1..] {
let ch = extract_character(arg, "char=?")?;
if first != ch {
return Ok(Value::boolean(false));
}
}
Ok(Value::boolean(true))
}
fn primitive_char_less(args: &[Value]) -> Result<Value> {
if args.len() < 2 {
return Err(Box::new(DiagnosticError::runtime_error(
"char<? requires at least 2 arguments".to_string(),
None,
)));
}
for window in args.windows(2) {
let c1 = extract_character(&window[0], "char<?")?;
let c2 = extract_character(&window[1], "char<?")?;
if c1 >= c2 {
return Ok(Value::boolean(false));
}
}
Ok(Value::boolean(true))
}
fn primitive_char_greater(args: &[Value]) -> Result<Value> {
if args.len() < 2 {
return Err(Box::new(DiagnosticError::runtime_error(
"char>? requires at least 2 arguments".to_string(),
None,
)));
}
for window in args.windows(2) {
let c1 = extract_character(&window[0], "char>?")?;
let c2 = extract_character(&window[1], "char>?")?;
if c1 <= c2 {
return Ok(Value::boolean(false));
}
}
Ok(Value::boolean(true))
}
fn primitive_char_less_equal(args: &[Value]) -> Result<Value> {
if args.len() < 2 {
return Err(Box::new(DiagnosticError::runtime_error(
"char<=? requires at least 2 arguments".to_string(),
None,
)));
}
for window in args.windows(2) {
let c1 = extract_character(&window[0], "char<=?")?;
let c2 = extract_character(&window[1], "char<=?")?;
if c1 > c2 {
return Ok(Value::boolean(false));
}
}
Ok(Value::boolean(true))
}
fn primitive_char_greater_equal(args: &[Value]) -> Result<Value> {
if args.len() < 2 {
return Err(Box::new(DiagnosticError::runtime_error(
"char>=? requires at least 2 arguments".to_string(),
None,
)));
}
for window in args.windows(2) {
let c1 = extract_character(&window[0], "char>=?")?;
let c2 = extract_character(&window[1], "char>=?")?;
if c1 < c2 {
return Ok(Value::boolean(false));
}
}
Ok(Value::boolean(true))
}
fn primitive_char_ci_equal(args: &[Value]) -> Result<Value> {
if args.len() < 2 {
return Err(Box::new(DiagnosticError::runtime_error(
"char-ci=? requires at least 2 arguments".to_string(),
None,
)));
}
let first_ch = extract_character(&args[0], "char-ci=?")?;
let first = first_ch.to_lowercase().next().unwrap_or(first_ch);
for arg in &args[1..] {
let ch = extract_character(arg, "char-ci=?")?;
let ch_lower = ch.to_lowercase().next().unwrap_or(ch);
if first != ch_lower {
return Ok(Value::boolean(false));
}
}
Ok(Value::boolean(true))
}
fn primitive_char_ci_less(args: &[Value]) -> Result<Value> {
if args.len() < 2 {
return Err(Box::new(DiagnosticError::runtime_error(
"char-ci<? requires at least 2 arguments".to_string(),
None,
)));
}
for window in args.windows(2) {
let ch1 = extract_character(&window[0], "char-ci<?")?;
let ch2 = extract_character(&window[1], "char-ci<?")?;
let c1 = ch1.to_lowercase().next().unwrap_or(ch1);
let c2 = ch2.to_lowercase().next().unwrap_or(ch2);
if c1 >= c2 {
return Ok(Value::boolean(false));
}
}
Ok(Value::boolean(true))
}
fn primitive_char_ci_greater(args: &[Value]) -> Result<Value> {
if args.len() < 2 {
return Err(Box::new(DiagnosticError::runtime_error(
"char-ci>? requires at least 2 arguments".to_string(),
None,
)));
}
for window in args.windows(2) {
let ch1 = extract_character(&window[0], "char-ci>?")?;
let ch2 = extract_character(&window[1], "char-ci>?")?;
let c1 = ch1.to_lowercase().next().unwrap_or(ch1);
let c2 = ch2.to_lowercase().next().unwrap_or(ch2);
if c1 <= c2 {
return Ok(Value::boolean(false));
}
}
Ok(Value::boolean(true))
}
fn primitive_char_ci_less_equal(args: &[Value]) -> Result<Value> {
if args.len() < 2 {
return Err(Box::new(DiagnosticError::runtime_error(
"char-ci<=? requires at least 2 arguments".to_string(),
None,
)));
}
for window in args.windows(2) {
let ch1 = extract_character(&window[0], "char-ci<=?")?;
let ch2 = extract_character(&window[1], "char-ci<=?")?;
let c1 = ch1.to_lowercase().next().unwrap_or(ch1);
let c2 = ch2.to_lowercase().next().unwrap_or(ch2);
if c1 > c2 {
return Ok(Value::boolean(false));
}
}
Ok(Value::boolean(true))
}
fn primitive_char_ci_greater_equal(args: &[Value]) -> Result<Value> {
if args.len() < 2 {
return Err(Box::new(DiagnosticError::runtime_error(
"char-ci>=? requires at least 2 arguments".to_string(),
None,
)));
}
for window in args.windows(2) {
let ch1 = extract_character(&window[0], "char-ci>=?")?;
let ch2 = extract_character(&window[1], "char-ci>=?")?;
let c1 = ch1.to_lowercase().next().unwrap_or(ch1);
let c2 = ch2.to_lowercase().next().unwrap_or(ch2);
if c1 < c2 {
return Ok(Value::boolean(false));
}
}
Ok(Value::boolean(true))
}
fn primitive_char_to_integer(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("char->integer expects 1 argument, got {}", args.len()),
None,
)));
}
let ch = extract_character(&args[0], "char->integer")?;
Ok(Value::integer(ch as u32 as i64))
}
fn primitive_integer_to_char(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("integer->char expects 1 argument, got {}", args.len()),
None,
)));
}
let n = args[0].as_integer().ok_or_else(|| {
DiagnosticError::runtime_error(
"integer->char requires an integer argument".to_string(),
None,
)
})?;
if n < 0 || n > char::MAX as u32 as i64 {
return Err(Box::new(DiagnosticError::runtime_error(
"integer->char argument out of character range".to_string(),
None,
)));
}
match char::from_u32(n as u32) {
Some(ch) => Ok(Value::Literal(crate::ast::Literal::Character(ch))),
None => Err(Box::new(DiagnosticError::runtime_error(
"integer->char invalid character code".to_string(),
None,
))),
}
}
fn primitive_char_upcase(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("char-upcase expects 1 argument, got {}", args.len()),
None,
)));
}
let ch = extract_character(&args[0], "char-upcase")?;
let upper_ch = ch.to_uppercase().next().unwrap_or(ch);
Ok(Value::Literal(crate::ast::Literal::Character(upper_ch)))
}
fn primitive_char_downcase(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("char-downcase expects 1 argument, got {}", args.len()),
None,
)));
}
let ch = extract_character(&args[0], "char-downcase")?;
let lower_ch = ch.to_lowercase().next().unwrap_or(ch);
Ok(Value::Literal(crate::ast::Literal::Character(lower_ch)))
}
fn primitive_char_foldcase(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("char-foldcase expects 1 argument, got {}", args.len()),
None,
)));
}
let ch = extract_character(&args[0], "char-foldcase")?;
let folded_ch = ch.to_lowercase().next().unwrap_or(ch);
Ok(Value::Literal(crate::ast::Literal::Character(folded_ch)))
}
fn primitive_char_titlecase(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("char-titlecase expects 1 argument, got {}", args.len()),
None,
)));
}
let ch = extract_character(&args[0], "char-titlecase")?;
Ok(Value::Literal(crate::ast::Literal::Character(ch.to_ascii_uppercase())))
}
fn extract_character(value: &Value, operation: &str) -> Result<char> {
match value {
Value::Literal(crate::ast::Literal::Character(c)) => Ok(*c),
_ => Err(Box::new(DiagnosticError::runtime_error(
format!("{operation} requires character arguments"),
None,
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_char_predicates() {
let char_a = Value::Literal(crate::ast::Literal::Character('a'));
let result = primitive_char_p(&[char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let not_char = Value::integer(42);
let result = primitive_char_p(&[not_char]).unwrap();
assert_eq!(result, Value::boolean(false));
let result = primitive_char_alphabetic_p(&[char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let char_5 = Value::Literal(crate::ast::Literal::Character('5'));
let result = primitive_char_alphabetic_p(&[char_5.clone()]).unwrap();
assert_eq!(result, Value::boolean(false));
let result = primitive_char_numeric_p(&[char_5.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_numeric_p(&[char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(false));
let char_space = Value::Literal(crate::ast::Literal::Character(' '));
let result = primitive_char_whitespace_p(&[char_space]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_whitespace_p(&[char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(false));
let char_A = Value::Literal(crate::ast::Literal::Character('A'));
let result = primitive_char_upper_case_p(&[char_A.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_upper_case_p(&[char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(false));
let result = primitive_char_lower_case_p(&[char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_lower_case_p(&[char_A]).unwrap();
assert_eq!(result, Value::boolean(false));
}
#[test]
fn test_char_comparison() {
let char_a = Value::Literal(crate::ast::Literal::Character('a'));
let char_b = Value::Literal(crate::ast::Literal::Character('b'));
let char_c = Value::Literal(crate::ast::Literal::Character('c'));
let result = primitive_char_equal(&[char_a.clone(), char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_equal(&[char_a.clone(), char_b.clone()]).unwrap();
assert_eq!(result, Value::boolean(false));
let result = primitive_char_equal(&[char_a.clone(), char_a.clone(), char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_equal(&[char_a.clone(), char_a.clone(), char_b.clone()]).unwrap();
assert_eq!(result, Value::boolean(false));
let result = primitive_char_less(&[char_a.clone(), char_b.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_less(&[char_b.clone(), char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(false));
let result = primitive_char_less(&[char_a.clone(), char_b.clone(), char_c.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_less(&[char_a.clone(), char_c.clone(), char_b.clone()]).unwrap();
assert_eq!(result, Value::boolean(false));
let result = primitive_char_greater(&[char_b.clone(), char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_greater(&[char_a.clone(), char_b.clone()]).unwrap();
assert_eq!(result, Value::boolean(false));
let result = primitive_char_less_equal(&[char_a.clone(), char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_less_equal(&[char_a.clone(), char_b.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_less_equal(&[char_b.clone(), char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(false));
let result = primitive_char_greater_equal(&[char_a.clone(), char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_greater_equal(&[char_b.clone(), char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_greater_equal(&[char_a, char_b]).unwrap();
assert_eq!(result, Value::boolean(false));
}
#[test]
fn test_char_ci_comparison() {
let char_a = Value::Literal(crate::ast::Literal::Character('a'));
let char_A = Value::Literal(crate::ast::Literal::Character('A'));
let char_b = Value::Literal(crate::ast::Literal::Character('b'));
let char_B = Value::Literal(crate::ast::Literal::Character('B'));
let result = primitive_char_ci_equal(&[char_a.clone(), char_A.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_ci_equal(&[char_a.clone(), char_b.clone()]).unwrap();
assert_eq!(result, Value::boolean(false));
let result = primitive_char_ci_less(&[char_a.clone(), char_B.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_ci_less(&[char_B.clone(), char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(false));
let result = primitive_char_ci_greater(&[char_B.clone(), char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_ci_greater(&[char_a.clone(), char_B.clone()]).unwrap();
assert_eq!(result, Value::boolean(false));
let result = primitive_char_ci_less_equal(&[char_a.clone(), char_A.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_ci_less_equal(&[char_a.clone(), char_B.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_ci_greater_equal(&[char_A.clone(), char_a.clone()]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_char_ci_greater_equal(&[char_B, char_a]).unwrap();
assert_eq!(result, Value::boolean(true));
}
#[test]
fn test_char_conversion() {
let char_a = Value::Literal(crate::ast::Literal::Character('a'));
let result = primitive_char_to_integer(&[char_a]).unwrap();
assert_eq!(result, Value::integer(97));
let char_zero = Value::Literal(crate::ast::Literal::Character('0'));
let result = primitive_char_to_integer(&[char_zero]).unwrap();
assert_eq!(result, Value::integer(48));
let int_97 = Value::integer(97);
let result = primitive_integer_to_char(&[int_97]).unwrap();
assert_eq!(result, Value::Literal(crate::ast::Literal::Character('a')));
let int_48 = Value::integer(48);
let result = primitive_integer_to_char(&[int_48]).unwrap();
assert_eq!(result, Value::Literal(crate::ast::Literal::Character('0')));
let char_lambda = Value::Literal(crate::ast::Literal::Character('λ'));
let result = primitive_char_to_integer(&[char_lambda]).unwrap();
assert_eq!(result, Value::integer(955));
let int_955 = Value::integer(955);
let result = primitive_integer_to_char(&[int_955]).unwrap();
assert_eq!(result, Value::Literal(crate::ast::Literal::Character('λ')));
}
#[test]
fn test_char_conversion_errors() {
let negative = Value::integer(-1);
let result = primitive_integer_to_char(&[negative]);
assert!(result.is_err());
let too_large = Value::integer(0x110000); let result = primitive_integer_to_char(&[too_large]);
assert!(result.is_err());
let not_int = Value::string("not-an-integer");
let result = primitive_integer_to_char(&[not_int]);
assert!(result.is_err());
let not_char = Value::integer(42);
let result = primitive_char_to_integer(&[not_char]);
assert!(result.is_err());
}
#[test]
fn test_char_case() {
let char_a = Value::Literal(crate::ast::Literal::Character('a'));
let result = primitive_char_upcase(&[char_a]).unwrap();
assert_eq!(result, Value::Literal(crate::ast::Literal::Character('A')));
let char_A = Value::Literal(crate::ast::Literal::Character('A'));
let result = primitive_char_downcase(&[char_A]).unwrap();
assert_eq!(result, Value::Literal(crate::ast::Literal::Character('a')));
let char_5 = Value::Literal(crate::ast::Literal::Character('5'));
let result = primitive_char_upcase(&[char_5.clone()]).unwrap();
assert_eq!(result, char_5);
let result = primitive_char_downcase(&[char_5]).unwrap();
assert_eq!(result, Value::Literal(crate::ast::Literal::Character('5')));
let char_A_fold = Value::Literal(crate::ast::Literal::Character('A'));
let result = primitive_char_foldcase(&[char_A_fold]).unwrap();
assert_eq!(result, Value::Literal(crate::ast::Literal::Character('a')));
}
#[test]
fn test_unicode_case() {
let char_alpha = Value::Literal(crate::ast::Literal::Character('α')); let result = primitive_char_upcase(&[char_alpha]).unwrap();
assert_eq!(result, Value::Literal(crate::ast::Literal::Character('Α')));
let char_Alpha = Value::Literal(crate::ast::Literal::Character('Α')); let result = primitive_char_downcase(&[char_Alpha]).unwrap();
assert_eq!(result, Value::Literal(crate::ast::Literal::Character('α')));
let char_eszett = Value::Literal(crate::ast::Literal::Character('ß'));
let result = primitive_char_upcase(&[char_eszett.clone()]).unwrap();
assert!(matches!(result, Value::Literal(crate::ast::Literal::Character(_))));
}
#[test]
fn test_char_errors() {
let result = primitive_char_p(&[]);
assert!(result.is_err());
let char_a = Value::Literal(crate::ast::Literal::Character('a'));
let char_b = Value::Literal(crate::ast::Literal::Character('b'));
let result = primitive_char_p(&[char_a.clone(), char_b]);
assert!(result.is_err());
let not_char = Value::integer(42);
let result = primitive_char_equal(&[char_a.clone(), not_char]);
assert!(result.is_err());
let result = primitive_char_equal(&[char_a]);
assert!(result.is_err());
let result = primitive_char_equal(&[]);
assert!(result.is_err());
}
#[test]
fn test_char_general_category() {
let char_a = Value::Literal(crate::ast::Literal::Character('a'));
let result = primitive_char_general_category(&[char_a]).unwrap();
assert_eq!(result, Value::symbol(intern_symbol("Ll")));
let char_A = Value::Literal(crate::ast::Literal::Character('A'));
let result = primitive_char_general_category(&[char_A]).unwrap();
assert_eq!(result, Value::symbol(intern_symbol("Lu")));
let char_5 = Value::Literal(crate::ast::Literal::Character('5'));
let result = primitive_char_general_category(&[char_5]).unwrap();
assert_eq!(result, Value::symbol(intern_symbol("Nd")));
let char_space = Value::Literal(crate::ast::Literal::Character(' '));
let result = primitive_char_general_category(&[char_space]).unwrap();
assert_eq!(result, Value::symbol(intern_symbol("Zs"))); }
}