luaur_web/functions/execute_script.rs
1//! `extern "C" const char* executeScript(const char* source)`
2//! (`CLI/src/Web.cpp:184-208`).
3//!
4//! The wasm entry point: enables every `Luau*` bool fast flag, spins up a fresh
5//! sandboxed Lua state, runs the script via [`run_code`], and returns the result
6//! string (or null when empty). The C++ caches the result in a function-`static`
7//! `std::string` so the returned pointer outlives the call; the Rust analog is a
8//! thread-local `CString` whose pointer is returned.
9
10use crate::functions::run_code::run_code;
11use crate::functions::setup_state::setup_state;
12use core::cell::RefCell;
13use core::ffi::{c_char, CStr};
14use std::ffi::CString;
15
16use luaur_common::set_luau_bool_flags;
17use luaur_vm::functions::lua_close::lua_close;
18use luaur_vm::functions::lua_l_newstate::lua_l_newstate;
19use luaur_vm::functions::lua_l_sandboxthread::lua_l_sandboxthread;
20use luaur_vm::type_aliases::lua_state::lua_State;
21
22thread_local! {
23 /// Mirror of the C++ `static std::string result;` — keeps the returned
24 /// C string alive after the call returns.
25 static RESULT: RefCell<Option<CString>> = const { RefCell::new(None) };
26}
27
28/// # Safety
29/// `source` must be a valid, NUL-terminated C string (the wasm/JS caller's
30/// contract), or null.
31#[cfg_attr(not(test), no_mangle)]
32pub unsafe extern "C" fn execute_script(source: *const c_char) -> *const c_char {
33 // setup flags:
34 // for (FValue<bool>* flag = FValue<bool>::list; flag; flag = flag->next)
35 // if (strncmp(flag->name, "Luau", 4) == 0)
36 // flag->value = true;
37 //
38 // In this port the per-type intrusive `FValue<bool>::list` is never
39 // populated (Rust statics cannot self-register in a ctor, and no startup
40 // `register()` runs), so the C++ name-prefix walk is expressed through the
41 // crate's public flag-enabling analog `set_luau_bool_flags`, which turns on
42 // every non-`Debug` bool FastFlag (the `Luau*` flags dominate that set).
43 set_luau_bool_flags(true);
44
45 // create new state: unique_ptr<lua_State, lua_close> globalState(luaL_newstate(), lua_close);
46 let l: *mut lua_State = lua_l_newstate();
47
48 // setup state
49 setup_state(l);
50
51 // sandbox thread
52 lua_l_sandboxthread(l);
53
54 // run code + collect error
55 let source_str = if source.is_null() {
56 ""
57 } else {
58 core::str::from_utf8_unchecked(CStr::from_ptr(source).to_bytes())
59 };
60 let result = run_code(l, source_str);
61
62 // unique_ptr destructor: lua_close(L)
63 lua_close(l);
64
65 if result.is_empty() {
66 // cache empty (drop any prior held string) and return nullptr.
67 RESULT.with(|r| *r.borrow_mut() = None);
68 return core::ptr::null();
69 }
70
71 RESULT.with(|r| {
72 let cstring = CString::new(result).unwrap_or_default();
73 let ptr = cstring.as_ptr();
74 *r.borrow_mut() = Some(cstring);
75 ptr
76 })
77}