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
126
127
128
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<(rhai::AST, 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((ast, 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 mut ast = self.compile(content)?;
        if let Some((yolk_file_ast, _)) = self.yolk_file_module.as_ref() {
            ast = yolk_file_ast.merge(&ast);
        }
        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<&(rhai::AST, 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))
    }
}