hyperon/metta/runner/stdlib/
space.rs

1use hyperon_atom::*;
2use hyperon_space::*;
3use crate::metta::*;
4use crate::metta::text::Tokenizer;
5use crate::metta::runner::stdlib::{grounded_op, unit_result, regex};
6use hyperon_atom::gnd::GroundedFunctionAtom;
7
8use std::rc::Rc;
9use std::cell::RefCell;
10use std::fmt::Display;
11
12#[derive(Clone, Debug)]
13pub struct NewSpaceOp {}
14
15grounded_op!(NewSpaceOp, "new-space");
16
17impl Grounded for NewSpaceOp {
18    fn type_(&self) -> Atom {
19        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_SPACE])
20    }
21
22    fn as_execute(&self) -> Option<&dyn CustomExecute> {
23        Some(self)
24    }
25}
26
27impl CustomExecute for NewSpaceOp {
28    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
29        if args.len() == 0 {
30            let space = Atom::gnd(DynSpace::new(GroundingSpace::new()));
31            Ok(vec![space])
32        } else {
33            Err("new-space doesn't expect arguments".into())
34        }
35    }
36}
37
38#[derive(Clone, PartialEq, Debug)]
39pub struct StateAtom {
40    state: Rc<RefCell<(Atom, Atom)>>
41}
42
43impl StateAtom {
44    pub fn new(atom: Atom, typ: Atom) -> Self {
45        Self{ state: Rc::new(RefCell::new((atom, typ))) }
46    }
47}
48
49impl Display for StateAtom {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        write!(f, "(State {})", self.state.borrow().0)
52    }
53}
54
55impl Grounded for StateAtom {
56    fn type_(&self) -> Atom {
57        self.state.borrow().1.clone()
58    }
59}
60
61fn new_state(args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
62    let arg_error = "new-state expects atom as a first argument and non-empty list of types as a second argument";
63    let atom = args.get(0).ok_or(arg_error)?;
64    let typ = args.get(1)
65        .and_then(|t| TryInto::<&ExpressionAtom>::try_into(t).ok())
66        // TODO: when grounded atom will be able returning few types then
67        // all types should be returned
68        .and_then(|e| e.children().get(0))
69        .ok_or(arg_error)?;
70    Ok(vec![Atom::gnd(StateAtom::new(atom.clone(),
71        Atom::expr([Atom::sym("StateMonad"), typ.clone()])))])
72}
73
74#[derive(Clone, Debug)]
75pub struct GetStateOp { }
76
77grounded_op!(GetStateOp, "get-state");
78
79impl Grounded for GetStateOp {
80    fn type_(&self) -> Atom {
81        Atom::expr([ARROW_SYMBOL, expr!("StateMonad" tgso), expr!(tgso)])
82    }
83
84    fn as_execute(&self) -> Option<&dyn CustomExecute> {
85        Some(self)
86    }
87}
88
89impl CustomExecute for GetStateOp {
90    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
91        let arg_error = "get-state expects single state atom as an argument";
92        let state = args.get(0).ok_or(arg_error)?;
93        let atom = Atom::as_gnd::<StateAtom>(state).ok_or(arg_error)?;
94        Ok(vec![atom.state.borrow().0.clone()])
95    }
96}
97
98#[derive(Clone, Debug)]
99pub struct ChangeStateOp { }
100
101grounded_op!(ChangeStateOp, "change-state!");
102
103impl Grounded for ChangeStateOp {
104    fn type_(&self) -> Atom {
105        Atom::expr([ARROW_SYMBOL, expr!("StateMonad" tcso), expr!(tcso), expr!("StateMonad" tcso)])
106    }
107
108    fn as_execute(&self) -> Option<&dyn CustomExecute> {
109        Some(self)
110    }
111}
112
113impl CustomExecute for ChangeStateOp {
114    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
115        let arg_error = "change-state! expects a state atom and its new value as arguments";
116        let atom = args.get(0).ok_or(arg_error)?;
117        let state = Atom::as_gnd::<StateAtom>(atom).ok_or("change-state! expects a state as the first argument")?;
118        let new_value = args.get(1).ok_or(arg_error)?;
119        state.state.borrow_mut().0 = new_value.clone();
120        Ok(vec![atom.clone()])
121    }
122}
123
124#[derive(Clone, Debug)]
125pub struct GetAtomsOp {}
126
127grounded_op!(GetAtomsOp, "get-atoms");
128
129impl Grounded for GetAtomsOp {
130    fn type_(&self) -> Atom {
131        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_SPACE, ATOM_TYPE_ATOM])
132    }
133
134    fn as_execute(&self) -> Option<&dyn CustomExecute> {
135        Some(self)
136    }
137}
138
139impl CustomExecute for GetAtomsOp {
140    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
141        let arg_error = || ExecError::from("get-atoms expects one argument: space");
142        let space = args.get(0).ok_or_else(arg_error)?;
143        let space = Atom::as_gnd::<DynSpace>(space).ok_or("get-atoms expects a space as its argument")?;
144        let mut result = Vec::new();
145        space.borrow().visit(&mut |atom: std::borrow::Cow<Atom>| {
146            result.push(make_variables_unique(atom.into_owned()))
147        }).map_or(Err(ExecError::Runtime("Unsupported Operation. Can't traverse atoms in this space".to_string())), |_| Ok(result))
148    }
149}
150
151#[derive(Clone, Debug)]
152pub struct AddAtomOp {}
153
154grounded_op!(AddAtomOp, "add-atom");
155
156impl Grounded for AddAtomOp {
157    fn type_(&self) -> Atom {
158        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_SPACE,
159            ATOM_TYPE_ATOM, UNIT_TYPE])
160    }
161
162    fn as_execute(&self) -> Option<&dyn CustomExecute> {
163        Some(self)
164    }
165}
166
167impl CustomExecute for AddAtomOp {
168    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
169        let arg_error = || ExecError::from("add-atom expects two arguments: space and atom");
170        let space = args.get(0).ok_or_else(arg_error)?;
171        let atom = args.get(1).ok_or_else(arg_error)?;
172        let space = Atom::as_gnd::<DynSpace>(space).ok_or("add-atom expects a space as the first argument")?;
173        space.borrow_mut().add(atom.clone());
174        unit_result()
175    }
176}
177
178#[derive(Clone, Debug)]
179pub struct RemoveAtomOp {}
180
181grounded_op!(RemoveAtomOp, "remove-atom");
182
183impl Grounded for RemoveAtomOp {
184    fn type_(&self) -> Atom {
185        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_SPACE,
186            ATOM_TYPE_ATOM, UNIT_TYPE])
187    }
188
189    fn as_execute(&self) -> Option<&dyn CustomExecute> {
190        Some(self)
191    }
192}
193
194impl CustomExecute for RemoveAtomOp {
195    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
196        let arg_error = || ExecError::from("remove-atom expects two arguments: space and atom");
197        let space = args.get(0).ok_or_else(arg_error)?;
198        let atom = args.get(1).ok_or_else(arg_error)?;
199        let space = Atom::as_gnd::<DynSpace>(space).ok_or("remove-atom expects a space as the first argument")?;
200        space.borrow_mut().remove(atom);
201        // TODO? Is it necessary to distinguish whether the atom was removed or not?
202        unit_result()
203    }
204}
205
206pub(super) fn register_context_independent_tokens(tref: &mut Tokenizer) {
207    let new_space_op = Atom::gnd(NewSpaceOp{});
208    tref.register_token(regex(r"new-space"), move |_| { new_space_op.clone() });
209    let add_atom_op = Atom::gnd(AddAtomOp{});
210    tref.register_token(regex(r"add-atom"), move |_| { add_atom_op.clone() });
211    let remove_atom_op = Atom::gnd(RemoveAtomOp{});
212    tref.register_token(regex(r"remove-atom"), move |_| { remove_atom_op.clone() });
213    tref.register_function(GroundedFunctionAtom::new(
214            r"_new-state".into(),
215            expr!("->" t "Expression" ("StateMonad" t)),
216            |args: &[Atom]| -> Result<Vec<Atom>, ExecError> { new_state(args) }, 
217        ));
218    let change_state_op = Atom::gnd(ChangeStateOp{});
219    tref.register_token(regex(r"change-state!"), move |_| { change_state_op.clone() });
220    let get_state_op = Atom::gnd(GetStateOp{});
221    tref.register_token(regex(r"get-state"), move |_| { get_state_op.clone() });
222    let get_atoms_op = Atom::gnd(GetAtomsOp{});
223    tref.register_token(regex(r"get-atoms"), move |_| { get_atoms_op.clone() });
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    use crate::metta::text::SExprParser;
230    use crate::space::grounding::metta_space;
231    use crate::metta::runner::Metta;
232    use hyperon_common::assert_eq_no_order;
233    use hyperon_macros::metta;
234
235    #[test]
236    fn mod_space_op() {
237        let program = r#"
238            !(bind! &new_space (new-space))
239            !(add-atom &new_space (mod-space! stdlib))
240            !(get-atoms &new_space)
241        "#;
242        let runner = Metta::new(Some(runner::environment::EnvBuilder::test_env()));
243        let result = runner.run(SExprParser::new(program)).unwrap();
244
245        assert_eq!(result[2], vec![Atom::expr([Atom::gnd(super::super::module::ModSpaceOp::new(runner.clone())), Atom::sym("stdlib")])]);
246    }
247
248    fn collect_atoms(space: &DynSpace) -> Vec<Atom> {
249        let mut atoms = Vec::new();
250        space.borrow().visit(&mut |atom: std::borrow::Cow<Atom>| atoms.push(atom.into_owned()))
251            .expect("Space::visit is not implemented");
252        atoms
253    }
254
255    #[test]
256    fn remove_atom_op() {
257        let space = metta_space("
258            (foo bar)
259            (bar foo)
260        ");
261        let satom = Atom::gnd(space.clone());
262        let res = RemoveAtomOp{}.execute(&mut vec![satom, expr!(("foo" "bar"))]).expect("No result returned");
263        // REM: can return Bool in future
264        assert_eq!(res, vec![UNIT_ATOM]);
265        let space_atoms = collect_atoms(&space);
266        assert_eq_no_order!(space_atoms, vec![expr!(("bar" "foo"))]);
267    }
268
269    #[test]
270    fn get_atoms_op() {
271        let space = metta_space("
272            (foo bar)
273            (bar foo)
274        ");
275        let satom = Atom::gnd(space.clone());
276        let res = GetAtomsOp{}.execute(&mut vec![satom]).expect("No result returned");
277        let space_atoms = collect_atoms(&space);
278        assert_eq_no_order!(res, space_atoms);
279        assert_eq_no_order!(res, vec![expr!(("foo" "bar")), expr!(("bar" "foo"))]);
280    }
281
282    #[test]
283    fn new_space_op() {
284        let res = NewSpaceOp{}.execute(&mut vec![]).expect("No result returned");
285        let space = res.get(0).expect("Result is empty");
286        let space = space.as_gnd::<DynSpace>().expect("Result is not space");
287        let space_atoms = collect_atoms(&space);
288        assert_eq_no_order!(space_atoms, Vec::<Atom>::new());
289    }
290
291    #[test]
292    fn add_atom_op() {
293        let space = DynSpace::new(GroundingSpace::new());
294        let satom = Atom::gnd(space.clone());
295        let res = AddAtomOp{}.execute(&mut vec![satom, expr!(("foo" "bar"))]).expect("No result returned");
296        assert_eq!(res, vec![UNIT_ATOM]);
297        let space_atoms = collect_atoms(&space);
298        assert_eq_no_order!(space_atoms, vec![expr!(("foo" "bar"))]);
299    }
300
301    #[test]
302    fn state_ops() {
303        let program = r#"
304            (: a A)
305            (: aa A)
306            (: b B)
307            (: F (-> $t $t))
308            !(bind! &stateAB (new-state (F a)))
309            !(change-state! &stateAB (F aa))
310            !(get-state &stateAB)
311            !(change-state! &stateAB (F b))
312        "#;
313        let runner = Metta::new(Some(runner::environment::EnvBuilder::test_env()));
314        let result = runner.run(SExprParser::new(program));
315
316        let faa = StateAtom::new(metta!((F aa)), metta!((StateMonad A)));
317        assert_eq!(result, Ok(vec![
318            vec![UNIT_ATOM],
319            vec![Atom::gnd(faa.clone())],
320            vec![metta!((F aa))],
321            vec![metta!((Error ({ChangeStateOp{}} {faa} (F b)) (BadArgType 2 A B)))],
322        ]));
323    }
324}