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#[derive(Clone, Debug)]
164pub enum CompileConstant {
165 Nil,
166 Boolean(bool),
167 Number(crate::Number),
168 Vector(crate::Vector),
169 String(String),
170}
171
172#[cfg(any(feature = "luau", doc))]
173impl From<bool> for CompileConstant {
174 fn from(b: bool) -> Self {
175 CompileConstant::Boolean(b)
176 }
177}
178
179#[cfg(any(feature = "luau", doc))]
180impl From<crate::Number> for CompileConstant {
181 fn from(n: crate::Number) -> Self {
182 CompileConstant::Number(n)
183 }
184}
185
186#[cfg(any(feature = "luau", doc))]
187impl From<crate::Vector> for CompileConstant {
188 fn from(v: crate::Vector) -> Self {
189 CompileConstant::Vector(v)
190 }
191}
192
193#[cfg(any(feature = "luau", doc))]
194impl From<&str> for CompileConstant {
195 fn from(s: &str) -> Self {
196 CompileConstant::String(s.to_owned())
197 }
198}
199
200#[cfg(any(feature = "luau", doc))]
201type LibraryMemberConstantMap = HashMap<(String, String), CompileConstant>;
202
203#[cfg(any(feature = "luau", doc))]
205#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
206#[derive(Clone, Debug)]
207pub struct Compiler {
208 optimization_level: u8,
209 debug_level: u8,
210 type_info_level: u8,
211 coverage_level: u8,
212 vector_lib: Option<String>,
213 vector_ctor: Option<String>,
214 vector_type: Option<String>,
215 mutable_globals: Vec<String>,
216 userdata_types: Vec<String>,
217 libraries_with_known_members: Vec<String>,
218 library_constants: Option<LibraryMemberConstantMap>,
219 disabled_builtins: Vec<String>,
220}
221
222#[cfg(any(feature = "luau", doc))]
223impl Default for Compiler {
224 fn default() -> Self {
225 const { Self::new() }
226 }
227}
228
229#[cfg(any(feature = "luau", doc))]
230impl Compiler {
231 pub const fn new() -> Self {
233 Compiler {
235 optimization_level: 1,
236 debug_level: 1,
237 type_info_level: 0,
238 coverage_level: 0,
239 vector_lib: None,
240 vector_ctor: None,
241 vector_type: None,
242 mutable_globals: Vec::new(),
243 userdata_types: Vec::new(),
244 libraries_with_known_members: Vec::new(),
245 library_constants: None,
246 disabled_builtins: Vec::new(),
247 }
248 }
249
250 #[must_use]
257 pub const fn set_optimization_level(mut self, level: u8) -> Self {
258 self.optimization_level = level;
259 self
260 }
261
262 #[must_use]
269 pub const fn set_debug_level(mut self, level: u8) -> Self {
270 self.debug_level = level;
271 self
272 }
273
274 #[must_use]
280 pub const fn set_type_info_level(mut self, level: u8) -> Self {
281 self.type_info_level = level;
282 self
283 }
284
285 #[must_use]
292 pub const fn set_coverage_level(mut self, level: u8) -> Self {
293 self.coverage_level = level;
294 self
295 }
296
297 #[doc(hidden)]
302 #[must_use]
303 pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
304 let ctor = ctor.into();
305 let lib_ctor = ctor.split_once('.');
306 self.vector_lib = lib_ctor.as_ref().map(|&(lib, _)| lib.to_owned());
307 self.vector_ctor = (lib_ctor.as_ref())
308 .map(|&(_, ctor)| ctor.to_owned())
309 .or(Some(ctor));
310 self
311 }
312
313 #[doc(hidden)]
315 #[must_use]
316 pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self {
317 self.vector_type = Some(r#type.into());
318 self
319 }
320
321 #[must_use]
325 pub fn add_mutable_global(mut self, global: impl Into<String>) -> Self {
326 self.mutable_globals.push(global.into());
327 self
328 }
329
330 #[must_use]
334 pub fn set_mutable_globals<S: Into<String>>(mut self, globals: impl IntoIterator<Item = S>) -> Self {
335 self.mutable_globals = globals.into_iter().map(|s| s.into()).collect();
336 self
337 }
338
339 #[must_use]
341 pub fn add_userdata_type(mut self, r#type: impl Into<String>) -> Self {
342 self.userdata_types.push(r#type.into());
343 self
344 }
345
346 #[must_use]
348 pub fn set_userdata_types<S: Into<String>>(mut self, types: impl IntoIterator<Item = S>) -> Self {
349 self.userdata_types = types.into_iter().map(|s| s.into()).collect();
350 self
351 }
352
353 #[must_use]
361 pub fn add_library_constant(
362 mut self,
363 name: impl AsRef<str>,
364 r#const: impl Into<CompileConstant>,
365 ) -> Self {
366 let Some((lib, member)) = name.as_ref().split_once('.') else {
367 return self;
368 };
369 let (lib, member) = (lib.to_owned(), member.to_owned());
370
371 if !self.libraries_with_known_members.contains(&lib) {
372 self.libraries_with_known_members.push(lib.clone());
373 }
374 self.library_constants
375 .get_or_insert_default()
376 .insert((lib, member), r#const.into());
377 self
378 }
379
380 #[must_use]
382 pub fn add_disabled_builtin(mut self, builtin: impl Into<String>) -> Self {
383 self.disabled_builtins.push(builtin.into());
384 self
385 }
386
387 #[must_use]
389 pub fn set_disabled_builtins<S: Into<String>>(mut self, builtins: impl IntoIterator<Item = S>) -> Self {
390 self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect();
391 self
392 }
393
394 pub fn compile(&self, source: impl AsRef<[u8]>) -> Result<Vec<u8>> {
398 use std::cell::RefCell;
399 use std::ffi::CStr;
400 use std::os::raw::{c_char, c_int};
401 use std::ptr;
402
403 let vector_lib = self.vector_lib.clone();
404 let vector_lib = vector_lib.and_then(|lib| CString::new(lib).ok());
405 let vector_lib = vector_lib.as_ref();
406 let vector_ctor = self.vector_ctor.clone();
407 let vector_ctor = vector_ctor.and_then(|ctor| CString::new(ctor).ok());
408 let vector_ctor = vector_ctor.as_ref();
409 let vector_type = self.vector_type.clone();
410 let vector_type = vector_type.and_then(|t| CString::new(t).ok());
411 let vector_type = vector_type.as_ref();
412
413 macro_rules! vec2cstring_ptr {
414 ($name:ident, $name_ptr:ident) => {
415 let $name = self
416 .$name
417 .iter()
418 .map(|name| CString::new(name.clone()).ok())
419 .collect::<Option<Vec<_>>>()
420 .unwrap_or_default();
421 let mut $name = $name.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
422 let mut $name_ptr = ptr::null();
423 if !$name.is_empty() {
424 $name.push(ptr::null());
425 $name_ptr = $name.as_ptr();
426 }
427 };
428 }
429
430 vec2cstring_ptr!(mutable_globals, mutable_globals_ptr);
431 vec2cstring_ptr!(userdata_types, userdata_types_ptr);
432 vec2cstring_ptr!(libraries_with_known_members, libraries_with_known_members_ptr);
433 vec2cstring_ptr!(disabled_builtins, disabled_builtins_ptr);
434
435 thread_local! {
436 static LIBRARY_MEMBER_CONSTANT_MAP: RefCell<LibraryMemberConstantMap> = Default::default();
437 }
438
439 #[cfg(feature = "luau")]
440 unsafe extern "C-unwind" fn library_member_constant_callback(
441 library: *const c_char,
442 member: *const c_char,
443 constant: *mut ffi::lua_CompileConstant,
444 ) {
445 let library = CStr::from_ptr(library).to_string_lossy();
446 let member = CStr::from_ptr(member).to_string_lossy();
447 LIBRARY_MEMBER_CONSTANT_MAP.with_borrow(|map| {
448 if let Some(cons) = map.get(&(library.to_string(), member.to_string())) {
449 match cons {
450 CompileConstant::Nil => ffi::luau_set_compile_constant_nil(constant),
451 CompileConstant::Boolean(b) => {
452 ffi::luau_set_compile_constant_boolean(constant, *b as c_int)
453 }
454 CompileConstant::Number(n) => ffi::luau_set_compile_constant_number(constant, *n),
455 CompileConstant::Vector(v) => {
456 #[cfg(not(feature = "luau-vector4"))]
457 ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), 0.0);
458 #[cfg(feature = "luau-vector4")]
459 ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), v.w());
460 }
461 CompileConstant::String(s) => ffi::luau_set_compile_constant_string(
462 constant,
463 s.as_ptr() as *const c_char,
464 s.len(),
465 ),
466 }
467 }
468 })
469 }
470
471 let bytecode = unsafe {
472 let mut options = ffi::lua_CompileOptions::default();
473 options.optimizationLevel = self.optimization_level as c_int;
474 options.debugLevel = self.debug_level as c_int;
475 options.typeInfoLevel = self.type_info_level as c_int;
476 options.coverageLevel = self.coverage_level as c_int;
477 options.vectorLib = vector_lib.map_or(ptr::null(), |s| s.as_ptr());
478 options.vectorCtor = vector_ctor.map_or(ptr::null(), |s| s.as_ptr());
479 options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr());
480 options.mutableGlobals = mutable_globals_ptr;
481 options.userdataTypes = userdata_types_ptr;
482 options.librariesWithKnownMembers = libraries_with_known_members_ptr;
483 if let Some(map) = self.library_constants.as_ref()
484 && !self.libraries_with_known_members.is_empty()
485 {
486 LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone());
487 options.libraryMemberConstantCallback = Some(library_member_constant_callback);
488 }
489 options.disabledBuiltins = disabled_builtins_ptr;
490 ffi::luau_compile(source.as_ref(), options)
491 };
492
493 if bytecode.first() == Some(&0) {
494 let message = String::from_utf8_lossy(&bytecode[2..]).into_owned();
497 return Err(Error::SyntaxError {
498 incomplete_input: message.ends_with("<eof>"),
499 message,
500 });
501 }
502
503 Ok(bytecode)
504 }
505}
506
507impl Chunk<'_> {
508 pub fn name(&self) -> &str {
510 &self.name
511 }
512
513 pub fn set_name(mut self, name: impl Into<String>) -> Self {
520 self.name = name.into();
521 self
522 }
523
524 pub fn environment(&self) -> Option<&Table> {
526 self.env.as_ref().ok()?.as_ref()
527 }
528
529 pub fn set_environment(mut self, env: Table) -> Self {
541 self.env = Ok(Some(env));
542 self
543 }
544
545 pub fn mode(&self) -> ChunkMode {
547 self.detect_mode()
548 }
549
550 pub fn set_mode(mut self, mode: ChunkMode) -> Self {
555 self.mode = Some(mode);
556 self
557 }
558
559 #[cfg(any(feature = "luau", doc))]
563 #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
564 pub fn set_compiler(mut self, compiler: Compiler) -> Self {
565 self.compiler = Some(compiler);
566 self
567 }
568
569 pub fn exec(self) -> Result<()> {
573 self.call(())
574 }
575
576 #[cfg(feature = "async")]
582 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
583 pub async fn exec_async(self) -> Result<()> {
584 self.call_async(()).await
585 }
586
587 pub fn eval<R: FromLuaMulti>(self) -> Result<R> {
593 if self.detect_mode() == ChunkMode::Binary {
598 self.call(())
599 } else if let Ok(function) = self.to_expression() {
600 function.call(())
601 } else {
602 self.call(())
603 }
604 }
605
606 #[cfg(feature = "async")]
612 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
613 pub async fn eval_async<R>(self) -> Result<R>
614 where
615 R: FromLuaMulti,
616 {
617 if self.detect_mode() == ChunkMode::Binary {
618 self.call_async(()).await
619 } else if let Ok(function) = self.to_expression() {
620 function.call_async(()).await
621 } else {
622 self.call_async(()).await
623 }
624 }
625
626 pub fn call<R: FromLuaMulti>(self, args: impl IntoLuaMulti) -> Result<R> {
630 self.into_function()?.call(args)
631 }
632
633 #[cfg(feature = "async")]
639 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
640 pub async fn call_async<R>(self, args: impl IntoLuaMulti) -> Result<R>
641 where
642 R: FromLuaMulti,
643 {
644 self.into_function()?.call_async(args).await
645 }
646
647 #[cfg_attr(not(feature = "luau"), allow(unused_mut))]
651 pub fn into_function(mut self) -> Result<Function> {
652 #[cfg(feature = "luau")]
653 if self.compiler.is_some() {
654 self.compile();
656 }
657
658 let name = Self::convert_name(self.name)?;
659 self.lua
660 .lock()
661 .load_chunk(Some(&name), self.env?.as_ref(), self.mode, self.source?.as_ref())
662 }
663
664 fn compile(&mut self) {
668 if let Ok(ref source) = self.source
669 && self.detect_mode() == ChunkMode::Text
670 {
671 #[cfg(feature = "luau")]
672 if let Ok(data) = self.compiler.get_or_insert_default().compile(source) {
673 self.source = Ok(Cow::Owned(data));
674 self.mode = Some(ChunkMode::Binary);
675 }
676 #[cfg(not(feature = "luau"))]
677 if let Ok(func) = self.lua.lock().load_chunk(None, None, None, source.as_ref()) {
678 let data = func.dump(false);
679 self.source = Ok(Cow::Owned(data));
680 self.mode = Some(ChunkMode::Binary);
681 }
682 }
683 }
684
685 pub(crate) fn try_cache(mut self) -> Self {
689 struct ChunksCache(HashMap<Vec<u8>, Vec<u8>>);
690
691 let mut text_source = None;
693 if let Ok(ref source) = self.source
694 && self.detect_mode() == ChunkMode::Text
695 {
696 let lua = self.lua.lock();
697 if let Some(cache) = lua.priv_app_data_ref::<ChunksCache>()
698 && let Some(data) = cache.0.get(source.as_ref())
699 {
700 self.source = Ok(Cow::Owned(data.clone()));
701 self.mode = Some(ChunkMode::Binary);
702 return self;
703 }
704 text_source = Some(source.as_ref().to_vec());
705 }
706
707 if let Some(text_source) = text_source {
709 self.compile();
710 if let Ok(ref binary_source) = self.source
711 && self.detect_mode() == ChunkMode::Binary
712 {
713 let lua = self.lua.lock();
714 if let Some(mut cache) = lua.priv_app_data_mut::<ChunksCache>() {
715 cache.0.insert(text_source, binary_source.to_vec());
716 } else {
717 let mut cache = ChunksCache(HashMap::new());
718 cache.0.insert(text_source, binary_source.to_vec());
719 lua.set_priv_app_data(cache);
720 }
721 }
722 }
723
724 self
725 }
726
727 fn to_expression(&self) -> Result<Function> {
728 let source = self.source.as_ref();
730 let source = source.map_err(Error::runtime)?;
731 let source = Self::expression_source(source);
732 #[cfg(feature = "luau")]
734 let source = self
735 .compiler
736 .as_ref()
737 .map(|c| c.compile(&source))
738 .transpose()?
739 .unwrap_or(source);
740
741 let name = Self::convert_name(self.name.clone())?;
742 let env = match &self.env {
743 Ok(Some(env)) => Some(env),
744 Ok(None) => None,
745 Err(err) => return Err(err.clone()),
746 };
747 self.lua.lock().load_chunk(Some(&name), env, None, &source)
748 }
749
750 fn detect_mode(&self) -> ChunkMode {
751 if let Some(mode) = self.mode {
752 return mode;
753 }
754 if let Ok(source) = &self.source {
755 #[cfg(not(feature = "luau"))]
756 if source.starts_with(ffi::LUA_SIGNATURE) {
757 return ChunkMode::Binary;
758 }
759 #[cfg(feature = "luau")]
760 if *source.first().unwrap_or(&u8::MAX) < b'\n' {
761 return ChunkMode::Binary;
762 }
763 }
764 ChunkMode::Text
765 }
766
767 fn convert_name(name: String) -> Result<CString> {
768 CString::new(name).map_err(|err| Error::runtime(format!("invalid name: {err}")))
769 }
770
771 fn expression_source(source: &[u8]) -> Vec<u8> {
772 let mut buf = Vec::with_capacity(b"return ".len() + source.len());
773 buf.extend(b"return ");
774 buf.extend(source);
775 buf
776 }
777}
778
779struct WrappedChunk<T: AsChunk> {
780 chunk: T,
781 caller: &'static Location<'static>,
782}
783
784impl Chunk<'_> {
785 #[track_caller]
790 pub fn wrap(chunk: impl AsChunk) -> impl IntoLua {
791 WrappedChunk {
792 chunk,
793 caller: Location::caller(),
794 }
795 }
796}
797
798impl<T: AsChunk> IntoLua for WrappedChunk<T> {
799 fn into_lua(self, lua: &Lua) -> Result<Value> {
800 lua.load_with_location(self.chunk, self.caller)
801 .into_function()
802 .map(Value::Function)
803 }
804}