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, ErrorContext, Result};
9use crate::function::Function;
10use crate::lua::Lua;
11use crate::table::Table;
12use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti};
13
14pub trait AsChunk<'lua, 'a> {
19 fn name(&self) -> Option<StdString> {
21 None
22 }
23
24 fn environment(&self, lua: &'lua Lua) -> Result<Option<Table<'lua>>> {
28 let _lua = lua; Ok(None)
30 }
31
32 fn mode(&self) -> Option<ChunkMode> {
34 None
35 }
36
37 fn source(self) -> IoResult<Cow<'a, [u8]>>;
39}
40
41impl<'a> AsChunk<'_, 'a> for &'a str {
42 fn source(self) -> IoResult<Cow<'a, [u8]>> {
43 Ok(Cow::Borrowed(self.as_ref()))
44 }
45}
46
47impl AsChunk<'_, 'static> for StdString {
48 fn source(self) -> IoResult<Cow<'static, [u8]>> {
49 Ok(Cow::Owned(self.into_bytes()))
50 }
51}
52
53impl<'a> AsChunk<'_, 'a> for &'a StdString {
54 fn source(self) -> IoResult<Cow<'a, [u8]>> {
55 Ok(Cow::Borrowed(self.as_bytes()))
56 }
57}
58
59impl<'a> AsChunk<'_, 'a> for &'a [u8] {
60 fn source(self) -> IoResult<Cow<'a, [u8]>> {
61 Ok(Cow::Borrowed(self))
62 }
63}
64
65impl AsChunk<'_, 'static> for Vec<u8> {
66 fn source(self) -> IoResult<Cow<'static, [u8]>> {
67 Ok(Cow::Owned(self))
68 }
69}
70
71impl<'a> AsChunk<'_, 'a> for &'a Vec<u8> {
72 fn source(self) -> IoResult<Cow<'a, [u8]>> {
73 Ok(Cow::Borrowed(self.as_ref()))
74 }
75}
76
77impl AsChunk<'_, 'static> for &Path {
78 fn name(&self) -> Option<StdString> {
79 Some(format!("@{}", self.display()))
80 }
81
82 fn source(self) -> IoResult<Cow<'static, [u8]>> {
83 std::fs::read(self).map(Cow::Owned)
84 }
85}
86
87impl AsChunk<'_, 'static> for PathBuf {
88 fn name(&self) -> Option<StdString> {
89 Some(format!("@{}", self.display()))
90 }
91
92 fn source(self) -> IoResult<Cow<'static, [u8]>> {
93 std::fs::read(self).map(Cow::Owned)
94 }
95}
96
97#[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"]
101pub struct Chunk<'lua, 'a> {
102 pub(crate) lua: &'lua Lua,
103 pub(crate) name: StdString,
104 pub(crate) env: Result<Option<Table<'lua>>>,
105 pub(crate) mode: Option<ChunkMode>,
106 pub(crate) source: IoResult<Cow<'a, [u8]>>,
107 #[cfg(feature = "luau")]
108 pub(crate) compiler: Option<Compiler>,
109}
110
111#[derive(Clone, Copy, Debug, PartialEq, Eq)]
113pub enum ChunkMode {
114 Text,
115 Binary,
116}
117
118#[cfg(any(feature = "luau", doc))]
120#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
121#[derive(Clone, Debug)]
122pub struct Compiler {
123 optimization_level: u8,
124 debug_level: u8,
125 type_info_level: u8,
126 coverage_level: u8,
127 vector_lib: Option<String>,
128 vector_ctor: Option<String>,
129 vector_type: Option<String>,
130 mutable_globals: Vec<String>,
131 userdata_types: Vec<String>,
132}
133
134#[cfg(any(feature = "luau", doc))]
135impl Default for Compiler {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141#[cfg(any(feature = "luau", doc))]
142impl Compiler {
143 pub const fn new() -> Self {
145 Compiler {
147 optimization_level: 1,
148 debug_level: 1,
149 type_info_level: 0,
150 coverage_level: 0,
151 vector_lib: None,
152 vector_ctor: None,
153 vector_type: None,
154 mutable_globals: Vec::new(),
155 userdata_types: Vec::new(),
156 }
157 }
158
159 #[must_use]
166 pub const fn set_optimization_level(mut self, level: u8) -> Self {
167 self.optimization_level = level;
168 self
169 }
170
171 #[must_use]
178 pub const fn set_debug_level(mut self, level: u8) -> Self {
179 self.debug_level = level;
180 self
181 }
182
183 pub const fn set_type_info_level(mut self, level: u8) -> Self {
189 self.type_info_level = level;
190 self
191 }
192
193 #[must_use]
200 pub const fn set_coverage_level(mut self, level: u8) -> Self {
201 self.coverage_level = level;
202 self
203 }
204
205 #[doc(hidden)]
206 #[must_use]
207 pub fn set_vector_lib(mut self, lib: impl Into<String>) -> Self {
208 self.vector_lib = Some(lib.into());
209 self
210 }
211
212 #[doc(hidden)]
213 #[must_use]
214 pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
215 self.vector_ctor = Some(ctor.into());
216 self
217 }
218
219 #[doc(hidden)]
220 #[must_use]
221 pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self {
222 self.vector_type = Some(r#type.into());
223 self
224 }
225
226 #[must_use]
230 pub fn set_mutable_globals(mut self, globals: Vec<String>) -> Self {
231 self.mutable_globals = globals;
232 self
233 }
234
235 #[must_use]
237 pub fn set_userdata_types(mut self, types: Vec<String>) -> Self {
238 self.userdata_types = types;
239 self
240 }
241
242 pub fn compile(&self, source: impl AsRef<[u8]>) -> Vec<u8> {
244 use std::os::raw::c_int;
245 use std::ptr;
246
247 let vector_lib = self.vector_lib.clone();
248 let vector_lib = vector_lib.and_then(|lib| CString::new(lib).ok());
249 let vector_lib = vector_lib.as_ref();
250 let vector_ctor = self.vector_ctor.clone();
251 let vector_ctor = vector_ctor.and_then(|ctor| CString::new(ctor).ok());
252 let vector_ctor = vector_ctor.as_ref();
253 let vector_type = self.vector_type.clone();
254 let vector_type = vector_type.and_then(|t| CString::new(t).ok());
255 let vector_type = vector_type.as_ref();
256
257 macro_rules! vec2cstring_ptr {
258 ($name:ident, $name_ptr:ident) => {
259 let $name = self
260 .$name
261 .iter()
262 .map(|name| CString::new(name.clone()).ok())
263 .collect::<Option<Vec<_>>>()
264 .unwrap_or_default();
265 let mut $name = $name.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
266 let mut $name_ptr = ptr::null();
267 if !$name.is_empty() {
268 $name.push(ptr::null());
269 $name_ptr = $name.as_ptr();
270 }
271 };
272 }
273
274 vec2cstring_ptr!(mutable_globals, mutable_globals_ptr);
275 vec2cstring_ptr!(userdata_types, userdata_types_ptr);
276
277 unsafe {
278 let mut options = ffi::lua_CompileOptions::default();
279 options.optimizationLevel = self.optimization_level as c_int;
280 options.debugLevel = self.debug_level as c_int;
281 options.typeInfoLevel = self.type_info_level as c_int;
282 options.coverageLevel = self.coverage_level as c_int;
283 options.vectorLib = vector_lib.map_or(ptr::null(), |s| s.as_ptr());
284 options.vectorCtor = vector_ctor.map_or(ptr::null(), |s| s.as_ptr());
285 options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr());
286 options.mutableGlobals = mutable_globals_ptr;
287 options.userdataTypes = userdata_types_ptr;
288 ffi::luau_compile(source.as_ref(), options)
289 }
290 }
291}
292
293impl<'lua, 'a> Chunk<'lua, 'a> {
294 pub fn set_name(mut self, name: impl Into<String>) -> Self {
296 self.name = name.into();
297 self
298 }
299
300 pub fn set_environment<V: IntoLua<'lua>>(mut self, env: V) -> Self {
312 self.env = env
313 .into_lua(self.lua)
314 .and_then(|val| self.lua.unpack(val))
315 .context("bad environment value");
316 self
317 }
318
319 pub fn set_mode(mut self, mode: ChunkMode) -> Self {
324 self.mode = Some(mode);
325 self
326 }
327
328 #[cfg(any(feature = "luau", doc))]
334 #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
335 pub fn set_compiler(mut self, compiler: Compiler) -> Self {
336 self.compiler = Some(compiler);
337 self
338 }
339
340 pub fn exec(self) -> Result<()> {
344 self.call::<_, ()>(())?;
345 Ok(())
346 }
347
348 #[cfg(feature = "async")]
356 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
357 pub async fn exec_async(self) -> Result<()> {
358 self.call_async(()).await
359 }
360
361 pub fn eval<R: FromLuaMulti<'lua>>(self) -> Result<R> {
367 if self.detect_mode() == ChunkMode::Binary {
372 self.call(())
373 } else if let Ok(function) = self.to_expression() {
374 function.call(())
375 } else {
376 self.call(())
377 }
378 }
379
380 #[cfg(feature = "async")]
388 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
389 pub async fn eval_async<R>(self) -> Result<R>
390 where
391 R: FromLuaMulti<'lua> + 'lua,
392 {
393 if self.detect_mode() == ChunkMode::Binary {
394 self.call_async(()).await
395 } else if let Ok(function) = self.to_expression() {
396 function.call_async(()).await
397 } else {
398 self.call_async(()).await
399 }
400 }
401
402 pub fn call<A: IntoLuaMulti<'lua>, R: FromLuaMulti<'lua>>(self, args: A) -> Result<R> {
406 self.into_function()?.call(args)
407 }
408
409 #[cfg(feature = "async")]
417 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
418 pub async fn call_async<A, R>(self, args: A) -> Result<R>
419 where
420 A: IntoLuaMulti<'lua>,
421 R: FromLuaMulti<'lua> + 'lua,
422 {
423 self.into_function()?.call_async(args).await
424 }
425
426 #[cfg_attr(not(feature = "luau"), allow(unused_mut))]
430 pub fn into_function(mut self) -> Result<Function<'lua>> {
431 #[cfg(feature = "luau")]
432 if self.compiler.is_some() {
433 self.compile();
435 }
436
437 let name = Self::convert_name(self.name)?;
438 self.lua
439 .load_chunk(Some(&name), self.env?, self.mode, self.source?.as_ref())
440 }
441
442 fn compile(&mut self) {
446 if let Ok(ref source) = self.source {
447 if self.detect_mode() == ChunkMode::Text {
448 #[cfg(feature = "luau")]
449 {
450 let data = self
451 .compiler
452 .get_or_insert_with(Default::default)
453 .compile(source);
454 self.source = Ok(Cow::Owned(data));
455 self.mode = Some(ChunkMode::Binary);
456 }
457 #[cfg(not(feature = "luau"))]
458 if let Ok(func) = self.lua.load_chunk(None, None, None, source.as_ref()) {
459 let data = func.dump(false);
460 self.source = Ok(Cow::Owned(data));
461 self.mode = Some(ChunkMode::Binary);
462 }
463 }
464 }
465 }
466
467 pub(crate) fn try_cache(mut self) -> Self {
471 struct ChunksCache(HashMap<Vec<u8>, Vec<u8>>);
472
473 let mut text_source = None;
475 if let Ok(ref source) = self.source {
476 if self.detect_mode() == ChunkMode::Text {
477 if let Some(cache) = self.lua.app_data_ref::<ChunksCache>() {
478 if let Some(data) = cache.0.get(source.as_ref()) {
479 self.source = Ok(Cow::Owned(data.clone()));
480 self.mode = Some(ChunkMode::Binary);
481 return self;
482 }
483 }
484 text_source = Some(source.as_ref().to_vec());
485 }
486 }
487
488 if let Some(text_source) = text_source {
490 self.compile();
491 if let Ok(ref binary_source) = self.source {
492 if self.detect_mode() == ChunkMode::Binary {
493 if let Some(mut cache) = self.lua.app_data_mut::<ChunksCache>() {
494 cache.0.insert(text_source, binary_source.as_ref().to_vec());
495 } else {
496 let mut cache = ChunksCache(HashMap::new());
497 cache.0.insert(text_source, binary_source.as_ref().to_vec());
498 let _ = self.lua.try_set_app_data(cache);
499 }
500 }
501 }
502 }
503
504 self
505 }
506
507 fn to_expression(&self) -> Result<Function<'lua>> {
508 let source = self.source.as_ref();
510 let source = source.map_err(Error::runtime)?;
511 let source = Self::expression_source(source);
512 #[cfg(feature = "luau")]
514 let source = self
515 .compiler
516 .as_ref()
517 .map(|c| c.compile(&source))
518 .unwrap_or(source);
519
520 let name = Self::convert_name(self.name.clone())?;
521 self.lua
522 .load_chunk(Some(&name), self.env.clone()?, None, &source)
523 }
524
525 fn detect_mode(&self) -> ChunkMode {
526 match (self.mode, &self.source) {
527 (Some(mode), _) => mode,
528 (None, Ok(source)) => {
529 #[cfg(not(feature = "luau"))]
530 if source.starts_with(ffi::LUA_SIGNATURE) {
531 return ChunkMode::Binary;
532 }
533 #[cfg(feature = "luau")]
534 if *source.first().unwrap_or(&u8::MAX) < b'\n' {
535 return ChunkMode::Binary;
536 }
537 ChunkMode::Text
538 }
539 (None, Err(_)) => ChunkMode::Text, }
541 }
542
543 fn convert_name(name: String) -> Result<CString> {
544 CString::new(name).map_err(|err| Error::runtime(format!("invalid name: {err}")))
545 }
546
547 fn expression_source(source: &[u8]) -> Vec<u8> {
548 let mut buf = Vec::with_capacity(b"return ".len() + source.len());
549 buf.extend(b"return ");
550 buf.extend(source);
551 buf
552 }
553}