pub mod safety;
pub mod node;
use oxc::allocator::Allocator;
use oxc::ast::ast::{Expression, Program};
use oxc::semantic::Scoping;
use oxc_traverse::{Traverse, TraverseCtx, traverse_mut};
use crate::ast::{codegen, create, extract, query};
use crate::engine::error::Result;
use crate::engine::module::{Module, TransformResult};
use crate::value::JsValue;
#[derive(Default)]
pub struct DynamicEvaluator {
node: Option<node::NodeProcess>,
}
impl DynamicEvaluator {
pub fn new() -> Self {
Self::default()
}
fn ensure_node(&mut self) -> Option<&mut node::NodeProcess> {
if self.node.is_none() {
self.node = node::NodeProcess::spawn().ok();
}
self.node.as_mut()
}
}
impl Module for DynamicEvaluator {
fn name(&self) -> &'static str {
"DynamicEvaluator"
}
fn transform<'a>(
&mut self,
allocator: &'a Allocator,
program: &mut Program<'a>,
scoping: Scoping,
) -> Result<TransformResult> {
let mut visitor = EvalVisitor {
evaluator: self,
modifications: 0,
};
let scoping = traverse_mut(&mut visitor, allocator, program, scoping, ());
Ok(TransformResult { modifications: visitor.modifications, scoping })
}
}
struct EvalVisitor<'e> {
evaluator: &'e mut DynamicEvaluator,
modifications: usize,
}
impl<'a, 'e> Traverse<'a, ()> for EvalVisitor<'e> {
fn exit_expression(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a, ()>,
) {
if extract::js_value(expr).is_some() {
return;
}
if !query::is_side_effect_free(expr) {
return;
}
if !safety::is_safe_expr(expr) {
return;
}
let code = codegen::expr_to_code(expr);
let Some(node) = self.evaluator.ensure_node() else {
return;
};
let Some(result) = node.eval(&code) else {
return;
};
let Some(val) = json_to_jsvalue(&result) else {
return;
};
*expr = create::from_js_value(&val, &ctx.ast);
self.modifications += 1;
}
}
fn json_to_jsvalue(val: &serde_json::Value) -> Option<JsValue> {
match val {
serde_json::Value::Number(n) => n.as_f64().map(JsValue::Number),
serde_json::Value::String(s) => Some(JsValue::String(s.clone())),
serde_json::Value::Bool(b) => Some(JsValue::Boolean(*b)),
serde_json::Value::Null => Some(JsValue::Null),
_ => None,
}
}