1use std::os::raw::c_void;
4
5use libquickjs_ng_sys as q;
6
7use crate::utils::{ensure_no_excpetion, get_exception, make_cstring};
8use crate::value::{JsCompiledFunction, OwnedJsValue};
9use crate::ExecutionError;
10
11pub fn compile(
14 context: *mut q::JSContext,
15 script: &str,
16 file_name: &str,
17) -> Result<OwnedJsValue, ExecutionError> {
18 let filename_c = make_cstring(file_name)?;
19 let code_c = make_cstring(script)?;
20
21 let value = unsafe {
22 let v = q::JS_Eval(
23 context,
24 code_c.as_ptr(),
25 script.len() as _,
26 filename_c.as_ptr(),
27 q::JS_EVAL_FLAG_COMPILE_ONLY as i32,
28 );
29 OwnedJsValue::new(context, v)
30 };
31
32 ensure_no_excpetion(context)?;
34 Ok(value)
35}
36
37pub fn compile_module(
40 context: *mut q::JSContext,
41 script: &str,
42 file_name: &str,
43) -> Result<OwnedJsValue, ExecutionError> {
44 let filename_c = make_cstring(file_name)?;
45 let code_c = make_cstring(script)?;
46
47 let value = unsafe {
48 let v = q::JS_Eval(
49 context,
50 code_c.as_ptr(),
51 script.len() as _,
52 filename_c.as_ptr(),
53 q::JS_EVAL_TYPE_MODULE as i32 | q::JS_EVAL_FLAG_COMPILE_ONLY as i32,
54 );
55 OwnedJsValue::new(context, v)
56 };
57
58 ensure_no_excpetion(context)?;
60 Ok(value)
61}
62
63pub fn run_compiled_function(func: &JsCompiledFunction) -> Result<OwnedJsValue, ExecutionError> {
65 let context = func.as_value().context();
66 let value = unsafe {
67 let f = func.clone().into_value().extract();
70 let v = q::JS_EvalFunction(context, f);
71 OwnedJsValue::new(context, v)
72 };
73
74 ensure_no_excpetion(context).map_err(|e| {
75 if let ExecutionError::Internal(msg) = e {
76 ExecutionError::Internal(format!("Could not evaluate compiled function: {}", msg))
77 } else {
78 e
79 }
80 })?;
81
82 Ok(value)
83}
84
85pub fn to_bytecode(context: *mut q::JSContext, compiled_func: &JsCompiledFunction) -> Vec<u8> {
87 unsafe {
88 let mut len = 0;
89 let raw = q::JS_WriteObject(
90 context,
91 &mut len,
92 *compiled_func.as_value().as_inner(),
93 q::JS_WRITE_OBJ_BYTECODE as i32,
94 );
95 let slice = std::slice::from_raw_parts(raw, len);
96 let data = slice.to_vec();
97 q::js_free(context, raw as *mut c_void);
98 data
99 }
100}
101
102pub fn from_bytecode(
104 context: *mut q::JSContext,
105 bytecode: &[u8],
106) -> Result<OwnedJsValue, ExecutionError> {
107 assert!(!bytecode.is_empty());
108 {
109 let len = bytecode.len();
110 let buf = bytecode.as_ptr();
111 let raw =
112 unsafe { q::JS_ReadObject(context, buf, len as _, q::JS_READ_OBJ_BYTECODE as i32) };
113
114 let func_ref = OwnedJsValue::new(context, raw);
115 if func_ref.is_exception() {
116 let ex_opt = get_exception(context);
117 if let Some(ex) = ex_opt {
118 Err(ex)
119 } else {
120 Err(ExecutionError::Internal(
121 "from_bytecode failed and could not get exception".to_string(),
122 ))
123 }
124 } else {
125 Ok(func_ref)
126 }
127 }
128}
129
130#[cfg(test)]
131pub mod tests {
132 use super::*;
133 use crate::Context;
134
135 #[test]
136 fn test_compile_function() {
137 let ctx = Context::new(None).unwrap();
138
139 let func_res = compile(
140 ctx.context,
141 "{let a_tb3 = 7; let b_tb3 = 5; a_tb3 * b_tb3;}",
142 "test_func.es",
143 );
144 let func = func_res
145 .ok()
146 .expect("func compile failed")
147 .try_into_compiled_function()
148 .unwrap();
149 let bytecode: Vec<u8> = to_bytecode(ctx.context, &func);
150 drop(func);
151 assert!(!bytecode.is_empty());
152
153 let func2_res = from_bytecode(ctx.context, &bytecode);
154 let func2 = func2_res
155 .ok()
156 .expect("could not read bytecode")
157 .try_into_compiled_function()
158 .unwrap();
159 let run_res = run_compiled_function(&func2);
160 match run_res {
161 Ok(res) => {
162 assert_eq!(res.to_int().unwrap(), 7 * 5);
163 }
164 Err(e) => {
165 panic!("run failed1: {}", e);
166 }
167 }
168 }
169
170 #[test]
171 fn test_load_and_eval_compiled_function() {
172 let ctx = Context::new(None).unwrap();
173
174 let func_res = compile(
175 ctx.context,
176 "{let a_tb4 = 7; let b_tb4 = 5; a_tb4 * b_tb4;}",
177 "test_func.es",
178 );
179 let func = func_res
180 .ok()
181 .expect("func compile failed")
182 .try_into_compiled_function()
183 .unwrap();
184 let bytecode: Vec<u8> = to_bytecode(ctx.context, &func);
185 drop(func);
186 assert!(!bytecode.is_empty());
187 let func2_res = from_bytecode(ctx.context, &bytecode);
188 let func2 = func2_res
189 .ok()
190 .expect("could not read bytecode")
191 .try_into_compiled_function()
192 .unwrap();
193 let run_res = run_compiled_function(&func2);
194
195 match run_res {
196 Ok(res) => {
197 assert_eq!(res.to_int().unwrap(), 7 * 5);
198 }
199 Err(e) => {
200 panic!("run failed: {}", e);
201 }
202 }
203 }
204
205 #[test]
206 fn test_load_compiled_function_fail() {
207 let ctx = Context::new(None).unwrap();
208
209 let func_res = compile(
210 ctx.context,
211 "{the changes of me compil1ng a're slim to 0-0}",
212 "test_func_fail.es",
213 );
214 func_res.expect_err("func compiled unexpectedly");
215 }
216
217 #[test]
218 fn test_compiled_func_bad_eval() {
219 let ctx = Context::new(None).unwrap();
220
221 let func_res = compile(ctx.context, "let abcdef = 1;", "test_func_runfail.es");
222 let func = func_res
223 .ok()
224 .expect("func compile failed")
225 .try_into_compiled_function()
226 .unwrap();
227 assert_eq!(1, func.as_value().get_ref_count());
228
229 let bytecode: Vec<u8> = to_bytecode(ctx.context, &func);
230
231 assert_eq!(1, func.as_value().get_ref_count());
232
233 drop(func);
234
235 assert!(!bytecode.is_empty());
236
237 let func2_res = from_bytecode(ctx.context, &bytecode);
238 let func2 = func2_res
239 .ok()
240 .expect("could not read bytecode")
241 .try_into_compiled_function()
242 .unwrap();
243
244 assert_eq!(1, func2.as_value().get_ref_count());
247
248 let run_res1 = run_compiled_function(&func2)
249 .ok()
250 .expect("run 1 failed unexpectedly");
251 drop(run_res1);
252
253 assert_eq!(1, func2.as_value().get_ref_count());
254
255 let _run_res2 = run_compiled_function(&func2).expect_err("run 2 succeeded unexpectedly");
256
257 assert_eq!(1, func2.as_value().get_ref_count());
258 }
259}