Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
TeamSPoon committed Nov 19, 2023
2 parents 4c23a21 + 21cbf3a commit af4221a
Show file tree
Hide file tree
Showing 28 changed files with 516 additions and 434 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ cargo build
cargo test
```

The experimental features can be enabled by editing
[Cargo.toml](./lib/Cargo.toml) file before compilation or by using `--features`
[command line option](https://doc.rust-lang.org/cargo/reference/features.html#command-line-feature-options).
See comments in the `[features]` section of the file for the features
descriptions.

Run examples:
```
cargo run --example sorted_list
Expand Down
15 changes: 0 additions & 15 deletions c/src/atom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1155,21 +1155,6 @@ pub extern "C" fn bindings_resolve(bindings: *const bindings_t, var_name: *const
bindings.resolve(&var).into()
}

/// @brief Returns the atom bound to the supplied variable name in the `bindings_t`, and removes it from the `bindings_t`
/// @ingroup matching_group
/// @param[in] bindings A pointer to the `bindings_t` to inspect
/// @param[in] var_name A NULL-terminated C-style string containing the name of the variable
/// @return The `atom_t` representing the atom that corresponds to the specified variable, or a NULL `atom_ref_t` if the variable is not present.
/// @note The caller must take ownership responsibility for the returned `atom_t`, if it is not NULL
///
#[no_mangle]
pub extern "C" fn bindings_resolve_and_remove(bindings: *mut bindings_t, var_name: *const c_char) -> atom_t {
let bindings = unsafe{ &mut*bindings }.borrow_mut();
let var = VariableAtom::new(cstr_into_string(var_name));

bindings.resolve_and_remove(&var).into()
}

/// @brief Merges two `bindings_t` Bindings frames together into a Bindings Set
/// @ingroup matching_group
/// @param[in] _self The first `bindings_t` to merge. Ownership of this argument is taken by this function
Expand Down
16 changes: 14 additions & 2 deletions c/src/metta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -926,7 +926,7 @@ pub extern "C" fn metta_nth_search_path(metta: *const metta_t, idx: usize, buf:
let path = metta.search_paths().nth(idx);
match path {
Some(path) => write_into_buf(path.display(), buf, buf_len),
None => 0
None => write_into_buf("", buf, buf_len) //Write just the terminator char, if there is room
}
}

Expand Down Expand Up @@ -1217,7 +1217,7 @@ pub extern "C" fn runner_state_current_results(state: *const runner_state_t,
pub extern "C" fn environment_config_dir(buf: *mut c_char, buf_len: usize) -> usize {
match Environment::common_env().config_dir() {
Some(path) => write_into_buf(path.display(), buf, buf_len),
None => 0
None => write_into_buf("", buf, buf_len) //Write just the terminator char, if there is room
}
}

Expand Down Expand Up @@ -1338,6 +1338,18 @@ pub extern "C" fn env_builder_set_config_dir(builder: *mut env_builder_t, path:
*builder_arg_ref = builder.into();
}

/// @brief Configures the environment to create the config dir if it doesn't already exist
/// @ingroup environment_group
/// @param[in] builder A pointer to the in-process environment builder state
///
#[no_mangle]
pub extern "C" fn env_builder_create_config_dir(builder: *mut env_builder_t) {
let builder_arg_ref = unsafe{ &mut *builder };
let builder = core::mem::replace(builder_arg_ref, env_builder_t::null()).into_inner();
let builder = builder.create_config_dir();
*builder_arg_ref = builder.into();
}

/// @brief Configures the environment so that no config directory will be read nor created
/// @ingroup environment_group
/// @param[in] builder A pointer to the in-process environment builder state
Expand Down
9 changes: 7 additions & 2 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,10 @@ path = "src/lib.rs"
crate-type = ["lib"]

[features]
#default = ["minimal"]
minimal = []
default = []
# Add one of the features below into default list to enable.
# See https://doc.rust-lang.org/cargo/reference/features.html#the-features-section
minimal = [] # enables minimal MeTTa interpreter
variable_operation = [] # enables evaluation of the expressions which have
# a variable on the first position, doesn't affect
# minimal MeTTa functionality
27 changes: 14 additions & 13 deletions lib/examples/sorted_list.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
use hyperon::metta::*;
use hyperon::metta::interpreter::*;
use hyperon::*;
use hyperon::metta::runner::*;
use hyperon::metta::runner::arithmetics::*;
use hyperon::metta::text::SExprParser;

fn main() {
let space = metta_space("
fn main() -> Result<(), String> {
let metta = Metta::new(None);
metta.run(SExprParser::new("
(: List (-> $a Type))
(: Nil (List $a))
(: Cons (-> $a (List $a) (List $a)))
(: if (-> bool Any Any) Any)
(= (if true $then $else) $then)
(= (if false $then $else) $else)
(: insert (-> $a (List $a) (List $a)))
(= (insert $x Nil) (Cons $x Nil))
(= (insert $x (Cons $head $tail)) (if (< $x $head)
(Cons $x (Cons $head $tail))
(Cons $head (insert $x $tail))))
");
"))?;

assert_eq!(metta.run(SExprParser::new("!(insert 1 Nil)"))?[0],
vec![expr!("Cons" {Number::Integer(1)} "Nil")]);
assert_eq!(metta.run(SExprParser::new("!(insert 3 (insert 2 (insert 1 Nil)))"))?[0],
vec![expr!("Cons" {Number::Integer(1)} ("Cons" {Number::Integer(2)} ("Cons" {Number::Integer(3)} "Nil")))]);

assert_eq!(interpret(&space, &metta_atom("(insert 1 Nil)")),
Ok(vec![metta_atom("(Cons 1 Nil)")]));
assert_eq!(interpret(&space, &metta_atom("(insert 3 (insert 2 (insert 1 Nil)))")),
Ok(vec![metta_atom("(Cons 1 (Cons 2 (Cons 3 Nil)))")]));
Ok(())
}
22 changes: 2 additions & 20 deletions lib/src/atom/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,25 +429,6 @@ impl Bindings {
var_by_id
}

/// Resolve variable, remove it from [Bindings] and return result.
///
/// # Examples
///
/// ```
/// use hyperon::*;
///
/// let mut bindings = bind!{ x: expr!(y), y: expr!("A" z), z: expr!("B") };
///
/// assert_eq!(bindings.resolve_and_remove(&VariableAtom::new("x")), Some(expr!("A" "B")));
/// assert_eq!(bindings.resolve(&VariableAtom::new("x")), None);
/// assert_eq!(bindings.resolve(&VariableAtom::new("y")), Some(expr!("A" "B")));
/// ```
pub fn resolve_and_remove(&mut self, var: &VariableAtom) -> Option<Atom> {
let result = self.resolve(&var);
self.remove(&var);
result
}

fn remove(&mut self, var: &VariableAtom) -> Option<Atom> {
match self.id_by_var.remove(var) {
None => None,
Expand Down Expand Up @@ -1269,7 +1250,8 @@ mod test {

#[test]
fn match_variable_with_unique_itself() {
let x_uniq = Atom::Variable(VariableAtom::new_id("x", 1));
let last_id = VariableAtom::new("x").make_unique().id;
let x_uniq = Atom::Variable(VariableAtom::new_id("x", last_id + 1));
assert_match(
make_variables_unique(expr!(("A" x) ("B" x))),
expr!(("A" x) z ),
Expand Down
8 changes: 6 additions & 2 deletions lib/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ mod arithmetics;
pub use arithmetics::*;

use crate::*;
use crate::metta::text::{Tokenizer, SExprParser};
use std::cell::RefCell;
use std::fmt::{Debug, Display};
use std::collections::HashMap;

use crate::metta::metta_atom;
#[cfg(test)]
pub(crate) mod test_utils;

// TODO: move Operation and arithmetics under metta package as it uses metta_atom
// Operation implements stateless operations as GroundedAtom.
Expand All @@ -32,7 +34,9 @@ pub struct Operation {

impl Grounded for &'static Operation {
fn type_(&self) -> Atom {
metta_atom(self.typ)
//TODO: Replace this parsing with a static Atom
let mut parser = SExprParser::new(self.typ);
parser.parse(&Tokenizer::new()).unwrap().unwrap()
}

fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
Expand Down
19 changes: 19 additions & 0 deletions lib/src/common/test_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

use crate::*;
use crate::metta::text::{Tokenizer, SExprParser};
use crate::space::grounding::GroundingSpace;

pub(crate) fn metta_space(text: &str) -> GroundingSpace {
let mut space = GroundingSpace::new();
let mut parser = SExprParser::new(text);
while let Some(atom) = parser.parse(&Tokenizer::new()).unwrap() {
space.add(atom);
}
space
}

pub(crate) fn metta_atom(atom_str: &str) -> Atom {
let mut parser = SExprParser::new(atom_str);
let atom = parser.parse(&Tokenizer::new()).unwrap().expect("Single atom is expected");
atom
}
69 changes: 49 additions & 20 deletions lib/src/metta/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,21 @@ fn is_grounded_op(expr: &ExpressionAtom) -> bool {
}
}

fn has_grounded_sub_expr(expr: &Atom) -> bool {
return SubexprStream::from_expr(expr.clone(), TOP_DOWN_DEPTH_WALK)
.any(|sub| if let Atom::Expression(sub) = sub {
is_grounded_op(&sub)
} else {
panic!("Expression is expected");
});
fn is_variable_op(expr: &ExpressionAtom) -> bool {
match expr.children().get(0) {
Some(Atom::Variable(_)) => true,
_ => false,
}
}

fn has_grounded_sub_expr(expr: &ExpressionAtom) -> bool {
return is_grounded_op(expr) ||
SubexprStream::from_expr(Atom::Expression(expr.clone()), TOP_DOWN_DEPTH_WALK)
.any(|sub| if let Atom::Expression(sub) = sub {
is_grounded_op(&sub)
} else {
panic!("Expression is expected");
});
}

fn interpret_as_type_plan<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>,
Expand Down Expand Up @@ -513,8 +521,8 @@ fn call_op<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, input: In
}).collect();
return_cached_result_plan(result)
} else {
if let Atom::Expression(_) = input.atom() {
if !has_grounded_sub_expr(input.atom()) {
if let Atom::Expression(expr) = input.atom() {
if !has_grounded_sub_expr(expr) {
let key = input.atom().clone();
StepResult::execute(SequencePlan::new(
OrPlan::new(
Expand Down Expand Up @@ -551,6 +559,12 @@ fn interpret_reducted_plan<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'
if let Atom::Expression(ref expr) = input.atom() {
if is_grounded_op(expr) {
Box::new(execute_plan(context, input))
} else if is_variable_op(expr) {
#[cfg(feature = "variable_operation")]
let result = Box::new(match_plan(context, input));
#[cfg(not(feature = "variable_operation"))]
let result = Box::new(StepResult::ret(vec![input]));
result
} else {
Box::new(match_plan(context, input))
}
Expand Down Expand Up @@ -610,9 +624,8 @@ fn match_op<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, input: I
let mut query_bindings = context.space.query(&query);
let results: Vec<InterpretedAtom> = query_bindings
.drain(0..)
.map(|mut query_binding| {
let result = query_binding.resolve_and_remove(&var_x).unwrap();
let result = apply_bindings_to_atom(&result, &query_binding);
.map(|query_binding| {
let result = apply_bindings_to_atom(&Atom::Variable(var_x.clone()), &query_binding);
// TODO: sometimes we apply bindings twice: first time here,
// second time when inserting matched argument into nesting
// expression. It should be enough doing it only once.
Expand Down Expand Up @@ -724,6 +737,8 @@ impl<T: Debug> Debug for AlternativeInterpretationsPlan<'_, T> {
#[cfg(test)]
mod tests {
use super::*;
use crate::common::*;
use crate::common::test_utils::*;

#[test]
fn test_match_all() {
Expand All @@ -743,16 +758,16 @@ mod tests {
space.add(expr!("=" ("and" "True" "True") "True"));
space.add(expr!("=" ("if" "True" then else) then));
space.add(expr!("=" ("if" "False" then else) else));
space.add(expr!("=" ("Fritz" "croaks") "True"));
space.add(expr!("=" ("Fritz" "eats-flies") "True"));
space.add(expr!("=" ("Tweety" "chirps") "True"));
space.add(expr!("=" ("Tweety" "yellow") "True"));
space.add(expr!("=" ("Tweety" "eats-flies") "True"));
let expr = expr!("if" ("and" (x "croaks") (x "eats-flies"))
("=" (x "frog") "True") "nop");
space.add(expr!("=" ("croaks" "Fritz") "True"));
space.add(expr!("=" ("eats-flies" "Fritz") "True"));
space.add(expr!("=" ("chirps" "Tweety") "True"));
space.add(expr!("=" ("yellow" "Tweety") "True"));
space.add(expr!("=" ("eats-flies" "Tweety") "True"));
let expr = expr!("if" ("and" ("croaks" x) ("eats-flies" x))
("=" ("frog" x) "True") "nop");

assert_eq!(interpret(&space, &expr),
Ok(vec![expr!("=" ("Fritz" "frog") "True")]));
Ok(vec![expr!("=" ("frog" "Fritz") "True")]));
}

fn results_are_equivalent(actual: &Result<Vec<Atom>, String>,
Expand Down Expand Up @@ -1079,5 +1094,19 @@ mod tests {
panic!("Non-empty result is expected");
}
}

#[test]
fn interpret_match_variable_operation() {
let mut space = GroundingSpace::new();
space.add(expr!("=" ("foo" x) ("foo result" x)));
space.add(expr!("=" ("bar" x) ("bar result" x)));

let actual = interpret(&space, &expr!(op "arg")).unwrap();

#[cfg(feature = "variable_operation")]
assert_eq_no_order!(actual, vec![expr!("foo result" "arg"), expr!("bar result" "arg")]);
#[cfg(not(feature = "variable_operation"))]
assert_eq!(actual, vec![expr!(op "arg")]);
}
}

9 changes: 4 additions & 5 deletions lib/src/metta/interpreter2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,14 +363,13 @@ fn query<'a, T: SpaceRef<'a>>(space: T, atom: Atom, bindings: Bindings) -> Vec<I
let var = VariableAtom::new("X").make_unique();
let query = Atom::expr([EQUAL_SYMBOL, atom, Atom::Variable(var.clone())]);
let results = space.query(&query);
let atom = Atom::Variable(var);
if results.is_empty() {
vec![InterpretedAtom(return_not_reducible(), bindings)]
} else {
results.into_iter()
.flat_map(|mut b| {
let atom = b.resolve_and_remove(&var).unwrap();
let bindings = b.merge_v2(&bindings);
bindings.into_iter().map(move |b| {
.flat_map(|b| {
b.merge_v2(&bindings).into_iter().map(|b| {
let atom = apply_bindings_to_atom(&atom, &b);
InterpretedAtom(atom, b)
})
Expand Down Expand Up @@ -508,6 +507,7 @@ fn cons(bindings: Bindings, head: Atom, tail: ExpressionAtom) -> Vec<Interpreted
#[cfg(test)]
mod tests {
use super::*;
use crate::common::test_utils::{metta_atom, metta_space};

#[test]
fn interpret_atom_evaluate_incorrect_args() {
Expand Down Expand Up @@ -577,7 +577,6 @@ mod tests {
assert_eq!(result[0].0, sym!("True"));
}


#[test]
fn interpret_atom_evaluate_grounded_expression() {
let result = interpret_atom(&space(""), InterpretedAtom(expr!("eval" ({MulXUndefinedType(7)} {6})), bind!{}));
Expand Down
Loading

0 comments on commit af4221a

Please sign in to comment.