use tan::{
context::Context,
error::Error,
expr::{format_value, Expr},
util::{args::unpack_stringable_arg, module_util::require_module},
};
pub fn string_new(args: &[Expr]) -> Result<Expr, Error> {
let output = args.iter().fold(String::new(), |mut str, x| {
str.push_str(&format_value(x));
str
});
Ok(Expr::String(output))
}
pub fn string_get_length(args: &[Expr]) -> Result<Expr, Error> {
let [this] = args else {
return Err(Error::invalid_arguments(
"`chars` requires `this` argument",
None,
));
};
let Expr::String(s) = this.unpack() else {
return Err(Error::invalid_arguments(
"`this` argument should be a String",
this.range(),
));
};
Ok(Expr::Int(s.len() as i64))
}
pub fn string_is_empty(args: &[Expr]) -> Result<Expr, Error> {
let s = unpack_stringable_arg(args, 0, "s")?;
Ok(Expr::Bool(s.is_empty()))
}
pub fn string_trim(args: &[Expr]) -> Result<Expr, Error> {
let [this] = args else {
return Err(Error::invalid_arguments("requires `this` argument", None));
};
let Expr::String(s) = this.unpack() else {
return Err(Error::invalid_arguments(
"`this` argument should be a String",
this.range(),
));
};
Ok(Expr::string(s.trim()))
}
pub fn string_pop(_args: &[Expr]) -> Result<Expr, Error> {
todo!()
}
pub fn string_slice(args: &[Expr]) -> Result<Expr, Error> {
let [this, start, ..] = args else {
return Err(Error::invalid_arguments(
"`slice` requires `this` and start arguments",
None,
));
};
let Some(s) = this.as_stringable() else {
return Err(Error::invalid_arguments(
"`this` argument should be a String",
this.range(),
));
};
let Expr::Int(start) = start.unpack() else {
return Err(Error::invalid_arguments(
"`start` argument should be an Int",
this.range(),
));
};
let start = *start;
let end = if let Some(end) = args.get(2) {
let Expr::Int(end) = end.unpack() else {
return Err(Error::invalid_arguments(
"`end` argument should be an Int",
this.range(),
));
};
*end
} else {
s.len() as i64
};
let start = start as usize;
let end = if end < 0 {
(s.len() as i64 + end) as usize
} else {
end as usize
};
let string_slice = &s[start..end];
Ok(Expr::string(string_slice))
}
pub fn string_slice_range(args: &[Expr]) -> Result<Expr, Error> {
let [this, start, ..] = args else {
return Err(Error::invalid_arguments(
"`slice` requires `this` and range arguments",
None,
));
};
let Expr::String(s) = this.unpack() else {
return Err(Error::invalid_arguments(
"`this` argument should be a String",
this.range(),
));
};
let Expr::IntRange(start, end, ..) = start.unpack() else {
return Err(Error::invalid_arguments(
"`range` argument should be a Range",
this.range(),
));
};
let start = *start;
let end = *end;
let start = start as usize;
let end = if end < 0 {
(s.len() as i64 + end) as usize
} else {
end as usize
};
let string_slice = &s[start..end];
Ok(Expr::string(string_slice))
}
pub fn string_chars(args: &[Expr]) -> Result<Expr, Error> {
let [this] = args else {
return Err(Error::invalid_arguments(
"`chars` requires `this` argument",
None,
));
};
let Some(this) = this.as_string() else {
return Err(Error::invalid_arguments(
"`this` argument should be a String",
this.range(),
));
};
let mut exprs: Vec<Expr> = Vec::new();
for char in this.chars() {
exprs.push(Expr::Char(char));
}
Ok(Expr::array(exprs))
}
pub fn string_constructor_from_chars(args: &[Expr]) -> Result<Expr, Error> {
let [chars] = args else {
return Err(Error::invalid_arguments("requires `chars` argument", None));
};
let Some(exprs) = chars.as_array() else {
return Err(Error::invalid_arguments(
"`chars` argument should be a (Array Char)",
chars.range(),
));
};
let mut chars: Vec<char> = Vec::new();
for expr in exprs.iter() {
if let Some(c) = expr.as_char() {
chars.push(c);
}
}
let string = String::from_iter(chars);
Ok(Expr::String(string))
}
pub fn char_to_upper_case(args: &[Expr]) -> Result<Expr, Error> {
let [this] = args else {
return Err(Error::invalid_arguments(
"`to-upper-case` requires `this` argument",
None,
));
};
let Expr::Char(this) = this.unpack() else {
return Err(Error::invalid_arguments(
"`this` argument should be a Char",
this.range(),
));
};
let uppercased = this.to_uppercase().next().unwrap();
Ok(Expr::Char(uppercased))
}
pub fn string_to_lower_case(args: &[Expr]) -> Result<Expr, Error> {
let [this] = args else {
return Err(Error::invalid_arguments("requires `this` argument", None));
};
let Some(this) = this.as_string() else {
return Err(Error::invalid_arguments(
"`this` argument should be a String",
this.range(),
));
};
let lowercased = this.to_lowercase();
Ok(Expr::String(lowercased))
}
pub fn string_format(args: &[Expr]) -> Result<Expr, Error> {
let output = args.iter().fold(String::new(), |mut str, x| {
str.push_str(&format_value(x));
str
});
Ok(Expr::String(output))
}
pub fn string_split(args: &[Expr]) -> Result<Expr, Error> {
let string = unpack_stringable_arg(args, 0, "this")?;
let separator = unpack_stringable_arg(args, 1, "separator")?;
let parts: Vec<Expr> = string.split(separator).map(Expr::string).collect();
Ok(Expr::array(parts))
}
pub fn string_contains(args: &[Expr]) -> Result<Expr, Error> {
let [this, string] = args else {
return Err(Error::invalid_arguments(
"`contains` requires `this` and `string` arguments",
None,
));
};
let Some(this) = this.as_string() else {
return Err(Error::invalid_arguments(
"`this` argument should be a String",
this.range(),
));
};
let Some(string) = string.as_string() else {
return Err(Error::invalid_arguments(
"`string` argument should be a String",
string.range(),
));
};
Ok(Expr::Bool(this.contains(string)))
}
pub fn string_starts_with(args: &[Expr]) -> Result<Expr, Error> {
let [this, prefix] = args else {
return Err(Error::invalid_arguments(
"`starts-with` requires `this` and `prefix` arguments",
None,
));
};
let Some(this) = this.as_string() else {
return Err(Error::invalid_arguments(
"`this` argument should be a String",
this.range(),
));
};
let Some(prefix) = prefix.as_string() else {
return Err(Error::invalid_arguments(
"`prefix` argument should be a String",
prefix.range(),
));
};
Ok(Expr::Bool(this.starts_with(prefix)))
}
pub fn string_ends_with(args: &[Expr]) -> Result<Expr, Error> {
let [this, postfix] = args else {
return Err(Error::invalid_arguments(
"`ends-with` requires `this` and `postfix` arguments",
None,
));
};
let Some(this) = this.as_string() else {
return Err(Error::invalid_arguments(
"`this` argument should be a String",
this.range(),
));
};
let Some(postfix) = postfix.as_string() else {
return Err(Error::invalid_arguments(
"`postfix` argument should be a String",
postfix.range(),
));
};
Ok(Expr::Bool(this.ends_with(postfix)))
}
pub fn string_replace(args: &[Expr]) -> Result<Expr, Error> {
let [this, _from, _to, ..] = args else {
return Err(Error::invalid_arguments(
"`replace` requires `this`, `from`, and `to` arguments",
None,
));
};
let Some(this) = this.as_string() else {
return Err(Error::invalid_arguments(
"`this` argument should be a String",
this.range(),
));
};
let mut output: String = this.to_string();
let mut i = 1;
while i < args.len() {
let from = &args[i];
let Some(from) = from.as_string() else {
return Err(Error::invalid_arguments(
"`from` argument should be a String",
from.range(),
));
};
let to = &args[i + 1];
let Some(to) = to.as_string() else {
return Err(Error::invalid_arguments(
"`to` argument should be a String",
to.range(),
));
};
output = output.replace(from, to);
i += 2;
}
Ok(Expr::String(output))
}
pub fn string_compare(args: &[Expr]) -> Result<Expr, Error> {
let [a, b] = args else {
return Err(Error::invalid_arguments(
"requires at least two arguments",
None,
));
};
let Some(a) = a.as_stringable() else {
return Err(Error::invalid_arguments(
&format!("{a} is not a String"),
a.range(),
));
};
let Some(b) = b.as_stringable() else {
return Err(Error::invalid_arguments(
&format!("{b} is not a String"),
b.range(),
));
};
let ordering = match a.cmp(b) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
};
Ok(Expr::Int(ordering))
}
pub fn setup_lib_string(context: &mut Context) {
let module = require_module("prelude", context);
module.insert_invocable("String", Expr::foreign_func(&string_new));
module.insert_invocable(
"String/from-chars",
Expr::foreign_func(&string_constructor_from_chars),
);
module.insert_invocable("chars", Expr::foreign_func(&string_chars));
module.insert_invocable("chars$$String", Expr::foreign_func(&string_chars));
module.insert_invocable("is-empty?", Expr::foreign_func(&string_is_empty));
module.insert_invocable("is-empty?$$String", Expr::foreign_func(&string_is_empty));
module.insert_invocable("to-upper-case", Expr::foreign_func(&char_to_upper_case));
module.insert_invocable(
"to-upper-case$$Char",
Expr::foreign_func(&char_to_upper_case),
);
module.insert_invocable("to-lower-case", Expr::foreign_func(&string_to_lower_case));
module.insert_invocable(
"to-lower-case$$String",
Expr::foreign_func(&string_to_lower_case),
);
module.insert_invocable("format-string", Expr::foreign_func(&string_format));
module.insert_invocable("split", Expr::foreign_func(&string_split));
module.insert_invocable("replace", Expr::foreign_func(&string_replace));
module.insert_invocable("slice", Expr::foreign_func(&string_slice));
module.insert_invocable("slice$$String$$Int$$Int", Expr::foreign_func(&string_slice));
module.insert_invocable(
"slice$$String$$(Range Int)",
Expr::foreign_func(&string_slice_range),
);
module.insert_invocable("get-length", Expr::foreign_func(&string_get_length));
module.insert_invocable("get-length$$String", Expr::foreign_func(&string_get_length));
module.insert_invocable("trim", Expr::foreign_func(&string_trim));
module.insert_invocable(
"contains?$$String$$String",
Expr::foreign_func(&string_contains),
);
module.insert_invocable("starts-with?", Expr::foreign_func(&string_starts_with));
module.insert_invocable("ends-with?", Expr::foreign_func(&string_ends_with));
}