1use std::{
2 ffi::{c_char, c_int, CString},
3 ptr::null,
4};
5
6use crate::{
7 cstdlib_free, luau_compile, LuauCompileOptions, LuauLibraryMemberConstantCallback,
8 LuauLibraryMemberTypeCallback,
9};
10
11#[derive(Debug, Clone)]
12pub struct CompilerLibraries {
13 libraries: Vec<String>,
14 member_type_callback: LuauLibraryMemberTypeCallback,
15 member_constant_callback: LuauLibraryMemberConstantCallback,
16}
17
18impl CompilerLibraries {
19 pub fn new(
20 libraries: Vec<String>,
21 member_type_callback: LuauLibraryMemberTypeCallback,
22 member_constant_callback: LuauLibraryMemberConstantCallback,
23 ) -> Self {
24 Self {
25 libraries,
26 member_type_callback,
27 member_constant_callback,
28 }
29 }
30}
31
32#[derive(Clone, Debug)]
33pub struct Compiler {
34 optimization_level: u8,
35 debug_level: u8,
36 type_info_level: u8,
37 coverage_level: u8,
38 vector_lib: Option<String>,
39 vector_ctor: Option<String>,
40 vector_type: Option<String>,
41 mutable_globals: Vec<String>,
42 userdata_types: Vec<String>,
43 disabled_builtins: Vec<String>,
44 libs: Option<CompilerLibraries>,
45}
46
47impl Compiler {
48 pub fn new() -> Self {
49 Self {
50 optimization_level: 1,
51 debug_level: 1,
52 type_info_level: 0,
53 coverage_level: 0,
54 vector_lib: None,
55 vector_ctor: None,
56 vector_type: None,
57 mutable_globals: Vec::new(),
58 userdata_types: Vec::new(),
59 disabled_builtins: Vec::new(),
60 libs: None,
61 }
62 }
63 #[must_use]
70 pub const fn set_optimization_level(mut self, level: u8) -> Self {
71 self.optimization_level = level;
72 self
73 }
74
75 #[must_use]
82 pub const fn set_debug_level(mut self, level: u8) -> Self {
83 self.debug_level = level;
84 self
85 }
86
87 pub const fn set_type_info_level(mut self, level: u8) -> Self {
93 self.type_info_level = level;
94 self
95 }
96
97 #[must_use]
104 pub const fn set_coverage_level(mut self, level: u8) -> Self {
105 self.coverage_level = level;
106 self
107 }
108
109 #[must_use]
110 pub fn set_vector_lib(mut self, lib: impl Into<String>) -> Self {
111 self.vector_lib = Some(lib.into());
112 self
113 }
114
115 #[must_use]
116 pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
117 self.vector_ctor = Some(ctor.into());
118 self
119 }
120
121 #[must_use]
122 pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self {
123 self.vector_type = Some(r#type.into());
124 self
125 }
126
127 #[must_use]
131 pub fn set_mutable_globals(mut self, globals: Vec<String>) -> Self {
132 self.mutable_globals = globals;
133 self
134 }
135
136 #[must_use]
138 pub fn set_userdata_types(mut self, types: Vec<String>) -> Self {
139 self.userdata_types = types;
140 self
141 }
142
143 pub fn set_disabled_builtins(mut self, libs: Vec<String>) -> Self {
145 self.disabled_builtins = libs;
146 self
147 }
148
149 pub fn set_libraries(&mut self, libraries: CompilerLibraries) -> &mut Self {
150 let mut pointer_vec = Vec::with_capacity(libraries.libraries.len());
151
152 for v in &libraries.libraries {
153 pointer_vec.push(v.as_ptr());
154 }
155
156 self.libs = Some(libraries);
157
158 self
159 }
160
161 #[must_use]
162 pub fn compile(&self, source: impl AsRef<[u8]>) -> CompilerResult {
163 let vector_lib = self.vector_lib.clone();
164 let vector_lib = vector_lib.and_then(|lib| CString::new(lib).ok());
165 let vector_lib = vector_lib.as_ref();
166 let vector_ctor = self.vector_ctor.clone();
167 let vector_ctor = vector_ctor.and_then(|ctor| CString::new(ctor).ok());
168 let vector_ctor = vector_ctor.as_ref();
169 let vector_type = self.vector_type.clone();
170 let vector_type = vector_type.and_then(|t| CString::new(t).ok());
171 let vector_type = vector_type.as_ref();
172
173 macro_rules! vec2cstring_ptr {
174 ($name:ident, $name_ptr:ident) => {
175 let $name = self
176 .$name
177 .iter()
178 .map(|name| CString::new(name.clone()).ok())
179 .collect::<Option<Vec<_>>>()
180 .unwrap_or_default();
181 let mut $name = $name.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
182 let mut $name_ptr = null();
183 if !$name.is_empty() {
184 $name.push(null());
185 $name_ptr = $name.as_ptr();
186 }
187 };
188 }
189
190 vec2cstring_ptr!(mutable_globals, mutable_globals_ptr);
191 vec2cstring_ptr!(userdata_types, userdata_types_ptr);
192 vec2cstring_ptr!(disabled_builtins, disabled_builtins_ptr);
193
194 let known_members_vec = self.libs.clone().map(|v| {
195 v.libraries
196 .into_iter()
197 .map(|s| CString::new(s).expect("Known members should not contain null byte"))
198 .collect::<Vec<_>>()
199 });
200
201 let mut known_members_vec_pointer = known_members_vec.map_or_else(
202 || vec![null()],
203 |v| v.iter().map(|c| c.as_ptr()).collect::<Vec<_>>(),
204 );
205
206 known_members_vec_pointer.push(null());
207
208 unsafe {
209 let mut options = LuauCompileOptions {
210 optimizationLevel: self.optimization_level as c_int,
211 debugLevel: self.debug_level as c_int,
212 typeInfoLevel: self.type_info_level as c_int,
213 coverageLevel: self.coverage_level as c_int,
214 vectorLib: vector_lib.map_or(null(), |s| s.as_ptr()),
215 vectorCtor: vector_ctor.map_or(null(), |s| s.as_ptr()),
216 vectorType: vector_type.map_or(null(), |s| s.as_ptr()),
217 mutableGlobals: mutable_globals_ptr,
218 userdataTypes: userdata_types_ptr,
219 librariesWithKnownMembers: known_members_vec_pointer.as_ptr(),
220 libraryMemberTypeCallback: self.libs.clone().map(|v| v.member_type_callback),
221 libraryMemberConstantCallback: self
222 .libs
223 .clone()
224 .map(|v| v.member_constant_callback),
225 disabledBuiltins: disabled_builtins_ptr,
226 };
227
228 let source = source.as_ref();
229 let mut len: usize = 0;
230
231 let bytecode = luau_compile(
232 source.as_ptr() as _,
233 source.len(),
234 &raw mut options,
235 &raw mut len,
236 );
237
238 CompilerResult { bytecode, len }
239 }
240 }
241}
242
243impl Default for Compiler {
244 fn default() -> Self {
245 Self::new()
246 }
247}
248
249pub struct CompilerResult {
250 bytecode: *const c_char,
251 len: usize,
252}
253
254impl CompilerResult {
255 fn bytecode_unchecked(&self) -> &[u8] {
257 unsafe { std::slice::from_raw_parts(self.bytecode as _, self.len) }
258 }
259
260 pub fn bytecode(&self) -> Option<&[u8]> {
261 if self.is_err() {
262 None
263 } else {
264 Some(self.bytecode_unchecked())
265 }
266 }
267
268 pub fn error(&self) -> Option<&str> {
269 if self.is_ok() {
270 None
271 } else {
272 unsafe {
273 Some(
274 std::str::from_utf8(std::slice::from_raw_parts(
275 self.bytecode.add(1) as _,
276 self.len - 1,
277 ))
278 .expect("Luau error was not valid UTF-8"),
279 )
280 }
281 }
282 }
283
284 pub fn is_err(&self) -> bool {
286 unsafe { !self.bytecode.is_null() && self.bytecode.read() == 0 }
287 }
288
289 pub fn is_ok(&self) -> bool {
291 !self.is_err()
292 }
293}
294
295impl Drop for CompilerResult {
296 fn drop(&mut self) {
297 unsafe {
298 cstdlib_free(self.bytecode as _);
299 }
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use std::ffi::c_char;
306
307 use crate::{Luau, LuauBytecodeType, LuauCompilerConstant};
308
309 use super::{Compiler, CompilerLibraries};
310
311 unsafe extern "C-unwind" fn member_type_callback(
312 _: *const c_char,
313 _: *const c_char,
314 ) -> LuauBytecodeType {
315 LuauBytecodeType::LBC_TYPE_BOOLEAN
316 }
317
318 unsafe extern "C-unwind" fn member_constant_callback(
319 _: *const c_char,
320 _: *const c_char,
321 _: LuauCompilerConstant,
322 ) {
323 }
324
325 #[test]
326 fn compiler_success() {
327 let mut compiler = Compiler::new();
328
329 let result = compiler
331 .set_optimization_level(2)
332 .set_coverage_level(1)
333 .set_mutable_globals(vec!["a".to_string()])
334 .compile("v()");
335
336 assert!(result.is_ok(), "Expected result to be a success");
337 assert!(
338 result.bytecode().is_some(),
339 "Expected resultant bytecode to be some"
340 );
341 assert!(
342 result.bytecode().is_some_and(|v| !v.is_empty()),
343 "Expected resultant bytecode to be non-empty"
344 );
345
346 let luau = Luau::default();
347
348 let load_result = luau.load(None, result.bytecode().unwrap(), 0);
349
350 assert_eq!(load_result, Ok(()));
351 }
352
353 #[test]
354 fn libs() {
355 let mut compiler = Compiler::new();
356
357 compiler.set_libraries(CompilerLibraries::new(
358 vec!["test".to_string()],
359 member_type_callback,
360 member_constant_callback,
361 ));
362
363 let compiler_result = compiler.compile("local a = test.test");
364
365 assert!(compiler_result.is_ok(), "Expected compiler to succeed");
366 }
367
368 #[test]
369 fn cloned_compiler() {
370 let mut compiler = {
371 let original_compiler = Compiler::new();
372 original_compiler.clone()
373 };
374
375 compiler.set_libraries(CompilerLibraries::new(
376 vec!["test".to_string()],
377 member_type_callback,
378 member_constant_callback,
379 ));
380
381 let result = compiler.compile("v()");
383
384 assert!(result.is_ok(), "Expected result to be a success");
385 assert!(
386 result.bytecode().is_some(),
387 "Expected resultant bytecode to be some"
388 );
389 assert!(
390 result.bytecode().is_some_and(|v| !v.is_empty()),
391 "Expected resultant bytecode to be non-empty"
392 );
393 }
394
395 #[test]
396 fn compiler_error() {
397 let compiler = Compiler::new();
398
399 let result = compiler.compile("$");
401
402 assert!(
403 result.is_err(),
404 "Expected the compiler result to be an error"
405 );
406
407 assert!(
408 result.bytecode().is_none(),
409 "Expected the bytecode to be none"
410 );
411 assert!(
412 result.error().is_some(),
413 "Expected the compiler result output a string"
414 );
415 }
416}