yolk/script/
eval_ctx.rs

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
use std::path::Path;
use std::sync::Arc;

use miette::Result;
use rhai::module_resolvers::FileModuleResolver;
use rhai::Engine;
use rhai::Module;
use rhai::Scope;
use rhai::Variant;

use crate::yolk::EvalMode;

use super::rhai_error::RhaiError;
use super::stdlib;

pub const YOLK_TEXT_NAME: &str = "YOLK_TEXT";

#[derive(Debug)]
pub struct EvalCtx {
    engine: Engine,
    scope: Scope<'static>,
    yolk_file_module: Option<Arc<Module>>,
}

impl Default for EvalCtx {
    fn default() -> Self {
        Self::new_empty()
    }
}

impl EvalCtx {
    pub fn new_empty() -> Self {
        let mut engine = Engine::new();
        engine.set_optimization_level(rhai::OptimizationLevel::Simple);

        engine.build_type::<super::sysinfo::SystemInfo>();
        engine.build_type::<super::sysinfo::SystemInfoPaths>();
        Self {
            engine,
            scope: Scope::new(),
            yolk_file_module: None,
        }
    }

    /// Initialize a new [`EvalCtx`] with set up modules and module resolver.
    ///
    /// The given mode is used when initializing the `io` module,
    /// to determine whether to actually perform any IO or to just simulate it.
    pub fn new_in_mode(mode: EvalMode) -> Result<Self> {
        let mut ctx = Self::new_empty();
        ctx.engine
            .register_global_module(Arc::new(stdlib::global_stuff()));
        ctx.engine
            .register_static_module("utils", Arc::new(stdlib::utils_module()));
        ctx.engine
            .register_static_module("io", Arc::new(stdlib::io_module(mode)));
        let template_module = Arc::new(stdlib::tag_module());
        ctx.engine
            .register_static_module("template", template_module);

        Ok(ctx)
    }

    /// Set the directory to look for imports in.
    ///
    /// The given `path` is used as the path for the a [`FileModuleResolver`],
    /// such that `import` statements can be used in rhai code relative to this path.
    pub fn set_module_path(&mut self, path: &Path) {
        self.engine
            .set_module_resolver(FileModuleResolver::new_with_path(path));
    }

    /// Load a given rhai string as a global module, and store it as the `yolk_file_module`.
    pub fn load_rhai_file_to_module(&mut self, content: &str) -> Result<(), RhaiError> {
        let ast = self.compile(content)?;
        let module = Module::eval_ast_as_new(self.scope.clone(), &ast, &self.engine)
            .map_err(|e| RhaiError::from_rhai(content, *e))?;
        let module = Arc::new(module);
        self.engine.register_global_module(module.clone());
        self.yolk_file_module = Some(module.clone());
        Ok(())
    }

    /// Eval a given string of rhai and return the result. Execute in the scope of this [`EvalCtx`].
    pub fn eval_rhai<T: Variant + Clone>(&mut self, content: &str) -> Result<T, RhaiError> {
        let ast = self.compile(content)?;
        self.engine
            .eval_ast_with_scope(&mut self.scope, &ast)
            .map_err(|e| RhaiError::from_rhai(content, *e))
    }

    /// Eval a rhai expression in a scope that has a special `get_yolk_text()` function that returns the given `text`.
    /// After the expression is evaluated, the scope is rewound to its state before the function was added.
    pub fn eval_text_transformation(
        &mut self,
        text: &str,
        expr: &str,
    ) -> Result<String, RhaiError> {
        let scope_before = self.scope.len();
        let text = text.to_string();
        self.engine
            .register_fn("get_yolk_text", move || text.clone());
        let result = self.eval_rhai::<String>(expr)?;
        self.scope.rewind(scope_before);
        Ok(result.to_string())
    }

    pub fn set_global<T: Variant + Clone>(&mut self, name: &str, value: T) {
        self.scope.set_or_push(name, value);
    }

    pub fn engine_mut(&mut self) -> &mut Engine {
        &mut self.engine
    }

    pub fn yolk_file_module(&self) -> Option<&Arc<Module>> {
        self.yolk_file_module.as_ref()
    }

    fn compile(&mut self, text: &str) -> Result<rhai::AST, RhaiError> {
        self.engine
            .compile(text)
            .map_err(|e| RhaiError::from_rhai_compile(text, e))
    }
}