luau-vm 0.732.0

Pure-Rust Luau virtual machine, garbage collector, and standard libraries
Documentation
use core::str;

use luau_common::ByteSlice;

use crate::debug::LuaDebug;
use crate::native::{NativeCallContext, NativeCallResult, NativeFunction};
use crate::thread::Thread;
use crate::types::LUA_TFUNCTION;

static DEBUG_LIB: [NativeFunction; 2] = [
    NativeFunction {
        name: "info",
        function: db_info,
    },
    NativeFunction {
        name: "traceback",
        function: db_traceback,
    },
];

/// `getthread`
fn get_thread(thread: &Thread) -> (Option<Thread>, i32) {
    if let Some(other) = unsafe { thread.to_thread(1) } {
        (Some(other), 1)
    } else {
        (None, 0)
    }
}

/// `db_info`
fn db_info(ctx: NativeCallContext) -> NativeCallResult {
    let thread = ctx.raw_thread();
    unsafe {
        let (source, arg) = get_thread(thread);
        let source = source.as_ref().unwrap_or(thread);
        let mut source_top = 0;

        if source != thread {
            source.raw_check_stack(1)?;
            source_top = source.get_top();
        }

        let level = if thread.is_number(arg + 1) != 0 {
            let level = thread.check_integer(arg + 1)?;
            if level < 0 {
                return thread
                    .lua_arg_error(arg + 1, "level can't be negative")
                    .map_err(Into::into);
            }
            level
        } else if arg == 0 && thread.type_of(1) == LUA_TFUNCTION {
            -thread.get_top()
        } else {
            return thread
                .lua_arg_error(arg + 1, "function or level expected")
                .map_err(Into::into);
        };

        let options = thread.check_string(arg + 2)?;
        let options = match str::from_utf8(options) {
            Ok(options) => options,
            Err(_) => {
                return thread
                    .lua_arg_error(arg + 2, "invalid option")
                    .map_err(Into::into);
            }
        };
        let mut ar = LuaDebug::default();
        if source.get_info(level, options, &mut ar)? == 0 {
            return Ok(0);
        }

        let mut results = 0;
        let mut occurs = [false; 26];
        for &byte in options.as_bytes() {
            if byte.is_ascii_lowercase() {
                let slot = (byte - b'a') as usize;
                if occurs[slot] {
                    if source != thread {
                        source.restore_top(source_top);
                    }
                    return thread
                        .lua_arg_error(arg + 2, "duplicate option")
                        .map_err(Into::into);
                }
                occurs[slot] = true;
            }

            match byte {
                b's' => {
                    thread.push_string(ar.short_src())?;
                    results += 1;
                }
                b'l' => {
                    thread.push_integer(ar.currentline)?;
                    results += 1;
                }
                b'n' => {
                    if let Some(name) = &ar.name {
                        thread.push_string(name)?;
                    } else {
                        thread.push_string("")?;
                    }
                    results += 1;
                }
                b'f' => {
                    if source == thread {
                        thread.push_value(-1 - results)?;
                    } else {
                        source.x_move(thread, 1)?;
                    }
                    results += 1;
                }
                b'a' => {
                    thread.push_integer(ar.nparams as i32)?;
                    thread.push_boolean(i32::from(ar.is_vararg))?;
                    results += 2;
                }
                _ => {
                    if source != thread {
                        source.restore_top(source_top);
                    }
                    return thread
                        .lua_arg_error(arg + 2, "invalid option")
                        .map_err(Into::into);
                }
            }
        }

        Ok(results as usize)
    }
}

/// `db_traceback`
fn db_traceback(ctx: NativeCallContext) -> NativeCallResult {
    let thread = ctx.raw_thread();
    unsafe {
        let (source, arg) = get_thread(thread);
        let source = source.as_ref().unwrap_or(thread);
        let message = thread.opt_string(arg + 1)?.map(|value| value.as_bstr());
        let level = thread.opt_integer(arg + 2, if source == thread { 1 } else { 0 })?;
        if level < 0 {
            return thread
                .lua_arg_error(arg + 2, "level can't be negative")
                .map_err(Into::into);
        }

        thread.traceback(Some(source), message, level)?;
        Ok(1)
    }
}

impl Thread {
    /// `luaopen_debug`
    pub unsafe fn open_debug(&self) -> NativeCallResult {
        unsafe { self.register(Some(super::LUA_DBLIB_NAME), &DEBUG_LIB[..])? };
        Ok(1)
    }
}