hyperon/metta/runner/stdlib/
space.rs1use 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 .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 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 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}