spo_rhai/module/resolvers/
file.rs1#![cfg(not(feature = "no_std"))]
2#![cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]
3
4use crate::eval::GlobalRuntimeState;
5use crate::func::{locked_read, locked_write};
6use crate::{
7 Engine, Identifier, Locked, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared,
8 SharedModule, ERR,
9};
10
11use std::{
12 collections::BTreeMap,
13 io::Error as IoError,
14 path::{Path, PathBuf},
15};
16
17pub const RHAI_SCRIPT_EXTENSION: &str = "rhai";
18
19#[derive(Debug)]
50pub struct FileModuleResolver {
51 base_path: Option<PathBuf>,
52 extension: Identifier,
53 cache_enabled: bool,
54 scope: Scope<'static>,
55 cache: Locked<BTreeMap<PathBuf, SharedModule>>,
56}
57
58impl Default for FileModuleResolver {
59 #[inline(always)]
60 #[must_use]
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl FileModuleResolver {
67 #[inline(always)]
85 #[must_use]
86 pub fn new() -> Self {
87 Self::new_with_extension(RHAI_SCRIPT_EXTENSION)
88 }
89
90 #[inline(always)]
108 #[must_use]
109 pub fn new_with_path(path: impl Into<PathBuf>) -> Self {
110 Self::new_with_path_and_extension(path, RHAI_SCRIPT_EXTENSION)
111 }
112
113 #[inline(always)]
128 #[must_use]
129 pub fn new_with_extension(extension: impl Into<Identifier>) -> Self {
130 Self {
131 base_path: None,
132 extension: extension.into(),
133 cache_enabled: true,
134 cache: BTreeMap::new().into(),
135 scope: Scope::new(),
136 }
137 }
138
139 #[inline(always)]
155 #[must_use]
156 pub fn new_with_path_and_extension(
157 path: impl Into<PathBuf>,
158 extension: impl Into<Identifier>,
159 ) -> Self {
160 Self {
161 base_path: Some(path.into()),
162 extension: extension.into(),
163 cache_enabled: true,
164 cache: BTreeMap::new().into(),
165 scope: Scope::new(),
166 }
167 }
168
169 #[inline(always)]
171 #[must_use]
172 pub fn base_path(&self) -> Option<&Path> {
173 self.base_path.as_deref()
174 }
175 #[inline(always)]
177 pub fn set_base_path(&mut self, path: impl Into<PathBuf>) -> &mut Self {
178 self.base_path = Some(path.into());
179 self
180 }
181
182 #[inline(always)]
184 #[must_use]
185 pub fn extension(&self) -> &str {
186 &self.extension
187 }
188
189 #[inline(always)]
191 pub fn set_extension(&mut self, extension: impl Into<Identifier>) -> &mut Self {
192 self.extension = extension.into();
193 self
194 }
195
196 #[inline(always)]
200 #[must_use]
201 pub const fn scope(&self) -> &Scope {
202 &self.scope
203 }
204
205 #[inline(always)]
209 pub fn set_scope(&mut self, scope: Scope<'static>) {
210 self.scope = scope;
211 }
212
213 #[inline(always)]
217 #[must_use]
218 pub fn scope_mut(&mut self) -> &mut Scope<'static> {
219 &mut self.scope
220 }
221
222 #[inline(always)]
224 pub fn enable_cache(&mut self, enable: bool) -> &mut Self {
225 self.cache_enabled = enable;
226 self
227 }
228 #[inline(always)]
230 #[must_use]
231 pub const fn is_cache_enabled(&self) -> bool {
232 self.cache_enabled
233 }
234
235 #[inline]
237 #[must_use]
238 pub fn is_cached(&self, path: impl AsRef<Path>) -> bool {
239 if !self.cache_enabled {
240 return false;
241 }
242 locked_read(&self.cache).contains_key(path.as_ref())
243 }
244 #[inline]
246 pub fn clear_cache(&mut self) -> &mut Self {
247 locked_write(&self.cache).clear();
248 self
249 }
250 #[inline]
254 #[must_use]
255 pub fn clear_cache_for_path(&mut self, path: impl AsRef<Path>) -> Option<SharedModule> {
256 locked_write(&self.cache)
257 .remove_entry(path.as_ref())
258 .map(|(.., v)| v)
259 }
260 #[must_use]
262 pub fn get_file_path(&self, path: &str, source_path: Option<&Path>) -> PathBuf {
263 let path = Path::new(path);
264
265 let mut file_path;
266
267 if path.is_relative() {
268 file_path = self
269 .base_path
270 .clone()
271 .or_else(|| source_path.map(Into::into))
272 .unwrap_or_default();
273 file_path.push(path);
274 } else {
275 file_path = path.into();
276 }
277
278 file_path.set_extension(self.extension.as_str()); file_path
280 }
281
282 fn impl_resolve(
284 &self,
285 engine: &Engine,
286 global: &mut GlobalRuntimeState,
287 scope: &mut Scope,
288 source: Option<&str>,
289 path: &str,
290 pos: Position,
291 ) -> Result<SharedModule, Box<crate::EvalAltResult>> {
292 let source_path = global
294 .source()
295 .or(source)
296 .and_then(|p| Path::new(p).parent());
297
298 let file_path = self.get_file_path(path, source_path);
299
300 if self.is_cache_enabled() {
301 if let Some(module) = locked_read(&self.cache).get(&file_path) {
302 return Ok(module.clone());
303 }
304 }
305
306 let mut ast = engine
307 .compile_file_with_scope(&self.scope, file_path.clone())
308 .map_err(|err| match *err {
309 ERR::ErrorSystem(.., err) if err.is::<IoError>() => {
310 Box::new(ERR::ErrorModuleNotFound(path.to_string(), pos))
311 }
312 _ => Box::new(ERR::ErrorInModule(path.to_string(), err, pos)),
313 })?;
314
315 ast.set_source(path);
316
317 let m: Shared<_> = Module::eval_ast_as_new_raw(engine, scope, global, &ast)
318 .map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))?
319 .into();
320
321 if self.is_cache_enabled() {
322 locked_write(&self.cache).insert(file_path, m.clone());
323 }
324
325 Ok(m)
326 }
327}
328
329impl ModuleResolver for FileModuleResolver {
330 fn resolve_raw(
331 &self,
332 engine: &Engine,
333 global: &mut GlobalRuntimeState,
334 scope: &mut Scope,
335 path: &str,
336 pos: Position,
337 ) -> RhaiResultOf<SharedModule> {
338 self.impl_resolve(engine, global, scope, None, path, pos)
339 }
340
341 #[inline(always)]
342 fn resolve(
343 &self,
344 engine: &Engine,
345 source: Option<&str>,
346 path: &str,
347 pos: Position,
348 ) -> RhaiResultOf<SharedModule> {
349 let global = &mut GlobalRuntimeState::new(engine);
350 let scope = &mut Scope::new();
351 self.impl_resolve(engine, global, scope, source, path, pos)
352 }
353
354 fn resolve_ast(
358 &self,
359 engine: &Engine,
360 source_path: Option<&str>,
361 path: &str,
362 pos: Position,
363 ) -> Option<RhaiResultOf<crate::AST>> {
364 let file_path = self.get_file_path(path, source_path.map(Path::new));
366
367 Some(
369 engine
370 .compile_file(file_path)
371 .map(|mut ast| {
372 ast.set_source(path);
373 ast
374 })
375 .map_err(|err| match *err {
376 ERR::ErrorSystem(.., err) if err.is::<IoError>() => {
377 ERR::ErrorModuleNotFound(path.to_string(), pos).into()
378 }
379 _ => ERR::ErrorInModule(path.to_string(), err, pos).into(),
380 }),
381 )
382 }
383}