use crate::VmResult;
use crate::native::{NativeCallContext, NativeCallResult, NativeFunction};
use crate::thread::{LuaStringBuilder, LuaStringBuilderStorage, Thread};
const MAX_UNICODE: u32 = 0x10ffff;
const UTF8_BUFF_SIZE: usize = 8;
const UTF8_PATTERN: &[u8] = b"[\0-\x7F\xC2-\xF4][\x80-\xBF]*";
static UTF8_FUNCS: [NativeFunction; 5] = [
NativeFunction {
name: "offset",
function: byte_offset,
},
NativeFunction {
name: "codepoint",
function: codepoint,
},
NativeFunction {
name: "char",
function: utfchar,
},
NativeFunction {
name: "len",
function: utf_len,
},
NativeFunction {
name: "codes",
function: iter_codes,
},
];
fn is_cont(byte: u8) -> bool {
(byte & 0xc0) == 0x80
}
fn u_posrelat(pos: i32, len: usize) -> i32 {
if pos >= 0 {
pos
} else if (0usize).wrapping_sub(pos as usize) > len {
0
} else {
len as i32 + pos + 1
}
}
fn utf8_decode(bytes: &[u8], start: usize) -> Option<(usize, i32)> {
const LIMITS: [u32; 4] = [0xff, 0x7f, 0x7ff, 0xffff];
let mut c = *bytes.get(start)? as u32;
let mut result = 0u32;
if c < 0x80 {
return Some((start + 1, c as i32));
}
let mut count = 0usize;
while (c & 0x40) != 0 {
count += 1;
let cc = *bytes.get(start + count)? as u32;
if (cc & 0xc0) != 0x80 {
return None;
}
result = (result << 6) | (cc & 0x3f);
c <<= 1;
}
result |= (c & 0x7f) << (count * 5);
if count > 3 || result > MAX_UNICODE || result <= LIMITS[count] {
return None;
}
if (0xd800..=0xdfff).contains(&result) {
return None;
}
Some((start + count + 1, result as i32))
}
fn utf_len(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let bytes = thread.check_string(1)?;
let mut pos_i = u_posrelat(thread.opt_integer(2, 1)?, bytes.len());
let mut pos_j = u_posrelat(thread.opt_integer(3, -1)?, bytes.len());
if !(1 <= pos_i && {
pos_i -= 1;
pos_i as usize <= bytes.len()
}) {
return thread
.lua_arg_error(2, "initial position out of string")
.map_err(Into::into);
}
pos_j -= 1;
if !bytes.is_empty() && pos_j as usize >= bytes.len() {
return thread
.lua_arg_error(3, "final position out of string")
.map_err(Into::into);
}
let mut count = 0;
while pos_i <= pos_j {
let Some((next, _)) = utf8_decode(bytes, pos_i as usize) else {
thread.push_nil()?;
thread.push_integer(pos_i + 1)?;
return Ok(2);
};
pos_i = next as i32;
count += 1;
}
thread.push_integer(count)?;
Ok(1)
}
}
fn codepoint(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let bytes = thread.check_string(1)?;
let pos_i = u_posrelat(thread.opt_integer(2, 1)?, bytes.len());
let pos_e = u_posrelat(thread.opt_integer(3, pos_i)?, bytes.len());
if pos_i < 1 {
return thread.lua_arg_error(2, "out of range").map_err(Into::into);
}
if pos_e as usize > bytes.len() {
return thread.lua_arg_error(3, "out of range").map_err(Into::into);
}
if pos_i > pos_e {
return Ok(0);
}
thread.lua_check_stack(pos_e - pos_i + 1, Some("string slice too long"))?;
let mut count = 0;
let mut pos = (pos_i - 1) as usize;
let end = pos_e as usize;
while pos < end {
let Some((next, code)) = utf8_decode(bytes, pos) else {
return crate::error!(thread, "invalid UTF-8 code").map_err(Into::into);
};
thread.push_integer(code)?;
count += 1;
pos = next;
}
Ok(count)
}
}
fn lua_o_utf8esc(buffer: &mut [u8; UTF8_BUFF_SIZE], mut x: u32) -> usize {
let mut n = 1usize;
debug_assert!(x <= MAX_UNICODE);
if x < 0x80 {
buffer[UTF8_BUFF_SIZE - 1] = x as u8;
} else {
let mut mfb = 0x3f;
while x > mfb {
buffer[UTF8_BUFF_SIZE - n] = (0x80 | (x & 0x3f)) as u8;
n += 1;
x >>= 6;
mfb >>= 1;
}
buffer[UTF8_BUFF_SIZE - n] = (((!mfb) << 1) | x) as u8;
}
n
}
fn buff_utfchar<'a>(
thread: &Thread,
argument: i32,
buffer: &'a mut [u8; UTF8_BUFF_SIZE],
) -> VmResult<&'a [u8]> {
let code = unsafe { thread.check_integer(argument)? };
if !(0..=MAX_UNICODE as i32).contains(&code) {
return unsafe { thread.lua_arg_error(argument, "value out of range") }.map_err(Into::into);
}
let len = lua_o_utf8esc(buffer, code as u32);
Ok(&buffer[UTF8_BUFF_SIZE - len..])
}
fn utfchar(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let count = thread.get_top();
let mut buffer = [0u8; UTF8_BUFF_SIZE];
if count == 1 {
let bytes = buff_utfchar(thread, 1, &mut buffer)?;
thread.push_string(bytes)?;
} else {
let mut out_storage = LuaStringBuilderStorage::uninit();
let mut out = LuaStringBuilder::new(thread, &mut out_storage);
for index in 1..=count {
let bytes = buff_utfchar(thread, index, &mut buffer)?;
out.push_bytes(bytes)?;
}
out.finish()?;
}
}
Ok(1)
}
fn byte_offset(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let bytes = thread.check_string(1)?;
let n = thread.check_integer(2)?;
let mut pos_i = if n >= 0 { 1 } else { bytes.len() as i32 + 1 };
pos_i = u_posrelat(thread.opt_integer(3, pos_i)?, bytes.len());
if !(1 <= pos_i && {
pos_i -= 1;
pos_i as usize <= bytes.len()
}) {
return thread
.lua_arg_error(3, "position out of range")
.map_err(Into::into);
}
if n == 0 {
while pos_i > 0 && is_cont(bytes[pos_i as usize]) {
pos_i -= 1;
}
} else {
if pos_i < bytes.len() as i32 && is_cont(bytes[pos_i as usize]) {
return crate::error!(thread, "initial position is a continuation byte")
.map_err(Into::into);
}
let mut remaining = n;
if remaining < 0 {
while remaining < 0 && pos_i > 0 {
loop {
pos_i -= 1;
if pos_i == 0 || !is_cont(bytes[pos_i as usize]) {
break;
}
}
remaining += 1;
}
} else {
remaining -= 1;
while remaining > 0 && pos_i < bytes.len() as i32 {
loop {
pos_i += 1;
if pos_i as usize >= bytes.len() || !is_cont(bytes[pos_i as usize]) {
break;
}
}
remaining -= 1;
}
}
if remaining != 0 {
thread.push_nil()?;
return Ok(1);
}
}
thread.push_integer(pos_i + 1)?;
Ok(1)
}
}
fn iter_aux(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let bytes = thread.check_string(1)?;
let mut n = thread.to_integer(2).unwrap_or(0) - 1;
if n < 0 {
n = 0;
} else if (n as usize) < bytes.len() {
n += 1;
while (n as usize) < bytes.len() && is_cont(bytes[n as usize]) {
n += 1;
}
}
if (n as usize) >= bytes.len() {
return Ok(0);
}
let Some((next, code)) = utf8_decode(bytes, n as usize) else {
return crate::error!(thread, "invalid UTF-8 code").map_err(Into::into);
};
if next < bytes.len() && is_cont(bytes[next]) {
return crate::error!(thread, "invalid UTF-8 code").map_err(Into::into);
}
thread.push_integer(n + 1)?;
thread.push_integer(code)?;
Ok(2)
}
}
fn iter_codes(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
let _ = unsafe { thread.check_string(1)? };
unsafe {
thread.push_native_closure_k(iter_aux, None, 0, None)?;
thread.push_value(1)?;
thread.push_integer(0)?;
}
Ok(3)
}
impl Thread {
pub unsafe fn open_utf8(&self) -> NativeCallResult {
unsafe {
self.register(Some(super::LUA_UTF8LIB_NAME), &UTF8_FUNCS[..])?;
self.push_string(UTF8_PATTERN)?;
self.raw_set_field(-2, "charpattern")?;
}
Ok(1)
}
}