use super::Entry;
use crate::builtins::{cp_to_byte, expect_int, expect_list, expect_str, none, remember_cursor, some, str_arg};
use lex_bytecode::Value;
pub(crate) const TABLE: &[Entry] = &[
("is_empty", is_empty),
("to_int", to_int),
("to_float", to_float),
("concat", concat),
("len", len),
("char_at", char_at),
("split", split),
("join", join),
("starts_with", starts_with),
("ends_with", ends_with),
("contains", contains),
("cmp", cmp),
("replace", replace),
("trim", trim),
("to_upper", to_upper),
("to_lower", to_lower),
("strip_prefix", strip_prefix),
("strip_suffix", strip_suffix),
("slice", slice),
("is_ascii", is_ascii),
("find", find),
("find_any", find_any),
];
fn is_empty(args: Vec<Value>) -> Result<Value, String> {
Ok(Value::Bool(expect_str(args.first())?.is_empty()))
}
fn len(args: Vec<Value>) -> Result<Value, String> {
Ok(Value::Int(expect_str(args.first())?.len() as i64))
}
fn char_at(args: Vec<Value>) -> Result<Value, String> {
let s = expect_str(args.first())?;
let i = expect_int(args.get(1))?;
if i < 0 {
return Ok(Value::Str("".into()));
}
Ok(match s.as_bytes().get(i as usize) {
Some(&b) if b < 128 => Value::Str((b as char).to_string().into()),
_ => Value::Str("".into()),
})
}
fn concat(args: Vec<Value>) -> Result<Value, String> {
let a = expect_str(args.first())?;
let b = expect_str(args.get(1))?;
Ok(Value::Str(format!("{a}{b}").into()))
}
fn to_int(args: Vec<Value>) -> Result<Value, String> {
let s = expect_str(args.first())?;
Ok(match s.parse::<i64>() {
Ok(n) => some(Value::Int(n)),
Err(_) => none(),
})
}
fn to_float(args: Vec<Value>) -> Result<Value, String> {
let s = expect_str(args.first())?;
Ok(match s.parse::<f64>() {
Ok(f) => some(Value::Float(f)),
Err(_) => none(),
})
}
fn split(args: Vec<Value>) -> Result<Value, String> {
let s = expect_str(args.first())?;
let sep = expect_str(args.get(1))?;
let items: std::collections::VecDeque<Value> = if sep.is_empty() {
s.chars().map(|c| Value::Str(c.to_string().into())).collect()
} else {
s.split(sep).map(|p| Value::Str(p.into())).collect()
};
Ok(Value::List(items.into()))
}
fn join(args: Vec<Value>) -> Result<Value, String> {
let parts = expect_list(args.first())?;
let sep = expect_str(args.get(1))?;
let mut out = String::new();
for (i, p) in parts.iter().enumerate() {
if i > 0 {
out.push_str(sep);
}
match p {
Value::Str(s) => out.push_str(s),
other => return Err(format!("str.join element must be Str, got {other:?}")),
}
}
Ok(Value::Str(out.into()))
}
fn starts_with(args: Vec<Value>) -> Result<Value, String> {
let s = expect_str(args.first())?;
let prefix = expect_str(args.get(1))?;
Ok(Value::Bool(s.starts_with(prefix)))
}
fn ends_with(args: Vec<Value>) -> Result<Value, String> {
let s = expect_str(args.first())?;
let suffix = expect_str(args.get(1))?;
Ok(Value::Bool(s.ends_with(suffix)))
}
fn contains(args: Vec<Value>) -> Result<Value, String> {
let s = expect_str(args.first())?;
let needle = expect_str(args.get(1))?;
Ok(Value::Bool(s.contains(needle)))
}
fn cmp(args: Vec<Value>) -> Result<Value, String> {
let a = expect_str(args.first())?;
let b = expect_str(args.get(1))?;
Ok(Value::Int(match a.cmp(b) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
}))
}
fn replace(args: Vec<Value>) -> Result<Value, String> {
let s = expect_str(args.first())?;
let from = expect_str(args.get(1))?;
let to = expect_str(args.get(2))?;
Ok(Value::Str(s.replace(from, to).into()))
}
fn trim(args: Vec<Value>) -> Result<Value, String> {
Ok(Value::Str(expect_str(args.first())?.trim().into()))
}
fn to_upper(args: Vec<Value>) -> Result<Value, String> {
Ok(Value::Str(expect_str(args.first())?.to_uppercase().into()))
}
fn to_lower(args: Vec<Value>) -> Result<Value, String> {
Ok(Value::Str(expect_str(args.first())?.to_lowercase().into()))
}
fn strip_prefix(args: Vec<Value>) -> Result<Value, String> {
let s = expect_str(args.first())?;
let prefix = expect_str(args.get(1))?;
Ok(match s.strip_prefix(prefix) {
Some(rest) => some(Value::Str(rest.into())),
None => none(),
})
}
fn strip_suffix(args: Vec<Value>) -> Result<Value, String> {
let s = expect_str(args.first())?;
let suffix = expect_str(args.get(1))?;
Ok(match s.strip_suffix(suffix) {
Some(rest) => some(Value::Str(rest.into())),
None => none(),
})
}
fn slice(args: Vec<Value>) -> Result<Value, String> {
let s = expect_str(args.first())?;
let lo_i = expect_int(args.get(1))?;
let hi_i = expect_int(args.get(2))?;
let lo_cp = lo_i.max(0) as usize;
let hi_cp = hi_i.max(0) as usize;
if lo_cp > hi_cp {
return Err(format!("str.slice: reversed range [{lo_cp}..{hi_cp}]"));
}
let sv = str_arg(args.first())?;
let lo_byte = cp_to_byte(sv, lo_cp);
let hi_byte = cp_to_byte(sv, hi_cp);
Ok(Value::Str(s[lo_byte..hi_byte].into()))
}
fn is_ascii(args: Vec<Value>) -> Result<Value, String> {
Ok(Value::Bool(expect_str(args.first())?.is_ascii()))
}
fn find(args: Vec<Value>) -> Result<Value, String> {
let s = expect_str(args.first())?;
let needle = expect_str(args.get(1))?;
let from_cp = expect_int(args.get(2))?.max(0) as usize;
let sv = str_arg(args.first())?;
let from_byte = cp_to_byte(sv, from_cp);
if from_byte >= s.len() && !(from_byte == s.len() && needle.is_empty()) {
return Ok(none());
}
Ok(match s[from_byte..].find(needle) {
Some(off) => {
let cp = from_cp + s[from_byte..from_byte + off].chars().count();
remember_cursor(sv, cp, from_byte + off);
some(Value::Int(cp as i64))
}
None => none(),
})
}
fn find_any(args: Vec<Value>) -> Result<Value, String> {
let s = expect_str(args.first())?;
let set = expect_str(args.get(1))?;
let from_cp = expect_int(args.get(2))?.max(0) as usize;
let sv = str_arg(args.first())?;
let from_byte = cp_to_byte(sv, from_cp);
if from_byte >= s.len() {
return Ok(none());
}
let hit = s[from_byte..]
.char_indices()
.enumerate()
.find(|(_, (_, c))| set.contains(*c));
Ok(match hit {
Some((n, (off, _))) => {
let cp = from_cp + n;
remember_cursor(sv, cp, from_byte + off);
some(Value::Int(cp as i64))
}
None => none(),
})
}