hyperon/metta/runner/
mod.rs

1//!
2//! # MeTTa Runner Implementation
3//!
4//! This documentation addresses the different objects involved with the MeTTa runner, and how they fit together.
5//!
6//! ## [Environment]
7//! [Environment] is the gateway to the outside world.  It creates a platform-abstraction layer for MeTTa, and
8//! is responsible for managing configuration and implementing a security model with permissions.  In a typical
9//! situation, there will only be one [Environment] needed.
10//!
11//! ## [Metta]
12//! [Metta] is the runner object.  It is owned by the caller and hosts all state associated with MeTTa execution,
13//! including loaded [MettaMod] modules.  A [Metta] runner has one top-level module, (named "top") and
14//! all other modules are loaded as dependents (or transitive dependents) of the top-level module. [Metta] is a
15//! long-lived object, and it may be sufficient to create one [Metta] runner that lasts for the duration of the
16//! host program.
17//!
18//! ## [RunnerState]
19//! A [RunnerState] object encapsulates one conceptual "thread" of MeTTa execution (although it may be
20//! parallelized in its implementation)  A [RunnerState] is short-lived; it is created to evaluate some
21//! MeTTa code, and can be run until it finishes or encounters an error.  Multiple [RunnerState] objects may
22//! be executing within the same [Metta] at the same time.
23//! UPDATE: I think I will be removing the [RunnerState] shortly, in favor of a delegate interface that allows
24//! a function to interact with the runner in specific ways for the implementation of a debugger.
25//!
26//! ## [ModuleDescriptor]
27//! A self-contained data-structure that uniquely identifies a specific version of a specific module.  Two
28//! modules that have the same ModuleDescriptor are considered to be the same module from the perspective of
29//! the implementation.
30//!
31//! ## [MettaMod]
32//! A [MettaMod] contains a loaded module.  A module is fundamentally a [Space] of atoms, although it also
33//! contains an associated [Tokenizer] to help with the conversion from text to [Atom]s and sometimes a
34//! resources directory.  Modules are loaded via loader functions, and they can originate from code
35//! in pure MeTTa as well as through extensions in other host languages, namely Rust, C, and Python.
36//!
37//! ## [RunContext]
38//! A [RunContext] objects encapsulates the interface accessible to code running inside a [RunnerState].  It
39//! provides access to the currently loaded module and any other shared state required for the atoms executing
40//! within the MeTTa interpreter.  A [RunContext] is created inside the runner, and it is not possible for
41//! code outside the MeTTa core library to own a [RunContext].
42//!
43//!  Metta (Runner)
44//!  ┌─────────────────────────────────────────────────────────────────┐
45//!  │ MettaMods (Modules)                                             │
46//!  │ ┌────────────────────────────────────────────────────┐          │
47//!  │ │ Space                  Tokenizer                   ├─┐        │
48//!  │ │ ┌─────────────────┐    ┌─────────────────────┐     │ ├─┐      │
49//!  │ │ │                 │    │                     │     │ │ │      │
50//!  │ │ └─────────────────┘    └─────────────────────┘     │ │ │      │
51//!  │ └─┬──────────────────────────────────────────────────┘ │ │      │
52//!  │   └─┬──────────────────────────────────────────────────┘ │      │
53//!  │     └────────────────────────────────────────────────────┘      │
54//!  └─────────────────────────────────────────────────────────────────┘
55//!
56
57//LP-TODO-NEXT: This description above is correct, but it's not complete.  Update with latest design.
58
59use hyperon_atom::*;
60use hyperon_common::shared::Shared;
61
62use super::*;
63use hyperon_space::*;
64use super::text::{Tokenizer, Parser, SExprParser};
65use super::types::{AtomType, get_atom_types};
66
67pub mod modules;
68use modules::{MettaMod, ModId, ModuleInitState, ModNameNode, ModuleLoader, ResourceKey, Resource, TOP_MOD_NAME, ModNameNodeDisplayWrapper, normalize_relative_module_name};
69#[cfg(feature = "pkg_mgmt")]
70use modules::{decompose_name_path, compose_name_path};
71
72#[cfg(feature = "pkg_mgmt")]
73pub mod pkg_mgmt;
74#[cfg(feature = "pkg_mgmt")]
75use pkg_mgmt::*;
76
77#[cfg(not(feature = "pkg_mgmt"))]
78pub(crate) type ModuleDescriptor = ();
79
80use std::rc::Rc;
81use std::path::PathBuf;
82use std::collections::HashMap;
83use std::sync::{Arc, Mutex, OnceLock};
84
85mod environment;
86pub use environment::{Environment, EnvBuilder};
87
88use super::interpreter::{interpret, interpret_init, interpret_step, InterpreterState};
89
90#[macro_use]
91pub mod stdlib;
92use stdlib::CoreLibLoader;
93
94mod builtin_mods;
95use builtin_mods::*;
96
97const EXEC_SYMBOL : Atom = sym!("!");
98
99// *-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*
100// Metta & related objects
101// *-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*
102
103/// A Metta object encapsulates everything needed to execute MeTTa code
104#[derive(Clone, Debug)]
105pub struct Metta(Rc<MettaContents>);
106
107impl PartialEq for Metta {
108    fn eq(&self, other: &Self) -> bool {
109        Rc::ptr_eq(&self.0, &other.0)
110    }
111}
112
113#[derive(Clone, Debug)]
114pub struct PragmaSettings(Shared<HashMap<String, Atom>>);
115
116impl PragmaSettings {
117    pub fn new() -> Self {
118        Self(Shared::new(HashMap::new()))
119    }
120
121    pub fn set(&self, key: String, value: Atom) {
122        self.0.borrow_mut().insert(key, value);
123    }
124
125    pub fn get(&self, key: &str) -> Option<Atom> {
126        self.0.borrow().get(key).cloned()
127    }
128
129    pub fn get_string(&self, key: &str) -> Option<String> {
130        self.0.borrow().get(key).map(|a| a.to_string())
131    }
132}
133
134#[derive(Debug)]
135pub(crate) struct MettaContents {
136    /// All the runner's loaded modules
137    modules: Mutex<Vec<Rc<MettaMod>>>,
138    /// A tree to locate loaded mods by name
139    module_names: Mutex<ModNameNode>,
140    #[cfg(feature = "pkg_mgmt")]
141    /// An index, to find a loaded module from a ModuleDescriptor
142    module_descriptors: Mutex<HashMap<ModuleDescriptor, ModId>>,
143    /// A clone of the top module's Space, so we don't need to do any locking to access it,
144    /// to support the metta.space() public function. Actual module space is an instance
145    /// of the [module::ModuleSpace]. This instance contains dependencies of the
146    /// top module. This space is an original space passed to the Metta constructor
147    /// thus it doesn't contain any dependencies.
148    top_mod_space: DynSpace,
149    /// A clone of the top module's Tokenizer
150    top_mod_tokenizer: Shared<Tokenizer>,
151    /// The ModId of the extended corelib to import into some modules loaded into the runner
152    corelib_mod: OnceLock<ModId>,
153    /// The ModId of the extended stdlib to import into some modules loaded into the runner
154    stdlib_mod: OnceLock<ModId>,
155    /// The runner's pragmas, affecting runner-wide behavior
156    settings: PragmaSettings,
157    /// The runner's Environment
158    environment: Arc<Environment>,
159    //TODO-HACK: This is a terrible horrible ugly hack that should not be merged.  Delete this field
160    // The real context is an interface to the state in a run, and should not live across runs
161    // This hack will fail badly if we end up running code from two different modules in parallel
162    context: Arc<Mutex<Vec<Arc<Mutex<&'static mut RunContext<'static, 'static>>>>>>,
163}
164
165impl Metta {
166
167    /// A 1-line method to create a fully initialized MeTTa runner
168    ///
169    /// NOTE: pass `None` for `env_builder` to use the common environment
170    pub fn new(env_builder: Option<EnvBuilder>) -> Metta {
171        Self::new_with_stdlib_loader(None, None, env_builder)
172    }
173
174    /// Create and initialize a MeTTa runner with a custom stdlib, for example a language-specific stdlib
175    ///
176    /// NOTE: The custom stdlib loader may import the corelib if desired, but it won't be imported automatically.
177    ///
178    /// NOTE: Is `None` is passed as the `loader` parameter, `stdlib` will be an alias to `corelib`
179    /// pass `None` for space to create a new [GroundingSpace]
180    /// pass `None` for `env_builder` to use the common environment
181    pub fn new_with_stdlib_loader(loader: Option<Box<dyn ModuleLoader>>, space: Option<DynSpace>, env_builder: Option<EnvBuilder>) -> Metta {
182
183        //Create the raw MeTTa runner
184        let metta = Metta::new_core(space, env_builder);
185
186        //Load the "corelib" module into the runner
187        let corelib_mod_id = metta.load_module_direct(Box::new(CoreLibLoader), "corelib").expect("Failed to load corelib");
188        metta.0.corelib_mod.set(corelib_mod_id).unwrap();
189
190        //Load the stdlib if we have one, and otherwise make an alias to corelib
191        match loader {
192            Some(loader) => {
193                let stdlib_mod_id = metta.load_module_direct(loader, "stdlib").expect("Failed to load stdlib");
194                metta.0.stdlib_mod.set(stdlib_mod_id).unwrap();
195            },
196            None => {
197                metta.load_module_alias("stdlib", corelib_mod_id).expect("Failed to create stdlib alias for corelib");
198                ()
199            }
200        };
201
202        //Load the rest of the builtin mods, but don't `import` (aka "use") them
203        load_builtin_mods(&metta).unwrap();
204
205        //Import the corelib and stdlib into the top module, now that it is loaded
206        let mut runner_state = RunnerState::new(&metta);
207       
208        if let Some(corelib_mod_id) = metta.0.corelib_mod.get() {
209            runner_state.run_in_context(|context| {
210                context.import_all_from_dependency(*corelib_mod_id).unwrap();
211                Ok(())
212            }).expect("Failed to import corelib");
213        }
214
215        if let Some(stdlib_mod_id) = metta.0.stdlib_mod.get() {
216            runner_state.run_in_context(|context| {
217                context.import_all_from_dependency(*stdlib_mod_id).unwrap();
218                Ok(())
219            }).expect("Failed to import stdlib");
220        }
221        drop(runner_state);
222
223        //Run the `init.metta` file
224        if let Some(init_meta_file_path) = metta.0.environment.initialization_metta_file_path() {
225            let metta_file = match std::fs::File::open(init_meta_file_path).map(std::io::BufReader::new)
226            {
227                Ok(metta_file) => metta_file,
228                Err(err) => panic!("Could not read file, path: {}, error: {}", init_meta_file_path.display(), err)
229            };
230            metta.run(SExprParser::new(metta_file)).unwrap();
231        }
232        metta
233    }
234
235    /// Returns a new core MeTTa interpreter without any loaded corelib, stdlib, or initialization
236    ///
237    /// NOTE: If `space` is `None`, a [GroundingSpace] will be created
238    /// NOTE: If `env_builder` is `None`, the common environment will be used
239    /// NOTE: This function does not load any modules, nor run the [Environment]'s 'init.metta'
240    pub fn new_core(space: Option<DynSpace>, env_builder: Option<EnvBuilder>) -> Self {
241        let space = match space {
242            Some(space) => space,
243            None => GroundingSpace::new().into(),
244        };
245        let settings = PragmaSettings::new();
246        let environment = match env_builder {
247            Some(env_builder) => Arc::new(env_builder.build()),
248            None => Environment::common_env_arc()
249        };
250        let top_mod_resource_dir = environment.working_dir().map(|path| path.into());
251        let top_mod_tokenizer = Shared::new(Tokenizer::new());
252        let contents = MettaContents{
253            modules: Mutex::new(vec![]),
254            module_names: Mutex::new(ModNameNode::top()),
255            #[cfg(feature = "pkg_mgmt")]
256            module_descriptors: Mutex::new(HashMap::new()),
257            top_mod_space: space.clone(),
258            top_mod_tokenizer: top_mod_tokenizer.clone(),
259            corelib_mod: OnceLock::new(),
260            stdlib_mod: OnceLock::new(),
261            settings,
262            environment,
263            context: std::sync::Arc::new(std::sync::Mutex::new(vec![])),
264        };
265        let metta = Self(Rc::new(contents));
266
267        let top_mod = MettaMod::new_with_tokenizer(&metta, TOP_MOD_NAME.to_string(), space, top_mod_tokenizer, top_mod_resource_dir, false);
268        assert_eq!(metta.add_module(top_mod).unwrap(), ModId::TOP);
269
270        metta
271    }
272
273    /// Loads a module into a Runner, directly from a [ModuleLoader]
274    ///
275    /// NOTE: `mod_name` may be a module name path if this module is being loaded as a sub-module of
276    /// another loaded module.  Relative paths may not be used with this API, however.  Use
277    /// [RunContext::load_module_direct] if you are loading a sub-module from within a running module.
278    pub fn load_module_direct(&self, loader: Box<dyn ModuleLoader>, mod_name: &str) -> Result<ModId, String> {
279        let mut state = RunnerState::new_with_module(self, ModId::TOP);
280        state.run_in_context(|context| {
281            context.load_module_direct(loader, mod_name)
282        })
283    }
284
285    /// Loads a module into a runner from a resource at the specified path
286    ///
287    /// This method will try each [FsModuleFormat] in order until one can sucessfully load the module
288    ///
289    /// NOTE: `mod_name` may be a module name path if the module is being loaded as a sub-module of
290    /// another loaded module.  Relative paths may not be used with this API, however.  Use
291    /// [RunContext::load_module_at_path] if you are loading a sub-module from within a running module.
292    ///
293    /// Requires the `pkg_mgmt` feature
294    #[cfg(feature = "pkg_mgmt")]
295    pub fn load_module_at_path<P: AsRef<std::path::Path>>(&self, path: P, mod_name: Option<&str>) -> Result<ModId, String> {
296        let mut state = RunnerState::new_with_module(self, ModId::TOP);
297        state.run_in_context(|context| {
298            context.load_module_at_path(&path, mod_name)
299        })
300    }
301
302    /// Internal method to look up a module from a ModId
303    pub(crate) fn get_mod_ptr(&self, mod_id: ModId) -> Rc<MettaMod> {
304        let mod_ref = self.0.modules.lock().unwrap();
305        mod_ref.get(mod_id.0).unwrap().clone()
306    }
307
308    /// Locates and retrieves a loaded module based on its name, relative to the top of the runner
309    ///
310    /// NOTE: this function will not find any modules in the process of being loaded; use
311    /// [RunContext::get_module_by_name] if you require that
312    fn get_module_by_name(&self, mod_name: &str) -> Result<ModId, String> {
313        let module_names = self.0.module_names.lock().unwrap();
314        module_names.resolve(mod_name).ok_or_else(|| format!("Unable to locate module: {mod_name}"))
315    }
316
317    /// Adds a ModId to the named module tree with the specified name, relative to the top of the runer
318    fn add_module_to_name_tree(&self, mod_name: &str, mod_id: ModId) -> Result<(), String>  {
319        assert!(!mod_id.is_relative());
320        let mut module_names = self.0.module_names.lock().unwrap();
321        module_names.add(mod_name, mod_id)
322    }
323
324    /// Makes a public alias for a loaded module inside the runner
325    ///
326    /// NOTE: `mod_name` may be a module name path if this alias is being loaded as a sub-module of
327    /// another loaded module.  Relative paths may not be used with this API, however.  Use
328    /// [RunContext::load_module_alias] if you are creating an alias from within a running module.
329    pub fn load_module_alias(&self, mod_name: &str, mod_id: ModId) -> Result<ModId, String> {
330        let mut state = RunnerState::new_with_module(self, ModId::TOP);
331        state.run_in_context(|context| {
332            context.load_module_alias(mod_name, mod_id)
333        })
334    }
335
336    /// Writes a textual description of the loaded modules to stdout
337    pub fn display_loaded_modules(&self) {
338        let module_names = self.0.module_names.lock().unwrap();
339        let wrapper = ModNameNodeDisplayWrapper::new(TOP_MOD_NAME, &*module_names, |mod_id: ModId, f: &mut std::fmt::Formatter| write!(f, "{}", mod_id.0));
340        println!("{wrapper}");
341    }
342
343    #[cfg(feature = "pkg_mgmt")]
344    /// Returns the ModId of a loaded module, based on its descriptor, or None if it isn't loaded
345    pub fn get_module_with_descriptor(&self, descriptor: &ModuleDescriptor) -> Option<ModId> {
346        let descriptors = self.0.module_descriptors.lock().unwrap();
347        descriptors.get(descriptor).cloned()
348    }
349
350    #[cfg(feature = "pkg_mgmt")]
351    /// Internal method to add a ModuleDescriptor, ModId pair to the runner's lookup table
352    fn add_module_descriptor(&self, descriptor: ModuleDescriptor, mod_id: ModId) {
353        let mut descriptors = self.0.module_descriptors.lock().unwrap();
354        descriptors.insert(descriptor, mod_id);
355    }
356
357    /// Merges all modules in a [ModuleInitState] into the runner
358    fn merge_init_state(&self, init_state: ModuleInitState) -> Result<ModId, String> {
359        let mut main_mod_id = ModId::INVALID;
360        let (frames, descriptors) = init_state.decompose();
361
362        // Unpack each frame and ,erge the modules from the ModuleInitState into the
363        // runner, and build the mapping table for ModIds
364        let mut mod_name_subtrees: Vec<(String, ModNameNode)> = Vec::with_capacity(frames.len());
365        let mut mod_id_mapping = HashMap::with_capacity(frames.len());
366        for (frame_idx, frame) in frames.into_iter().enumerate() {
367            let old_mod_id = ModId::new_relative(frame_idx);
368            let mod_name = frame.new_mod_name.unwrap();
369            let module = frame.the_mod.unwrap();
370
371            mod_name_subtrees.push((mod_name, frame.sub_module_names));
372
373            let new_mod_id = self.add_module(Rc::into_inner(module).unwrap())?;
374            mod_id_mapping.insert(old_mod_id, new_mod_id);
375
376            if frame_idx == 0 {
377                main_mod_id = new_mod_id;
378            }
379        }
380
381        // Merge the name trees into the runner
382        let mut module_names = self.0.module_names.lock().unwrap();
383        for (mod_name, mut subtree) in mod_name_subtrees.into_iter() {
384            subtree.visit_mut("", |_name, node: &mut ModNameNode| {
385                if let Some(new_mod_id) = mod_id_mapping.get(&node.mod_id) {
386                    node.mod_id = *new_mod_id;
387                }
388            });
389            module_names.merge_subtree_into(&mod_name, subtree)?;
390        }
391
392        // Merge the [ModuleDescriptor]s into the runner's table
393        #[cfg(feature = "pkg_mgmt")]
394        for (descriptor, mod_id) in descriptors.into_iter() {
395            let mod_id = match mod_id_mapping.get(&mod_id) {
396                Some(mapped_id) => *mapped_id,
397                None => mod_id,
398            };
399            self.add_module_descriptor(descriptor, mod_id);
400        }
401        #[cfg(not(feature = "pkg_mgmt"))]
402        let _ = descriptors;
403
404        // Finally, re-map the module's "deps" ModIds
405        for added_mod_id in mod_id_mapping.values() {
406            let mod_ptr = self.get_mod_ptr(*added_mod_id);
407            mod_ptr.remap_imported_deps(&mod_id_mapping);
408        }
409
410        Ok(main_mod_id)
411    }
412
413    /// Internal function to add a loaded module to the runner, assigning it a ModId
414    fn add_module(&self, module: MettaMod) -> Result<ModId, String> {
415        let mut vec_ref = self.0.modules.lock().unwrap();
416        let new_id = ModId(vec_ref.len());
417        vec_ref.push(Rc::new(module));
418        Ok(new_id)
419    }
420
421    /// Returns a reference to the Environment used by the runner
422    pub fn environment(&self) -> &Environment {
423        &self.0.environment
424    }
425
426    /// Returns a reference to the Space associated with the runner's top module
427    pub fn space(&self) -> &DynSpace {
428        &self.0.top_mod_space
429    }
430
431    /// Returns the [DynSpace] handle associated with any loaded module's Space
432    pub fn module_space(&self, mod_id: ModId) -> DynSpace {
433        let modules = self.0.modules.lock().unwrap();
434        modules.get(mod_id.0).unwrap().space().clone()
435    }
436
437    /// Returns a buffer containing the specified resource, if it is available from a loaded module
438    pub fn get_module_resource(&self, mod_id: ModId, res_key: ResourceKey) -> Result<Resource, String> {
439        let modules = self.0.modules.lock().unwrap();
440        modules.get(mod_id.0).unwrap().get_resource(res_key)
441    }
442
443    /// Returns a reference to the Tokenizer associated with the runner's top module
444    pub fn tokenizer(&self) -> &Shared<Tokenizer> {
445        &self.0.top_mod_tokenizer
446    }
447
448    pub fn settings(&self) -> &PragmaSettings {
449        &self.0.settings
450    }
451
452    pub fn get_setting_string(&self, key: &str) -> Option<String> {
453        self.0.settings.get(key).map(|a| a.to_string())
454    }
455
456    pub fn run(&self, parser: impl Parser) -> Result<Vec<Vec<Atom>>, String> {
457        let state = RunnerState::new_with_parser(self, Box::new(parser));
458        state.run_to_completion()
459    }
460
461    pub fn run_in_module(&self, mod_id: ModId, parser: impl Parser) -> Result<Vec<Vec<Atom>>, String> {
462        let mut state = RunnerState::new_with_module(self, mod_id);
463        state.i_wrapper.input_src.push_parser(Box::new(parser));
464        state.run_to_completion()
465    }
466
467    pub fn evaluate_atom(&self, atom: Atom) -> Result<Vec<Atom>, String> {
468        let atom = if is_bare_minimal_interpreter(self) {
469            atom
470        } else {
471            wrap_atom_by_metta_interpreter(self.module_space(ModId::TOP), atom)
472        };
473        if self.type_check_is_enabled()  {
474            let types = get_atom_types(&self.module_space(ModId::TOP), &atom);
475            if types.iter().all(AtomType::is_error) {
476                return Ok(types.into_iter().map(AtomType::into_error_unchecked).collect());
477            }
478        }
479        interpret(self.space().clone(), &atom)
480    }
481
482    fn type_check_is_enabled(&self) -> bool {
483        self.settings().get_string("type-check").map_or(false, |val| val == "auto")
484    }
485
486}
487
488// *-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*
489// RunnerState & related objects
490// *-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*
491
492//TODO: Proposed API change to eliminate RunnerState from public API
493// After a lot of experimentation with a design that is capable of moving execution work across threads,
494// I think it makes sense to reverse course on the idea to expose "RunnerState" public API object.
495//
496//In essence, I am running into all exactly the same set of challenges that async Rust went through,
497// but their solution is very involved.  For example: https://rust-lang.github.io/async-book/04_pinning/01_chapter.html 
498// We could end up using the same approaches (and possibly utilizing the same mechanims (like pinning)), but
499// I feel like that is overkill for what we require from Rust-language interoperability.
500//
501//Instead, I would like to simplify the Runner API to include a delegate interface that the runner will call
502// with periodic events and status updates.  This delegate interface would then be used to implement a
503// debugger and any other code that needs to influence execution from outside the MeTTa runner.
504//
505//Multi-threading inside a single Runner is tricky no matter which design we choose, but my thinking is that
506// the delegate would be an FnMut closure that is called by the receiver on a mpsc channel, so delegate
507// callbacks will always happen on the same thread, and the callback won't need to be Send nor Sync.
508//
509//So, the API change will be:
510// - `run_step` goes away, and is replaced by a delegate that is called periodically, and has the ability to:
511//  * receive incremental new results
512//  * interrupt (terminate) execution early
513//  * access additional debug info (specifics TBD)
514// - New runner APIs including: `Metta::run_from_parser`, `Metta::run_atoms`, etc. will replace existing
515//    RunnerState APIs like `RunnerState::new_with_parser`, etc.
516//
517
518/// A RunnerState encapsulates a single in-flight process, executing code within a [Metta] runner
519pub struct RunnerState<'m, 'i> {
520    metta: &'m Metta,
521    mod_id: ModId,
522    mod_ptr: Option<Rc<MettaMod>>,
523    init_state: ModuleInitState,
524    i_wrapper: InterpreterWrapper<'i>,
525}
526
527impl std::fmt::Debug for RunnerState<'_, '_> {
528    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
529        f.debug_struct("RunnerState")
530            .field("mode", &self.i_wrapper.mode)
531            .field("interpreter_state", &self.i_wrapper.interpreter_state)
532            .finish()
533    }
534}
535
536impl<'m, 'input> RunnerState<'m, 'input> {
537
538    fn new_internal(metta: &'m Metta, mod_id: ModId, init_state: ModuleInitState) -> Self {
539        Self {
540            metta,
541            mod_id,
542            mod_ptr: None,
543            init_state: init_state,
544            i_wrapper: InterpreterWrapper::default(),
545        }
546    }
547
548    /// Returns a new RunnerState to execute code in the context of a MeTTa runner's top module
549    pub fn new(metta: &'m Metta) -> Self {
550        Self::new_with_module(metta, ModId::TOP)
551    }
552
553    /// Creates a new RunnerState to be used in the process of loading a new module
554    pub(crate) fn new_for_loading(metta: &'m Metta, new_mod_name: &str, init_state: &mut ModuleInitState) -> Self {
555        let normalized_name = normalize_relative_module_name("top", &new_mod_name).unwrap();
556        let mod_id = init_state.push(normalized_name);
557        Self::new_internal(metta, mod_id, init_state.new_child())
558    }
559
560    /// Creates a new RunnerState to be used in the process of loading a new module
561    pub(crate) fn new_with_module_and_init_state(metta: &'m Metta, mod_id: ModId, init_state: ModuleInitState) -> Self {
562        let mut state = Self::new_internal(metta, mod_id, init_state);
563        let mod_ptr = state.init_state.get_mod_ptr(metta, mod_id).unwrap();
564        state.mod_ptr = Some(mod_ptr);
565        state
566    }
567
568    /// Returns a new RunnerState to execute code in the context a module in the runner
569    pub(crate) fn new_with_module(metta: &'m Metta, mod_id: ModId) -> Self {
570        Self::new_with_module_and_init_state(metta, mod_id, ModuleInitState::empty())
571    }
572
573    /// Returns a new RunnerState, for running code from the [SExprParser] with the specified [Metta] runner
574    pub fn new_with_parser(metta: &'m Metta, parser: Box<dyn Parser + 'input>) -> Self {
575        let mut state = Self::new(metta);
576        state.i_wrapper.input_src.push_parser(parser);
577        state
578    }
579
580    /// Returns a new RunnerState, for running code encoded as a slice of [Atom]s with the specified [Metta] runner
581    pub fn new_with_atoms(metta: &'m Metta, atoms: &'input[Atom]) -> Self {
582        let mut state = Self::new(metta);
583        state.i_wrapper.input_src.push_parser(Box::new(atoms));
584        state
585    }
586
587    /// Repeatedly steps a RunnerState until it is complete, and then returns the results
588    pub fn run_to_completion(mut self) -> Result<Vec<Vec<Atom>>, String> {
589        while !self.is_complete() {
590            self.run_step()?;
591        }
592        Ok(self.into_results())
593    }
594
595    /// Runs one step of the interpreter
596    pub fn run_step(&mut self) -> Result<(), String> {
597        self.run_in_context(|context| context.step())
598    }
599
600    /// Returns `true` if the RunnerState has completed execution of all input or has encountered a
601    ///    fatal error, otherwise returns `false`
602    pub fn is_complete(&self) -> bool {
603        self.i_wrapper.mode == MettaRunnerMode::TERMINATE
604    }
605
606    /// Returns a reference to the current in-progress results within the RunnerState
607    pub fn current_results(&self) -> &Vec<Vec<Atom>> {
608        &self.i_wrapper.results
609    }
610
611    /// Consumes the RunnerState and returns the final results
612    pub fn into_results(self) -> Vec<Vec<Atom>> {
613        self.i_wrapper.results
614    }
615
616    /// Private method.  Creates the Runner's context, and executes an arbitrary function within that context
617    //TODO: When we eliminate the RunnerState, this method should become a private method of Metta,
618    // and an argument of type `Option<ModId>` should be added.  When this function is used to initialize
619    // modules, the module type can be returned from this function
620    fn run_in_context<T, F: FnOnce(&mut RunContext<'_, 'input>) -> Result<T, String>>(&mut self, f: F) -> Result<T, String> {
621
622        // Construct the RunContext
623        let mut context = RunContext {
624            metta: &self.metta,
625            mod_id: self.mod_id,
626            mod_ptr: &mut self.mod_ptr,
627            init_state: &mut self.init_state,
628            i_wrapper: &mut self.i_wrapper,
629        };
630
631        //TODO-HACK: This is a terrible horrible ugly hack that should be cleaned up ASAP.  It will cause
632        // UB when we have multiple runner threads that execute concurrently.
633        //Push the RunContext so the MeTTa Ops can access it.  The context ought to be passed as an argument
634        // to the execute functions, in the absence of the hack
635        self.metta.0.context.lock().unwrap().push(Arc::new(Mutex::new( unsafe{ std::mem::transmute(&mut context) } )));
636        //END HORRIBLE HACK
637
638        // Call our function
639        let result = f(&mut context);
640
641        //TODO-HACK: This is a terrible horrible ugly hack that should be cleaned up ASAP.
642        //pop the context in the runner
643        self.metta.0.context.lock().unwrap().pop();
644        //END HORRIBLE HACK
645
646        result
647    }
648
649    /// Internal method to unpack a RunnerState that just initialized a module (and its children)
650    pub(crate) fn finalize_loading(self) -> Result<ModId, String> {
651        for result_vec in self.i_wrapper.results {
652            for result in result_vec {
653                if atom_is_error(&result) {
654                    return Err(atom_error_message(&result).to_owned())
655                }
656            }
657        }
658        let mod_ptr = match self.mod_ptr {
659            Some(mod_ptr) => mod_ptr,
660            None => return Err(format!("Module loader finished without running RunContext::init_self_module"))
661        };
662
663        self.init_state.in_frame(self.mod_id, |frame| frame.the_mod = Some(mod_ptr));
664        Ok(self.mod_id)
665    }
666}
667
668// *-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*
669// RunContext & related objects
670// *-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*
671
672/// Runtime data that is available to Grounded Atom execution
673// TODO: I think we may be able to remove the `'interpreter`` lifetime after the minimal MeTTa migration
674//  because the lifetime is separated on account of the inability of the compiler to shorten a lifetime
675//  used as a generic parameter on a trait.  In this case, the `Plan` trait.
676pub struct RunContext<'a, 'input> {
677    metta: &'a Metta,
678    mod_id: ModId,
679    mod_ptr: &'a mut Option<Rc<MettaMod>>,
680    init_state: &'a mut ModuleInitState,
681    i_wrapper: &'a mut InterpreterWrapper<'input>
682}
683
684impl std::fmt::Debug for RunContext<'_, '_> {
685    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
686        f.debug_struct("RunContext")
687         .finish()
688    }
689}
690
691impl<'input> RunContext<'_, 'input> {
692    /// Returns access to the Metta runner that is hosting the context 
693    pub fn metta(&self) -> &Metta {
694        &self.metta
695    }
696
697    /// Returns access to the context's current module
698    pub fn module(&self) -> &MettaMod {
699        self.mod_ptr.as_ref().unwrap_or_else(|| panic!("No module available"))
700    }
701
702    /// Returns mutable access the context's current module, if possible
703    pub fn module_mut(&mut self) -> Option<&mut MettaMod> {
704        Rc::get_mut(self.mod_ptr.as_mut().unwrap_or_else(|| panic!("No module available")))
705    }
706
707    /// Pushes the parser as a source of operations to subsequently execute
708    pub fn push_parser(&mut self, parser: Box<dyn Parser + 'input>) {
709        self.i_wrapper.input_src.push_parser(parser);
710    }
711
712    /// Pushes the atoms as a source of operations to subsequently execute
713    pub fn push_atoms(&mut self, atoms: &'input[Atom]) {
714        self.i_wrapper.input_src.push_parser(Box::new(atoms));
715    }
716
717    /// Pushes an executable function as an operation to be executed
718    pub fn push_func<F: FnOnce(&mut RunContext) -> Result<(), String> + 'input>(&mut self, f: F) {
719        self.i_wrapper.input_src.push_func(f);
720    }
721
722    /// Creates a child RunContext within the current context, for immediate inline execution
723    //
724    //Internal Note: There were two implementation options here: 1. for the RunContext / RunnerState
725    // to contain a stack of contexts / interpreter states or 2. to resolve the child context inline.
726    // This function implements option 2.
727    //
728    // If we intended to stay with the RunnerState design where the caller has direct control over
729    // the step-loop, Option 1 would be a superior design because it keeps an outer step correlated
730    // to a unit of inner interpreter-work, regardless of whether that work is happening at a context
731    // or a nested sub-context.  However I want to move toward removing the step-loop from the public
732    // API, so I chose Option 2 for now.  See the comment beginning with:
733    // "Proposed API change to eliminate RunnerState from public API"
734    //
735    pub fn run_inline<F: FnOnce(&mut RunContext) -> Result<(), String>>(&mut self, f: F) -> Result<Vec<Vec<Atom>>, String> {
736        let mut new_interpreter = InterpreterWrapper::default();
737        let mut new_context = RunContext {
738            metta: &self.metta,
739            i_wrapper: &mut new_interpreter,
740            mod_id: self.mod_id,
741            mod_ptr: self.mod_ptr,
742            init_state: self.init_state,
743        };
744
745        let mut err = None;
746        match f(&mut new_context) {
747            Ok(_) => {
748                while new_context.i_wrapper.mode != MettaRunnerMode::TERMINATE {
749                    if new_context.step().is_err() {
750                        break;
751                    }
752                }
753            },
754            Err(e) => err = Some(e)
755        }
756
757        match err {
758            None => Ok(new_interpreter.results),
759            Some(e) => Err(e)
760        }
761    }
762
763    /// Runs the function in the context of the mod_id
764    #[allow(dead_code)] //Some clients are behind feature gates
765    fn in_mod_context<T, F: FnOnce(&mut RunContext) -> Result<T, String>>(&mut self, mod_id: ModId, f: F) -> Result<T, String> {
766        if mod_id == self.mod_id {
767            f(self)
768        } else {
769            let mut state = RunnerState::new_with_module_and_init_state(&self.metta, mod_id, self.init_state.new_child());
770            state.run_in_context(f)
771        }
772    }
773
774    /// Locates and retrieves a loaded module based on its name
775    pub fn get_module_by_name(&self, mod_name: &str) -> Result<ModId, String> {
776        let normalized_mod_name = normalize_relative_module_name(self.module().path(), mod_name)?;
777        self.init_state.get_module_by_name(&self.metta, &normalized_mod_name)
778    }
779
780    /// Adds a ModId to the named module tree with the specified name
781    ///
782    /// NOTE: If this method is called during module load, and the module load fails, the
783    /// added name will not become part of the runner's module namespace
784    fn add_module_to_name_tree(&mut self, mod_name: &str, mod_id: ModId) -> Result<(), String>  {
785        let normalized_mod_name = normalize_relative_module_name(self.module().path(), mod_name)?;
786        self.init_state.add_module_to_name_tree(&self.metta, self.mod_id, &normalized_mod_name, mod_id)
787    }
788
789    /// Normalize a module name into a canonical name-path form, and expanding a relative module-path
790    pub fn normalize_module_name(&self, mod_name: &str) -> Result<String, String> {
791        let self_mod_path = self.mod_ptr.as_ref()
792            .map(|mod_ptr| mod_ptr.path()).ok_or_else(|| "RunContext::init_self_module must be called prior to this operation".to_string())?;
793        normalize_relative_module_name(self_mod_path, mod_name)
794    }
795
796    /// Initiates the loading of a module from a runner thread.  Useful for loading sub-modules 
797    pub fn load_module_direct(&mut self, loader: Box<dyn ModuleLoader>, mod_name: &str) -> Result<ModId, String> {
798
799        //Make sure we don't have a conflicting mod, before attempting to load this one
800        if self.get_module_by_name(mod_name).is_ok() {
801            return Err(format!("Attempt to load module with name that conflicts with existing module: {mod_name}"));
802        }
803
804        let absolute_mod_name = self.normalize_module_name(mod_name)?;
805        self.init_module(&absolute_mod_name, loader)
806    }
807
808    /// A version of [Metta::load_module_at_path] Useful for loading sub-modules 
809    #[cfg(feature = "pkg_mgmt")]
810    pub fn load_module_at_path<P: AsRef<std::path::Path>>(&mut self, path: P, mod_name: Option<&str>) -> Result<ModId, String> {
811
812        let absolute_mod_name = match mod_name {
813            Some(mod_name) => {
814                if self.get_module_by_name(mod_name).is_ok() {
815                    return Err(format!("Attempt to load module with name that conflicts with existing module: {mod_name}"));
816                }
817                Some(self.normalize_module_name(mod_name)?)
818            },
819            None => None
820        };
821
822        // Get the loader and descriptor by trying the module formats
823        let (loader, descriptor) = match loader_for_module_at_path(self.metta.environment().fs_mod_formats(), &path, absolute_mod_name.as_deref(), self.module().resource_dir())? {
824            Some((loader, descriptor)) => (loader, descriptor),
825            None => return Err(format!("Failed to resolve module at path: {}", path.as_ref().display()))
826        };
827
828        let mod_name = match absolute_mod_name {
829            Some(mod_name) => mod_name,
830            None => descriptor.name().to_string()
831        };
832
833        // Load the module from the loader
834        self.get_or_init_module_with_descriptor(&mod_name, descriptor, loader)
835    }
836
837    /// A version of [Metta::load_module_alias] Useful for defining sub-module aliases
838    pub fn load_module_alias(&mut self, mod_name: &str, mod_id: ModId) -> Result<ModId, String> {
839
840        if self.get_module_by_name(&mod_name).is_ok() {
841            return Err(format!("Attempt to create module alias with name that conflicts with existing module: {mod_name}"));
842        }
843        self.add_module_to_name_tree(&mod_name, mod_id)?;
844        Ok(mod_id)
845    }
846
847    /// Initializes the context's module.  Used in the implementation of a module loader function
848    ///
849    /// Prior to calling this function, any attempt to access the active module in the RunContext will
850    /// lead to a panic.
851    pub fn init_self_module(&mut self, space: DynSpace, resource_dir: Option<PathBuf>) {
852        if self.mod_ptr.is_some() {
853            panic!("Module already initialized")
854        }
855        *self.mod_ptr = Some(self.init_state.in_frame(self.mod_id, |frame| {
856            frame.init_self_module(self.mod_id, &self.metta, space, resource_dir)
857        }));
858    }
859
860    /// Resolves a dependency module from a name, according to the [PkgInfo] of the current module,
861    /// and loads it into the runner, if it's not already loaded
862    pub fn load_module(&mut self, mod_name: &str) -> Result<ModId, String> {
863        let absolute_mod_path = self.normalize_module_name(mod_name)?;
864
865        // See if we already have the module loaded
866        if let Ok(mod_id) = self.get_module_by_name(&absolute_mod_path) {
867            return Ok(mod_id);
868        }
869
870        #[cfg(not(feature = "pkg_mgmt"))]
871        return Err(format!("Failed to resolve module {absolute_mod_path}"));
872
873        // Resolve the module name into a loader object using the resolution logic in the pkg_info
874        #[cfg(feature = "pkg_mgmt")]
875        {
876            let parent_mod_id = self.load_module_parents(&absolute_mod_path)?;
877            self.load_module_internal(&absolute_mod_path, parent_mod_id)
878        }
879    }
880
881    /// Internal function used for recursive loading of parent modules by [Self::load_module]
882    /// Returns the ModId of the loaded parent module
883    #[cfg(feature = "pkg_mgmt")]
884    fn load_module_parents(&mut self, mod_name: &str) -> Result<ModId, String> {
885
886        //Make sure the parent module is loaded, and descend recursively until we find a loaded parent
887        let mod_name_components = decompose_name_path(mod_name)?;
888        let parent_mod_id = if mod_name_components.len() > 1 {
889            let parent_name = compose_name_path(&mod_name_components[..mod_name_components.len()-1])?;
890            if let Ok(parent_mod_id) = self.get_module_by_name(&parent_name) {
891                parent_mod_id
892            } else {
893                let parent_of_parent = self.load_module_parents(&parent_name)?;
894                self.load_module_internal(&parent_name, parent_of_parent)?
895            }
896        } else {
897            ModId::TOP
898        };
899        Ok(parent_mod_id)
900    }
901
902    /// Internal method to retrieve the mod_ptr to a module that's either loading in the
903    /// InitFrame, or loaded into the runner
904    fn get_mod_ptr(&self, mod_id: ModId) -> Result<Rc<MettaMod>, String> {
905        self.init_state.get_mod_ptr(&self.metta, mod_id)
906    }
907
908    /// Internal function to load a module in the context of a parent module, assuming the path is normalized
909    #[cfg(feature = "pkg_mgmt")]
910    fn load_module_internal(&mut self, mod_path: &str, parent_mod_id: ModId) -> Result<ModId, String> {
911        self.in_mod_context(parent_mod_id, |context| {
912            match resolve_module(context.module().pkg_info(), context, mod_path)? {
913                Some((loader, descriptor)) => {
914                    context.get_or_init_module_with_descriptor(mod_path, descriptor, loader)
915                },
916                None => {return Err(format!("Failed to resolve module {mod_path}"))}
917            }
918        })
919    }
920
921    /// Resolves a dependency module from a name, according to the [PkgInfo] of the current module,
922    /// and loads the specified resource from the module, without loading the module itself
923    ///
924    /// NOTE: Although this method won't load the module itself, it will load parent modules if necessary
925    pub fn load_resource_from_module(&mut self, mod_name: &str, res_key: ResourceKey) -> Result<Resource, String> {
926
927        // Resolve the module name and see if the module is already loaded into the runner
928        if let Ok(mod_id) = self.get_module_by_name(mod_name) {
929            self.metta().get_module_resource(mod_id, res_key)
930        } else {
931            #[cfg(not(feature = "pkg_mgmt"))]
932            return Err(format!("Failed to resolve module {mod_name}"));
933
934            // Ensure the module's parents are loaded if a module path was provided
935            #[cfg(feature = "pkg_mgmt")]
936            {
937                let parent_mod_id = self.load_module_parents(mod_name)?;
938                let normalized_mod_path = self.normalize_module_name(mod_name)?;
939                self.in_mod_context(parent_mod_id, |context| {
940                    match resolve_module(context.module().pkg_info(), context, &normalized_mod_path)? {
941                        Some((loader, _descriptor)) => {
942                            loader.get_resource(res_key)
943                        },
944                        None => {return Err(format!("Failed to resolve module {mod_name}"))}
945                    }
946                })
947            }
948        }
949    }
950
951    #[cfg(feature = "pkg_mgmt")]
952    /// Checks the loaded [ModuleDescriptor]s to see if a given module has already been loaded, and returns
953    /// that if it has.  Otherwise loads the module
954    ///
955    /// ## Explanation of behavior
956    /// * `mod_name` should not speicify an existing loaded module; If it does the caller should not have
957    ///   called this method
958    /// * If `descriptor` matches an existing loaded module, alias in the module name-space will be created,
959    ///   and the module's ModId will be returned, otherwise,
960    /// * The `loader` will be used to initialize a new module, and the new ModId will be returned
961    pub(crate) fn get_or_init_module_with_descriptor(&mut self, mod_name: &str, descriptor: ModuleDescriptor, loader: Box<dyn ModuleLoader>) -> Result<ModId, String> {
962        match self.init_state.get_module_with_descriptor(&self.metta, &descriptor) {
963            Some(mod_id) => {
964                self.load_module_alias(mod_name, mod_id)
965            },
966            None => {
967                let new_id = self.init_module(mod_name, loader)?;
968                self.init_state.add_module_descriptor(&self.metta, descriptor, new_id);
969                Ok(new_id)
970            }
971        }
972    }
973
974    /// Internal method, Returns the ModId of a module initialized with the provided loader
975    ///
976    /// The init function will then call `context.init_self_module()` along with any other initialization code
977    fn init_module(&mut self, mod_name: &str, loader: Box<dyn ModuleLoader>) -> Result<ModId, String> {
978        let new_mod_id = self.init_state.init_module(&self.metta, mod_name, loader)?;
979
980        if self.init_state.is_root() {
981            let mut init_state = ModuleInitState::empty();
982            core::mem::swap(&mut init_state, self.init_state);
983            self.metta.merge_init_state(init_state)
984        } else {
985            Ok(new_mod_id)
986        }
987    }
988
989    /// Adds a loaded module as a dependency of the `&self` [MettaMod], and adds a [Tokenizer] entry to access
990    /// the dependent module's Space.
991    pub fn import_dependency_as(&self, mod_id: ModId, name: Option<String>) -> Result<(), String> {
992        self.module().import_dependency_as(self.get_mod_ptr(mod_id)?, name)
993    }
994
995    /// Adds a specific atom and/or Tokenizer entry from a dependency module to the &self module
996    ///
997    /// Behavior:
998    /// * If the `from_name` argument exactly matches a [Tokenizer] entry in the source module,
999    ///     then that entry will be imported, and the `name` argument will be ignored. In this case
1000    ///     no atom is imported.
1001    /// * If an exact [Tokenizer] entry was not found, this method will attempt to resolve `from_name`
1002    ///     into an atom, using the [Tokenizer] and [Space] associated with the dependent module, and
1003    ///     the resolved atom will be imported into the `&self` [Space]
1004    /// * If `name` is provided, then if the resolved atom not a Symbol or if the resolved atom is a
1005    ///     symbol that doesn't perfectly match `name`, a new [Tokenizer] entry will be created to
1006    ///     access the atom in the &self module
1007    ///
1008    // QUESTION: This behavior of exactly matching a regex makes importing a Tokenizer pattern pretty
1009    // unfriendly.  Does it make sense to require Tokenizers entries to be associated with atoms, for
1010    // example "Type Atoms"?  For example, we could have an "Number" type that is tied to all the
1011    // Tokenizer regex patters used to parse different types of numbers?  Then a user could
1012    // "!(import! Number from Arithmetic)" or whatever, and get all the Tokenizer patterns that parse
1013    // numbers?
1014    // More discussion on the topic of tokenizer entry names is here https://github.com/trueagi-io/hyperon-experimental/issues/510
1015    pub fn import_item_from_dependency_as(&self, from_name: &str, mod_id: ModId, name: Option<&str>) -> Result<(), String> {
1016        self.module().import_item_from_dependency_as(from_name, self.get_mod_ptr(mod_id)?, name)
1017    }
1018
1019    /// Effectively adds all atoms in a dependency module to the &self module, by adding the dependency
1020    /// module's space as an atom inside the &self module
1021    ///
1022    /// WARNING: Module import behavior is still WIP, specifically around "import *" behavior, and
1023    /// especially around transitive imports
1024    pub fn import_all_from_dependency(&self, mod_id: ModId) -> Result<(), String> {
1025        self.module().import_all_from_dependency(mod_id, self.get_mod_ptr(mod_id)?, self.metta)
1026    }
1027
1028    /// Private method to advance the context forward one step
1029    fn step(&mut self) -> Result<(), String> {
1030
1031        // If we're in the middle of interpreting an atom...
1032        if let Some(interpreter_state) = core::mem::take(&mut self.i_wrapper.interpreter_state) {
1033
1034            if interpreter_state.has_next() {
1035
1036                //Take a step with the interpreter, and put it back for next time
1037                self.i_wrapper.interpreter_state = Some(interpret_step(interpreter_state))
1038            } else {
1039
1040                //This interpreter is finished, process the results
1041                let result = interpreter_state.into_result().unwrap();
1042                let error = result.iter().any(|atom| atom_is_error(atom));
1043                self.i_wrapper.results.push(result);
1044                if error {
1045                    self.i_wrapper.mode = MettaRunnerMode::TERMINATE;
1046                    return Ok(());
1047                }
1048            }
1049
1050            Ok(())
1051        } else {
1052
1053            // Get the next operation
1054            let tokenizer_option = self.mod_ptr.as_ref().map(|module| module.tokenizer().borrow());
1055            let tokenizer = tokenizer_option.as_ref().map(|tok| &**tok as &Tokenizer);
1056            let next_op = match self.i_wrapper.input_src.next_op(tokenizer) {
1057                Ok(atom) => atom,
1058                Err(err) => {
1059                    self.i_wrapper.mode = MettaRunnerMode::TERMINATE;
1060                    return Err(err);
1061                }
1062            };
1063            drop(tokenizer_option);
1064
1065            // Start execution of the operation
1066            match next_op {
1067                Some(Executable::Func(func)) => {
1068                    func(self)
1069                },
1070                // If the next operation is an atom, start a new intperpreter
1071                Some(Executable::Atom(atom)) => {
1072                    if atom == EXEC_SYMBOL {
1073                        self.i_wrapper.mode = MettaRunnerMode::INTERPRET;
1074                        return Ok(());
1075                    }
1076                    match self.i_wrapper.mode {
1077                        MettaRunnerMode::ADD => {
1078                            if let Err(errors) = self.module().add_atom(atom, self.metta.type_check_is_enabled()) {
1079                                self.i_wrapper.results.push(errors);
1080                                self.i_wrapper.mode = MettaRunnerMode::TERMINATE;
1081                                return Ok(());
1082                            }
1083                        },
1084                        MettaRunnerMode::INTERPRET => {
1085
1086                            if self.metta.type_check_is_enabled() {
1087                                let types = get_atom_types(&self.module().space(), &atom);
1088                                if types.iter().all(AtomType::is_error) {
1089                                    self.i_wrapper.interpreter_state = Some(InterpreterState::new_finished(self.module().space().clone(),
1090                                        types.into_iter().map(AtomType::into_error_unchecked).collect()));
1091                                    return Ok(())
1092                                }
1093                            }
1094                            let atom = if is_bare_minimal_interpreter(self.metta) {
1095                                atom
1096                            } else {
1097                                wrap_atom_by_metta_interpreter(self.module().space().clone(), atom)
1098                            };
1099                            self.i_wrapper.interpreter_state = Some(interpret_init(self.module().space().clone(), &atom));
1100                            if let Some(depth) = self.metta.settings().get_string("max-stack-depth") {
1101                                let depth = depth.parse::<usize>().unwrap();
1102                                self.i_wrapper.interpreter_state.as_mut().map(|state| state.set_max_stack_depth(depth));
1103                            }
1104                        },
1105                        MettaRunnerMode::TERMINATE => {
1106                            return Ok(());
1107                        },
1108                    }
1109                    self.i_wrapper.mode = MettaRunnerMode::ADD;
1110                    Ok(())
1111                },
1112                None => {
1113                    self.i_wrapper.mode = MettaRunnerMode::TERMINATE;
1114                    Ok(())
1115                }
1116            }
1117        }
1118    }
1119
1120}
1121
1122fn is_bare_minimal_interpreter(metta: &Metta) -> bool {
1123    metta.settings().get_string("interpreter") == Some("bare-minimal".into())
1124}
1125
1126// *-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*
1127// InterpreterWrapper & related objects
1128// *-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*
1129
1130/// Private structure to contain everything associated with an InterpreterState.
1131/// This is basically the part of RunContext that lasts across calls to run_step
1132#[derive(Default)]
1133struct InterpreterWrapper<'i> {
1134    mode: MettaRunnerMode,
1135    input_src: InputStream<'i>,
1136    interpreter_state: Option<InterpreterState>,
1137    results: Vec<Vec<Atom>>,
1138}
1139
1140#[derive(Debug, Default, PartialEq, Eq)]
1141enum MettaRunnerMode {
1142    #[default]
1143    ADD,
1144    INTERPRET,
1145    TERMINATE,
1146}
1147
1148/// Private type representing a source for operations for the runner
1149enum InputSource<'i> {
1150    Parser(Box<dyn Parser + 'i>),
1151    Func(Box<dyn FnOnce(&mut RunContext) -> Result<(), String> + 'i>)
1152}
1153
1154/// Private type representing an input operation for the runner
1155/// FUTURE-CLEANUP-TODO: I would like to be able to delete this `Executable` type and simplify this code
1156/// by making the runner's only instructions be a stream of atoms.  However, it an important aspect of
1157/// the runner's abstactions (and necessary functionality for module loading, etc.) is the ability to
1158/// dispatch one-off functions to execute inside the runner.  Therefore, the most sensible design would
1159/// be to allow those functions to be embedded within grounded atoms.  Right now, there are two things
1160/// that stand in the way of that design:
1161/// 1.  Atoms have a 'static lifetime, but a lot of the value of dispatching special functions is to
1162///   interact with the caller, and this requiring a 'static lifetime bound on the function drastically
1163///   limits the usefullness of the feature.
1164///     More specifically, the module loader functions are borrowed from the Environment, and the
1165///   environment may not be 'static.  So we would need to move loader functions out of the Environment,
1166///   Which is doable.
1167///     However, if we implement "inside-out atoms" (atoms with a lifetime bound) we may be able to solve
1168///   this more elegantly.
1169/// 2.  The runner's RunContext is not available to execution of atoms in the current API.  Although
1170///   hopefully this will be addressed shortly
1171enum Executable<'i> {
1172    Atom(Atom),
1173    Func(Box<dyn FnOnce(&mut RunContext) -> Result<(), String> + 'i>)
1174}
1175
1176/// A private structure representing a heterogeneous source of operations for the runner to execute
1177#[derive(Default)]
1178struct InputStream<'a>(Vec<InputSource<'a>>);
1179
1180impl<'i> InputStream<'i> {
1181    fn push_parser(&mut self, parser: Box<dyn Parser + 'i>) {
1182        self.0.push(InputSource::Parser(parser))
1183    }
1184    fn push_func<F: FnOnce(&mut RunContext) -> Result<(), String> + 'i>(&mut self, f: F) {
1185        self.0.push(InputSource::Func(Box::new(f)))
1186    }
1187    /// Returns the next operation in the InputStream, and removes it from the stream.  Returns None if the
1188    /// InputStream is empty.
1189    fn next_op(&mut self, tokenizer: Option<&Tokenizer>) -> Result<Option<Executable<'i>>, String> {
1190        match self.0.get_mut(0) {
1191            None => Ok(None),
1192            Some(src) => {
1193                match src {
1194                    InputSource::Func(_) => match self.0.remove(0) {
1195                        InputSource::Func(f) => Ok(Some(Executable::Func(f))),
1196                        _ => unreachable!()
1197                    },
1198                    InputSource::Parser(parser) => {
1199                        match parser.next_atom(tokenizer.as_ref()
1200                            .unwrap_or_else(|| panic!("Module must be initialized to parse MeTTa code")))? {
1201                            Some(atom) => Ok(Some(Executable::Atom(atom))),
1202                            None => {
1203                                self.0.remove(0);
1204                                self.next_op(tokenizer)
1205                            }
1206                        }
1207                    }
1208                }
1209            }
1210        }
1211    }
1212}
1213
1214fn wrap_atom_by_metta_interpreter(space: DynSpace, atom: Atom) -> Atom {
1215    let space = Atom::gnd(space);
1216    let interpret = Atom::expr([METTA_SYMBOL, atom, ATOM_TYPE_UNDEFINED, space]);
1217    interpret
1218}
1219
1220// *-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*
1221// Tests
1222// *-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*-=-*
1223
1224#[cfg(test)]
1225pub fn run_program(program: &str) -> Result<Vec<Vec<Atom>>, String> {
1226    let metta = Metta::new(Some(EnvBuilder::test_env()));
1227    metta.run(SExprParser::new(program))
1228}
1229
1230#[cfg(test)]
1231mod tests {
1232    use hyperon_atom::gnd::number::Number;
1233    use super::*;
1234    use hyperon_atom::gnd::bool::Bool;
1235    use hyperon_macros::metta;
1236
1237    #[test]
1238    fn test_space() {
1239        let program = "
1240            (= (And T T) T)
1241            (= (frog $x)
1242                (And (croaks $x)
1243                     (eat_flies $x)))
1244            (= (croaks Fritz) T)
1245            (= (eat_flies Fritz) T)
1246            (= (green $x) (frog $x))
1247            !(green Fritz)
1248        ";
1249
1250        let metta = Metta::new(Some(EnvBuilder::test_env()));
1251        let result = metta.run(SExprParser::new(program));
1252        assert_eq!(result, Ok(vec![vec![Atom::sym("T")]]));
1253    }
1254
1255    #[test]
1256    fn metta_type_check_incorrect_number_of_arguments() {
1257        let program = "
1258            (: foo (-> A B))
1259            (foo)
1260        ";
1261
1262        let metta = Metta::new_core(None, Some(EnvBuilder::test_env()));
1263        metta.settings().set("type-check".into(), sym!("auto"));
1264        let result = metta.run(SExprParser::new(program));
1265        assert_eq!(result, Ok(vec![vec![expr!("Error" ("foo") "IncorrectNumberOfArguments")]]));
1266    }
1267
1268    #[test]
1269    fn metta_add_type_check() {
1270        let program = "
1271            (: foo (-> A B))
1272            (: b B)
1273            (foo b)
1274        ";
1275
1276        let metta = Metta::new_core(None, Some(EnvBuilder::test_env()));
1277        metta.settings().set("type-check".into(), sym!("auto"));
1278        let result = metta.run(SExprParser::new(program));
1279        assert_eq!(result, Ok(vec![vec![expr!("Error" ("foo" "b") ("BadArgType" {Number::Integer(1)} "A" "B"))]]));
1280    }
1281
1282    #[test]
1283    fn metta_interpret_type_check() {
1284        let program = "
1285            (: foo (-> A B))
1286            (: b B)
1287            !(foo b)
1288        ";
1289
1290        let metta = Metta::new_core(None, Some(EnvBuilder::test_env()));
1291        metta.settings().set("type-check".into(), sym!("auto"));
1292        let result = metta.run(SExprParser::new(program));
1293        assert_eq!(result, Ok(vec![vec![expr!("Error" ("foo" "b") ("BadArgType" {Number::Integer(1)} "A" "B"))]]));
1294    }
1295
1296    #[derive(Clone, Debug)]
1297    struct ErrorOp{}
1298
1299    grounded_op!(ErrorOp, "error");
1300
1301    impl Grounded for ErrorOp {
1302        fn type_(&self) -> Atom {
1303            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_UNDEFINED])
1304        }
1305        fn as_execute(&self) -> Option<&dyn CustomExecute> {
1306            Some(self)
1307        }
1308    }
1309
1310    impl CustomExecute for ErrorOp {
1311        fn execute(&self, _args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
1312            Err("TestError".into())
1313        }
1314    }
1315
1316    #[test]
1317    fn metta_stop_run_after_error() {
1318        let program = "
1319            (= (foo) ok)
1320            !(error)
1321            !(foo)
1322        ";
1323
1324        let metta = Metta::new(Some(EnvBuilder::test_env()));
1325        metta.tokenizer().borrow_mut().register_token_with_regex_str("error",
1326            |_| Atom::gnd(ErrorOp{}));
1327        let result = metta.run(SExprParser::new(program));
1328
1329        assert_eq!(result, Ok(vec![vec![expr!("Error" ({ErrorOp{}}) "TestError")]]));
1330    }
1331
1332    #[test]
1333    fn metta_stop_after_type_check_fails_on_add() {
1334        let program = "
1335            (: foo (-> A B))
1336            (: a A)
1337            (: b B)
1338            (foo b)
1339            !(foo a)
1340        ";
1341
1342        let metta = Metta::new_core(None, Some(EnvBuilder::test_env()));
1343        metta.settings().set("type-check".into(), sym!("auto"));
1344        let result = metta.run(SExprParser::new(program));
1345        assert_eq!(result, Ok(vec![vec![expr!("Error" ("foo" "b") ("BadArgType" {Number::Integer(1)} "A" "B"))]]));
1346    }
1347
1348    #[test]
1349    fn metta_stop_after_error_happens_inside_tuple() {
1350        let metta = Metta::new_core(None, Some(EnvBuilder::test_env()));
1351        let program = "
1352            !(a b c (Error e SomeError))
1353        ";
1354        let result = metta.run(SExprParser::new(program));
1355        assert_eq!(result, Ok(vec![vec![expr!("Error" "e" "SomeError")]]));
1356
1357        let metta = Metta::new_core(None, Some(EnvBuilder::test_env()));
1358        let program = "
1359            !((Error e SomeError) a b c)
1360        ";
1361        let result = metta.run(SExprParser::new(program));
1362        assert_eq!(result, Ok(vec![vec![expr!("Error" "e" "SomeError")]]));
1363
1364        let metta = Metta::new_core(None, Some(EnvBuilder::test_env()));
1365        let program = "
1366            (: foo (-> A B))
1367            (: b B)
1368            !(s (foo b))
1369        ";
1370        let result = metta.run(SExprParser::new(program));
1371        assert_eq!(result, Ok(vec![vec![expr!("Error" ("foo" "b") ("BadArgType" {Number::Integer(1)} "A" "B"))]]));
1372    }
1373
1374    #[test]
1375    fn metta_first_call_in_the_tuple_has_incorrect_typing() {
1376        let metta = Metta::new_core(None, Some(EnvBuilder::test_env()));
1377        let program = "
1378            (: foo (-> A B))
1379            (: b B)
1380            !((foo b) s)
1381        ";
1382        let result = metta.run(SExprParser::new(program));
1383        assert_eq!(result, Ok(vec![vec![expr!("Error" ("foo" "b") ("BadArgType" {Number::Integer(1)} "A" "B"))]]));
1384    }
1385
1386
1387    #[derive(Clone, PartialEq, Debug)]
1388    struct ReturnAtomOp(Atom);
1389
1390    impl std::fmt::Display for ReturnAtomOp {
1391        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1392            write!(f, "return-atom {}", self.0)
1393        }
1394    }
1395
1396    impl Grounded for ReturnAtomOp {
1397        fn type_(&self) -> Atom {
1398            Atom::expr([ARROW_SYMBOL, ATOM_TYPE_UNDEFINED])
1399        }
1400        fn as_execute(&self) -> Option<&dyn CustomExecute> {
1401            Some(self)
1402        }
1403    }
1404
1405    impl CustomExecute for ReturnAtomOp {
1406        fn execute(&self, _args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
1407            Ok(vec![self.0.clone()])
1408        }
1409    }
1410
1411    #[test]
1412    fn metta_no_crash_on_empty_expression_returned() {
1413        let program = "
1414            !(empty)
1415        ";
1416
1417        let metta = Metta::new(Some(EnvBuilder::test_env()));
1418        metta.tokenizer().borrow_mut().register_token_with_regex_str("empty",
1419            |_| Atom::gnd(ReturnAtomOp(expr!())));
1420        let result = metta.run(SExprParser::new(program));
1421
1422        assert_eq!(result, Ok(vec![vec![expr!()]]));
1423    }
1424
1425    #[test]
1426    fn metta_empty_results_issue_481() {
1427        let metta = Metta::new(Some(EnvBuilder::test_env()));
1428
1429        let program = "
1430            !(== () (collapse
1431              (let* (($L ()) ($x (superpose $L))) $x) ))
1432        ";
1433        let result = metta.run(SExprParser::new(program));
1434        assert_eq!(result, Ok(vec![vec![Atom::gnd(Bool(true))]]));
1435
1436        let program = "!(let $x (empty) OK)";
1437        let result = metta.run(SExprParser::new(program));
1438        assert_eq!(result, Ok(vec![vec![]]));
1439
1440        let program = "!(let* (($x (empty))) OK)";
1441        let result = metta.run(SExprParser::new(program));
1442        assert_eq!(result, Ok(vec![vec![]]));
1443    }
1444
1445    #[test]
1446    fn metta_no_config_dir_by_default() {
1447        let metta = Metta::new(None);
1448        assert_eq!(metta.environment().config_dir(), None);
1449    }
1450
1451    #[test]
1452    fn metta_enable_auto_type_check() {
1453        let metta = Metta::new(Some(EnvBuilder::test_env()));
1454
1455        let program = "
1456            (: a A)
1457            (: b B)
1458            (: foo (-> A A))
1459            (= (foo $x) $x)
1460
1461            !(pragma! type-check auto)
1462            !(foo a)
1463            !(foo b)
1464        ";
1465        let result = metta.run(SExprParser::new(program));
1466        assert_eq!(result, Ok(vec![
1467                vec![UNIT_ATOM],
1468                vec![metta!(a)],
1469                vec![metta!((Error (foo b) (BadArgType 1 A B)))]
1470        ]));
1471    }
1472}