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#[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 pub const INVALID: ModId = ModId(usize::MAX);
38
39 pub const TOP: ModId = ModId(0);
41
42 pub(crate) const fn new_relative(idx: usize) -> Self {
43 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#[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 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 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 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 pub(crate) fn set_loader(&mut self, loader: Box<dyn ModuleLoader>) {
102 self.loader = Some(loader);
103 }
104
105 pub(crate) fn import_dependency_as(&self, mod_ptr: Rc<MettaMod>, name: Option<String>) -> Result<(), String> {
108
109 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 let new_space_token = if name.starts_with('&') {
118 name
119 } else {
120 format!("&{name}")
121 };
122
123 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 pub(crate) fn import_item_from_dependency_as(&self, from_name: &str, mod_ptr: Rc<MettaMod>, name: Option<&str>) -> Result<(), String> {
132
133 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 if let Some(found_constructor) = dep_tokenizer.borrow().find_exact(from_name) {
140
141 self.tokenizer.borrow_mut().register_token_with_func_ptr(Regex::new(from_name).unwrap(), found_constructor);
143 } else {
144 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 self.add_atom(src_atom.clone(), false).expect("Unexpected type check error");
160
161 let name = match name {
163 Some(name) => name,
164 None => from_name
165 };
166 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 pub(crate) fn import_all_from_dependency(&self, mod_id: ModId, mod_ptr: Rc<MettaMod>, metta: &Metta) -> Result<(), String> {
182
183 if self.contains_imported_dep(&mod_id) {
185 return Ok(())
186 }
187
188 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 self.insert_dep(mod_id, dep_space.clone())?;
194
195 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 match &mod_ptr.loader {
204 Some(loader) => loader.load_tokens(self, metta.clone()),
205 None => Ok(()), }
207 }
208
209 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 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 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 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 pub fn path(&self) -> &str {
252 &self.mod_path
253 }
254
255 pub fn name(&self) -> &str {
257 mod_name_from_path(&self.mod_path)
258 }
259
260 #[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 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
299pub(crate) enum ModuleInitState {
302 None,
304 Root(Rc<RefCell<ModuleInitStateInsides>>),
307 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 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 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 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 pub fn init_module(&mut self, runner: &Metta, mod_name: &str, loader: Box<dyn ModuleLoader>) -> Result<ModId, String> {
440
441 #[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 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 while !runner_state.is_complete() {
458 runner_state.run_step()?;
459 }
460 let mod_id = runner_state.finalize_loading()?;
461
462 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 pub new_mod_name: Option<String>,
504 pub the_mod: Option<Rc<MettaMod>>,
506 pub sub_module_names: ModNameNode,
508}
509
510impl ModuleInitFrame {
511 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 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
559pub trait ModuleLoader: std::fmt::Debug + Send + Sync {
564 fn load(&self, context: &mut RunContext) -> Result<(), String>;
568
569 #[cfg(feature = "pkg_mgmt")]
573 fn pkg_info(&self) -> Option<&PkgInfo> {
574 None
575 }
576
577 #[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 fn get_resource(&self, _res_key: ResourceKey) -> Result<Resource, String> {
596 Err("resource not found".to_string())
597 }
598
599 fn load_tokens(&self, _target: &MettaMod, _metta: Metta) -> Result<(), String> {
603 Ok(())
604 }
605}
606
607pub enum Resource {
609 File(std::io::BufReader<std::fs::File>),
611 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
636pub enum ResourceKey<'a> {
640 MainMettaSrc,
646 Version,
648 Authors,
650 Description,
652 Custom(&'a str)
654}
655
656#[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 #[test]
701 fn hierarchical_module_import_test() {
702 let runner = Metta::new(Some(EnvBuilder::test_env()));
703
704 let result = runner.load_module_direct(Box::new(InnerLoader), "outer:inner");
706 assert!(result.is_err());
707
708 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 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 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 assert!(context.get_module_by_name("self:inner").is_ok());
744
745 Ok(())
746 }
747 }
748
749 #[test]
751 fn relative_submodule_import_test() {
752 let runner = Metta::new(Some(EnvBuilder::test_env()));
753
754 let _outer_mod_id = runner.load_module_direct(Box::new(RelativeOuterLoader), "outer").unwrap();
756
757 assert!(runner.get_module_by_name("inner").is_err());
761
762 assert!(runner.get_module_by_name("self:inner").is_err());
764 assert!(runner.get_module_by_name("self").is_err());
765
766 runner.get_module_by_name("outer:inner").unwrap();
768
769 }
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 let space = GroundingSpace::new();
791 context.init_self_module(space.into(), None);
792
793 let _ = self.load_tokens(context.module(), context.metta.clone())?;
795
796 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 }