use crate::error::{VmError, VmResult};
use crate::state::VmState;
use super::vector::VEC_HEADER_SIZE;
const OFFSET_CAPACITY: usize = 0;
const OFFSET_LENGTH: usize = 8;
const OFFSET_ELEM_SIZE: usize = 16;
const OFFSET_DATA: usize = 24;
#[inline]
fn str_get_length(state: &VmState, str_addr: usize) -> VmResult<u64> {
state.heap_read_u64(str_addr + OFFSET_LENGTH)
}
#[inline]
fn str_get_capacity(state: &VmState, str_addr: usize) -> VmResult<u64> {
state.heap_read_u64(str_addr + OFFSET_CAPACITY)
}
#[inline]
fn str_set_length(state: &mut VmState, str_addr: usize, length: u64) -> VmResult<()> {
state.heap_write_u64(str_addr + OFFSET_LENGTH, length)
}
#[inline]
fn str_read_byte(state: &VmState, str_addr: usize, index: u64) -> VmResult<u8> {
state.heap_read_u8(str_addr + OFFSET_DATA + index as usize)
}
#[inline]
fn str_write_byte(state: &mut VmState, str_addr: usize, index: u64, value: u8) -> VmResult<()> {
state.heap_write_u8(str_addr + OFFSET_DATA + index as usize, value)
}
pub fn handle_str_new(state: &mut VmState) -> VmResult<()> {
let capacity = state.pop()?;
let total_size = VEC_HEADER_SIZE as u64 + capacity;
let str_addr = state.heap_alloc(total_size as usize)? as usize;
state.heap_write_u64(str_addr + OFFSET_CAPACITY, capacity)?;
state.heap_write_u64(str_addr + OFFSET_LENGTH, 0)?;
state.heap_write_u64(str_addr + OFFSET_ELEM_SIZE, 1)?;
state.push(str_addr as u64)
}
pub fn handle_str_len(state: &mut VmState) -> VmResult<()> {
let str_addr = state.pop()? as usize;
let length = str_get_length(state, str_addr)?;
state.push(length)
}
pub fn handle_str_push(state: &mut VmState) -> VmResult<()> {
let byte = state.pop()? as u8;
let str_addr = state.pop()? as usize;
let length = str_get_length(state, str_addr)?;
let capacity = str_get_capacity(state, str_addr)?;
if length >= capacity {
return Err(VmError::HeapOutOfMemory);
}
str_write_byte(state, str_addr, length, byte)?;
str_set_length(state, str_addr, length + 1)
}
pub fn handle_str_get(state: &mut VmState) -> VmResult<()> {
let index = state.pop()?;
let str_addr = state.pop()? as usize;
let length = str_get_length(state, str_addr)?;
if index >= length {
return Err(VmError::HeapOutOfBounds);
}
let byte = str_read_byte(state, str_addr, index)?;
state.push(byte as u64)
}
pub fn handle_str_set(state: &mut VmState) -> VmResult<()> {
let byte = state.pop()? as u8;
let index = state.pop()?;
let str_addr = state.pop()? as usize;
let length = str_get_length(state, str_addr)?;
if index >= length {
return Err(VmError::HeapOutOfBounds);
}
str_write_byte(state, str_addr, index, byte)
}
pub fn handle_str_cmp(state: &mut VmState) -> VmResult<()> {
let str2_addr = state.pop()? as usize;
let str1_addr = state.pop()? as usize;
let len1 = str_get_length(state, str1_addr)?;
let len2 = str_get_length(state, str2_addr)?;
let min_len = len1.min(len2);
for i in 0..min_len {
let b1 = str_read_byte(state, str1_addr, i)?;
let b2 = str_read_byte(state, str2_addr, i)?;
if b1 < b2 {
return state.push(u64::MAX); } else if b1 > b2 {
return state.push(1);
}
}
let result = if len1 < len2 {
u64::MAX } else if len1 > len2 {
1
} else {
0 };
state.push(result)
}
pub fn handle_str_eq(state: &mut VmState) -> VmResult<()> {
let str2_addr = state.pop()? as usize;
let str1_addr = state.pop()? as usize;
let len1 = str_get_length(state, str1_addr)?;
let len2 = str_get_length(state, str2_addr)?;
if len1 != len2 {
return state.push(0);
}
for i in 0..len1 {
let b1 = str_read_byte(state, str1_addr, i)?;
let b2 = str_read_byte(state, str2_addr, i)?;
if b1 != b2 {
return state.push(0);
}
}
state.push(1) }
pub fn handle_str_hash(state: &mut VmState) -> VmResult<()> {
let str_addr = state.pop()? as usize;
let length = str_get_length(state, str_addr)?;
let mut hash = crate::build_config::FNV_BASIS_64;
let prime = crate::build_config::FNV_PRIME_64;
for i in 0..length {
let byte = str_read_byte(state, str_addr, i)?;
hash ^= byte as u64;
hash = hash.wrapping_mul(prime);
}
state.push(hash)
}
pub fn handle_str_concat(state: &mut VmState) -> VmResult<()> {
let str2_addr = state.pop()? as usize;
let str1_addr = state.pop()? as usize;
let len1 = str_get_length(state, str1_addr)?;
let len2 = str_get_length(state, str2_addr)?;
let new_len = len1.checked_add(len2)
.ok_or(VmError::HeapOutOfMemory)?;
let total_size = VEC_HEADER_SIZE as u64 + new_len;
let new_addr = state.heap_alloc(total_size as usize)? as usize;
state.heap_write_u64(new_addr + OFFSET_CAPACITY, new_len)?;
state.heap_write_u64(new_addr + OFFSET_LENGTH, new_len)?;
state.heap_write_u64(new_addr + OFFSET_ELEM_SIZE, 1)?;
for i in 0..len1 {
let byte = str_read_byte(state, str1_addr, i)?;
str_write_byte(state, new_addr, i, byte)?;
}
for i in 0..len2 {
let byte = str_read_byte(state, str2_addr, i)?;
str_write_byte(state, new_addr, len1 + i, byte)?;
}
state.push(new_addr as u64)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_constants() {
assert_eq!(VEC_HEADER_SIZE, 24);
assert_eq!(OFFSET_DATA, 24);
}
}