Skip to main content

mlua/
chunk.rs

1//! Lua chunk loading and execution.
2//!
3//! This module provides types for loading Lua source code or bytecode into a [`Chunk`],
4//! configuring how it is compiled and executed, and converting it into a callable [`Function`].
5//!
6//! Chunks can be loaded from strings, byte slices, or files via the [`AsChunk`] trait.
7
8use 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
22/// Trait for types [loadable by Lua] and convertible to a [`Chunk`]
23///
24/// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2
25pub trait AsChunk {
26    /// Returns optional chunk name
27    ///
28    /// See [`Chunk::set_name`] for possible name prefixes.
29    fn name(&self) -> Option<String> {
30        None
31    }
32
33    /// Returns optional chunk [environment]
34    ///
35    /// [environment]: https://www.lua.org/manual/5.4/manual.html#2.2
36    fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
37        let _lua = lua; // suppress warning
38        Ok(None)
39    }
40
41    /// Returns optional chunk mode (text or binary)
42    fn mode(&self) -> Option<ChunkMode> {
43        None
44    }
45
46    /// Returns chunk data (can be text or binary)
47    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/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
142#[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/// Represents chunk mode (text or binary).
154#[derive(Clone, Copy, Debug, PartialEq, Eq)]
155pub enum ChunkMode {
156    Text,
157    Binary,
158}
159
160/// Represents a constant value that can be used by Luau compiler.
161#[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/// Luau compiler
205#[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    /// Creates Luau compiler instance with default options
233    pub const fn new() -> Self {
234        // Defaults are taken from luacode.h
235        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    /// Sets Luau compiler optimization level.
252    ///
253    /// Possible values:
254    /// * 0 - no optimization
255    /// * 1 - baseline optimization level that doesn't prevent debuggability (default)
256    /// * 2 - includes optimizations that harm debuggability such as inlining
257    #[must_use]
258    pub const fn set_optimization_level(mut self, level: u8) -> Self {
259        self.optimization_level = level;
260        self
261    }
262
263    /// Sets Luau compiler debug level.
264    ///
265    /// Possible values:
266    /// * 0 - no debugging support
267    /// * 1 - line info & function names only; sufficient for backtraces (default)
268    /// * 2 - full debug info with local & upvalue names; necessary for debugger
269    #[must_use]
270    pub const fn set_debug_level(mut self, level: u8) -> Self {
271        self.debug_level = level;
272        self
273    }
274
275    /// Sets Luau type information level used to guide native code generation decisions.
276    ///
277    /// Possible values:
278    /// * 0 - generate for native modules (default)
279    /// * 1 - generate for all modules
280    #[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    /// Sets Luau compiler code coverage level.
287    ///
288    /// Possible values:
289    /// * 0 - no code coverage support (default)
290    /// * 1 - statement coverage
291    /// * 2 - statement and expression coverage (verbose)
292    #[must_use]
293    pub const fn set_coverage_level(mut self, level: u8) -> Self {
294        self.coverage_level = level;
295        self
296    }
297
298    /// Sets alternative global builtin to construct vectors, in addition to default builtin
299    /// `vector.create`.
300    ///
301    /// To set the library and method name, use the `lib.ctor` format.
302    #[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    /// Sets alternative vector type name for type tables, in addition to default type `vector`.
315    #[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    /// Adds a mutable global.
323    ///
324    /// It disables the import optimization for fields accessed through it.
325    #[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    /// Sets a list of globals that are mutable.
332    ///
333    /// It disables the import optimization for fields accessed through these.
334    #[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    /// Adds a userdata type to the list that will be included in the type information.
341    #[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    /// Sets a list of userdata types that will be included in the type information.
348    #[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    /// Adds a constant for a known library member.
355    ///
356    /// The constants are used by the compiler to optimize the generated bytecode.
357    /// Optimization level must be at least 2 for this to have any effect.
358    ///
359    /// The `name` is a string in the format `lib.member`, where `lib` is the library name
360    /// and `member` is the member (constant) name.
361    #[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    /// Adds a builtin that should be disabled.
382    #[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    /// Sets a list of builtins that should be disabled.
389    #[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    /// Compiles the `source` into bytecode.
396    ///
397    /// Returns [`Error::SyntaxError`] if the source code is invalid.
398    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            // The rest of the bytecode is the error message starting with `:`
496            // See https://github.com/luau-lang/luau/blob/0.640/Compiler/src/Compiler.cpp#L4336
497            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    /// Returns the name of this chunk.
510    pub fn name(&self) -> &str {
511        &self.name
512    }
513
514    /// Sets the name of this chunk, which results in more informative error traces.
515    ///
516    /// Possible name prefixes:
517    /// - `@` - file path (when truncation is needed, the end of the file path is kept, as this is
518    ///   more useful for identifying the file)
519    /// - `=` - custom chunk name (when truncation is needed, the beginning of the name is kept)
520    pub fn set_name(mut self, name: impl Into<String>) -> Self {
521        self.name = name.into();
522        self
523    }
524
525    /// Returns the environment of this chunk.
526    pub fn environment(&self) -> Option<&Table> {
527        self.env.as_ref().ok()?.as_ref()
528    }
529
530    /// Sets the environment of the loaded chunk to the given value.
531    ///
532    /// In Lua >=5.2 main chunks always have exactly one upvalue, and this upvalue is used as the
533    /// `_ENV` variable inside the chunk. By default this value is set to the global environment.
534    ///
535    /// Calling this method changes the `_ENV` upvalue to the value provided, and variables inside
536    /// the chunk will refer to the given environment rather than the global one.
537    ///
538    /// All global variables (including the standard library!) are looked up in `_ENV`, so it may be
539    /// necessary to populate the environment in order for scripts using custom environments to be
540    /// useful.
541    pub fn set_environment(mut self, env: Table) -> Self {
542        self.env = Ok(Some(env));
543        self
544    }
545
546    /// Returns the mode (auto-detected by default) of this chunk.
547    pub fn mode(&self) -> ChunkMode {
548        self.detect_mode()
549    }
550
551    /// Sets whether the chunk is text or binary (autodetected by default).
552    ///
553    /// Be aware, Lua does not check the consistency of the code inside binary chunks.
554    /// Running maliciously crafted bytecode can crash the interpreter.
555    pub fn set_mode(mut self, mode: ChunkMode) -> Self {
556        self.mode = Some(mode);
557        self
558    }
559
560    /// Sets or overwrites a Luau compiler used for this chunk.
561    ///
562    /// See [`Compiler`] for details and possible options.
563    #[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    /// Execute this chunk of code.
571    ///
572    /// This is equivalent to calling the chunk function with no arguments and no return values.
573    pub fn exec(self) -> Result<()> {
574        self.call(())
575    }
576
577    /// Asynchronously execute this chunk of code.
578    ///
579    /// See [`exec`] for more details.
580    ///
581    /// [`exec`]: Chunk::exec
582    #[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    /// Evaluate the chunk as either an expression or block.
589    ///
590    /// If the chunk can be parsed as an expression, this loads and executes the chunk and returns
591    /// the value that it evaluates to. Otherwise, the chunk is interpreted as a block as normal,
592    /// and this is equivalent to calling `exec`.
593    pub fn eval<R: FromLuaMulti>(self) -> Result<R> {
594        // Bytecode is always interpreted as a statement.
595        // For source code, first try interpreting the lua as an expression by adding
596        // "return", then as a statement. This is the same thing the
597        // actual lua repl does.
598        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    /// Asynchronously evaluate the chunk as either an expression or block.
608    ///
609    /// See [`eval`] for more details.
610    ///
611    /// [`eval`]: Chunk::eval
612    #[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    /// Load the chunk function and call it with the given arguments.
628    ///
629    /// This is equivalent to `into_function` and calling the resulting function.
630    pub fn call<R: FromLuaMulti>(self, args: impl IntoLuaMulti) -> Result<R> {
631        self.into_function()?.call(args)
632    }
633
634    /// Load the chunk function and asynchronously call it with the given arguments.
635    ///
636    /// See [`call`] for more details.
637    ///
638    /// [`call`]: Chunk::call
639    #[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    /// Load this chunk into a regular [`Function`].
649    ///
650    /// This simply compiles the chunk without actually executing it.
651    #[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            // We don't need to compile source if no compiler set
656            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    /// Compiles the chunk and changes mode to binary.
666    ///
667    /// It does nothing if the chunk is already binary or invalid.
668    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    /// Fetches compiled bytecode of this chunk from the cache.
687    ///
688    /// If not found, compiles the source code and stores it on the cache.
689    pub(crate) fn try_cache(mut self) -> Self {
690        struct ChunksCache(HashMap<Vec<u8>, Vec<u8>>);
691
692        // Try to fetch compiled chunk from cache
693        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        // Compile and cache the chunk
709        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        // We assume that mode is Text
730        let source = self.source.as_ref();
731        let source = source.map_err(Error::runtime)?;
732        let source = Self::expression_source(source);
733        // We don't need to compile source if no compiler options set
734        #[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    /// Wraps a chunk of Lua code, returning an opaque type that implements [`IntoLua`] trait.
787    ///
788    /// The resulted `IntoLua` implementation will convert the chunk into a Lua function without
789    /// executing it.
790    #[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}