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!();