factorio_mlua/
chunk.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::ffi::CString;
4use std::io::Result as IoResult;
5use std::path::{Path, PathBuf};
6use std::string::String as StdString;
7
8use crate::error::{Error, Result};
9use crate::ffi;
10use crate::function::Function;
11use crate::lua::Lua;
12use crate::value::{FromLuaMulti, ToLua, ToLuaMulti, Value};
13
14#[cfg(feature = "async")]
15use {futures_core::future::LocalBoxFuture, futures_util::future};
16
17/// Trait for types [loadable by Lua] and convertible to a [`Chunk`]
18///
19/// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2
20/// [`Chunk`]: crate::Chunk
21pub trait AsChunk<'lua> {
22    /// Returns chunk data (can be text or binary)
23    fn source(&self) -> IoResult<Cow<[u8]>>;
24
25    /// Returns optional chunk name
26    fn name(&self) -> Option<StdString> {
27        None
28    }
29
30    /// Returns optional chunk [environment]
31    ///
32    /// [environment]: https://www.lua.org/manual/5.4/manual.html#2.2
33    fn env(&self, _lua: &'lua Lua) -> Result<Option<Value<'lua>>> {
34        Ok(None)
35    }
36
37    /// Returns optional chunk mode (text or binary)
38    fn mode(&self) -> Option<ChunkMode> {
39        None
40    }
41}
42
43impl<'lua> AsChunk<'lua> for str {
44    fn source(&self) -> IoResult<Cow<[u8]>> {
45        Ok(Cow::Borrowed(self.as_ref()))
46    }
47}
48
49impl<'lua> AsChunk<'lua> for StdString {
50    fn source(&self) -> IoResult<Cow<[u8]>> {
51        Ok(Cow::Borrowed(self.as_ref()))
52    }
53}
54
55impl<'lua> AsChunk<'lua> for [u8] {
56    fn source(&self) -> IoResult<Cow<[u8]>> {
57        Ok(Cow::Borrowed(self))
58    }
59}
60
61impl<'lua> AsChunk<'lua> for Vec<u8> {
62    fn source(&self) -> IoResult<Cow<[u8]>> {
63        Ok(Cow::Borrowed(self))
64    }
65}
66
67impl<'lua> AsChunk<'lua> for Path {
68    fn source(&self) -> IoResult<Cow<[u8]>> {
69        std::fs::read(self).map(Cow::Owned)
70    }
71
72    fn name(&self) -> Option<StdString> {
73        Some(format!("@{}", self.display()))
74    }
75}
76
77impl<'lua> AsChunk<'lua> for PathBuf {
78    fn source(&self) -> IoResult<Cow<[u8]>> {
79        std::fs::read(self).map(Cow::Owned)
80    }
81
82    fn name(&self) -> Option<StdString> {
83        Some(format!("@{}", self.display()))
84    }
85}
86
87/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
88///
89/// [`Lua::load`]: crate::Lua::load
90#[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"]
91pub struct Chunk<'lua, 'a> {
92    pub(crate) lua: &'lua Lua,
93    pub(crate) source: IoResult<Cow<'a, [u8]>>,
94    pub(crate) name: Option<StdString>,
95    pub(crate) env: Result<Option<Value<'lua>>>,
96    pub(crate) mode: Option<ChunkMode>,
97    #[cfg(feature = "luau")]
98    pub(crate) compiler: Option<Compiler>,
99}
100
101/// Represents chunk mode (text or binary).
102#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103pub enum ChunkMode {
104    Text,
105    Binary,
106}
107
108/// Luau compiler
109#[cfg(any(feature = "luau", doc))]
110#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
111#[derive(Clone, Debug)]
112pub struct Compiler {
113    optimization_level: u8,
114    debug_level: u8,
115    coverage_level: u8,
116    vector_lib: Option<String>,
117    vector_ctor: Option<String>,
118    mutable_globals: Vec<String>,
119}
120
121#[cfg(any(feature = "luau", doc))]
122impl Default for Compiler {
123    fn default() -> Self {
124        // Defaults are taken from luacode.h
125        Compiler {
126            optimization_level: 1,
127            debug_level: 1,
128            coverage_level: 0,
129            vector_lib: None,
130            vector_ctor: None,
131            mutable_globals: Vec::new(),
132        }
133    }
134}
135
136#[cfg(any(feature = "luau", doc))]
137impl Compiler {
138    /// Creates Luau compiler instance with default options
139    pub fn new() -> Self {
140        Compiler::default()
141    }
142
143    /// Sets Luau compiler optimization level.
144    ///
145    /// Possible values:
146    /// * 0 - no optimization
147    /// * 1 - baseline optimization level that doesn't prevent debuggability (default)
148    /// * 2 - includes optimizations that harm debuggability such as inlining
149    pub fn set_optimization_level(mut self, level: u8) -> Self {
150        self.optimization_level = level;
151        self
152    }
153
154    /// Sets Luau compiler debug level.
155    ///
156    /// Possible values:
157    /// * 0 - no debugging support
158    /// * 1 - line info & function names only; sufficient for backtraces (default)
159    /// * 2 - full debug info with local & upvalue names; necessary for debugger
160    pub fn set_debug_level(mut self, level: u8) -> Self {
161        self.debug_level = level;
162        self
163    }
164
165    /// Sets Luau compiler code coverage level.
166    ///
167    /// Possible values:
168    /// * 0 - no code coverage support (default)
169    /// * 1 - statement coverage
170    /// * 2 - statement and expression coverage (verbose)
171    pub fn set_coverage_level(mut self, level: u8) -> Self {
172        self.coverage_level = level;
173        self
174    }
175
176    #[doc(hidden)]
177    pub fn set_vector_lib(mut self, lib: Option<String>) -> Self {
178        self.vector_lib = lib;
179        self
180    }
181
182    #[doc(hidden)]
183    pub fn set_vector_ctor(mut self, ctor: Option<String>) -> Self {
184        self.vector_ctor = ctor;
185        self
186    }
187
188    /// Sets a list of globals that are mutable.
189    ///
190    /// It disables the import optimization for fields accessed through these.
191    pub fn set_mutable_globals(mut self, globals: Vec<String>) -> Self {
192        self.mutable_globals = globals;
193        self
194    }
195
196    /// Compiles the `source` into bytecode.
197    pub fn compile(&self, source: impl AsRef<[u8]>) -> Vec<u8> {
198        use std::os::raw::c_int;
199        use std::ptr;
200
201        let vector_lib = self.vector_lib.clone();
202        let vector_lib = vector_lib.and_then(|lib| CString::new(lib).ok());
203        let vector_lib = vector_lib.as_ref();
204        let vector_ctor = self.vector_ctor.clone();
205        let vector_ctor = vector_ctor.and_then(|ctor| CString::new(ctor).ok());
206        let vector_ctor = vector_ctor.as_ref();
207
208        let mutable_globals = self
209            .mutable_globals
210            .iter()
211            .map(|name| CString::new(name.clone()).ok())
212            .collect::<Option<Vec<_>>>()
213            .unwrap_or_default();
214        let mut mutable_globals = mutable_globals
215            .iter()
216            .map(|s| s.as_ptr())
217            .collect::<Vec<_>>();
218        let mut mutable_globals_ptr = ptr::null_mut();
219        if !mutable_globals.is_empty() {
220            mutable_globals.push(ptr::null());
221            mutable_globals_ptr = mutable_globals.as_mut_ptr();
222        }
223
224        unsafe {
225            let options = ffi::lua_CompileOptions {
226                optimizationLevel: self.optimization_level as c_int,
227                debugLevel: self.debug_level as c_int,
228                coverageLevel: self.coverage_level as c_int,
229                vectorLib: vector_lib.map_or(ptr::null(), |s| s.as_ptr()),
230                vectorCtor: vector_ctor.map_or(ptr::null(), |s| s.as_ptr()),
231                mutableGlobals: mutable_globals_ptr,
232            };
233            ffi::luau_compile(source.as_ref(), options)
234        }
235    }
236}
237
238impl<'lua, 'a> Chunk<'lua, 'a> {
239    /// Sets the name of this chunk, which results in more informative error traces.
240    pub fn set_name(mut self, name: impl AsRef<str>) -> Result<Self> {
241        self.name = Some(name.as_ref().to_string());
242        // Do extra validation
243        let _ = self.convert_name()?;
244        Ok(self)
245    }
246
247    /// Sets the first upvalue (`_ENV`) of the loaded chunk to the given value.
248    ///
249    /// Lua main chunks always have exactly one upvalue, and this upvalue is used as the `_ENV`
250    /// variable inside the chunk. By default this value is set to the global environment.
251    ///
252    /// Calling this method changes the `_ENV` upvalue to the value provided, and variables inside
253    /// the chunk will refer to the given environment rather than the global one.
254    ///
255    /// All global variables (including the standard library!) are looked up in `_ENV`, so it may be
256    /// necessary to populate the environment in order for scripts using custom environments to be
257    /// useful.
258    pub fn set_environment<V: ToLua<'lua>>(mut self, env: V) -> Result<Self> {
259        // Prefer to propagate errors here and wrap to `Ok`
260        self.env = Ok(Some(env.to_lua(self.lua)?));
261        Ok(self)
262    }
263
264    /// Sets whether the chunk is text or binary (autodetected by default).
265    ///
266    /// Be aware, Lua does not check the consistency of the code inside binary chunks.
267    /// Running maliciously crafted bytecode can crash the interpreter.
268    pub fn set_mode(mut self, mode: ChunkMode) -> Self {
269        self.mode = Some(mode);
270        self
271    }
272
273    /// Sets or overwrites a Luau compiler used for this chunk.
274    ///
275    /// See [`Compiler`] for details and possible options.
276    ///
277    /// Requires `feature = "luau"`
278    #[cfg(any(feature = "luau", doc))]
279    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
280    pub fn set_compiler(mut self, compiler: Compiler) -> Self {
281        self.compiler = Some(compiler);
282        self
283    }
284
285    /// Execute this chunk of code.
286    ///
287    /// This is equivalent to calling the chunk function with no arguments and no return values.
288    pub fn exec(self) -> Result<()> {
289        self.call(())?;
290        Ok(())
291    }
292
293    /// Asynchronously execute this chunk of code.
294    ///
295    /// See [`exec`] for more details.
296    ///
297    /// Requires `feature = "async"`
298    ///
299    /// [`exec`]: #method.exec
300    #[cfg(feature = "async")]
301    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
302    pub fn exec_async<'fut>(self) -> LocalBoxFuture<'fut, Result<()>>
303    where
304        'lua: 'fut,
305    {
306        self.call_async(())
307    }
308
309    /// Evaluate the chunk as either an expression or block.
310    ///
311    /// If the chunk can be parsed as an expression, this loads and executes the chunk and returns
312    /// the value that it evaluates to. Otherwise, the chunk is interpreted as a block as normal,
313    /// and this is equivalent to calling `exec`.
314    pub fn eval<R: FromLuaMulti<'lua>>(self) -> Result<R> {
315        // Bytecode is always interpreted as a statement.
316        // For source code, first try interpreting the lua as an expression by adding
317        // "return", then as a statement. This is the same thing the
318        // actual lua repl does.
319        if self.detect_mode() == ChunkMode::Binary {
320            self.call(())
321        } else if let Ok(function) = self.to_expression() {
322            function.call(())
323        } else {
324            self.call(())
325        }
326    }
327
328    /// Asynchronously evaluate the chunk as either an expression or block.
329    ///
330    /// See [`eval`] for more details.
331    ///
332    /// Requires `feature = "async"`
333    ///
334    /// [`eval`]: #method.eval
335    #[cfg(feature = "async")]
336    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
337    pub fn eval_async<'fut, R>(self) -> LocalBoxFuture<'fut, Result<R>>
338    where
339        'lua: 'fut,
340        R: FromLuaMulti<'lua> + 'fut,
341    {
342        if self.detect_mode() == ChunkMode::Binary {
343            self.call_async(())
344        } else if let Ok(function) = self.to_expression() {
345            function.call_async(())
346        } else {
347            self.call_async(())
348        }
349    }
350
351    /// Load the chunk function and call it with the given arguments.
352    ///
353    /// This is equivalent to `into_function` and calling the resulting function.
354    pub fn call<A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua>>(self, args: A) -> Result<R> {
355        self.into_function()?.call(args)
356    }
357
358    /// Load the chunk function and asynchronously call it with the given arguments.
359    ///
360    /// See [`call`] for more details.
361    ///
362    /// Requires `feature = "async"`
363    ///
364    /// [`call`]: #method.call
365    #[cfg(feature = "async")]
366    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
367    pub fn call_async<'fut, A, R>(self, args: A) -> LocalBoxFuture<'fut, Result<R>>
368    where
369        'lua: 'fut,
370        A: ToLuaMulti<'lua>,
371        R: FromLuaMulti<'lua> + 'fut,
372    {
373        match self.into_function() {
374            Ok(func) => func.call_async(args),
375            Err(e) => Box::pin(future::err(e)),
376        }
377    }
378
379    /// Load this chunk into a regular `Function`.
380    ///
381    /// This simply compiles the chunk without actually executing it.
382    #[cfg_attr(not(feature = "luau"), allow(unused_mut))]
383    pub fn into_function(mut self) -> Result<Function<'lua>> {
384        #[cfg(feature = "luau")]
385        if self.compiler.is_some() {
386            // We don't need to compile source if no compiler set
387            self.compile();
388        }
389
390        let name = self.convert_name()?;
391        self.lua
392            .load_chunk(self.source?.as_ref(), name.as_deref(), self.env?, self.mode)
393    }
394
395    /// Compiles the chunk and changes mode to binary.
396    ///
397    /// It does nothing if the chunk is already binary.
398    fn compile(&mut self) {
399        if let Ok(ref source) = self.source {
400            if self.detect_mode() == ChunkMode::Text {
401                #[cfg(feature = "luau")]
402                {
403                    let data = self
404                        .compiler
405                        .get_or_insert_with(Default::default)
406                        .compile(source);
407                    self.source = Ok(Cow::Owned(data));
408                    self.mode = Some(ChunkMode::Binary);
409                }
410                #[cfg(not(feature = "luau"))]
411                if let Ok(func) = self.lua.load_chunk(source.as_ref(), None, None, None) {
412                    let data = func.dump(false);
413                    self.source = Ok(Cow::Owned(data));
414                    self.mode = Some(ChunkMode::Binary);
415                }
416            }
417        }
418    }
419
420    /// Fetches compiled bytecode of this chunk from the cache.
421    ///
422    /// If not found, compiles the source code and stores it on the cache.
423    pub(crate) fn try_cache(mut self) -> Self {
424        struct ChunksCache(HashMap<Vec<u8>, Vec<u8>>);
425
426        // Try to fetch compiled chunk from cache
427        let mut text_source = None;
428        if let Ok(ref source) = self.source {
429            if self.detect_mode() == ChunkMode::Text {
430                if let Some(cache) = self.lua.app_data_ref::<ChunksCache>() {
431                    if let Some(data) = cache.0.get(source.as_ref()) {
432                        self.source = Ok(Cow::Owned(data.clone()));
433                        self.mode = Some(ChunkMode::Binary);
434                        return self;
435                    }
436                }
437                text_source = Some(source.as_ref().to_vec());
438            }
439        }
440
441        // Compile and cache the chunk
442        if let Some(text_source) = text_source {
443            self.compile();
444            if let Ok(ref binary_source) = self.source {
445                if self.detect_mode() == ChunkMode::Binary {
446                    if let Some(mut cache) = self.lua.app_data_mut::<ChunksCache>() {
447                        cache.0.insert(text_source, binary_source.as_ref().to_vec());
448                    } else {
449                        let mut cache = ChunksCache(HashMap::new());
450                        cache.0.insert(text_source, binary_source.as_ref().to_vec());
451                        self.lua.set_app_data(cache);
452                    }
453                }
454            }
455        }
456
457        self
458    }
459
460    fn to_expression(&self) -> Result<Function<'lua>> {
461        // We assume that mode is Text
462        let source = self.source.as_ref();
463        let source = source.map_err(|err| Error::RuntimeError(err.to_string()))?;
464        let source = Self::expression_source(source);
465        // We don't need to compile source if no compiler options set
466        #[cfg(feature = "luau")]
467        let source = self
468            .compiler
469            .as_ref()
470            .map(|c| c.compile(&source))
471            .unwrap_or(source);
472
473        let name = self.convert_name()?;
474        self.lua
475            .load_chunk(&source, name.as_deref(), self.env.clone()?, None)
476    }
477
478    fn detect_mode(&self) -> ChunkMode {
479        match (self.mode, &self.source) {
480            (Some(mode), _) => mode,
481            (None, Ok(source)) => {
482                #[cfg(not(feature = "luau"))]
483                if source.starts_with(ffi::LUA_SIGNATURE) {
484                    return ChunkMode::Binary;
485                }
486                #[cfg(feature = "luau")]
487                if *source.get(0).unwrap_or(&u8::MAX) < b'\n' {
488                    return ChunkMode::Binary;
489                }
490                ChunkMode::Text
491            }
492            (None, Err(_)) => ChunkMode::Text, // any value is fine
493        }
494    }
495
496    fn convert_name(&self) -> Result<Option<CString>> {
497        self.name
498            .clone()
499            .map(CString::new)
500            .transpose()
501            .map_err(|err| Error::RuntimeError(format!("invalid name: {err}")))
502    }
503
504    fn expression_source(source: &[u8]) -> Vec<u8> {
505        let mut buf = Vec::with_capacity(b"return ".len() + source.len());
506        buf.extend(b"return ");
507        buf.extend(source);
508        buf
509    }
510}