Skip to content

Commit

Permalink
Merge pull request #609 from vsbogd/sealed
Browse files Browse the repository at this point in the history
Add sealed operation
  • Loading branch information
vsbogd authored Mar 27, 2024
2 parents 8f94822 + dc953fa commit f6afcc1
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ target/
Cargo.lock
.DS_Store
.vscode
.aider*
5 changes: 2 additions & 3 deletions lib/src/atom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ use std::fmt::{Display, Debug};
use std::convert::TryFrom;

use crate::common::collections::ImmutableString;
use crate::common::ReplacingMapper;

// Symbol atom

Expand Down Expand Up @@ -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
}

Expand Down
28 changes: 16 additions & 12 deletions lib/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,33 +101,37 @@ impl<T> Display for GndRefCell<T> {
}

#[derive(Clone)]
pub struct ReplacingMapper<T: Clone + std::hash::Hash + Eq + ?Sized, F: Fn(T) -> T> {
pub struct CachingMapper<K: Clone + std::hash::Hash + Eq + ?Sized, V: Clone, F: Fn(K) -> V> {
mapper: F,
mapping: HashMap<T, T>,
mapping: HashMap<K, V>,
}

impl<T: Clone + std::hash::Hash + Eq + ?Sized, F: Fn(T) -> T> ReplacingMapper<T, F> {
impl<K: Clone + std::hash::Hash + Eq + ?Sized, V: Clone, F: Fn(K) -> V> CachingMapper<K, V, F> {
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<T, T> {
pub fn mapping(&self) -> &HashMap<K, V> {
&self.mapping
}

pub fn mapping_mut(&mut self) -> &mut HashMap<K, V> {
&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) }
}
}

Expand Down
5 changes: 2 additions & 3 deletions lib/src/metta/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -230,7 +229,7 @@ impl InterpreterCache {
}

fn get(&self, key: &Atom) -> Option<Results> {
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()); });

Expand All @@ -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));
}
Expand Down
76 changes: 71 additions & 5 deletions lib/src/metta/runner/stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Vec<Atom>, 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 {}

Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -947,6 +989,8 @@ mod non_minimal_only_stdlib {
}
}

use std::collections::HashSet;

#[derive(Clone, PartialEq, Debug)]
pub struct CaseOp {
space: DynSpace,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1266,11 +1308,11 @@ mod non_minimal_only_stdlib {
}

fn make_conflicting_vars_unique(pattern: &mut Atom, template: &mut Atom, external_vars: &HashSet<VariableAtom>) {
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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))]]));
}
}
9 changes: 9 additions & 0 deletions lib/src/metta/runner/stdlib_minimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,8 @@ pub fn register_runner_tokens(tref: &mut Tokenizer, tokenizer: Shared<Tokenizer>
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
Expand Down Expand Up @@ -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 = "
Expand Down

0 comments on commit f6afcc1

Please sign in to comment.