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.clone();
405 let vector_lib = vector_lib.and_then(|lib| CString::new(lib).ok());
406 let vector_lib = vector_lib.as_ref();
407 let vector_ctor = self.vector_ctor.clone();
408 let vector_ctor = vector_ctor.and_then(|ctor| CString::new(ctor).ok());
409 let vector_ctor = vector_ctor.as_ref();
410 let vector_type = self.vector_type.clone();
411 let vector_type = vector_type.and_then(|t| CString::new(t).ok());
412 let vector_type = vector_type.as_ref();
413
414 macro_rules! vec2cstring_ptr {
415 ($name:ident, $name_ptr:ident) => {
416 let $name = self
417 .$name
418 .iter()
419 .map(|name| CString::new(name.clone()).ok())
420 .collect::<Option<Vec<_>>>()
421 .unwrap_or_default();
422 let mut $name = $name.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
423 let mut $name_ptr = ptr::null();
424 if !$name.is_empty() {
425 $name.push(ptr::null());
426 $name_ptr = $name.as_ptr();
427 }
428 };
429 }
430
431 vec2cstring_ptr!(mutable_globals, mutable_globals_ptr);
432 vec2cstring_ptr!(userdata_types, userdata_types_ptr);
433 vec2cstring_ptr!(libraries_with_known_members, libraries_with_known_members_ptr);
434 vec2cstring_ptr!(disabled_builtins, disabled_builtins_ptr);
435
436 thread_local! {
437 static LIBRARY_MEMBER_CONSTANT_MAP: RefCell<LibraryMemberConstantMap> = Default::default();
438 }
439
440 #[cfg(feature = "luau")]
441 unsafe extern "C-unwind" fn library_member_constant_callback(
442 library: *const c_char,
443 member: *const c_char,
444 constant: *mut ffi::lua_CompileConstant,
445 ) {
446 let library = CStr::from_ptr(library).to_string_lossy();
447 let member = CStr::from_ptr(member).to_string_lossy();
448 LIBRARY_MEMBER_CONSTANT_MAP.with_borrow(|map| {
449 if let Some(cons) = map.get(&(library.to_string(), member.to_string())) {
450 match cons {
451 CompileConstant::Nil => ffi::luau_set_compile_constant_nil(constant),
452 CompileConstant::Boolean(b) => {
453 ffi::luau_set_compile_constant_boolean(constant, *b as c_int)
454 }
455 CompileConstant::Number(n) => ffi::luau_set_compile_constant_number(constant, *n),
456 CompileConstant::Vector(v) => {
457 #[cfg(not(feature = "luau-vector4"))]
458 ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), 0.0);
459 #[cfg(feature = "luau-vector4")]
460 ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), v.w());
461 }
462 CompileConstant::String(s) => ffi::luau_set_compile_constant_string(
463 constant,
464 s.as_ptr() as *const c_char,
465 s.len(),
466 ),
467 }
468 }
469 })
470 }
471
472 let bytecode = unsafe {
473 let mut options = ffi::lua_CompileOptions::default();
474 options.optimizationLevel = self.optimization_level as c_int;
475 options.debugLevel = self.debug_level as c_int;
476 options.typeInfoLevel = self.type_info_level as c_int;
477 options.coverageLevel = self.coverage_level as c_int;
478 options.vectorLib = vector_lib.map_or(ptr::null(), |s| s.as_ptr());
479 options.vectorCtor = vector_ctor.map_or(ptr::null(), |s| s.as_ptr());
480 options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr());
481 options.mutableGlobals = mutable_globals_ptr;
482 options.userdataTypes = userdata_types_ptr;
483 options.librariesWithKnownMembers = libraries_with_known_members_ptr;
484 if let Some(map) = self.library_constants.as_ref()
485 && !self.libraries_with_known_members.is_empty()
486 {
487 LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone());
488 options.libraryMemberConstantCallback = Some(library_member_constant_callback);
489 }
490 options.disabledBuiltins = disabled_builtins_ptr;
491 ffi::luau_compile(source.as_ref(), options)
492 };
493
494 if bytecode.first() == Some(&0) {
495 let message = String::from_utf8_lossy(&bytecode[2..]).into_owned();
498 return Err(Error::SyntaxError {
499 incomplete_input: message.ends_with("<eof>"),
500 message,
501 });
502 }
503
504 Ok(bytecode)
505 }
506}
507
508impl Chunk<'_> {
509 pub fn name(&self) -> &str {
511 &self.name
512 }
513
514 pub fn set_name(mut self, name: impl Into<String>) -> Self {
521 self.name = name.into();
522 self
523 }
524
525 pub fn environment(&self) -> Option<&Table> {
527 self.env.as_ref().ok()?.as_ref()
528 }
529
530 pub fn set_environment(mut self, env: Table) -> Self {
542 self.env = Ok(Some(env));
543 self
544 }
545
546 pub fn mode(&self) -> ChunkMode {
548 self.detect_mode()
549 }
550
551 pub fn set_mode(mut self, mode: ChunkMode) -> Self {
556 self.mode = Some(mode);
557 self
558 }
559
560 #[cfg(any(feature = "luau", doc))]
564 #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
565 pub fn set_compiler(mut self, compiler: Compiler) -> Self {
566 self.compiler = Some(compiler);
567 self
568 }
569
570 pub fn exec(self) -> Result<()> {
574 self.call(())
575 }
576
577 #[cfg(feature = "async")]
583 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
584 pub async fn exec_async(self) -> Result<()> {
585 self.call_async(()).await
586 }
587
588 pub fn eval<R: FromLuaMulti>(self) -> Result<R> {
594 if self.detect_mode() == ChunkMode::Binary {
599 self.call(())
600 } else if let Ok(function) = self.to_expression() {
601 function.call(())
602 } else {
603 self.call(())
604 }
605 }
606
607 #[cfg(feature = "async")]
613 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
614 pub async fn eval_async<R>(self) -> Result<R>
615 where
616 R: FromLuaMulti,
617 {
618 if self.detect_mode() == ChunkMode::Binary {
619 self.call_async(()).await
620 } else if let Ok(function) = self.to_expression() {
621 function.call_async(()).await
622 } else {
623 self.call_async(()).await
624 }
625 }
626
627 pub fn call<R: FromLuaMulti>(self, args: impl IntoLuaMulti) -> Result<R> {
631 self.into_function()?.call(args)
632 }
633
634 #[cfg(feature = "async")]
640 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
641 pub async fn call_async<R>(self, args: impl IntoLuaMulti) -> Result<R>
642 where
643 R: FromLuaMulti,
644 {
645 self.into_function()?.call_async(args).await
646 }
647
648 #[cfg_attr(not(feature = "luau"), allow(unused_mut))]
652 pub fn into_function(mut self) -> Result<Function> {
653 #[cfg(feature = "luau")]
654 if self.compiler.is_some() {
655 self.compile();
657 }
658
659 let name = Self::convert_name(self.name)?;
660 self.lua
661 .lock()
662 .load_chunk(Some(&name), self.env?.as_ref(), self.mode, self.source?.as_ref())
663 }
664
665 fn compile(&mut self) {
669 if let Ok(ref source) = self.source
670 && self.detect_mode() == ChunkMode::Text
671 {
672 #[cfg(feature = "luau")]
673 if let Ok(data) = self.compiler.get_or_insert_default().compile(source) {
674 self.source = Ok(Cow::Owned(data));
675 self.mode = Some(ChunkMode::Binary);
676 }
677 #[cfg(not(feature = "luau"))]
678 if let Ok(func) = self.lua.lock().load_chunk(None, None, None, source.as_ref()) {
679 let data = func.dump(false);
680 self.source = Ok(Cow::Owned(data));
681 self.mode = Some(ChunkMode::Binary);
682 }
683 }
684 }
685
686 pub(crate) fn try_cache(mut self) -> Self {
690 struct ChunksCache(HashMap<Vec<u8>, Vec<u8>>);
691
692 let mut text_source = None;
694 if let Ok(ref source) = self.source
695 && self.detect_mode() == ChunkMode::Text
696 {
697 let lua = self.lua.lock();
698 if let Some(cache) = lua.priv_app_data_ref::<ChunksCache>()
699 && let Some(data) = cache.0.get(source.as_ref())
700 {
701 self.source = Ok(Cow::Owned(data.clone()));
702 self.mode = Some(ChunkMode::Binary);
703 return self;
704 }
705 text_source = Some(source.as_ref().to_vec());
706 }
707
708 if let Some(text_source) = text_source {
710 self.compile();
711 if let Ok(ref binary_source) = self.source
712 && self.detect_mode() == ChunkMode::Binary
713 {
714 let lua = self.lua.lock();
715 if let Some(mut cache) = lua.priv_app_data_mut::<ChunksCache>() {
716 cache.0.insert(text_source, binary_source.to_vec());
717 } else {
718 let mut cache = ChunksCache(HashMap::new());
719 cache.0.insert(text_source, binary_source.to_vec());
720 lua.set_priv_app_data(cache);
721 }
722 }
723 }
724
725 self
726 }
727
728 fn to_expression(&self) -> Result<Function> {
729 let source = self.source.as_ref();
731 let source = source.map_err(Error::runtime)?;
732 let source = Self::expression_source(source);
733 #[cfg(feature = "luau")]
735 let source = self
736 .compiler
737 .as_ref()
738 .map(|c| c.compile(&source))
739 .transpose()?
740 .unwrap_or(source);
741
742 let name = Self::convert_name(self.name.clone())?;
743 let env = match &self.env {
744 Ok(Some(env)) => Some(env),
745 Ok(None) => None,
746 Err(err) => return Err(err.clone()),
747 };
748 self.lua.lock().load_chunk(Some(&name), env, None, &source)
749 }
750
751 fn detect_mode(&self) -> ChunkMode {
752 if let Some(mode) = self.mode {
753 return mode;
754 }
755 if let Ok(source) = &self.source {
756 #[cfg(not(feature = "luau"))]
757 if source.starts_with(ffi::LUA_SIGNATURE) {
758 return ChunkMode::Binary;
759 }
760 #[cfg(feature = "luau")]
761 if *source.first().unwrap_or(&u8::MAX) < b'\n' {
762 return ChunkMode::Binary;
763 }
764 }
765 ChunkMode::Text
766 }
767
768 fn convert_name(name: String) -> Result<CString> {
769 CString::new(name).map_err(|err| Error::runtime(format!("invalid name: {err}")))
770 }
771
772 fn expression_source(source: &[u8]) -> Vec<u8> {
773 let mut buf = Vec::with_capacity(b"return ".len() + source.len());
774 buf.extend(b"return ");
775 buf.extend(source);
776 buf
777 }
778}
779
780struct WrappedChunk<T: AsChunk> {
781 chunk: T,
782 caller: &'static Location<'static>,
783}
784
785impl Chunk<'_> {
786 #[track_caller]
791 pub fn wrap(chunk: impl AsChunk) -> impl IntoLua {
792 WrappedChunk {
793 chunk,
794 caller: Location::caller(),
795 }
796 }
797}
798
799impl<T: AsChunk> IntoLua for WrappedChunk<T> {
800 fn into_lua(self, lua: &Lua) -> Result<Value> {
801 lua.load_with_location(self.chunk, self.caller)
802 .into_function()
803 .map(Value::Function)
804 }
805}