use luna_core::runtime::value::Value;
use luna_core::vm::error::LuaError;
use luna_core::vm::exec::Vm;
use std::cell::Cell;
use std::rc::Rc;
pub type DispatchFn = dyn Fn(&[&[u8]], bool) -> Vec<u8>;
pub type DispatchHandle = Rc<DispatchFn>;
pub(crate) struct DispatchSlot {
pub f: DispatchHandle,
pub read_only: Rc<Cell<bool>>,
}
impl luna_core::vm::LuaUserdata for DispatchSlot {}
pub(crate) const DISPATCH_KEY: &str = "__kevy_dispatch";
pub(crate) fn redis_call(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let reply = invoke_dispatch(vm, fs, nargs)?;
if reply.starts_with(b"-") {
let msg = error_payload(&reply);
let v = Value::Str(vm.heap.intern(&msg));
return Err(LuaError(v));
}
let v = parse_resp_value(vm, &reply, &mut 0)?;
Ok(vm.nat_return(fs, &[v]))
}
pub(crate) fn redis_pcall(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let reply = invoke_dispatch(vm, fs, nargs)?;
if reply.starts_with(b"-") {
let msg = error_payload(&reply);
let s = Value::Str(vm.heap.intern(&msg));
let t = vm.new_table().with("err", s).build();
return Ok(vm.nat_return(fs, &[Value::Table(t)]));
}
let v = parse_resp_value(vm, &reply, &mut 0)?;
Ok(vm.nat_return(fs, &[v]))
}
fn invoke_dispatch(vm: &mut Vm, fs: u32, nargs: u32) -> Result<Vec<u8>, LuaError> {
let (dispatch, read_only) = vm
.userdata_borrow::<DispatchSlot>(DISPATCH_KEY)
.map(|s| (Rc::clone(&s.f), Rc::clone(&s.read_only)))
.ok_or_else(|| {
LuaError(Value::Str(
vm.heap
.intern(b"redis.call: kevy host dispatch not installed"),
))
})?;
if nargs == 0 {
return Err(LuaError(Value::Str(
vm.heap
.intern(b"redis.call: wrong number of arguments (no command)"),
)));
}
let mut argv: Vec<Vec<u8>> = Vec::with_capacity(nargs as usize);
for i in 0..nargs {
let v = vm.nat_arg(fs, nargs, i);
argv.push(value_to_bytes(v));
}
let arg_refs: Vec<&[u8]> = argv.iter().map(|v| v.as_slice()).collect();
Ok(dispatch(&arg_refs, read_only.get()))
}
fn value_to_bytes(v: Value) -> Vec<u8> {
match v {
Value::Str(s) => s.as_bytes().to_vec(),
Value::Int(n) => n.to_string().into_bytes(),
Value::Float(f) => {
if f.is_finite() && f.fract() == 0.0
&& (i64::MIN as f64..=i64::MAX as f64).contains(&f)
{
(f as i64).to_string().into_bytes()
} else {
format!("{f}").into_bytes()
}
}
_ => Vec::new(),
}
}
fn error_payload(reply: &[u8]) -> Vec<u8> {
let mut s = reply;
if s.starts_with(b"-") {
s = &s[1..];
}
if s.ends_with(b"\r\n") {
s = &s[..s.len() - 2];
}
s.to_vec()
}
fn parse_resp_value(vm: &mut Vm, bytes: &[u8], pos: &mut usize) -> Result<Value, LuaError> {
if *pos >= bytes.len() {
return Err(LuaError(Value::Str(
vm.heap.intern(b"redis.call: empty RESP reply"),
)));
}
let tag = bytes[*pos];
*pos += 1;
match tag {
b'+' => {
let line = read_line(bytes, pos)?;
let s = Value::Str(vm.heap.intern(&line));
let t = vm.new_table().with("ok", s).build();
Ok(Value::Table(t))
}
b':' => {
let line = read_line(bytes, pos)?;
let n = std::str::from_utf8(&line)
.map_err(|_| {
LuaError(Value::Str(
vm.heap.intern(b"redis.call: invalid integer reply"),
))
})?
.parse::<i64>()
.map_err(|_| {
LuaError(Value::Str(
vm.heap.intern(b"redis.call: invalid integer reply"),
))
})?;
Ok(Value::Int(n))
}
b'$' => {
let len_line = read_line(bytes, pos)?;
let n: i64 = std::str::from_utf8(&len_line)
.map_err(|_| {
LuaError(Value::Str(vm.heap.intern(b"redis.call: invalid bulk length")))
})?
.parse()
.map_err(|_| {
LuaError(Value::Str(vm.heap.intern(b"redis.call: invalid bulk length")))
})?;
if n < 0 {
return Ok(Value::Bool(false));
}
let len = n as usize;
if *pos + len + 2 > bytes.len() {
return Err(LuaError(Value::Str(
vm.heap.intern(b"redis.call: truncated bulk reply"),
)));
}
let payload = &bytes[*pos..*pos + len];
let v = Value::Str(vm.heap.intern(payload));
*pos += len + 2; Ok(v)
}
b'*' => {
let len_line = read_line(bytes, pos)?;
let n: i64 = std::str::from_utf8(&len_line)
.map_err(|_| {
LuaError(Value::Str(vm.heap.intern(b"redis.call: invalid array length")))
})?
.parse()
.map_err(|_| {
LuaError(Value::Str(vm.heap.intern(b"redis.call: invalid array length")))
})?;
if n < 0 {
return Ok(Value::Bool(false));
}
let count = n as usize;
let mut entries: Vec<(i64, Value)> = Vec::with_capacity(count);
for i in 0..count {
let v = parse_resp_value(vm, bytes, pos)?;
entries.push(((i + 1) as i64, v));
}
let mut b = vm.new_table();
for (k, v) in entries {
b = b.with(k, v);
}
Ok(Value::Table(b.build()))
}
_ => Err(LuaError(Value::Str(
vm.heap.intern(b"redis.call: unknown RESP type tag"),
))),
}
}
fn read_line(bytes: &[u8], pos: &mut usize) -> Result<Vec<u8>, LuaError> {
let start = *pos;
while *pos + 1 < bytes.len() {
if bytes[*pos] == b'\r' && bytes[*pos + 1] == b'\n' {
let line = bytes[start..*pos].to_vec();
*pos += 2;
return Ok(line);
}
*pos += 1;
}
Err(LuaError(Value::Nil))
}