javascript_mel/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3
4mod engine;
5
6use async_std::sync::RwLock;
7use engine::Engine;
8use json_mel::*;
9use melodium_core::*;
10use melodium_macro::{check, mel_model, mel_package, mel_treatment};
11use std::{
12    collections::HashMap,
13    sync::{Arc, Weak},
14};
15
16/// Provides JavaScript execution engine.
17///
18/// The JavaScript/ECMAScript engine manages execution of JS language within Mélodium.
19/// First, an engine is instancied with `code` parameter, that code can contains functions definitions, variables setup, and whatever seems useful for incoming use.
20/// Then `process` treatment is used for doing JavaScript processing.
21///
22/// Other parameters are defined:
23/// - `stack_size_limit`: Maximum stack size the JavaScript code may use, in bytes.
24/// - `recursion_limit`: Maximum recursion that can be reached.
25/// - `loop_iteration_limit`: Maximum iteration that can occur on any loop.
26/// - `strict`: Defines JavaScript interpretation strictness, can be override by `"use strict"` instruction in JavaScript code.
27#[derive(Debug)]
28#[mel_model(
29    param stack_size_limit u64 1024
30    param recursion_limit u64 400
31    param loop_iteration_limit u64 4294967295
32    param strict bool false
33    param {content(javascript)} code string ""
34    initialize initialize
35    shutdown shutdown
36)]
37pub struct JavaScriptEngine {
38    model: Weak<JavaScriptEngineModel>,
39    engine: RwLock<Option<Engine>>,
40}
41
42impl JavaScriptEngine {
43    fn new(model: Weak<JavaScriptEngineModel>) -> Self {
44        Self {
45            model,
46            engine: RwLock::new(None),
47        }
48    }
49
50    fn initialize(&self) {
51        let model = self.model.upgrade().unwrap();
52
53        let engine = Engine::new(
54            model.get_stack_size_limit(),
55            model.get_recursion_limit(),
56            model.get_loop_iteration_limit(),
57            model.get_strict(),
58            model.get_code(),
59        );
60
61        async_std::task::block_on(async move {
62            *model.inner().engine.write().await = Some(engine);
63        });
64    }
65
66    fn shutdown(&self) {
67        let model = self.model.upgrade().unwrap();
68        async_std::task::block_on(async move {
69            if let Some(engine) = model.inner().engine.write().await.as_ref() {
70                engine.stop();
71            }
72        });
73    }
74
75    fn invoke_source(&self, _source: &str, _params: HashMap<String, Value>) {}
76
77    pub(crate) fn engine(&self) -> &RwLock<Option<Engine>> {
78        &self.engine
79    }
80}
81
82/// Executes JavaScript code on values.
83///
84/// For every incoming `value`, `code` is executed as-is within `engine`.
85/// Inside the `code` part the incoming value is reffered as globally-accessible `value` variable.
86/// `code` can return any JS object convertible into JSON data.
87///
88/// If `code` not actually processable JavaScript code, or its return value not convertible into JSON, a none `result` value is send.
89#[mel_treatment(
90    model engine JavaScriptEngine
91    input value Stream<Json>
92    output result Stream<Option<Json>>
93)]
94pub async fn process(#[mel(content(javascript))] code: string) {
95    let engine = JavaScriptEngineModel::into(engine);
96
97    'main: while let Ok(values) = value
98        .recv_many()
99        .await
100        .map(|values| Into::<Vec<Value>>::into(values))
101    {
102        for value in values {
103            if let Value::Data(value) = value {
104                match value.downcast_arc::<Json>() {
105                    Ok(value) => {
106                        let processed;
107                        if let Some(engine) = engine.inner().engine().read().await.as_ref() {
108                            processed = engine.process(value.0.clone(), code.clone()).await;
109                        } else {
110                            break;
111                        }
112
113                        match processed {
114                            Ok(Ok(value)) => {
115                                check!(
116                                    result
117                                        .send_one(
118                                            Some(Arc::new(Json(value)) as Arc<dyn Data>).into()
119                                        )
120                                        .await
121                                );
122                            }
123                            Ok(Err(_err)) => {
124                                check!(result.send_one(Option::<Arc<dyn Data>>::None.into()).await);
125                            }
126                            Err(_) => {
127                                break 'main;
128                            }
129                        }
130                    }
131                    Err(_) => break 'main,
132                }
133            }
134        }
135    }
136}
137
138mel_package!();