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