hyperon/metta/runner/stdlib/
package.rs

1use super::{grounded_op, regex, unit_result};
2use hyperon_atom::*;
3use crate::metta::*;
4use crate::metta::text::Tokenizer;
5use crate::metta::runner::{Metta, RunContext,
6                           git_catalog::ModuleGitLocation,
7                           mod_name_from_url,
8                           pkg_mgmt::UpdateMode};
9use hyperon_atom::gnd::str::expect_string_like_atom;
10
11/// Provides a way to access [Metta::load_module_at_path] from within MeTTa code
12#[derive(Clone, Debug)]
13pub struct RegisterModuleOp {
14    metta: Metta
15}
16
17grounded_op!(RegisterModuleOp, "register-module!");
18
19impl RegisterModuleOp {
20    pub fn new(metta: Metta) -> Self {
21        Self{ metta }
22    }
23}
24
25impl Grounded for RegisterModuleOp {
26    fn type_(&self) -> Atom {
27        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, UNIT_TYPE])
28    }
29
30    fn as_execute(&self) -> Option<&dyn CustomExecute> {
31        Some(self)
32    }
33}
34
35impl CustomExecute for RegisterModuleOp {
36    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
37        let arg_error = "register-module! expects a file system path; use quotes if needed";
38        let path = args.get(0).and_then(expect_string_like_atom).ok_or_else(|| ExecError::from(arg_error))?;
39
40        let path = std::path::PathBuf::from(path);
41
42        // Load the module from the path
43        // QUESTION: Do we want to expose the ability to give the module a different name and/ or
44        // load it into a different part of the namespace hierarchy?  For now I was just thinking
45        // it is better to keep the args simple.  IMO this is a place for optional var-args when we
46        // decide on the best way to handle them language-wide.
47        self.metta.load_module_at_path(path, None).map_err(|e| ExecError::from(e))?;
48
49        unit_result()
50    }
51}
52
53/// Provides access to module in a remote git repo, from within MeTTa code
54/// Similar to `register-module!`, this op will bypass the catalog search
55///
56/// NOTE: Even if Hyperon is build without git support, this operation may still be used to
57/// load existing modules from a git cache.  That situation may occur if modules were fetched
58/// earlier or by another tool that manages the module cache.  However this operation requres
59/// git support to actually clone or pull from a git repository.
60#[derive(Clone, Debug)]
61pub struct GitModuleOp {
62    //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP
63    context: std::sync::Arc<std::sync::Mutex<Vec<std::sync::Arc<std::sync::Mutex<&'static mut RunContext<'static, 'static>>>>>>,
64}
65
66grounded_op!(GitModuleOp, "git-module!");
67
68impl GitModuleOp {
69    pub fn new(metta: Metta) -> Self {
70        Self{ context: metta.0.context.clone() }
71    }
72}
73
74impl Grounded for GitModuleOp {
75    fn type_(&self) -> Atom {
76        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, UNIT_TYPE])
77    }
78
79    fn as_execute(&self) -> Option<&dyn CustomExecute> {
80        Some(self)
81    }
82}
83
84impl CustomExecute for GitModuleOp {
85    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
86        let arg_error = "git-module! expects a URL; use quotes if needed";
87        let url = args.get(0).and_then(expect_string_like_atom).ok_or_else(|| ExecError::from(arg_error))?;
88        // TODO: When we figure out how to address varargs, it will be nice to take an optional branch name
89
90        // TODO: Depending on what we do with `register-module!`, we might want to let the
91        // caller provide an optional mod_name here too, rather than extracting it from the url
92        let mod_name = match mod_name_from_url(&url) {
93            Some(mod_name) => mod_name,
94            None => return Err(ExecError::from("git-module! error extracting module name from URL"))
95        };
96
97        let ctx_ref = self.context.lock().unwrap().last().unwrap().clone();
98        let mut context = ctx_ref.lock().unwrap();
99
100        let git_mod_location = ModuleGitLocation::new(url.to_string());
101
102        match context.metta.environment().specified_mods.as_ref() {
103            Some(specified_mods) => if let Some((loader, descriptor)) = specified_mods.loader_for_explicit_git_module(&mod_name, UpdateMode::TryFetchLatest, &git_mod_location)? {
104                context.get_or_init_module_with_descriptor(&mod_name, descriptor, loader).map_err(|e| ExecError::from(e))?;
105            },
106            None => return Err(ExecError::from(format!("Unable to pull module \"{mod_name}\" from git; no local \"caches\" directory available")))
107        }
108
109        unit_result()
110    }
111}
112
113pub(super) fn register_context_dependent_tokens(tref: &mut Tokenizer, metta: &Metta) {
114    let register_module_op = Atom::gnd(RegisterModuleOp::new(metta.clone()));
115    tref.register_token(regex(r"register-module!"), move |_| { register_module_op.clone() });
116    let git_module_op = Atom::gnd(GitModuleOp::new(metta.clone()));
117    tref.register_token(regex(r"git-module!"), move |_| { git_module_op.clone() });
118}