o_toolchain_javascriptcore/
lib.rs1use jstd::console;
2use o_core::engine::JSEngine;
3use o_core::error::{JSError, JSResult};
4use rust_jsc::{JSContext, JSFunction, JSObject, JSValue, PropertyDescriptor, callback};
5
6type HostResult = rust_jsc::JSResult<JSValue>;
7
8fn js_values_to_strings(arguments: &[JSValue]) -> Vec<String> {
9 arguments
10 .iter()
11 .map(|value| {
12 if value.is_string() {
13 value
14 .as_string()
15 .map(|s| s.to_string())
16 .unwrap_or_else(|_| "[string]".to_string())
17 } else if value.is_number() {
18 value
19 .as_number()
20 .map(|n| {
21 if n.fract() == 0.0 {
22 (n as i64).to_string()
23 } else {
24 n.to_string()
25 }
26 })
27 .unwrap_or_else(|_| "[number]".to_string())
28 } else if value.is_boolean() {
29 value.as_boolean().to_string()
30 } else if value.is_null() {
31 "null".to_string()
32 } else if value.is_undefined() {
33 "undefined".to_string()
34 } else {
35 value
36 .as_string()
37 .map(|s| s.to_string())
38 .unwrap_or_else(|_| "[object]".to_string())
39 }
40 })
41 .collect()
42}
43
44fn first_label(arguments: &[JSValue]) -> String {
45 arguments
46 .first()
47 .map(|value| js_values_to_strings(std::slice::from_ref(value)).remove(0))
48 .unwrap_or_else(|| "default".to_string())
49}
50
51#[callback]
52fn host_console_log(
53 ctx: JSContext,
54 _function: JSObject,
55 _this: JSObject,
56 arguments: &[JSValue],
57) -> HostResult {
58 console::log(&js_values_to_strings(arguments));
59 Ok(JSValue::undefined(&ctx))
60}
61
62#[callback]
63fn host_console_info(
64 ctx: JSContext,
65 _function: JSObject,
66 _this: JSObject,
67 arguments: &[JSValue],
68) -> HostResult {
69 console::info(&js_values_to_strings(arguments));
70 Ok(JSValue::undefined(&ctx))
71}
72
73#[callback]
74fn host_console_warn(
75 ctx: JSContext,
76 _function: JSObject,
77 _this: JSObject,
78 arguments: &[JSValue],
79) -> HostResult {
80 console::warn(&js_values_to_strings(arguments));
81 Ok(JSValue::undefined(&ctx))
82}
83
84#[callback]
85fn host_console_error(
86 ctx: JSContext,
87 _function: JSObject,
88 _this: JSObject,
89 arguments: &[JSValue],
90) -> HostResult {
91 console::error(&js_values_to_strings(arguments));
92 Ok(JSValue::undefined(&ctx))
93}
94
95#[callback]
96fn host_console_debug(
97 ctx: JSContext,
98 _function: JSObject,
99 _this: JSObject,
100 arguments: &[JSValue],
101) -> HostResult {
102 console::debug(&js_values_to_strings(arguments));
103 Ok(JSValue::undefined(&ctx))
104}
105
106#[callback]
107fn host_console_trace(
108 ctx: JSContext,
109 _function: JSObject,
110 _this: JSObject,
111 arguments: &[JSValue],
112) -> HostResult {
113 console::trace(&js_values_to_strings(arguments));
114 Ok(JSValue::undefined(&ctx))
115}
116
117#[callback]
118fn host_console_assert(
119 ctx: JSContext,
120 _function: JSObject,
121 _this: JSObject,
122 arguments: &[JSValue],
123) -> HostResult {
124 let condition = arguments
125 .first()
126 .map(|value| value.as_boolean())
127 .unwrap_or(false);
128 let args = if arguments.len() > 1 {
129 js_values_to_strings(&arguments[1..])
130 } else {
131 Vec::new()
132 };
133 console::assert(condition, &args);
134 Ok(JSValue::undefined(&ctx))
135}
136
137#[callback]
138fn host_console_clear(
139 ctx: JSContext,
140 _function: JSObject,
141 _this: JSObject,
142 _arguments: &[JSValue],
143) -> HostResult {
144 console::clear();
145 Ok(JSValue::undefined(&ctx))
146}
147
148#[callback]
149fn host_console_count(
150 ctx: JSContext,
151 _function: JSObject,
152 _this: JSObject,
153 arguments: &[JSValue],
154) -> HostResult {
155 console::count(&first_label(arguments));
156 Ok(JSValue::undefined(&ctx))
157}
158
159#[callback]
160fn host_console_count_reset(
161 ctx: JSContext,
162 _function: JSObject,
163 _this: JSObject,
164 arguments: &[JSValue],
165) -> HostResult {
166 console::count_reset(&first_label(arguments));
167 Ok(JSValue::undefined(&ctx))
168}
169
170#[callback]
171fn host_console_time(
172 ctx: JSContext,
173 _function: JSObject,
174 _this: JSObject,
175 arguments: &[JSValue],
176) -> HostResult {
177 console::time(&first_label(arguments));
178 Ok(JSValue::undefined(&ctx))
179}
180
181#[callback]
182fn host_console_time_log(
183 ctx: JSContext,
184 _function: JSObject,
185 _this: JSObject,
186 arguments: &[JSValue],
187) -> HostResult {
188 console::time_log(&first_label(arguments));
189 Ok(JSValue::undefined(&ctx))
190}
191
192#[callback]
193fn host_console_time_end(
194 ctx: JSContext,
195 _function: JSObject,
196 _this: JSObject,
197 arguments: &[JSValue],
198) -> HostResult {
199 console::time_end(&first_label(arguments));
200 Ok(JSValue::undefined(&ctx))
201}
202
203#[callback]
204fn host_console_group(
205 ctx: JSContext,
206 _function: JSObject,
207 _this: JSObject,
208 arguments: &[JSValue],
209) -> HostResult {
210 console::group(&first_label(arguments));
211 Ok(JSValue::undefined(&ctx))
212}
213
214#[callback]
215fn host_console_group_collapsed(
216 ctx: JSContext,
217 _function: JSObject,
218 _this: JSObject,
219 arguments: &[JSValue],
220) -> HostResult {
221 console::group_collapsed(&first_label(arguments));
222 Ok(JSValue::undefined(&ctx))
223}
224
225#[callback]
226fn host_console_group_end(
227 ctx: JSContext,
228 _function: JSObject,
229 _this: JSObject,
230 _arguments: &[JSValue],
231) -> HostResult {
232 console::group_end();
233 Ok(JSValue::undefined(&ctx))
234}
235
236#[callback]
237fn host_print(
238 ctx: JSContext,
239 _function: JSObject,
240 _this: JSObject,
241 arguments: &[JSValue],
242) -> HostResult {
243 console::print(&js_values_to_strings(arguments));
244 Ok(JSValue::undefined(&ctx))
245}
246
247#[callback]
248fn host_println(
249 ctx: JSContext,
250 _function: JSObject,
251 _this: JSObject,
252 arguments: &[JSValue],
253) -> HostResult {
254 console::println(&js_values_to_strings(arguments));
255 Ok(JSValue::undefined(&ctx))
256}
257
258fn install_global_function(
259 ctx: &JSContext,
260 name: &str,
261 callback: rust_jsc::internal::JSObjectCallAsFunctionCallback,
262) -> Result<(), JSError> {
263 let function = JSFunction::callback(ctx, Some(name), callback);
264 ctx.global_object()
265 .set_property(name, &function, PropertyDescriptor::default())
266 .map_err(|err| JSError::internal(err.to_string()))
267}
268
269fn install_jstd(ctx: &JSContext) -> Result<(), JSError> {
270 install_global_function(ctx, "__jstd_console_log", Some(host_console_log))?;
271 install_global_function(ctx, "__jstd_console_info", Some(host_console_info))?;
272 install_global_function(ctx, "__jstd_console_warn", Some(host_console_warn))?;
273 install_global_function(ctx, "__jstd_console_error", Some(host_console_error))?;
274 install_global_function(ctx, "__jstd_console_debug", Some(host_console_debug))?;
275 install_global_function(ctx, "__jstd_console_trace", Some(host_console_trace))?;
276 install_global_function(ctx, "__jstd_console_assert", Some(host_console_assert))?;
277 install_global_function(ctx, "__jstd_console_clear", Some(host_console_clear))?;
278 install_global_function(ctx, "__jstd_console_count", Some(host_console_count))?;
279 install_global_function(
280 ctx,
281 "__jstd_console_count_reset",
282 Some(host_console_count_reset),
283 )?;
284 install_global_function(ctx, "__jstd_console_time", Some(host_console_time))?;
285 install_global_function(ctx, "__jstd_console_time_log", Some(host_console_time_log))?;
286 install_global_function(ctx, "__jstd_console_time_end", Some(host_console_time_end))?;
287 install_global_function(ctx, "__jstd_console_group", Some(host_console_group))?;
288 install_global_function(
289 ctx,
290 "__jstd_console_group_collapsed",
291 Some(host_console_group_collapsed),
292 )?;
293 install_global_function(
294 ctx,
295 "__jstd_console_group_end",
296 Some(host_console_group_end),
297 )?;
298 install_global_function(ctx, "__jstd_print", Some(host_print))?;
299 install_global_function(ctx, "__jstd_println", Some(host_println))?;
300 ctx.evaluate_script(jstd::bootstrap_script(), Some(1))
301 .map_err(|err| JSError::internal(err.to_string()).with_filename("<jstd>"))?;
302 Ok(())
303}
304
305pub struct JavaScriptCore {
306 ctx: JSContext,
307}
308
309impl JavaScriptCore {
310 pub fn new() -> Self {
311 let ctx = JSContext::new();
312 install_jstd(&ctx).expect("failed to initialize jstd builtins");
313 Self { ctx }
314 }
315}
316
317impl Default for JavaScriptCore {
318 fn default() -> Self {
319 Self::new()
320 }
321}
322
323impl JSEngine for JavaScriptCore {
324 fn run(&self, code: &str, _filename: &str) -> Result<JSResult, JSError> {
325 let filename = _filename;
326 let value = self.ctx.evaluate_script(code, Some(1)).map_err(|err| {
327 JSError::runtime(err.to_string())
328 .with_filename(filename)
329 .with_source(code)
330 })?;
331
332 if value.is_string() {
333 return Ok(JSResult::String(
334 value
335 .as_string()
336 .map(|s| s.to_string())
337 .map_err(|err| JSError::runtime(err.to_string()).with_filename(filename))?,
338 ));
339 }
340
341 if value.is_number() {
342 let number = value
343 .as_number()
344 .map_err(|err| JSError::runtime(err.to_string()).with_filename(filename))?;
345 if number.fract() == 0.0 {
346 return Ok(JSResult::String((number as i64).to_string()));
347 }
348 return Ok(JSResult::String(number.to_string()));
349 }
350
351 if value.is_boolean() {
352 return Ok(JSResult::String(value.as_boolean().to_string()));
353 }
354
355 if value.is_undefined() {
356 return Ok(JSResult::String("undefined".to_string()));
357 }
358
359 if value.is_null() {
360 return Ok(JSResult::String("null".to_string()));
361 }
362
363 Ok(JSResult::String("object".to_string()))
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370
371 #[test]
372 fn test_run_simple_expression() {
373 let engine = JavaScriptCore::new();
374 let result = engine.run("40 + 2", "<test>").unwrap();
375 match result {
376 JSResult::String(s) => assert_eq!(s, "42"),
377 }
378 }
379
380 #[test]
381 fn test_run_string_literal() {
382 let engine = JavaScriptCore::new();
383 let result = engine.run("'hello'", "<test>").unwrap();
384 match result {
385 JSResult::String(s) => assert_eq!(s, "hello"),
386 }
387 }
388
389 #[test]
390 fn test_run_boolean() {
391 let engine = JavaScriptCore::new();
392 let result = engine.run("true", "<test>").unwrap();
393 match result {
394 JSResult::String(s) => assert_eq!(s, "true"),
395 }
396 }
397
398 #[test]
399 fn test_console_log_builtin() {
400 let engine = JavaScriptCore::new();
401 let result = engine.run("console.log('hello'); 42", "<test>").unwrap();
402 match result {
403 JSResult::String(s) => assert_eq!(s, "42"),
404 }
405 }
406}