use std::sync::Arc;
use cljrs_gc::GcPtr;
use cljrs_value::{Arity, PersistentVector, Value, ValueError, ValueResult};
use crate::register_fns;
pub fn register(globals: &Arc<cljrs_eval::GlobalEnv>, ns: &str) {
register_fns!(
globals,
ns,
[
("upper-case", Arity::Fixed(1), upper_case),
("lower-case", Arity::Fixed(1), lower_case),
("capitalize", Arity::Fixed(1), capitalize),
("trim", Arity::Fixed(1), trim),
("triml", Arity::Fixed(1), triml),
("trimr", Arity::Fixed(1), trimr),
("trim-newline", Arity::Fixed(1), trim_newline),
("blank?", Arity::Fixed(1), blank_q),
("starts-with?", Arity::Fixed(2), starts_with_q),
("ends-with?", Arity::Fixed(2), ends_with_q),
("includes?", Arity::Fixed(2), includes_q),
("replace", Arity::Fixed(3), replace),
("replace-first", Arity::Fixed(3), replace_first),
("split", Arity::Variadic { min: 2 }, split),
("split-lines", Arity::Fixed(1), split_lines),
("join", Arity::Variadic { min: 1 }, join),
("index-of", Arity::Variadic { min: 2 }, index_of),
("last-index-of", Arity::Variadic { min: 2 }, last_index_of),
("reverse", Arity::Fixed(1), string_reverse),
("escape", Arity::Fixed(2), string_escape),
]
);
}
fn get_str(v: &Value) -> ValueResult<Arc<str>> {
match v {
Value::Str(s) => Ok(s.get().clone().into()),
Value::Keyword(k) => Ok(Arc::from(format!(":{}", k.get().full_name()))),
Value::Symbol(s) => {
let sym = s.get();
Ok(Arc::from(match &sym.namespace {
Some(ns) => format!("{}/{}", ns, sym.name),
None => sym.name.as_ref().to_string(),
}))
}
Value::Long(n) => Ok(Arc::from(n.to_string())),
Value::Double(f) => Ok(Arc::from(f.to_string())),
Value::Char(c) => Ok(Arc::from(c.to_string())),
Value::Bool(b) => Ok(Arc::from(b.to_string())),
Value::Nil => Err(ValueError::WrongType {
expected: "string",
got: "nil".to_string(),
}),
other => Err(ValueError::WrongType {
expected: "string",
got: other.type_name().to_string(),
}),
}
}
fn get_strict_str(v: &Value) -> ValueResult<Arc<str>> {
match v {
Value::Str(s) => Ok(s.get().clone().into()),
other => Err(ValueError::WrongType {
expected: "string",
got: other.type_name().to_string(),
}),
}
}
fn make_str(s: String) -> Value {
Value::Str(GcPtr::new(s))
}
fn upper_case(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
Ok(make_str(s.to_uppercase()))
}
fn lower_case(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
Ok(make_str(s.to_lowercase()))
}
fn capitalize(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
let mut chars = s.chars();
let result = match chars.next() {
None => String::new(),
Some(first) => {
let upper: String = first.to_uppercase().collect();
upper + &chars.as_str().to_lowercase()
}
};
Ok(make_str(result))
}
fn trim(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
Ok(make_str(s.trim().to_string()))
}
fn triml(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
Ok(make_str(s.trim_start().to_string()))
}
fn trimr(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
Ok(make_str(s.trim_end().to_string()))
}
fn trim_newline(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
let result = s.trim_end_matches(['\n', '\r']);
Ok(make_str(result.to_string()))
}
fn is_java_whitespace(c: char) -> bool {
matches!(
c,
' ' | '\t'
| '\n'
| '\r'
| '\x0B'
| '\x0C'
| '\u{1C}'
| '\u{1D}'
| '\u{1E}'
| '\u{1F}'
| '\u{2028}'
| '\u{2029}'
)
}
fn blank_q(args: &[Value]) -> ValueResult<Value> {
match &args[0] {
Value::Nil => Ok(Value::Bool(true)),
Value::Str(s) => Ok(Value::Bool(s.get().chars().all(is_java_whitespace))),
other => Err(ValueError::WrongType {
expected: "string or nil",
got: other.type_name().to_string(),
}),
}
}
fn starts_with_q(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
let prefix = get_strict_str(&args[1])?;
Ok(Value::Bool(s.starts_with(prefix.as_ref())))
}
fn ends_with_q(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
let suffix = get_strict_str(&args[1])?;
Ok(Value::Bool(s.ends_with(suffix.as_ref())))
}
fn includes_q(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
let needle = get_str(&args[1])?;
Ok(Value::Bool(s.contains(needle.as_ref())))
}
fn replace(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
let from = get_str(&args[1])?;
let to = get_str(&args[2])?;
Ok(make_str(s.replace(from.as_ref(), to.as_ref())))
}
fn replace_first(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
let from = get_str(&args[1])?;
let to = get_str(&args[2])?;
Ok(make_str(s.replacen(from.as_ref(), to.as_ref(), 1)))
}
fn split(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
let limit = if args.len() >= 3 {
match &args[2] {
Value::Long(n) => Some(*n as usize),
other => {
return Err(ValueError::WrongType {
expected: "integer limit",
got: other.type_name().to_string(),
});
}
}
} else {
None
};
let parts: Vec<Value> = match &args[1] {
Value::Pattern(re) => {
let re = re.get();
match limit {
Some(n) => re
.splitn(s.as_ref(), n)
.map(|p| make_str(p.to_string()))
.collect(),
None => re
.split(s.as_ref())
.map(|p| make_str(p.to_string()))
.collect(),
}
}
_ => {
let delim = get_str(&args[1])?;
match limit {
Some(n) => s
.splitn(n, delim.as_ref())
.map(|p| make_str(p.to_string()))
.collect(),
None => s
.split(delim.as_ref())
.map(|p| make_str(p.to_string()))
.collect(),
}
}
};
Ok(Value::Vector(GcPtr::new(PersistentVector::from_iter(
parts,
))))
}
fn split_lines(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
let parts: Vec<Value> = s.lines().map(|l| make_str(l.to_string())).collect();
Ok(Value::Vector(GcPtr::new(PersistentVector::from_iter(
parts,
))))
}
fn join(args: &[Value]) -> ValueResult<Value> {
let (sep, coll) = if args.len() == 1 {
("".to_string(), &args[0])
} else {
let s = get_str(&args[0])?;
(s.to_string(), &args[1])
};
let items = cljrs_interp::destructure::value_to_seq_vec(coll);
let result = items
.iter()
.map(|v| match v {
Value::Str(s) => s.get().clone(),
other => format!("{other}"),
})
.collect::<Vec<_>>()
.join(&sep);
Ok(make_str(result))
}
fn index_of(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
let needle = get_str(&args[1])?;
let from = if args.len() >= 3 {
match &args[2] {
Value::Long(n) => *n as usize,
_ => 0,
}
} else {
0
};
let haystack = if from == 0 {
s.as_ref()
} else {
&s[from.min(s.len())..]
};
match haystack.find(needle.as_ref()) {
Some(idx) => Ok(Value::Long((idx + from) as i64)),
None => Ok(Value::Nil),
}
}
fn last_index_of(args: &[Value]) -> ValueResult<Value> {
let s = get_str(&args[0])?;
let needle = get_str(&args[1])?;
let haystack = if args.len() >= 3 {
match &args[2] {
Value::Long(n) => &s[..(*n as usize).min(s.len())],
_ => s.as_ref(),
}
} else {
s.as_ref()
};
match haystack.rfind(needle.as_ref()) {
Some(idx) => Ok(Value::Long(idx as i64)),
None => Ok(Value::Nil),
}
}
fn string_reverse(args: &[Value]) -> ValueResult<Value> {
let s = get_strict_str(&args[0])?;
Ok(make_str(s.chars().rev().collect()))
}
fn string_escape(args: &[Value]) -> ValueResult<Value> {
let s = get_strict_str(&args[0])?;
let cmap = &args[1];
let mut result = String::with_capacity(s.len());
for ch in s.chars() {
let key = Value::Char(ch);
let replacement = match cmap {
Value::Map(m) => m.get(&key),
_ => None,
};
match replacement {
Some(v) => match v {
Value::Str(rs) => result.push_str(rs.get()),
other => result.push_str(&format!("{other}")),
},
None => result.push(ch),
}
}
Ok(make_str(result))
}