use alloc::vec::Vec;
use core::ffi::c_void;
use core::{mem, ptr, slice, str};
use pd_vm_nostd::{HostError, Value, Vm, VmError, decode_program};
use crate::{ReplResponse, ReplValue, decode_repl_state, encode_repl_response};
pub const RUSTSCRIPT_STATUS_OK: i32 = 0;
pub const RUSTSCRIPT_STATUS_INVALID_ARGUMENT: i32 = -1;
pub const RUSTSCRIPT_STATUS_INVALID_VMBC: i32 = -2;
pub const RUSTSCRIPT_STATUS_HOST_ERROR: i32 = -3;
pub const RUSTSCRIPT_STATUS_OUT_OF_FUEL: i32 = -4;
pub const RUSTSCRIPT_STATUS_VM_ERROR: i32 = -5;
pub const RUSTSCRIPT_STATUS_INVALID_REPL_STATE: i32 = -6;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum RustScriptValueTag {
Null = 0,
Int = 1,
Float = 2,
Bool = 3,
String = 4,
Bytes = 5,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RustScriptValueError {
InvalidTag(u8),
InvalidBool(u8),
NullData,
InvalidUtf8,
UnsupportedType,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct RustScriptValue {
pub tag: u8,
pub boolean: u8,
pub reserved: [u8; 6],
pub integer: i64,
pub float: f64,
pub data: *const u8,
pub len: usize,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct RustScriptBuffer {
pub data: *mut u8,
pub len: usize,
pub capacity: usize,
}
impl RustScriptBuffer {
pub const fn empty() -> Self {
Self {
data: ptr::null_mut(),
len: 0,
capacity: 0,
}
}
}
impl RustScriptValue {
pub const fn null() -> Self {
Self {
tag: RustScriptValueTag::Null as u8,
boolean: 0,
reserved: [0; 6],
integer: 0,
float: 0.0,
data: ptr::null(),
len: 0,
}
}
pub fn from_embedded(value: &Value) -> Result<Self, RustScriptValueError> {
let mut output = Self::null();
match value {
Value::Null => {}
Value::Int(value) => {
output.tag = RustScriptValueTag::Int as u8;
output.integer = *value;
}
Value::Float(value) => {
output.tag = RustScriptValueTag::Float as u8;
output.float = *value;
}
Value::Bool(value) => {
output.tag = RustScriptValueTag::Bool as u8;
output.boolean = u8::from(*value);
}
Value::String(value) => {
output.tag = RustScriptValueTag::String as u8;
output.data = value.as_ptr();
output.len = value.len();
}
Value::Bytes(value) => {
output.tag = RustScriptValueTag::Bytes as u8;
output.data = value.as_ptr();
output.len = value.len();
}
Value::Array(_) | Value::Map(_) => return Err(RustScriptValueError::UnsupportedType),
}
Ok(output)
}
pub unsafe fn to_embedded(self) -> Result<Value, RustScriptValueError> {
match self.tag {
value if value == RustScriptValueTag::Null as u8 => Ok(Value::Null),
value if value == RustScriptValueTag::Int as u8 => Ok(Value::Int(self.integer)),
value if value == RustScriptValueTag::Float as u8 => Ok(Value::Float(self.float)),
value if value == RustScriptValueTag::Bool as u8 => match self.boolean {
0 => Ok(Value::Bool(false)),
1 => Ok(Value::Bool(true)),
value => Err(RustScriptValueError::InvalidBool(value)),
},
value if value == RustScriptValueTag::String as u8 => {
let bytes = unsafe { borrowed_bytes(self.data, self.len)? };
let text = str::from_utf8(bytes).map_err(|_| RustScriptValueError::InvalidUtf8)?;
Ok(Value::string(text))
}
value if value == RustScriptValueTag::Bytes as u8 => {
let bytes = unsafe { borrowed_bytes(self.data, self.len)? };
Ok(Value::bytes(bytes))
}
value => Err(RustScriptValueError::InvalidTag(value)),
}
}
}
pub type RustScriptHostCallback = unsafe extern "C" fn(
context: *mut c_void,
name: *const u8,
name_len: usize,
args: *const RustScriptValue,
arg_count: usize,
result: *mut RustScriptValue,
) -> i32;
struct CallbackContext {
callback: Option<RustScriptHostCallback>,
user_context: *mut c_void,
}
fn dispatch_host(
context: &mut CallbackContext,
name: &str,
args: &[Value],
) -> Result<Option<Value>, HostError> {
let callback = context
.callback
.ok_or_else(|| HostError::new("host callback is missing"))?;
let mut ffi_args = Vec::new();
ffi_args
.try_reserve_exact(args.len())
.map_err(|_| HostError::new("host argument allocation failed"))?;
for value in args {
ffi_args.push(
RustScriptValue::from_embedded(value)
.map_err(|_| HostError::new("unsupported host argument"))?,
);
}
let mut result = RustScriptValue::null();
let status = unsafe {
callback(
context.user_context,
name.as_ptr(),
name.len(),
ffi_args.as_ptr(),
ffi_args.len(),
&mut result,
)
};
match status {
0 => Ok(None),
1 => unsafe { result.to_embedded() }
.map(Some)
.map_err(|_| HostError::new("invalid host return value")),
_ => Err(HostError::new("host callback failed")),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rustscript_run_vmbc(
program: *const u8,
program_len: usize,
callback: Option<RustScriptHostCallback>,
user_context: *mut c_void,
fuel: u64,
) -> i32 {
let bytes = match unsafe { borrowed_bytes(program, program_len) } {
Ok(bytes) if !bytes.is_empty() => bytes,
_ => return RUSTSCRIPT_STATUS_INVALID_ARGUMENT,
};
let program = match decode_program(bytes) {
Ok(program) => program,
Err(_) => return RUSTSCRIPT_STATUS_INVALID_VMBC,
};
let context = CallbackContext {
callback,
user_context,
};
let mut vm = Vm::with_host_dispatcher(program, context, dispatch_host);
if fuel != 0 {
vm.set_fuel(fuel);
}
vm_status(vm.run())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rustscript_repl_run_vmbc(
program: *const u8,
program_len: usize,
state: *const u8,
state_len: usize,
callback: Option<RustScriptHostCallback>,
user_context: *mut c_void,
fuel: u64,
output: *mut RustScriptBuffer,
) -> i32 {
if output.is_null() {
return RUSTSCRIPT_STATUS_INVALID_ARGUMENT;
}
unsafe { output.write(RustScriptBuffer::empty()) }
let bytes = match unsafe { borrowed_bytes(program, program_len) } {
Ok(bytes) if !bytes.is_empty() => bytes,
_ => return RUSTSCRIPT_STATUS_INVALID_ARGUMENT,
};
let state_bytes = match unsafe { borrowed_bytes(state, state_len) } {
Ok(bytes) if !bytes.is_empty() => bytes,
_ => return RUSTSCRIPT_STATUS_INVALID_ARGUMENT,
};
let program = match decode_program(bytes) {
Ok(program) => program,
Err(_) => return RUSTSCRIPT_STATUS_INVALID_VMBC,
};
let locals = match decode_repl_state(state_bytes) {
Ok(locals) => locals,
Err(_) => return RUSTSCRIPT_STATUS_INVALID_REPL_STATE,
};
if locals.len() != program.local_count() {
return RUSTSCRIPT_STATUS_INVALID_REPL_STATE;
}
let context = CallbackContext {
callback,
user_context,
};
let mut vm = Vm::with_host_dispatcher(program, context, dispatch_host);
for (index, value) in locals.into_iter().enumerate() {
let value = match repl_to_embedded(value) {
Ok(value) => value,
Err(()) => return RUSTSCRIPT_STATUS_INVALID_REPL_STATE,
};
if vm.set_local(index as u8, value).is_err() {
return RUSTSCRIPT_STATUS_INVALID_REPL_STATE;
}
}
if fuel != 0 {
vm.set_fuel(fuel);
}
let status = vm_status(vm.run());
let locals = vm.locals().iter().map(embedded_to_repl).collect();
let result = (status == RUSTSCRIPT_STATUS_OK)
.then(|| vm.stack().last().map(embedded_to_repl))
.flatten();
let mut encoded = match encode_repl_response(&ReplResponse { locals, result }) {
Ok(encoded) => encoded,
Err(_) => return RUSTSCRIPT_STATUS_INVALID_REPL_STATE,
};
let buffer = RustScriptBuffer {
data: encoded.as_mut_ptr(),
len: encoded.len(),
capacity: encoded.capacity(),
};
mem::forget(encoded);
unsafe { output.write(buffer) }
status
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rustscript_buffer_free(buffer: RustScriptBuffer) {
if buffer.data.is_null() {
return;
}
if buffer.capacity < buffer.len {
return;
}
drop(unsafe { Vec::from_raw_parts(buffer.data, buffer.len, buffer.capacity) });
}
fn vm_status(status: Result<pd_vm_nostd::VmStatus, VmError>) -> i32 {
match status {
Ok(_) => RUSTSCRIPT_STATUS_OK,
Err(VmError::OutOfFuel { .. }) => RUSTSCRIPT_STATUS_OUT_OF_FUEL,
Err(VmError::HostError(_) | VmError::HostCallsUnavailable(_)) => {
RUSTSCRIPT_STATUS_HOST_ERROR
}
Err(_) => RUSTSCRIPT_STATUS_VM_ERROR,
}
}
fn repl_to_embedded(value: ReplValue) -> Result<Value, ()> {
Ok(match value {
ReplValue::Null => Value::Null,
ReplValue::Int(value) => Value::Int(value),
ReplValue::Float(value) => Value::Float(value),
ReplValue::Bool(value) => Value::Bool(value),
ReplValue::String(value) => Value::string(value),
ReplValue::Bytes(value) => Value::bytes(value),
ReplValue::Array(values) => Value::array(
values
.into_iter()
.map(repl_to_embedded)
.collect::<Result<Vec<_>, _>>()?,
),
ReplValue::Map(entries) => Value::map(
entries
.into_iter()
.map(|(key, value)| Ok((repl_to_embedded(key)?, repl_to_embedded(value)?)))
.collect::<Result<Vec<_>, ()>>()?,
),
})
}
fn embedded_to_repl(value: &Value) -> ReplValue {
match value {
Value::Null => ReplValue::Null,
Value::Int(value) => ReplValue::Int(*value),
Value::Float(value) => ReplValue::Float(*value),
Value::Bool(value) => ReplValue::Bool(*value),
Value::String(value) => ReplValue::String(value.as_str().into()),
Value::Bytes(value) => ReplValue::Bytes(value.as_ref().clone()),
Value::Array(values) => ReplValue::Array(values.iter().map(embedded_to_repl).collect()),
Value::Map(entries) => ReplValue::Map(
entries
.iter()
.map(|(key, value)| (embedded_to_repl(key), embedded_to_repl(value)))
.collect(),
),
}
}
unsafe fn borrowed_bytes<'a>(
data: *const u8,
len: usize,
) -> Result<&'a [u8], RustScriptValueError> {
if len == 0 {
return Ok(&[]);
}
if data.is_null() {
return Err(RustScriptValueError::NullData);
}
Ok(unsafe { slice::from_raw_parts(data, len) })
}