yolk/script/
eval_ctx.rs

1use std::path::Path;
2use std::sync::Arc;
3
4use miette::Result;
5use rhai::module_resolvers::FileModuleResolver;
6use rhai::Engine;
7use rhai::Module;
8use rhai::Scope;
9use rhai::Variant;
10
11use crate::yolk::EvalMode;
12
13use super::rhai_error::RhaiError;
14use super::stdlib;
15
16pub const YOLK_TEXT_NAME: &str = "YOLK_TEXT";
17
18#[derive(Debug)]
19pub struct EvalCtx {
20    engine: Engine,
21    scope: Scope<'static>,
22    yolk_file_module: Option<(rhai::AST, Arc<Module>)>,
23}
24
25impl Default for EvalCtx {
26    fn default() -> Self {
27        Self::new_empty()
28    }
29}
30
31impl EvalCtx {
32    pub fn new_empty() -> Self {
33        let mut engine = Engine::new();
34        engine.set_optimization_level(rhai::OptimizationLevel::Simple);
35
36        engine.build_type::<super::sysinfo::SystemInfo>();
37        engine.build_type::<super::sysinfo::SystemInfoPaths>();
38        Self {
39            engine,
40            scope: Scope::new(),
41            yolk_file_module: None,
42        }
43    }
44
45    /// Initialize a new [`EvalCtx`] with set up modules and module resolver.
46    ///
47    /// The given mode is used when initializing the `io` module,
48    /// to determine whether to actually perform any IO or to just simulate it.
49    pub fn new_in_mode(mode: EvalMode) -> Result<Self> {
50        let mut ctx = Self::new_empty();
51        ctx.engine
52            .register_global_module(Arc::new(stdlib::global_stuff()));
53        ctx.engine
54            .register_static_module("utils", Arc::new(stdlib::utils_module()));
55        ctx.engine
56            .register_static_module("io", Arc::new(stdlib::io_module(mode)));
57        let template_module = Arc::new(stdlib::tag_module());
58        ctx.engine
59            .register_static_module("template", template_module);
60
61        Ok(ctx)
62    }
63
64    /// Set the directory to look for imports in.
65    ///
66    /// The given `path` is used as the path for the a [`FileModuleResolver`],
67    /// such that `import` statements can be used in rhai code relative to this path.
68    pub fn set_module_path(&mut self, path: &Path) {
69        self.engine
70            .set_module_resolver(FileModuleResolver::new_with_path(path));
71    }
72
73    /// Load a given rhai string as a global module, and store it as the `yolk_file_module`.
74    pub fn load_rhai_file_to_module(&mut self, content: &str) -> Result<(), RhaiError> {
75        let ast = self.compile(content)?;
76        let module = Module::eval_ast_as_new(self.scope.clone(), &ast, &self.engine)
77            .map_err(|e| RhaiError::from_rhai(content, *e))?;
78        let module = Arc::new(module);
79        self.engine.register_global_module(module.clone());
80        self.yolk_file_module = Some((ast, module.clone()));
81        Ok(())
82    }
83
84    /// Eval a given string of rhai and return the result. Execute in the scope of this [`EvalCtx`].
85    pub fn eval_rhai<T: Variant + Clone>(&mut self, content: &str) -> Result<T, RhaiError> {
86        let mut ast = self.compile(content)?;
87        if let Some((yolk_file_ast, _)) = self.yolk_file_module.as_ref() {
88            ast = yolk_file_ast.merge(&ast);
89        }
90        self.engine
91            .eval_ast_with_scope(&mut self.scope, &ast)
92            .map_err(|e| RhaiError::from_rhai(content, *e))
93    }
94
95    /// Eval a rhai expression in a scope that has a special `get_yolk_text()` function that returns the given `text`.
96    /// After the expression is evaluated, the scope is rewound to its state before the function was added.
97    pub fn eval_text_transformation(
98        &mut self,
99        text: &str,
100        expr: &str,
101    ) -> Result<String, RhaiError> {
102        let scope_before = self.scope.len();
103        let text = text.to_string();
104        self.engine
105            .register_fn("get_yolk_text", move || text.clone());
106        let result = self.eval_rhai::<String>(expr)?;
107        self.scope.rewind(scope_before);
108        Ok(result.to_string())
109    }
110
111    pub fn set_global<T: Variant + Clone>(&mut self, name: &str, value: T) {
112        self.scope.set_or_push(name, value);
113    }
114
115    pub fn engine_mut(&mut self) -> &mut Engine {
116        &mut self.engine
117    }
118
119    pub fn yolk_file_module(&self) -> Option<&(rhai::AST, Arc<Module>)> {
120        self.yolk_file_module.as_ref()
121    }
122
123    fn compile(&mut self, text: &str) -> Result<rhai::AST, RhaiError> {
124        self.engine
125            .compile(text)
126            .map_err(|e| RhaiError::from_rhai_compile(text, e))
127    }
128}