1use std::borrow::Cow;
9use std::collections::HashMap;
10use std::ffi::CString;
11use std::io::Result as IoResult;
12use std::panic::Location;
13use std::path::{Path, PathBuf};
14
15use crate::error::{Error, Result};
16use crate::function::Function;
17use crate::state::{Lua, WeakLua};
18use crate::table::Table;
19use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti};
20use crate::value::Value;
21
22pub trait AsChunk {
26 fn name(&self) -> Option<String> {
30 None
31 }
32
33 fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
37 let _lua = lua; Ok(None)
39 }
40
41 fn mode(&self) -> Option<ChunkMode> {
43 None
44 }
45
46 fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
48 where
49 Self: 'a;
50}
51
52impl AsChunk for &str {
53 fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
54 where
55 Self: 'a,
56 {
57 Ok(Cow::Borrowed(self.as_bytes()))
58 }
59}
60
61impl AsChunk for String {
62 fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
63 Ok(Cow::Owned(self.clone().into_bytes()))
64 }
65}
66
67impl AsChunk for &String {
68 fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
69 where
70 Self: 'a,
71 {
72 Ok(Cow::Borrowed(self.as_bytes()))
73 }
74}
75
76impl AsChunk for &[u8] {
77 fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
78 where
79 Self: 'a,
80 {
81 Ok(Cow::Borrowed(self))
82 }
83}
84
85impl AsChunk for Vec<u8> {
86 fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
87 Ok(Cow::Owned(self.clone()))
88 }
89}
90
91impl AsChunk for &Vec<u8> {
92 fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
93 where
94 Self: 'a,
95 {
96 Ok(Cow::Borrowed(self))
97 }
98}
99
100impl AsChunk for &Path {
101 fn name(&self) -> Option<String> {
102 Some(format!("@{}", self.display()))
103 }
104
105 fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
106 std::fs::read(self).map(Cow::Owned)
107 }
108}
109
110impl AsChunk for PathBuf {
111 fn name(&self) -> Option<String> {
112 Some(format!("@{}", self.display()))
113 }
114
115 fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
116 std::fs::read(self).map(Cow::Owned)
117 }
118}
119
120impl<C: AsChunk + ?Sized> AsChunk for Box<C> {
121 fn name(&self) -> Option<String> {
122 (**self).name()
123 }
124
125 fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
126 (**self).environment(lua)
127 }
128
129 fn mode(&self) -> Option<ChunkMode> {
130 (**self).mode()
131 }
132
133 fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
134 where
135 Self: 'a,
136 {
137 (**self).source()
138 }
139}
140
141#[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"]
143pub struct Chunk<'a> {
144 pub(crate) lua: WeakLua,
145 pub(crate) name: String,
146 pub(crate) env: Result<Option<Table>>,
147 pub(crate) mode: Option<ChunkMode>,
148 pub(crate) source: IoResult<Cow<'a, [u8]>>,
149 #[cfg(feature = "luau")]
150 pub(crate) compiler: Option<Compiler>,
151}
152
153#[derive(Clone, Copy, Debug, PartialEq, Eq)]
155pub enum ChunkMode {
156 Text,
157 Binary,
158}
159
160#[cfg(any(feature = "luau", doc))]
162#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
163#[non_exhaustive]
164#[derive(Clone, Debug)]
165pub enum CompileConstant {
166 Nil,
167 Boolean(bool),
168 Number(crate::Number),
169 Vector(crate::Vector),
170 String(String),
171}
172
173#[cfg(any(feature = "luau", doc))]
174impl From<bool> for CompileConstant {
175 fn from(b: bool) -> Self {
176 CompileConstant::Boolean(b)
177 }
178}
179
180#[cfg(any(feature = "luau", doc))]
181impl From<crate::Number> for CompileConstant {
182 fn from(n: crate::Number) -> Self {
183 CompileConstant::Number(n)
184 }
185}
186
187#[cfg(any(feature = "luau", doc))]
188impl From<crate::Vector> for CompileConstant {
189 fn from(v: crate::Vector) -> Self {
190 CompileConstant::Vector(v)
191 }
192}
193
194#[cfg(any(feature = "luau", doc))]
195impl From<&str> for CompileConstant {
196 fn from(s: &str) -> Self {
197 CompileConstant::String(s.to_owned())
198 }
199}
200
201#[cfg(any(feature = "luau", doc))]
202type LibraryMemberConstantMap = HashMap<(String, String), CompileConstant>;
203
204#[cfg(any(feature = "luau", doc))]
206#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
207#[derive(Clone, Debug)]
208pub struct Compiler {
209 optimization_level: u8,
210 debug_level: u8,
211 type_info_level: u8,
212 coverage_level: u8,
213 vector_lib: Option<String>,
214 vector_ctor: Option<String>,
215 vector_type: Option<String>,
216 mutable_globals: Vec<String>,
217 userdata_types: Vec<String>,
218 libraries_with_known_members: Vec<String>,
219 library_constants: Option<LibraryMemberConstantMap>,
220 disabled_builtins: Vec<String>,
221}
222
223#[cfg(any(feature = "luau", doc))]
224impl Default for Compiler {
225 fn default() -> Self {
226 const { Self::new() }
227 }
228}
229
230#[cfg(any(feature = "luau", doc))]
231impl Compiler {
232 pub const fn new() -> Self {
234 Compiler {
236 optimization_level: 1,
237 debug_level: 1,
238 type_info_level: 0,
239 coverage_level: 0,
240 vector_lib: None,
241 vector_ctor: None,
242 vector_type: None,
243 mutable_globals: Vec::new(),
244 userdata_types: Vec::new(),
245 libraries_with_known_members: Vec::new(),
246 library_constants: None,
247 disabled_builtins: Vec::new(),
248 }
249 }
250
251 #[must_use]
258 pub const fn set_optimization_level(mut self, level: u8) -> Self {
259 self.optimization_level = level;
260 self
261 }
262
263 #[must_use]
270 pub const fn set_debug_level(mut self, level: u8) -> Self {
271 self.debug_level = level;
272 self
273 }
274
275 #[must_use]
281 pub const fn set_type_info_level(mut self, level: u8) -> Self {
282 self.type_info_level = level;
283 self
284 }
285
286 #[must_use]
293 pub const fn set_coverage_level(mut self, level: u8) -> Self {
294 self.coverage_level = level;
295 self
296 }
297
298 #[doc(hidden)]
303 #[must_use]
304 pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
305 let ctor = ctor.into();
306 let lib_ctor = ctor.split_once('.');
307 self.vector_lib = lib_ctor.as_ref().map(|&(lib, _)| lib.to_owned());
308 self.vector_ctor = (lib_ctor.as_ref())
309 .map(|&(_, ctor)| ctor.to_owned())
310 .or(Some(ctor));
311 self
312 }
313
314 #[doc(hidden)]
316 #[must_use]
317 pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self {
318 self.vector_type = Some(r#type.into());
319 self
320 }
321
322 #[must_use]
326 pub fn add_mutable_global(mut self, global: impl Into<String>) -> Self {
327 self.mutable_globals.push(global.into());
328 self
329 }
330
331 #[must_use]
335 pub fn set_mutable_globals<S: Into<String>>(mut self, globals: impl IntoIterator<Item = S>) -> Self {
336 self.mutable_globals = globals.into_iter().map(|s| s.into()).collect();
337 self
338 }
339
340 #[must_use]
342 pub fn add_userdata_type(mut self, r#type: impl Into<String>) -> Self {
343 self.userdata_types.push(r#type.into());
344 self
345 }
346
347 #[must_use]
349 pub fn set_userdata_types<S: Into<String>>(mut self, types: impl IntoIterator<Item = S>) -> Self {
350 self.userdata_types = types.into_iter().map(|s| s.into()).collect();
351 self
352 }
353
354 #[must_use]
362 pub fn add_library_constant(
363 mut self,
364 name: impl AsRef<str>,
365 r#const: impl Into<CompileConstant>,
366 ) -> Self {
367 let Some((lib, member)) = name.as_ref().split_once('.') else {
368 return self;
369 };
370 let (lib, member) = (lib.to_owned(), member.to_owned());
371
372 if !self.libraries_with_known_members.contains(&lib) {
373 self.libraries_with_known_members.push(lib.clone());
374 }
375 self.library_constants
376 .get_or_insert_default()
377 .insert((lib, member), r#const.into());
378 self
379 }
380
381 #[must_use]
383 pub fn add_disabled_builtin(mut self, builtin: impl Into<String>) -> Self {
384 self.disabled_builtins.push(builtin.into());
385 self
386 }
387
388 #[must_use]
390 pub fn set_disabled_builtins<S: Into<String>>(mut self, builtins: impl IntoIterator<Item = S>) -> Self {
391 self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect();
392 self
393 }
394
395 pub fn compile(&self, source: impl AsRef<[u8]>) -> Result<Vec<u8>> {
399 use std::cell::RefCell;
400 use std::ffi::CStr;
401 use std::os::raw::{c_char, c_int};
402 use std::ptr;
403
404 let vector_lib = (self.vector_lib.as_deref()).and_then(|lib| CString::new(lib).ok());
405 let vector_lib = vector_lib.as_ref();
406 let vector_ctor = (self.vector_ctor.as_deref()).and_then(|ctor| CString::new(ctor).ok());
407 let vector_ctor = vector_ctor.as_ref();
408 let vector_type = (self.vector_type.as_deref()).and_then(|t| CString::new(t).ok());
409 let vector_type = vector_type.as_ref();
410
411 macro_rules! vec2cstring_ptr {
412 ($name:ident, $name_ptr:ident) => {
413 let $name = self
414 .$name
415 .iter()
416 .map(|name| CString::new(name.clone()).ok())
417 .collect::<Option<Vec<_>>>()
418 .unwrap_or_default();
419 let mut $name = $name.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
420 let mut $name_ptr = ptr::null();
421 if !$name.is_empty() {
422 $name.push(ptr::null());
423 $name_ptr = $name.as_ptr();
424 }
425 };
426 }
427
428 vec2cstring_ptr!(mutable_globals, mutable_globals_ptr);
429 vec2cstring_ptr!(userdata_types, userdata_types_ptr);
430 vec2cstring_ptr!(libraries_with_known_members, libraries_with_known_members_ptr);
431 vec2cstring_ptr!(disabled_builtins, disabled_builtins_ptr);
432
433 thread_local! {
434 static LIBRARY_MEMBER_CONSTANT_MAP: RefCell<LibraryMemberConstantMap> = Default::default();
435 }
436
437 #[cfg(feature = "luau")]
438 unsafe extern "C-unwind" fn library_member_constant_callback(
439 library: *const c_char,
440 member: *const c_char,
441 constant: *mut ffi::lua_CompileConstant,
442 ) {
443 let library = CStr::from_ptr(library).to_string_lossy();
444 let member = CStr::from_ptr(member).to_string_lossy();
445 LIBRARY_MEMBER_CONSTANT_MAP.with_borrow(|map| {
446 if let Some(cons) = map.get(&(library.to_string(), member.to_string())) {
447 match cons {
448 CompileConstant::Nil => ffi::luau_set_compile_constant_nil(constant),
449 CompileConstant::Boolean(b) => {
450 ffi::luau_set_compile_constant_boolean(constant, *b as c_int)
451 }
452 CompileConstant::Number(n) => ffi::luau_set_compile_constant_number(constant, *n),
453 CompileConstant::Vector(v) => {
454 #[cfg(not(feature = "luau-vector4"))]
455 ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), 0.0);
456 #[cfg(feature = "luau-vector4")]
457 ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), v.w());
458 }
459 CompileConstant::String(s) => ffi::luau_set_compile_constant_string(
460 constant,
461 s.as_ptr() as *const c_char,
462 s.len(),
463 ),
464 }
465 }
466 })
467 }
468
469 let bytecode = unsafe {
470 let mut options = ffi::lua_CompileOptions::default();
471 options.optimizationLevel = self.optimization_level as c_int;
472 options.debugLevel = self.debug_level as c_int;
473 options.typeInfoLevel = self.type_info_level as c_int;
474 options.coverageLevel = self.coverage_level as c_int;
475 options.vectorLib = vector_lib.map_or(ptr::null(), |s| s.as_ptr());
476 options.vectorCtor = vector_ctor.map_or(ptr::null(), |s| s.as_ptr());
477 options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr());
478 options.mutableGlobals = mutable_globals_ptr;
479 options.userdataTypes = userdata_types_ptr;
480 options.librariesWithKnownMembers = libraries_with_known_members_ptr;
481 if let Some(map) = self.library_constants.as_ref()
482 && !self.libraries_with_known_members.is_empty()
483 {
484 LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone());
485 options.libraryMemberConstantCallback = Some(library_member_constant_callback);
486 }
487 options.disabledBuiltins = disabled_builtins_ptr;
488 ffi::luau_compile(source.as_ref(), options)
489 };
490
491 if bytecode.first() == Some(&0) {
492 let message = String::from_utf8_lossy(&bytecode[2..]).into_owned();
495 return Err(Error::SyntaxError {
496 incomplete_input: message.ends_with("<eof>"),
497 message,
498 });
499 }
500
501 Ok(bytecode)
502 }
503}
504
505impl Chunk<'_> {
506 pub fn name(&self) -> &str {
508 &self.name
509 }
510
511 pub fn set_name(mut self, name: impl Into<String>) -> Self {
518 self.name = name.into();
519 self
520 }
521
522 pub fn environment(&self) -> Option<&Table> {
524 self.env.as_ref().ok()?.as_ref()
525 }
526
527 pub fn set_environment(mut self, env: Table) -> Self {
539 self.env = Ok(Some(env));
540 self
541 }
542
543 pub fn mode(&self) -> ChunkMode {
545 self.detect_mode()
546 }
547
548 pub fn set_mode(mut self, mode: ChunkMode) -> Self {
553 self.mode = Some(mode);
554 self
555 }
556
557 #[cfg(any(feature = "luau", doc))]
561 #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
562 pub fn set_compiler(mut self, compiler: Compiler) -> Self {
563 self.compiler = Some(compiler);
564 self
565 }
566
567 pub fn exec(self) -> Result<()> {
571 self.call(())
572 }
573
574 #[cfg(feature = "async")]
580 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
581 pub async fn exec_async(self) -> Result<()> {
582 self.call_async(()).await
583 }
584
585 pub fn eval<R: FromLuaMulti>(self) -> Result<R> {
591 if self.detect_mode() == ChunkMode::Binary {
596 self.call(())
597 } else if let Ok(function) = self.to_expression() {
598 function.call(())
599 } else {
600 self.call(())
601 }
602 }
603
604 #[cfg(feature = "async")]
610 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
611 pub async fn eval_async<R>(self) -> Result<R>
612 where
613 R: FromLuaMulti,
614 {
615 if self.detect_mode() == ChunkMode::Binary {
616 self.call_async(()).await
617 } else if let Ok(function) = self.to_expression() {
618 function.call_async(()).await
619 } else {
620 self.call_async(()).await
621 }
622 }
623
624 pub fn call<R: FromLuaMulti>(self, args: impl IntoLuaMulti) -> Result<R> {
628 self.into_function()?.call(args)
629 }
630
631 #[cfg(feature = "async")]
637 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
638 pub async fn call_async<R>(self, args: impl IntoLuaMulti) -> Result<R>
639 where
640 R: FromLuaMulti,
641 {
642 self.into_function()?.call_async(args).await
643 }
644
645 #[cfg_attr(not(feature = "luau"), allow(unused_mut))]
649 pub fn into_function(mut self) -> Result<Function> {
650 #[cfg(feature = "luau")]
651 if self.compiler.is_some() {
652 self.compile();
654 }
655
656 let name = Self::convert_name(self.name)?;
657 self.lua
658 .lock()
659 .load_chunk(Some(&name), self.env?.as_ref(), self.mode, self.source?.as_ref())
660 }
661
662 fn compile(&mut self) {
666 if let Ok(ref source) = self.source
667 && self.detect_mode() == ChunkMode::Text
668 {
669 #[cfg(feature = "luau")]
670 if let Ok(data) = self.compiler.get_or_insert_default().compile(source) {
671 self.source = Ok(Cow::Owned(data));
672 self.mode = Some(ChunkMode::Binary);
673 }
674 #[cfg(not(feature = "luau"))]
675 if let Ok(func) = self.lua.lock().load_chunk(None, None, None, source.as_ref()) {
676 let data = func.dump(false);
677 self.source = Ok(Cow::Owned(data));
678 self.mode = Some(ChunkMode::Binary);
679 }
680 }
681 }
682
683 pub(crate) fn try_cache(mut self) -> Self {
687 struct ChunksCache(HashMap<Vec<u8>, Vec<u8>>);
688
689 let mut text_source = None;
691 if let Ok(ref source) = self.source
692 && self.detect_mode() == ChunkMode::Text
693 {
694 let lua = self.lua.lock();
695 if let Some(cache) = lua.priv_app_data_ref::<ChunksCache>()
696 && let Some(data) = cache.0.get(source.as_ref())
697 {
698 self.source = Ok(Cow::Owned(data.clone()));
699 self.mode = Some(ChunkMode::Binary);
700 return self;
701 }
702 text_source = Some(source.as_ref().to_vec());
703 }
704
705 if let Some(text_source) = text_source {
707 self.compile();
708 if let Ok(ref binary_source) = self.source
709 && self.detect_mode() == ChunkMode::Binary
710 {
711 let lua = self.lua.lock();
712 if let Some(mut cache) = lua.priv_app_data_mut::<ChunksCache>() {
713 cache.0.insert(text_source, binary_source.to_vec());
714 } else {
715 let mut cache = ChunksCache(HashMap::new());
716 cache.0.insert(text_source, binary_source.to_vec());
717 lua.set_priv_app_data(cache);
718 }
719 }
720 }
721
722 self
723 }
724
725 fn to_expression(&self) -> Result<Function> {
726 let source = self.source.as_ref();
728 let source = source.map_err(Error::runtime)?;
729 let source = Self::expression_source(source);
730 #[cfg(feature = "luau")]
732 let source = self
733 .compiler
734 .as_ref()
735 .map(|c| c.compile(&source))
736 .transpose()?
737 .unwrap_or(source);
738
739 let name = Self::convert_name(self.name.clone())?;
740 let env = self.env.as_ref().map_err(Error::clone)?.as_ref();
741 self.lua.lock().load_chunk(Some(&name), env, None, &source)
742 }
743
744 fn detect_mode(&self) -> ChunkMode {
745 if let Some(mode) = self.mode {
746 return mode;
747 }
748 if let Ok(source) = &self.source {
749 #[cfg(not(feature = "luau"))]
750 if source.starts_with(ffi::LUA_SIGNATURE) {
751 return ChunkMode::Binary;
752 }
753 #[cfg(feature = "luau")]
754 if unsafe { ffi::luaL_isbytecode(source.as_ptr().cast(), source.len()) } {
755 return ChunkMode::Binary;
756 }
757 }
758 ChunkMode::Text
759 }
760
761 fn convert_name(name: String) -> Result<CString> {
762 CString::new(name).map_err(|err| Error::runtime(format!("invalid name: {err}")))
763 }
764
765 fn expression_source(source: &[u8]) -> Vec<u8> {
766 let mut buf = Vec::with_capacity(b"return ".len() + source.len());
767 buf.extend(b"return ");
768 buf.extend(source);
769 buf
770 }
771}
772
773struct WrappedChunk<T: AsChunk> {
774 chunk: T,
775 caller: &'static Location<'static>,
776}
777
778impl Chunk<'_> {
779 #[track_caller]
784 pub fn wrap(chunk: impl AsChunk) -> impl IntoLua {
785 WrappedChunk {
786 chunk,
787 caller: Location::caller(),
788 }
789 }
790}
791
792impl<T: AsChunk> IntoLua for WrappedChunk<T> {
793 fn into_lua(self, lua: &Lua) -> Result<Value> {
794 lua.load_with_location(self.chunk, self.caller)
795 .into_function()
796 .map(Value::Function)
797 }
798}