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
17pub trait AsChunk<'lua> {
22 fn source(&self) -> IoResult<Cow<[u8]>>;
24
25 fn name(&self) -> Option<StdString> {
27 None
28 }
29
30 fn env(&self, _lua: &'lua Lua) -> Result<Option<Value<'lua>>> {
34 Ok(None)
35 }
36
37 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#[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103pub enum ChunkMode {
104 Text,
105 Binary,
106}
107
108#[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 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 pub fn new() -> Self {
140 Compiler::default()
141 }
142
143 pub fn set_optimization_level(mut self, level: u8) -> Self {
150 self.optimization_level = level;
151 self
152 }
153
154 pub fn set_debug_level(mut self, level: u8) -> Self {
161 self.debug_level = level;
162 self
163 }
164
165 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 pub fn set_mutable_globals(mut self, globals: Vec<String>) -> Self {
192 self.mutable_globals = globals;
193 self
194 }
195
196 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 pub fn set_name(mut self, name: impl AsRef<str>) -> Result<Self> {
241 self.name = Some(name.as_ref().to_string());
242 let _ = self.convert_name()?;
244 Ok(self)
245 }
246
247 pub fn set_environment<V: ToLua<'lua>>(mut self, env: V) -> Result<Self> {
259 self.env = Ok(Some(env.to_lua(self.lua)?));
261 Ok(self)
262 }
263
264 pub fn set_mode(mut self, mode: ChunkMode) -> Self {
269 self.mode = Some(mode);
270 self
271 }
272
273 #[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 pub fn exec(self) -> Result<()> {
289 self.call(())?;
290 Ok(())
291 }
292
293 #[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 pub fn eval<R: FromLuaMulti<'lua>>(self) -> Result<R> {
315 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 #[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 pub fn call<A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua>>(self, args: A) -> Result<R> {
355 self.into_function()?.call(args)
356 }
357
358 #[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 #[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 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 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 pub(crate) fn try_cache(mut self) -> Self {
424 struct ChunksCache(HashMap<Vec<u8>, Vec<u8>>);
425
426 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 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 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 #[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, }
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}