use std::cell::RefCell;
use std::rc::Rc;
use crate::vm::libs::args::{int_arg, opt_int_arg, str_arg};
use crate::vm::libs::strlib::{pos_relat_end, pos_relat_start};
use crate::vm::machine::VM;
use crate::vm::value::{Value, VmTable};
pub fn create_utf8_lib() -> Value {
let mut table = VmTable::new();
macro_rules! reg {
($($name:literal => $func:ident),* $(,)?) => {
$(table.set_str($name, crate::native!(concat!("utf8.", $name), $func));)*
};
}
reg! {
"char" => utf8_char,
"chars" => utf8_chars,
"codepoint" => utf8_codepoint,
"codes" => utf8_codes,
"len" => utf8_len,
"offset" => utf8_offset,
"sub" => utf8_sub,
}
crate::vm::libs::args::frozen(table)
}
fn utf8_char(_vm: &mut VM, args: &[Value]) -> Result<Vec<Value>, String> {
let mut out = String::with_capacity(args.len());
for index in 0..args.len() {
let code = int_arg(args, index, &format!("code point {}", index + 1))?;
let c = u32::try_from(code)
.ok()
.and_then(char::from_u32)
.ok_or_else(|| format!("code point {} is not a valid character", code))?;
out.push(c);
}
Ok(vec![Value::string(out)])
}
fn byte_range(s: &str, i: i64, j: i64) -> Result<(usize, usize), String> {
let start = pos_relat_start(i, s.len()) - 1;
let end = pos_relat_end(j, s.len());
if start > s.len() {
return Err(format!("byte position {} is out of bounds", i));
}
if !s.is_char_boundary(start) {
return Err(format!("byte position {} is inside a character", start + 1));
}
Ok((start, end.max(start)))
}
fn utf8_codepoint(_vm: &mut VM, args: &[Value]) -> Result<Vec<Value>, String> {
let s = str_arg(args, 0, "s")?;
let i = opt_int_arg(args, 1, "i", 1)?;
let j = opt_int_arg(args, 2, "j", i)?;
let (start, end) = byte_range(s, i, j)?;
Ok(s[start..]
.char_indices()
.take_while(|(offset, _)| start + offset < end)
.map(|(_, c)| Value::Int(c as i64))
.collect())
}
fn utf8_len(_vm: &mut VM, args: &[Value]) -> Result<Vec<Value>, String> {
let s = str_arg(args, 0, "s")?;
let i = opt_int_arg(args, 1, "i", 1)?;
let j = opt_int_arg(args, 2, "j", -1)?;
let (start, end) = byte_range(s, i, j)?;
let count = s[start..]
.char_indices()
.take_while(|(offset, _)| start + offset < end)
.count();
Ok(vec![Value::from_usize(count)])
}
fn utf8_offset(_vm: &mut VM, args: &[Value]) -> Result<Vec<Value>, String> {
let s = str_arg(args, 0, "s")?;
let n = int_arg(args, 1, "n")?;
let default_i = if n >= 0 { 1 } else { s.len() as i64 + 1 };
let i = opt_int_arg(args, 2, "i", default_i)?;
let mut pos = pos_relat_start(i, s.len()) - 1;
if pos > s.len() {
return Ok(vec![Value::Nil]);
}
if n == 0 {
while pos > 0 && !s.is_char_boundary(pos) {
pos -= 1;
}
return Ok(vec![Value::from_usize(pos + 1)]);
}
if !s.is_char_boundary(pos) {
return Err(format!("byte position {} is inside a character", pos + 1));
}
let found = if n > 0 {
let skip = (n - 1) as usize;
s[pos..]
.char_indices()
.map(|(offset, _)| pos + offset)
.chain(std::iter::once(s.len()))
.nth(skip)
} else {
let back = n.unsigned_abs() as usize;
s[..pos]
.char_indices()
.rev()
.map(|(offset, _)| offset)
.nth(back - 1)
};
Ok(vec![match found {
Some(p) if p <= s.len() => Value::from_usize(p + 1),
_ => Value::Nil,
}])
}
fn utf8_sub(_vm: &mut VM, args: &[Value]) -> Result<Vec<Value>, String> {
let s = str_arg(args, 0, "s")?;
let i = opt_int_arg(args, 1, "i", 1)?;
let j = opt_int_arg(args, 2, "j", -1)?;
let count = s.chars().count();
let first = pos_relat_start(i, count);
let last = pos_relat_end(j, count);
if first > last {
return Ok(vec![Value::str("")]);
}
let out: String = s.chars().skip(first - 1).take(last + 1 - first).collect();
Ok(vec![Value::string(out)])
}
fn iterator(s: &str, step: Value) -> Value {
let mut meta = VmTable::new();
meta.set_str("__call", step);
let mut state = VmTable::new();
state.set_str("s", Value::str(s));
state.set_str("pos", Value::Int(0));
state.metatable = Some(Rc::new(RefCell::new(meta)));
Value::Table(Rc::new(RefCell::new(state)))
}
fn iterator_state(
args: &[Value],
) -> Option<(Rc<RefCell<VmTable>>, crate::vm::value::StrRef, usize)> {
let Some(Value::Table(state)) = args.first() else {
return None;
};
let st = state.borrow();
let (Value::String(s), Value::Int(pos)) = (st.get_str("s"), st.get_str("pos")) else {
return None;
};
drop(st);
Some((state.clone(), s, pos as usize))
}
fn codes_step(_vm: &mut VM, args: &[Value]) -> Result<Vec<Value>, String> {
let Some((state, s, pos)) = iterator_state(args) else {
return Ok(vec![Value::Nil]);
};
match s.get(pos..).and_then(|rest| rest.chars().next()) {
Some(c) => {
state
.borrow_mut()
.set_str("pos", Value::from_usize(pos + c.len_utf8()));
Ok(vec![Value::from_usize(pos + 1), Value::Int(c as i64)])
}
None => Ok(vec![Value::Nil]),
}
}
fn chars_step(_vm: &mut VM, args: &[Value]) -> Result<Vec<Value>, String> {
let Some((state, s, pos)) = iterator_state(args) else {
return Ok(vec![Value::Nil]);
};
match s.get(pos..).and_then(|rest| rest.chars().next()) {
Some(c) => {
state
.borrow_mut()
.set_str("pos", Value::from_usize(pos + c.len_utf8()));
Ok(vec![Value::string(c.to_string())])
}
None => Ok(vec![Value::Nil]),
}
}
fn utf8_codes(_vm: &mut VM, args: &[Value]) -> Result<Vec<Value>, String> {
let s = str_arg(args, 0, "s")?;
Ok(vec![iterator(
s,
crate::native!("utf8.codes iterator", codes_step),
)])
}
fn utf8_chars(_vm: &mut VM, args: &[Value]) -> Result<Vec<Value>, String> {
let s = str_arg(args, 0, "s")?;
Ok(vec![iterator(
s,
crate::native!("utf8.chars iterator", chars_step),
)])
}