hyperon/metta/runner/modules/
mod.rs

1
2use std::path::Path;
3use std::cell::RefCell;
4
5use crate::metta::runner::*;
6use crate::space::module::ModuleSpace;
7
8use regex::Regex;
9
10use super::interpreter::interpret;
11
12use std::mem;
13
14mod mod_names;
15pub(crate) use mod_names::{ModNameNode, mod_name_from_path, normalize_relative_module_name, mod_name_remove_prefix, ModNameNodeDisplayWrapper};
16#[cfg(feature = "pkg_mgmt")]
17pub(crate) use mod_names::{module_name_is_legal, module_name_make_legal, decompose_name_path, compose_name_path};
18
19pub use mod_names::{TOP_MOD_NAME, SELF_MOD_NAME, MOD_NAME_SEPARATOR};
20use crate::metta::types::{AtomType, get_atom_types};
21
22/// A reference to a [MettaMod] that is loaded into a [Metta] runner
23//
24//NOTE: I don't love exposing the internals of ModId, but because the C bindings are in a separate crate
25// it was a choice between that and using an unnecessary box
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
27pub struct ModId(pub usize);
28
29impl Default for ModId {
30    fn default() -> Self {
31        ModId::INVALID
32    }
33}
34
35impl ModId {
36    /// An invalid ModId that doesn't point to any loaded module
37    pub const INVALID: ModId = ModId(usize::MAX);
38
39    /// An reserved ModId for the runner's top module
40    pub const TOP: ModId = ModId(0);
41
42    pub(crate) const fn new_relative(idx: usize) -> Self {
43        //Set the highest bit to 1 to indicate a relative ID
44        Self((!(usize::MAX >> 1)) | idx)
45    }
46    pub(crate) const fn get_idx_from_relative(self) -> usize {
47        self.0 & (usize::MAX >> 1)
48    }
49    pub(crate) const fn is_relative(self) -> bool {
50        self.0 & (!(usize::MAX >> 1)) > 0
51    }
52}
53
54/// Contains state associated with a loaded MeTTa module
55#[derive(Debug)]
56pub struct MettaMod {
57    mod_path: String,
58    resource_dir: Option<PathBuf>,
59    space: DynSpace,
60    tokenizer: Shared<Tokenizer>,
61    imported_deps: Mutex<HashMap<ModId, DynSpace>>,
62    loader: Option<Box<dyn ModuleLoader>>,
63}
64
65impl MettaMod {
66
67    /// Internal method to initialize an empty MettaMod
68    pub(crate) fn new_with_tokenizer(metta: &Metta, mod_path: String, space: DynSpace, tokenizer: Shared<Tokenizer>, resource_dir: Option<PathBuf>, no_stdlib: bool) -> Self {
69
70        //Give the space a name based on the module, if it doesn't already have one
71        if let Some(g_space) = space.borrow_mut().as_any_mut().downcast_mut::<GroundingSpace>() {
72            if g_space.name().is_none() {
73                g_space.set_name(mod_path.clone());
74            }
75        }
76        let space = DynSpace::new(ModuleSpace::new(space));
77
78        let new_mod = Self {
79            mod_path,
80            space,
81            tokenizer,
82            imported_deps: Mutex::new(HashMap::new()),
83            resource_dir,
84            loader: None,
85        };
86        //Load the stdlib unless this module is no_std
87        if !no_stdlib {
88            if let Some(corelib_mod_id) = metta.0.corelib_mod.get() {
89                if new_mod.name() != "stdlib" {
90                    new_mod.import_all_from_dependency(*corelib_mod_id, metta.get_mod_ptr(*corelib_mod_id), metta).unwrap();
91                }
92            }
93            if let Some(stdlib_mod_id) = metta.0.stdlib_mod.get() {
94                new_mod.import_all_from_dependency(*stdlib_mod_id, metta.get_mod_ptr(*stdlib_mod_id), metta).unwrap();
95            }
96        }
97        new_mod
98    }
99
100    /// Internal method to store the loader with its module, for resource access later on
101    pub(crate) fn set_loader(&mut self, loader: Box<dyn ModuleLoader>) {
102        self.loader = Some(loader);
103    }
104
105    /// Adds a loaded module as a dependency of the `&self` [MettaMod], and adds a [Tokenizer] entry to access
106    /// the dependent module's Space.
107    pub(crate) fn import_dependency_as(&self, mod_ptr: Rc<MettaMod>, name: Option<String>) -> Result<(), String> {
108
109        // Get the space and name associated with the dependent module
110        let dep_space = mod_ptr.space().clone();
111        let name = match name {
112            Some(name) => name,
113            None => mod_ptr.name().to_string()
114        };
115
116        //If the space name doesn't begin with '&' then add it
117        let new_space_token = if name.starts_with('&') {
118            name
119        } else {
120            format!("&{name}")
121        };
122
123        // Add a new atom to the &self space, so we can access the dependent module
124        let dep_space_atom = Atom::gnd(dep_space);
125        self.tokenizer.borrow_mut().register_token_with_regex_str(&new_space_token, move |_| { dep_space_atom.clone() });
126
127        Ok(())
128    }
129
130    /// Adds a specific atom and/or Tokenizer entry from a dependency module to the &self module
131    pub(crate) fn import_item_from_dependency_as(&self, from_name: &str, mod_ptr: Rc<MettaMod>, name: Option<&str>) -> Result<(), String> {
132
133        // Get the space and tokenizer associated with the dependent module
134        let dep_space = mod_ptr.space().clone();
135        let dep_tokenizer = mod_ptr.tokenizer().clone();
136        let src_mod_name = mod_ptr.path().to_string();
137
138        //See if there is a match in the dependent module's Tokenizer
139        if let Some(found_constructor) = dep_tokenizer.borrow().find_exact(from_name) {
140
141            // If so, this method just transplants the Tokenizer entry
142            self.tokenizer.borrow_mut().register_token_with_func_ptr(Regex::new(from_name).unwrap(), found_constructor);
143        } else {
144            //Otherwise we will try and transplant an atom
145
146            // Get and reduce the atom we are importing, from the dependent module
147            let mut parser = SExprParser::new(from_name);
148            let import_sym = parser.parse(&dep_tokenizer.borrow())?.ok_or_else(|| format!("Import failed to resolve \"{from_name}\""))?;
149            if let Some(extra_atom) = parser.parse(&dep_tokenizer.borrow())? { return Err(format!("Extraneous token in import \"{extra_atom}\""));}
150            let src_atom_vec = interpret(dep_space, &import_sym)?;
151            match src_atom_vec.len() {
152                0 => return Err(format!("Failed to resolve import \"{from_name}\" in module \"{src_mod_name}\"")),
153                1 => {},
154                _ => return Err(format!("Ambiguous import \"{from_name}\" in module \"{src_mod_name}\"")),
155            }
156            let src_atom = src_atom_vec.into_iter().next().unwrap();
157
158            // Add the atom to our module's space
159            self.add_atom(src_atom.clone(), false).expect("Unexpected type check error");
160
161            // Finally, Add a Tokenizer entry to access this atom, if one is needed
162            let name = match name {
163                Some(name) => name,
164                None => from_name
165            };
166            //We will add Tokenizer entries for all non-symbols, and symbol atoms that don't match name
167            let should_add_tok = match &src_atom {
168                Atom::Symbol(s) => s.name() != name,
169                _ => true,
170            };
171            if should_add_tok {
172                self.tokenizer.borrow_mut().register_token_with_regex_str(&name, move |_| { src_atom.clone() });
173            }
174        }
175
176        Ok(())
177    }
178
179    /// Effectively adds all atoms in a dependency module to the &self module, by adding the dependency
180    /// module's space as an atom inside the &self module
181    pub(crate) fn import_all_from_dependency(&self, mod_id: ModId, mod_ptr: Rc<MettaMod>, metta: &Metta) -> Result<(), String> {
182
183        // See if the dependency has already been imported
184        if self.contains_imported_dep(&mod_id) {
185            return Ok(())
186        }
187
188        // Get the space associated with the dependent module
189        log::debug!("import_all_from_dependency: importing from {} into {}", mod_ptr.path(), self.path());
190        let (dep_space, transitive_deps) = mod_ptr.stripped_space();
191
192        // Add a new Grounded Space atom to the &self space, so we can access the dependent module
193        self.insert_dep(mod_id, dep_space.clone())?;
194
195        // Add all the transitive deps from the dependency
196        if let Some(transitive_deps) = transitive_deps {
197            for (dep_mod_id, dep_space) in transitive_deps {
198                self.insert_dep(dep_mod_id, dep_space)?;
199            }
200        }
201
202        // Finally, Import the tokens from the dependency
203        match &mod_ptr.loader {
204            Some(loader) => loader.load_tokens(self, metta.clone()),
205            None => Ok(()), // no tokens are exported by mod_ptr
206        }
207    }
208
209    /// Returns `true` if the `self` module has imported the `mod_id` module as a sub-dependency
210    pub fn contains_imported_dep(&self, mod_id: &ModId) -> bool {
211        let deps_table = self.imported_deps.lock().unwrap();
212        deps_table.contains_key(&mod_id)
213    }
214
215    /// Private function to insert a dependency's space in a grounded atom into a module's space
216    fn insert_dep(&self, mod_id: ModId, dep_space: DynSpace) -> Result<(), String> {
217        let mut deps_table = self.imported_deps.lock().unwrap();
218        if !deps_table.contains_key(&mod_id) {
219            match self.space.borrow_mut().as_any_mut().downcast_mut::<ModuleSpace>() {
220                Some(s) => s.add_dep(dep_space.clone()),
221                None => unreachable!(),
222            }
223            deps_table.insert(mod_id, dep_space);
224        }
225        Ok(())
226    }
227
228    /// Private method to iterate a module's imported_deps and replace a reference to one ModId
229    /// with another from a map.
230    pub(crate) fn remap_imported_deps(&self, mapping: &HashMap<ModId, ModId>) {
231        let mut deps = self.imported_deps.lock().unwrap();
232        let mut temp = HashMap::with_capacity(deps.len());
233        mem::swap(&mut temp, &mut *deps);
234        for (dep_mod_id, space) in temp.into_iter() {
235            let new_mod_id = match mapping.get(&dep_mod_id) {
236                Some(mapped_id) => *mapped_id,
237                None => dep_mod_id,
238            };
239            deps.insert(new_mod_id, space);
240        }
241    }
242
243    /// Private function that returns a deep copy of a module's space, with the module's dependency
244    /// sub-spaces stripped out and returned separately
245    fn stripped_space(&self) -> (DynSpace, Option<HashMap<ModId, DynSpace>>) {
246        let deps_table = self.imported_deps.lock().unwrap();
247        (self.space.clone(), Some(deps_table.clone()))
248    }
249
250    /// Returns the full path of a loaded module.  For example: "top:parent_mod:this_mod"
251    pub fn path(&self) -> &str {
252        &self.mod_path
253    }
254
255    /// Returns the name of the loaded module.
256    pub fn name(&self) -> &str {
257        mod_name_from_path(&self.mod_path)
258    }
259
260    /// Returns a reference to the module's [PkgInfo], if it has one
261    #[cfg(feature = "pkg_mgmt")]
262    pub fn pkg_info(&self) -> Option<&PkgInfo> {
263        self.loader.as_ref().and_then(|loader| loader.pkg_info())
264    }
265
266    pub fn space(&self) -> DynSpace {
267        self.space.clone()
268    }
269
270    pub fn tokenizer(&self) -> &Shared<Tokenizer> {
271        &self.tokenizer
272    }
273
274    pub fn resource_dir(&self) -> Option<&Path> {
275        self.resource_dir.as_deref()
276    }
277
278    pub fn get_resource(&self, res_key: ResourceKey) -> Result<Resource, String> {
279        if let Some(loader) = &self.loader {
280            loader.get_resource(res_key)
281        } else {
282            Err(format!("module resource loader not available"))
283        }
284    }
285
286    /// A convenience to add an an atom to a module's Space, if it passes type-checking
287    pub(crate) fn add_atom(&self, atom: Atom, type_check: bool) -> Result<(), Vec<Atom>> {
288        if type_check {
289            let types = get_atom_types(&self.space, &atom);
290            if types.iter().all(AtomType::is_error) {
291                return Err(types.into_iter().map(AtomType::into_error_unchecked).collect());
292            }
293        }
294        self.space.borrow_mut().add(atom);
295        Ok(())
296    }
297}
298
299/// ModuleInitState is a smart-pointer to a state that contains some objects to be merged
300/// into the runner after module initialization has completed sucessfully
301pub(crate) enum ModuleInitState {
302    /// Meaning: The RunnerState holding this pointer is not initializing modules
303    None,
304    /// Meaning: The RunnerState holding this pointer is the "top" of a module init process
305    /// When the init function is finished, all of the InitFrames will be merged into the runner
306    Root(Rc<RefCell<ModuleInitStateInsides>>),
307    /// Meaning: The RunnerState holding this pointer is nested within a module init process
308    /// When the init function is finished, the pointer will be simply dropped
309    Child(Rc<RefCell<ModuleInitStateInsides>>)
310}
311
312pub(crate) struct ModuleInitStateInsides {
313    frames: Vec<ModuleInitFrame>,
314    module_descriptors: HashMap<ModuleDescriptor, ModId>,
315}
316
317impl ModuleInitState {
318    pub fn empty() -> Self {
319        Self::None
320    }
321    pub fn new(mod_name: String) -> (Self, ModId) {
322        let new_insides = ModuleInitStateInsides {
323            frames: vec![ModuleInitFrame::new_with_name(mod_name)],
324            module_descriptors: HashMap::new(),
325        };
326        let init_state = Self::Root(Rc::new(RefCell::new(new_insides)));
327        (init_state, ModId::new_relative(0))
328    }
329    pub fn push(&mut self, mod_name: String) -> ModId {
330        match self {
331            Self::None => {
332                let (new_state, new_id) = Self::new(mod_name);
333                *self = new_state;
334                new_id
335            },
336            Self::Root(cell) |
337            Self::Child(cell) => {
338                let mut insides_ref = cell.borrow_mut();
339                let new_idx = insides_ref.frames.len();
340                insides_ref.frames.push(ModuleInitFrame::new_with_name(mod_name));
341                ModId::new_relative(new_idx)
342            }
343        }
344    }
345    pub fn new_child(&self) -> Self {
346        match self {
347            Self::None => Self::None,
348            Self::Root(rc) |
349            Self::Child(rc) => Self::Child(rc.clone())
350        }
351    }
352    pub fn is_root(&self) -> bool {
353        match self {
354            Self::Root(_) => true,
355            _ => false
356        }
357    }
358    pub fn decompose(self) -> (Vec<ModuleInitFrame>, HashMap<ModuleDescriptor, ModId>) {
359        match self {
360            Self::Root(cell) => {
361                let mut insides_ref = cell.borrow_mut();
362                let frames = mem::take(&mut insides_ref.frames);
363                let descriptors = mem::take(&mut insides_ref.module_descriptors);
364                (frames, descriptors)
365            },
366            _ => unreachable!()
367        }
368    }
369
370    /// Internal method to retrieve the mod_ptr to a module that's either loading in the
371    /// InitFrame, or loaded into the runner
372    pub fn get_mod_ptr(&self, metta: &Metta, mod_id: ModId) -> Result<Rc<MettaMod>, String> {
373        if mod_id.is_relative() {
374            let frame_idx = mod_id.get_idx_from_relative();
375            match &self {
376                Self::Root(cell) |
377                Self::Child(cell) => {
378                    let insides_ref = cell.borrow();
379                    match &insides_ref.frames.get(frame_idx).unwrap().the_mod {
380                        Some(the_mod) => Ok(the_mod.clone()),
381                        None => Err(format!("Attempt to access module before loader function has finished"))
382                    }
383                },
384                Self::None => unreachable!()
385            }
386        } else {
387            Ok(metta.get_mod_ptr(mod_id))
388        }
389    }
390
391    /// Locates and retrieves a loaded module, or a sub-module relative to the module being loaded
392    pub fn get_module_by_name(&self, runner: &Metta, mod_name: &str) -> Result<ModId, String> {
393        let mod_id = match self {
394            Self::Root(cell) |
395            Self::Child(cell) => {
396                let insides_ref = cell.borrow();
397                let mut subtree_pairs = vec![];
398                for frame in insides_ref.frames.iter() {
399                    subtree_pairs.push((frame.path(), &frame.sub_module_names));
400                }
401                let module_names = runner.0.module_names.lock().unwrap();
402                module_names.resolve_layered(&subtree_pairs[..], mod_name).ok_or_else(|| format!("Unable to locate module: {mod_name}"))
403            },
404            Self::None => runner.get_module_by_name(mod_name)
405        }?;
406
407        if mod_id == ModId::INVALID {
408            Err(format!("Attempt to resolve module that is not yet loaded: {mod_name}"))
409        } else {
410            Ok(mod_id)
411        }
412    }
413
414    pub fn add_module_to_name_tree(&self, runner: &Metta, frame_id: ModId, mod_name: &str, mod_id: ModId) -> Result<(), String> {
415        match self {
416            Self::Root(_) |
417            Self::Child(_) => self.in_frame(frame_id, |frame| frame.add_module_to_name_tree(mod_name, mod_id)),
418            _ => runner.add_module_to_name_tree(mod_name, mod_id)
419        }
420    }
421
422    /// Runs the provided function in the context of the frame specified by `frame_mod`
423    pub fn in_frame<R, F: FnOnce(&mut ModuleInitFrame)->R>(&self, frame_mod: ModId, func: F) -> R {
424        match self {
425            Self::Root(cell) |
426            Self::Child(cell) => {
427                let mut insides_ref = cell.borrow_mut();
428                let frame_idx = frame_mod.get_idx_from_relative();
429                let frame = insides_ref.frames.get_mut(frame_idx).unwrap();
430                func(frame)
431            },
432            _ => unreachable!()
433        }
434    }
435
436    /// Returns the ModId after initializing a module with the provided loader
437    ///
438    /// The init function will then call `context.init_self_module()` along with any other initialization code
439    pub fn init_module(&mut self, runner: &Metta, mod_name: &str, loader: Box<dyn ModuleLoader>) -> Result<ModId, String> {
440
441        //Give the prepare function a chance to run, in case it hasn't yet
442        #[cfg(feature = "pkg_mgmt")]
443        let loader = match loader.prepare(None, UpdateMode::FetchIfMissing)? {
444            Some(new_loader) => new_loader,
445            None => loader
446        };
447
448        //Create a new RunnerState in order to initialize the new module, and push the init function
449        // to run within the new RunnerState.  The init function will then call `context.init_self_module()`
450        let mut runner_state = RunnerState::new_for_loading(runner, mod_name, self);
451        runner_state.run_in_context(|context| {
452            context.push_func(|context| loader.load(context));
453            Ok(())
454        })?;
455
456        //Finish the execution
457        while !runner_state.is_complete() {
458            runner_state.run_step()?;
459        }
460        let mod_id = runner_state.finalize_loading()?;
461
462        //Set the loader on the module, so its resource can be accessed later
463        self.in_frame(mod_id, |frame| Rc::get_mut(frame.the_mod.as_mut().unwrap()).unwrap().set_loader(loader));
464
465        Ok(mod_id)
466    }
467
468    #[cfg(feature = "pkg_mgmt")]
469    pub fn get_module_with_descriptor(&self, runner: &Metta, descriptor: &ModuleDescriptor) -> Option<ModId> {
470        match self {
471            Self::Root(cell) |
472            Self::Child(cell) => {
473                let insides_ref = cell.borrow_mut();
474                match insides_ref.module_descriptors.get(descriptor) {
475                    Some(mod_id) => Some(mod_id.clone()),
476                    None => runner.get_module_with_descriptor(descriptor)
477                }
478            },
479            Self::None => {
480                runner.get_module_with_descriptor(descriptor)
481            }
482        }
483    }
484
485    #[cfg(feature = "pkg_mgmt")]
486    pub fn add_module_descriptor(&self, runner: &Metta, descriptor: ModuleDescriptor, mod_id: ModId) {
487        match self {
488            Self::Root(cell) |
489            Self::Child(cell) => {
490                let mut insides_ref = cell.borrow_mut();
491                insides_ref.module_descriptors.insert(descriptor, mod_id);
492            },
493            Self::None => {
494                runner.add_module_descriptor(descriptor, mod_id);
495            }
496        }
497    }
498
499}
500
501pub(crate) struct ModuleInitFrame {
502    /// The new module will get this name
503    pub new_mod_name: Option<String>,
504    /// The new module, after the init has finished
505    pub the_mod: Option<Rc<MettaMod>>,
506    /// Names of additional sub-modules loaded for this frame, relative to `self::path`
507    pub sub_module_names: ModNameNode,
508}
509
510impl ModuleInitFrame {
511    /// Creates a new ModuleInitFrame with a new module name.  Make sure this is normalized
512    pub fn new_with_name(new_mod_name: String) -> Self {
513        Self {
514            new_mod_name: Some(new_mod_name),
515            the_mod: None,
516            sub_module_names: ModNameNode::new(ModId::INVALID),
517        }
518    }
519    pub fn path(&self) -> &str {
520        match &self.new_mod_name {
521            Some(name) => name.as_str(),
522            None => self.the_mod.as_ref().unwrap().path()
523        }
524    }
525    pub fn init_self_module(&mut self, self_mod_id: ModId, metta: &Metta, space: DynSpace, resource_dir: Option<PathBuf>) -> Rc<MettaMod> {
526        let tokenizer = Shared::new(Tokenizer::new());
527        let mod_name = self.new_mod_name.clone().unwrap();
528        let new_mod = Rc::new(MettaMod::new_with_tokenizer(metta, mod_name, space, tokenizer, resource_dir, false));
529        self.sub_module_names.update("top", self_mod_id).unwrap();
530        new_mod
531    }
532    /// Adds a sub-module to this module's subtree
533    //
534    //DISCUSSION: Should a module loader be able to load modules into its parents?
535    // The argument for No is mainly hygene and isolation.  The argument for Yes is convenience.
536    //
537    // Currently this method implements the No behavior.  This is mostly because I feel that is
538    // correct, but partially because the Yes behavior raises an annoying paradox.
539    // If we wanted to implement the Yes behavior, we would want to assemble a layered tree from
540    // all the InitFrames, and use [ModNameNode::add_to_layered] to add the new node.  However,
541    // the added module now belongs to the node it was placed in.  So if the disjoint sub-module
542    // that added it went on to fail loading, the sub-module wouldn't get cleaned up.  Same goes
543    // for modules imported into the runner directly.  So the question of ownership over the module
544    // name-space gets a lot trickier if modules are allowed to add sub-modules outside themselves
545    pub(crate) fn add_module_to_name_tree(&mut self, mod_name: &str, mod_id: ModId) -> Result<(), String> {
546        let self_mod_path = &self.new_mod_name.as_ref().unwrap();
547        match mod_name_remove_prefix(mod_name, &self_mod_path) {
548            Some(sub_mod_name) => {
549                if sub_mod_name.len() == 0 {
550                    return Err(format!("Attempt to load {mod_name} recursively from within its own loader"));
551                }
552                self.sub_module_names.add(sub_mod_name, mod_id)
553            },
554            None => return Err(format!("Cannot load module {mod_name} from loader of {self_mod_path}.  Module loaders may only load sub-modules"))
555        }
556    }
557}
558
559/// Implemented to supply a loader functions for MeTTa modules
560///
561/// A ModuleLoader is responsible for loading a MeTTa module through the API.  Implementations of
562/// ModuleLoader can be used to define a module format or to supply programmatically defined modules
563pub trait ModuleLoader: std::fmt::Debug + Send + Sync {
564    /// A function to load the module my making MeTTa API calls.  This function will be called
565    /// as a downstream consequence of [Metta::load_module_at_path], [Metta::load_module_direct],
566    /// [RunContext::load_module], or any other method that leads to the loading of modules
567    fn load(&self, context: &mut RunContext) -> Result<(), String>;
568
569    /// A function to access the [PkgInfo] struct of meta-data associated with a module
570    ///
571    /// NOTE: Requires `pkg_mgmt` feature
572    #[cfg(feature = "pkg_mgmt")]
573    fn pkg_info(&self) -> Option<&PkgInfo> {
574        None
575    }
576
577    /// Prepares a module for loading.  This method is responsible for fetching resources
578    /// from the network, performing build or pre-computation steps, or any other operations
579    /// that only need to be performed once and then may be cached locally
580    ///
581    /// If this method returns `Ok(Some(_))` then the loader will be dropped and the returned
582    /// loader will replace it.
583    ///
584    /// NOTE: This method may become async in the future
585    ///
586    /// FUTURE-QUESTION: Should "Fetch" and "Build" be separated? Currently they are lumped
587    /// together into one interface but it may make sense to split them into separate entry
588    /// points. I will keep them as one until the need arises.
589    #[cfg(feature = "pkg_mgmt")]
590    fn prepare(&self, _local_dir: Option<&Path>, _update_mode: UpdateMode) -> Result<Option<Box<dyn ModuleLoader>>, String> {
591        Ok(None)
592    }
593
594    /// Returns a data blob containing a given named resource belonging to a module
595    fn get_resource(&self, _res_key: ResourceKey) -> Result<Resource, String> {
596        Err("resource not found".to_string())
597    }
598
599    /// Loads module's tokens into target module. This method is used for both
600    /// initial token loading and exporting module's tokens into importing
601    /// module.
602    fn load_tokens(&self, _target: &MettaMod, _metta: Metta) -> Result<(), String> {
603        Ok(())
604    }
605}
606
607/// Resource for loading
608pub enum Resource {
609    /// Resource backed by file
610    File(std::io::BufReader<std::fs::File>),
611    /// Resource backed by memory buffer
612    Bytes(std::io::Cursor<Vec<u8>>),
613}
614
615impl From<std::fs::File> for Resource {
616    fn from(file: std::fs::File) -> Self {
617        Self::File(std::io::BufReader::new(file))
618    }
619}
620
621impl From<Vec<u8>> for Resource {
622    fn from(bytes: Vec<u8>) -> Self {
623        Self::Bytes(std::io::Cursor::new(bytes))
624    }
625}
626
627impl std::io::Read for Resource {
628    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
629        match self {
630            Resource::File(file) => file.read(buf),
631            Resource::Bytes(bytes) => bytes.read(buf),
632        }
633    }
634}
635
636/// Identifies a resource to retrieve from a [ModuleLoader]
637///
638/// NOTE: Some resources may not be available from some modules or ModuleLoaders
639pub enum ResourceKey<'a> {
640    /// The MeTTa code for the module in S-Expression format, if the module is
641    /// implemented as MeTTa code.
642    ///
643    /// NOTE: there is no guarantee the code in the `module.metta` resource will work outside
644    /// the module's context.  This use case must be supported by each module individually.
645    MainMettaSrc,
646    /// A [semver compliant](https://semver.org) version string
647    Version,
648    /// A list of people or organizations responsible for the module **TODO**
649    Authors,
650    /// A short description of the module **TODO**
651    Description,
652    /// A custom identifier, to be interpreted by the [ModuleLoader] implementation
653    Custom(&'a str)
654}
655
656//-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-
657// TESTS
658//-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-=-=-+-
659
660#[cfg(test)]
661mod test {
662    use hyperon_atom::gnd::GroundedFunctionAtom;
663    use hyperon_atom::gnd::number::{Number, ATOM_TYPE_NUMBER};
664    use super::*;
665    use std::sync::Mutex;
666    use std::sync::LazyLock;
667
668    #[derive(Debug)]
669    struct OuterLoader;
670
671    impl ModuleLoader for OuterLoader {
672        fn load(&self, context: &mut RunContext) -> Result<(), String> {
673            let space = GroundingSpace::new();
674            context.init_self_module(space.into(), None);
675
676            let parser = SExprParser::new("outer-module-test-atom");
677            context.push_parser(Box::new(parser));
678
679            Ok(())
680        }
681    }
682
683    #[derive(Debug)]
684    struct InnerLoader;
685
686    impl ModuleLoader for InnerLoader {
687        fn load(&self, context: &mut RunContext) -> Result<(), String> {
688            let space = GroundingSpace::new();
689            context.init_self_module(space.into(), None);
690
691            let parser = SExprParser::new("inner-module-test-atom");
692            context.push_parser(Box::new(parser));
693
694            Ok(())
695        }
696    }
697
698    /// This tests loading a module as a sub-module of another loaded module using a hierarchical
699    /// namespace path
700    #[test]
701    fn hierarchical_module_import_test() {
702        let runner = Metta::new(Some(EnvBuilder::test_env()));
703
704        //Make sure we get a reasonable error, if we try to load a sub-module to a module that doesn't exist
705        let result = runner.load_module_direct(Box::new(InnerLoader), "outer:inner");
706        assert!(result.is_err());
707
708        //Make sure we can load sub-modules sucessfully
709        let _outer_mod_id = runner.load_module_direct(Box::new(OuterLoader), "outer").unwrap();
710        let _inner_mod_id = runner.load_module_direct(Box::new(InnerLoader), "outer:inner").unwrap();
711
712        //Make sure we load the outer module sucessfully and can match the outer module's atom, but not
713        // the inner module's
714        let result = runner.run(SExprParser::new("!(import! &self outer)"));
715        assert_eq!(result, Ok(vec![vec![expr!()]]));
716        let result = runner.run(SExprParser::new("!(match &self outer-module-test-atom found!)"));
717        assert_eq!(result, Ok(vec![vec![sym!("found!")]]));
718        let result = runner.run(SExprParser::new("!(match &self inner-module-test-atom found!)"));
719        assert_eq!(result, Ok(vec![vec![]]));
720
721        //Now import the inner module by relative module namespace, and check to make sure we can match
722        // its atom
723        let result = runner.run(SExprParser::new("!(import! &self outer:inner)"));
724        assert_eq!(result, Ok(vec![vec![expr!()]]));
725        let result = runner.run(SExprParser::new("!(match &self inner-module-test-atom found!)"));
726        assert_eq!(result, Ok(vec![vec![sym!("found!")]]));
727    }
728
729    #[derive(Debug)]
730    struct RelativeOuterLoader;
731
732    impl ModuleLoader for RelativeOuterLoader {
733        fn load(&self, context: &mut RunContext) -> Result<(), String> {
734            let space = GroundingSpace::new();
735            context.init_self_module(space.into(), None);
736
737            let _inner_mod_id = context.load_module_direct(Box::new(InnerLoader), "self:inner").unwrap();
738
739            let parser = SExprParser::new("outer-module-test-atom");
740            context.push_parser(Box::new(parser));
741
742            //Test to see if I can resolve the module we just loaded,
743            assert!(context.get_module_by_name("self:inner").is_ok());
744
745            Ok(())
746        }
747    }
748
749    /// This tests loading a sub-module from another module's runner, using a relative namespace path
750    #[test]
751    fn relative_submodule_import_test() {
752        let runner = Metta::new(Some(EnvBuilder::test_env()));
753
754        //Load the "outer" module, which will load the inner module as part of its loader
755        let _outer_mod_id = runner.load_module_direct(Box::new(RelativeOuterLoader), "outer").unwrap();
756
757        // runner.display_loaded_modules();
758
759        //Make sure we didn't accidentally load "inner" at the top level
760        assert!(runner.get_module_by_name("inner").is_err());
761
762        //Confirm we didn't end up with a module called "self"
763        assert!(runner.get_module_by_name("self:inner").is_err());
764        assert!(runner.get_module_by_name("self").is_err());
765
766        //Now make sure we can actually resolve the loaded sub-module
767        runner.get_module_by_name("outer:inner").unwrap();
768
769        //LP-TODO-NEXT, test that I can add a second inner from the runner, by adding "top:outer:inner2",
770        // and then that I can import it directly into "outer" from within the runner's context using the "self:inner2" mod path
771
772    }
773
774    #[derive(Debug)]
775    struct Issue982Loader;
776
777    static ISSUE_982_CALLED: LazyLock<Mutex<i64>> = LazyLock::new(|| Mutex::new(0));
778
779    pub static ISSUE982_METTA: &'static str = "!(i982 5)";
780
781    fn i982(args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
782        let mut data = ISSUE_982_CALLED.lock().unwrap();
783        *data = args.get(0).and_then(Number::from_atom).unwrap().into();
784        Ok(vec![args.get(0).unwrap().clone()])
785    }
786
787    impl ModuleLoader for Issue982Loader {
788        fn load(&self, context: &mut RunContext) -> Result<(), String> {
789            // Initialize module's space
790            let space = GroundingSpace::new();
791            context.init_self_module(space.into(), None);
792
793            // Load module's tokens
794            let _ = self.load_tokens(context.module(), context.metta.clone())?;
795
796            // Parse MeTTa code of the module
797            let parser = SExprParser::new(ISSUE982_METTA);
798            context.push_parser(Box::new(parser));
799
800            Ok(())
801        }
802
803        fn load_tokens(&self, target: &MettaMod, _metta: Metta) -> Result<(), String> {
804            let mut tref = target.tokenizer().borrow_mut();
805
806            tref.register_function(GroundedFunctionAtom::new(
807                r"i982".into(),
808                Atom::expr([ARROW_SYMBOL, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER]),
809                i982));
810
811            Ok(())
812        }
813    }
814
815    #[test]
816    fn issue982_test() {
817        let runner = Metta::new(Some(EnvBuilder::test_env()));
818
819        let _i982loader_mod_id = runner.load_module_direct(Box::new(Issue982Loader), "i982loader").unwrap();
820
821        let result = runner.run(SExprParser::new("!(import! &self i982loader)"));
822        assert_eq!(result, Ok(vec![vec![expr!()]]));
823
824        assert_eq!(*ISSUE_982_CALLED.lock().unwrap(), 5);
825
826        let result = runner.run(SExprParser::new("!(assertEqual (i982 6) 6)"));
827        assert_eq!(result, Ok(vec![vec![expr!()]]));
828
829        assert_eq!(*ISSUE_982_CALLED.lock().unwrap(), 6);
830    }
831
832
833    //LP-TODO-NEXT,  Make a test for an inner-loader that throws an error, blocking the outer-loader from loading sucessfully,
834    // and make sure neither module is loaded into the named index
835    //
836    //Also test the case where the inner loader is sucessul, but then the outer loader throws an error.  Also make sure neither
837    // module is loaded into the namespace
838    //
839}