diff --git a/.gitignore b/.gitignore index 3c1541d3b..2c61c02c1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ target/ Cargo.lock .DS_Store .vscode +.aider* diff --git a/lib/src/atom/mod.rs b/lib/src/atom/mod.rs index e42fbcd3b..dd82b19fd 100644 --- a/lib/src/atom/mod.rs +++ b/lib/src/atom/mod.rs @@ -119,7 +119,6 @@ use std::fmt::{Display, Debug}; use std::convert::TryFrom; use crate::common::collections::ImmutableString; -use crate::common::ReplacingMapper; // Symbol atom @@ -271,8 +270,8 @@ impl Display for VariableAtom { /// Returns `atom` with all variables replaced by unique instances. pub fn make_variables_unique(mut atom: Atom) -> Atom { - let mut mapper = ReplacingMapper::new(VariableAtom::make_unique); - atom.iter_mut().filter_type::<&mut VariableAtom>().for_each(|var| mapper.replace(var)); + let mut mapper = crate::common::CachingMapper::new(VariableAtom::make_unique); + atom.iter_mut().filter_type::<&mut VariableAtom>().for_each(|var| *var = mapper.replace(var.clone())); atom } diff --git a/lib/src/common/mod.rs b/lib/src/common/mod.rs index 0e2b1d532..2272970ce 100644 --- a/lib/src/common/mod.rs +++ b/lib/src/common/mod.rs @@ -101,33 +101,37 @@ impl Display for GndRefCell { } #[derive(Clone)] -pub struct ReplacingMapper T> { +pub struct CachingMapper V> { mapper: F, - mapping: HashMap, + mapping: HashMap, } -impl T> ReplacingMapper { +impl V> CachingMapper { pub fn new(mapper: F) -> Self { Self{ mapper, mapping: HashMap::new() } } - pub fn replace(&mut self, val: &mut T) { - match self.mapping.get(&val) { - Some(mapped) => *val = mapped.clone(), + pub fn replace(&mut self, key: K) -> V { + match self.mapping.get(&key) { + Some(mapped) => mapped.clone(), None => { - let mut key = (self.mapper)(val.clone()); - std::mem::swap(&mut key, val); - self.mapping.insert(key, val.clone()); + let new_val = (self.mapper)(key.clone()); + self.mapping.insert(key, new_val.clone()); + new_val } } } - pub fn mapping_mut(&mut self) -> &mut HashMap { + pub fn mapping(&self) -> &HashMap { + &self.mapping + } + + pub fn mapping_mut(&mut self) -> &mut HashMap { &mut self.mapping } - pub fn as_fn_mut<'a>(&'a mut self) -> impl 'a + FnMut(T) -> T { - move |mut t| { self.replace(&mut t); t } + pub fn as_fn_mut<'a>(&'a mut self) -> impl 'a + FnMut(K) -> V { + move |k| { self.replace(k) } } } diff --git a/lib/src/metta/interpreter.rs b/lib/src/metta/interpreter.rs index e5ad155d6..12647ad88 100644 --- a/lib/src/metta/interpreter.rs +++ b/lib/src/metta/interpreter.rs @@ -73,7 +73,6 @@ use crate::common::collections::ListMap; use crate::metta::*; use crate::metta::types::{is_func, get_arg_types, get_type_bindings, get_atom_types, match_reducted_types}; -use crate::common::ReplacingMapper; use std::ops::Deref; use std::rc::Rc; @@ -230,7 +229,7 @@ impl InterpreterCache { } fn get(&self, key: &Atom) -> Option { - let mut var_mapper = ReplacingMapper::new(VariableAtom::make_unique); + let mut var_mapper = crate::common::CachingMapper::new(VariableAtom::make_unique); key.iter().filter_type::<&VariableAtom>() .for_each(|v| { var_mapper.mapping_mut().insert(v.clone(), v.clone()); }); @@ -240,7 +239,7 @@ impl InterpreterCache { for res in results { let mut atom = res.atom().clone(); atom.iter_mut().filter_type::<&mut VariableAtom>() - .for_each(|var| var_mapper.replace(var)); + .for_each(|var| *var = var_mapper.replace(var.clone())); let bindings = res.bindings().clone().rename_vars(var_mapper.as_fn_mut()); result.push(InterpretedAtom(atom, bindings)); } diff --git a/lib/src/metta/runner/stdlib.rs b/lib/src/metta/runner/stdlib.rs index 6ca4d439b..17bfcc6ea 100644 --- a/lib/src/metta/runner/stdlib.rs +++ b/lib/src/metta/runner/stdlib.rs @@ -7,6 +7,7 @@ use crate::metta::text::SExprParser; use crate::metta::runner::{Metta, RunContext, ModuleLoader}; use crate::metta::types::{get_atom_types, get_meta_type}; use crate::common::shared::Shared; +use crate::common::CachingMapper; use std::rc::Rc; use std::cell::RefCell; @@ -776,6 +777,48 @@ impl Grounded for ChangeStateOp { } } +#[derive(Clone, PartialEq, Debug)] +pub struct SealedOp {} + +impl Display for SealedOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "sealed") + } +} + +impl Grounded for SealedOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) + } + + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("sealed expects two arguments: var_list and expression"); + + let mut term_to_seal = args.get(1).ok_or_else(arg_error)?.clone(); + let var_list = args.get(0).ok_or_else(arg_error)?.clone(); + + let mut local_var_mapper = CachingMapper::new(|var: &VariableAtom| var.clone().make_unique()); + + var_list.iter().filter_type::<&VariableAtom>() + .for_each(|var| { let _ = local_var_mapper.replace(var); }); + + term_to_seal.iter_mut().filter_type::<&mut VariableAtom>() + .for_each(|var| match local_var_mapper.mapping().get(var) { + Some(v) => *var = v.clone(), + None => {}, + }); + + let result = vec![term_to_seal.clone()]; + log::debug!("sealed::execute: var_list: {}, term_to_seal: {}, result: {:?}", var_list, term_to_seal, result); + + Ok(result) + } + + fn match_(&self, other: &Atom) -> MatchResultIter { + match_by_equality(self, other) + } +} + #[derive(Clone, PartialEq, Debug)] pub struct EqualOp {} @@ -839,7 +882,6 @@ mod non_minimal_only_stdlib { use super::*; use crate::metta::interpreter::interpret; use crate::common::assert::vec_eq_no_order; - use crate::common::ReplacingMapper; // TODO: move it into hyperon::atom module? pub(crate) fn atom_as_expr(atom: &Atom) -> Option<&ExpressionAtom> { @@ -947,6 +989,8 @@ mod non_minimal_only_stdlib { } } + use std::collections::HashSet; + #[derive(Clone, PartialEq, Debug)] pub struct CaseOp { space: DynSpace, @@ -1225,8 +1269,6 @@ mod non_minimal_only_stdlib { } } - use std::collections::HashSet; - impl Grounded for LetOp { fn type_(&self) -> Atom { // TODO: Undefined for the argument is necessary to make argument reductable. @@ -1266,11 +1308,11 @@ mod non_minimal_only_stdlib { } fn make_conflicting_vars_unique(pattern: &mut Atom, template: &mut Atom, external_vars: &HashSet) { - let mut local_var_mapper = ReplacingMapper::new(VariableAtom::make_unique); + let mut local_var_mapper = CachingMapper::new(VariableAtom::make_unique); pattern.iter_mut().filter_type::<&mut VariableAtom>() .filter(|var| external_vars.contains(var)) - .for_each(|var| local_var_mapper.replace(var)); + .for_each(|var| *var = local_var_mapper.replace(var.clone())); template.iter_mut().filter_type::<&mut VariableAtom>() .for_each(|var| match local_var_mapper.mapping_mut().get(var) { @@ -1375,6 +1417,8 @@ mod non_minimal_only_stdlib { tref.register_token(regex(r"mod-space!"), move |_| { mod_space_op.clone() }); let print_mods_op = Atom::gnd(PrintModsOp::new(metta.clone())); tref.register_token(regex(r"print-mods!"), move |_| { print_mods_op.clone() }); + let sealed_op = Atom::gnd(SealedOp{}); + tref.register_token(regex(r"sealed"), move |_| { sealed_op.clone() }); } //TODO: The metta argument is a temporary hack on account of the way the operation atoms store references @@ -1999,4 +2043,26 @@ mod tests { assert_eq_metta_results!(metta.run(parser), Ok(vec![vec![]])); } + + #[test] + fn sealed_op_runner() { + let nested = run_program("!(sealed ($x) (sealed ($a $b) (=($a $x $c) ($b))))"); + let simple_replace = run_program("!(sealed ($x $y) (=($y $z)))"); + + assert!(crate::atom::matcher::atoms_are_equivalent(&nested.unwrap()[0][0], &expr!("="(a b c) (z)))); + assert!(crate::atom::matcher::atoms_are_equivalent(&simple_replace.unwrap()[0][0], &expr!("="(y z)))); + } + + #[test] + fn sealed_op_execute() { + let val = SealedOp{}.execute(&mut vec![expr!(x y), expr!("="(y z))]); + assert!(crate::atom::matcher::atoms_are_equivalent(&val.unwrap()[0], &expr!("="(y z)))); + } + + #[test] + fn use_sealed_to_make_scoped_variable() { + assert_eq!(run_program("!(let $x (input $x) (output $x))"), Ok(vec![vec![expr!("output" ("input" x))]])); + assert_eq!(run_program("!(let ($sv $st) (sealed ($x) ($x (output $x))) + (let $sv (input $x) $st))"), Ok(vec![vec![expr!("output" ("input" x))]])); + } } diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index c33c7f7c2..8089b6aef 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -413,6 +413,8 @@ pub fn register_runner_tokens(tref: &mut Tokenizer, tokenizer: Shared tref.register_token(regex(r"trace!"), move |_| { trace_op.clone() }); let println_op = Atom::gnd(stdlib::PrintlnOp{}); tref.register_token(regex(r"println!"), move |_| { println_op.clone() }); + let sealed_op = Atom::gnd(stdlib::SealedOp{}); + tref.register_token(regex(r"sealed"), move |_| { sealed_op.clone() }); // &self should be updated // TODO: adding &self might be done not by stdlib, but by MeTTa itself. // TODO: adding &self introduces self referencing and thus prevents space @@ -947,6 +949,13 @@ mod tests { Ok(vec![vec![expr!("Error" "myAtom" "BadType")]])); } + #[test] + fn use_sealed_to_make_scoped_variable() { + assert_eq!(run_program("!(let $x (input $x) (output $x))"), Ok(vec![vec![]])); + assert_eq!(run_program("!(let ($sv $st) (sealed ($x) ($x (output $x))) + (let $sv (input $x) $st))"), Ok(vec![vec![expr!("output" ("input" x))]])); + } + #[test] fn test_pragma_interpreter_bare_minimal() { let program = "