From 9b3672b21ed53095cea4b4323c4539195eb2aeb4 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 8 Nov 2024 17:17:33 +0300 Subject: [PATCH 1/5] Remove old_interpreter feature --- .github/workflows/old_interpreter.yml | 117 -- lib/Cargo.toml | 1 - lib/benches/interpreter_minimal.rs | 1 - lib/src/common/mod.rs | 1 - lib/src/common/plan.rs | 432 ------- lib/src/metta/mod.rs | 6 - lib/src/metta/old_interpreter.rs | 1140 ----------------- .../metta/runner/builtin_mods/catalog_mods.rs | 2 +- lib/src/metta/runner/mod.rs | 10 +- lib/src/metta/runner/modules/mod.rs | 5 - lib/src/metta/runner/stdlib.metta | 771 ----------- lib/src/metta/runner/stdlib_minimal.rs | 92 +- .../metta/runner/{stdlib.rs => stdlib_old.rs} | 877 +------------ lib/src/metta/types.rs | 87 -- python/tests/scripts/c1_grounded_basic.metta | 9 +- python/tests/scripts/d5_auto_types.metta | 9 +- repl/Cargo.toml | 1 - 17 files changed, 60 insertions(+), 3501 deletions(-) delete mode 100644 .github/workflows/old_interpreter.yml delete mode 100644 lib/src/common/plan.rs delete mode 100644 lib/src/metta/old_interpreter.rs delete mode 100644 lib/src/metta/runner/stdlib.metta rename lib/src/metta/runner/{stdlib.rs => stdlib_old.rs} (67%) diff --git a/.github/workflows/old_interpreter.yml b/.github/workflows/old_interpreter.yml deleted file mode 100644 index 3a60554f2..000000000 --- a/.github/workflows/old_interpreter.yml +++ /dev/null @@ -1,117 +0,0 @@ -# This workflow is intended to run tests on old Rust MeTTa interpreter. -# It is indicative and temporary, it doesn't prevent any changes from merging. - -# This workflow uses actions that are not certified by GitHub. They are -# provided by a third-party and are governed by separate terms of service, -# privacy policy, and support documentation. - -name: old_interpreter - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - old_interpreter: - runs-on: "ubuntu-20.04" - - steps: - - name: Check out repository code - uses: actions/checkout@v4 - - - name: Install Rust stable - uses: actions-rs/toolchain@v1.0.6 - with: - toolchain: stable - override: true - - - name: Build Rust library - working-directory: ./lib - run: | - cargo check --features old_interpreter - cargo build --features old_interpreter - - - name: Test Rust library - working-directory: ./lib - run: | - RUST_LOG=hyperon=debug cargo test --features old_interpreter - - - name: Install cbindgen - uses: actions-rs/cargo@v1.0.1 - with: - command: install - args: cbindgen - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.7" - - - name: Install CMake - uses: jwlawson/actions-setup-cmake@v1.14.1 - with: - cmake-version: "3.24.x" - - - name: Install Conan - uses: turtlebrowser/get-conan@v1.2 - with: - version: "2.5.0" - - - name: Setup Conan profile - run: | - conan profile detect --force - - - name: Print environment - run: | - echo "uname -a" - uname -a - echo "rustc --version" - rustc --version - echo "cbindgen --version" - cbindgen --version - echo "python --version" - which python - python --version - python -c "import platform; print(platform.platform())" - echo "python3 --version" - which python3 - python3 --version - python3 -c "import platform; print(platform.platform())" - echo "conan --version" - conan --version - conan_python=$( head -1 $(which conan) | cut -c 3- ) - echo "conan Python: $conan_python" - echo -n "conan Python platform: " - $conan_python -c "import platform; print(platform.platform())" - echo "conan profile show" - conan profile show - echo "gcc --version" - gcc --version - echo "g++ --version" - g++ --version - echo "cmake --version" - cmake --version - file $(which cmake) - echo "make --version" - make --version - file $(which make) - - - name: Setup C API build - run: | - mkdir -p build - cd build - # specify C compiler as conan could not find it automatically - # see https://github.com/conan-io/conan/issues/4322 - cmake -DCARGO_ARGS="--features hyperon/old_interpreter" -DCMAKE_BUILD_TYPE=Release -DPython3_EXECUTABLE=`which python` -DCMAKE_C_COMPILER=gcc .. - - - name: Build C API - working-directory: ./build - run: make - - - name: Test C API - working-directory: ./build - run: make check diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 905150100..23a896e1c 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -30,7 +30,6 @@ crate-type = ["lib"] default = ["pkg_mgmt"] # Add one of the features below into default list to enable. # See https://doc.rust-lang.org/cargo/reference/features.html#the-features-section -old_interpreter = [] # enables old Rust interpreter variable_operation = [] # enables evaluation of the expressions which have # a variable on the first position git = ["git2", "pkg_mgmt"] diff --git a/lib/benches/interpreter_minimal.rs b/lib/benches/interpreter_minimal.rs index 6e1ef7d11..d4c225fa4 100644 --- a/lib/benches/interpreter_minimal.rs +++ b/lib/benches/interpreter_minimal.rs @@ -1,5 +1,4 @@ #![feature(test)] -#[cfg(not(feature = "old_interpreter"))] mod interpreter_minimal_bench { extern crate test; diff --git a/lib/src/common/mod.rs b/lib/src/common/mod.rs index 7194640c8..fa4b0f8d1 100644 --- a/lib/src/common/mod.rs +++ b/lib/src/common/mod.rs @@ -1,6 +1,5 @@ //! Common datastructures used in other modules. Common grounded atoms library. -pub mod plan; pub mod collections; pub mod shared; pub mod assert; diff --git a/lib/src/common/plan.rs b/lib/src/common/plan.rs deleted file mode 100644 index 5ff420278..000000000 --- a/lib/src/common/plan.rs +++ /dev/null @@ -1,432 +0,0 @@ -use std::fmt::{Debug, Formatter}; - -// Generic plan infrastructure - -/// Result of a single step of a plan -pub enum StepResult<'a, R: 'a, E: 'a> { - /// New plan to be executed to get a result - Execute(Box + 'a>), - /// Result returned - Return(R), - /// Plan execution error - Error(E), -} - -impl<'a, R: 'a, E: 'a> StepResult<'a, R, E> { - /// New result from a value which has the Plan trait - pub fn execute

(next: P) -> Self where P: 'a + Plan<'a, (), R, E> { - Self::Execute(Box::new(next)) - } - - /// New result from a returned value - pub fn ret(result: R) -> Self { - Self::Return(result) - } - - /// New error result - pub fn err(err: E) -> Self { - Self::Error(err) - } - - /// Return true if plan can be executed further - pub fn has_next(&self) -> bool { - match self { - StepResult::Execute(_) => true, - StepResult::Return(_) => false, - StepResult::Error(_) => false, - } - } -} - -/// Plan which gets a value of T type as an input and returns a result of -/// R type as an output after execution -pub trait Plan<'a, T, R: 'a, E: 'a> : Debug { - // `self: Box` allows moving content of the step into the next step. - // We cannot use `self: Self` because it will be called via `dyn Plan` - // which doesn't know anything about original type and cannot move it. - /// Execute one step of the plan - fn step(self: Box, arg: T) -> StepResult<'a, R, E>; -} - -// Specific plans to form calculations graph - -/// Boxed plan is a plan -impl<'a, T, R: 'a, E: 'a> Plan<'a, T, R, E> for Box + '_> { - fn step(self: Box, arg:T) -> StepResult<'a, R, E> { - (*self).step(arg) - } -} - -/// StepResult itself is a trivial plan which executes step of the plan or -/// itself when executed -impl<'a, R: 'a + Debug, E: 'a + Debug> Plan<'a, (), R, E> for StepResult<'a, R, E> { - fn step(self: Box, _:()) -> StepResult<'a, R, E> { - match *self { - StepResult::Execute(plan) => plan.step(()), - _ => *self, - } - } -} - -impl Debug for StepResult<'_, R, E> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - match self { - Self::Execute(plan) => write!(f, "{:?}", plan), - Self::Return(result) => write!(f, "return {:?}", result), - Self::Error(err) => write!(f, "error {:?}", err), - } - } -} - -/// Function from T to StepResult is a plan which calls itself when executed -pub struct FunctionPlan<'a, T, R: 'a, E: 'a> { - pub func: fn(T) -> StepResult<'a, R, E>, - pub name: &'a str, -} - -impl<'a, T, R: 'a, E: 'a> Plan<'a, T, R, E> for FunctionPlan<'a, T, R, E> { - fn step(self: Box, arg: T) -> StepResult<'a, R, E> { - (self.func)(arg) - } -} - -impl Debug for FunctionPlan<'_, T, R, E> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{}", self.name) - } -} - -impl Clone for FunctionPlan<'_, T, R, E> { - fn clone(&self) -> Self { - Self{ func: self.func, name: self.name } - } -} - -impl Copy for FunctionPlan<'_, T, R, E> {} - -/// Operator from T to StepResult is a plan which calls itself when executed -pub struct OperatorPlan<'a, T, R: 'a, E: 'a> { - operator: Box StepResult<'a, R, E>>, - name: String, -} - -impl<'a, T, R: 'a, E: 'a> OperatorPlan<'a, T, R, E> { - pub fn new StepResult<'a, R, E>, N: Into>(operator: F, name: N) -> Self { - Self { operator: Box::new(operator), name: name.into() } - } -} - -impl<'a, T, R: 'a, E: 'a> Plan<'a, T, R, E> for OperatorPlan<'a, T, R, E> { - fn step(self: Box, arg: T) -> StepResult<'a, R, E> { - (self.operator)(arg) - } -} - -impl Debug for OperatorPlan<'_, T, R, E> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{}", self.name) - } -} - -/// The plan applies an argument of T type to the underlying plan Plan. -/// Resulting plan has type Plan<(), R>. -pub struct ApplyPlan<'a, T: 'a, R: 'a, E: 'a> { - arg: T, - plan: Box + 'a>, -} - -impl<'a, T: 'a, R: 'a, E: 'a> ApplyPlan<'a, T, R, E> { - pub fn new

(plan: P, arg: T) -> Self where P: 'a + Plan<'a, T, R, E> { - ApplyPlan{ arg, plan: Box::new(plan) } - } -} - -impl<'a, T: 'a + Debug, R: 'a, E: 'a + Debug> Plan<'a, (), R, E> for ApplyPlan<'a, T, R, E> { - fn step(self: Box, _: ()) -> StepResult<'a, R, E> { - Plan::step(self.plan, self.arg) - } -} - -impl Debug for ApplyPlan<'_, T, R, E> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "apply \"{:?}\" to \"{:?}\"", self.plan, self.arg) - } -} - -/// The plan applies value of T1 type as a first argument to the underlying -/// plan which consumes pair of (T1, T2). Resulting plan has type Plan -/// and to be applied to the second argument. -pub struct PartialApplyPlan<'a, T1: 'a, T2, R: 'a, E: 'a> { - arg: T1, - plan: Box + 'a>, -} - -impl<'a, T1: 'a, T2, R: 'a, E: 'a> PartialApplyPlan<'a, T1, T2, R, E> { - pub fn new

(plan: P, arg: T1) -> Self where P: 'a + Plan<'a, (T1, T2), R, E> { - PartialApplyPlan{ arg, plan: Box::new(plan) } - } -} - -impl<'a, T1: 'a + Debug, T2, R: 'a, E: 'a> Plan<'a, T2, R, E> for PartialApplyPlan<'a, T1, T2, R, E> { - fn step(self: Box, arg: T2) -> StepResult<'a, R, E> { - self.plan.step((self.arg, arg)) - } -} - -impl Debug for PartialApplyPlan<'_, T1, T2, R, E> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "partially apply \"{:?}\" to \"{:?}\"", self.plan, self.arg) - } -} - -/// The plan concatenates two underlying plans via middle value of T2 type. -pub struct SequencePlan<'a, T1, T2: 'a, R: 'a, E: 'a> { - first: Box + 'a>, - second: Box + 'a>, -} - -impl<'a, T1, T2: 'a, R: 'a, E: 'a> SequencePlan<'a, T1, T2, R, E> { - pub fn new(first: P1, second: P2) -> Self - where P1: 'a + Plan<'a, T1, T2, E>, - P2: 'a + Plan<'a, T2, R, E> { - SequencePlan{ first: Box::new(first), second: Box::new(second) } - } -} - -impl<'a, T1, T2: 'a + Debug, R: 'a, E: 'a + Debug> Plan<'a, T1, R, E> for SequencePlan<'a, T1, T2, R, E> { - fn step(self: Box, arg: T1) -> StepResult<'a, R, E> { - match self.first.step(arg) { - StepResult::Execute(next) => StepResult::execute(SequencePlan{ - first: next, - second: self.second, - }), - StepResult::Return(result) => StepResult::execute(ApplyPlan{ - arg: result, - plan: self.second, - }), - StepResult::Error(error) => StepResult::err(error), - } - } -} - -impl Debug for SequencePlan<'_, T1, T2, R, E> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{:?} then {:?}", self.first, self.second) - } -} - -/// The plan to execute two underlying plans and return a pair formed from -/// their results. -pub struct ParallelPlan<'a, T1: 'a, T2: 'a, E: 'a> { - first: Box + 'a>, - second: Box + 'a>, -} - -impl<'a, T1: 'a, T2: 'a, E: 'a> ParallelPlan<'a, T1, T2, E> { - pub fn new(first: P1, second: P2) -> Self - where P1: 'a + Plan<'a, (), T1, E>, - P2: 'a + Plan<'a, (), T2, E> { - Self{ - first: Box::new(first), - second: Box::new(second) - } - } -} - -/// Return error if any of sub-plans returned error -impl<'a, T1: 'a + Debug, T2: 'a + Debug, E: 'a + Debug> Plan<'a, (), (T1, T2), E> for ParallelPlan<'a, T1, T2, E> { - fn step(self: Box, _: ()) -> StepResult<'a, (T1, T2), E> { - match self.first.step(()) { - StepResult::Execute(next) => StepResult::execute(ParallelPlan{ - first: next, - second: self.second, - }), - StepResult::Return(first_result) => { - let descr = format!("return tuple ({:?}, ?)", first_result); - StepResult::execute(SequencePlan{ - first: self.second, - second: Box::new(OperatorPlan::new(|second_result| - StepResult::ret((first_result, second_result)), - descr)) - }) - }, - StepResult::Error(err) => StepResult::Error(err), - } - } -} - -impl Debug for ParallelPlan<'_, T1, T2, E> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{:?}\n{:?}", self.first, self.second) - } -} - -/// Trait to fold the value (typically sequence of sub-values) to the plan -/// which processes each sub-value in parallel and merges the results. -pub trait FoldIntoParallelPlan<'a, I, T, R, E> - where I: 'a + Iterator, - T: 'a, R: 'a, E: 'a { - /// Method converts the `self` value into parallel plan. It starts from - /// the `empty` return value. It applies `step` to each sub-value to get - /// a plan to calculate result. It applies `merge` to plan result and - /// step result to calculate the final result. Resulting plan is returned. - fn into_parallel_plan(self, empty: R, step: S, merge: M) -> Box + 'a> - where - S: FnMut(I::Item) -> Box + 'a>, - M: 'a + FnMut(R, T) -> R + Clone; -} - -impl<'a, I: 'a, T: 'a, R, E> FoldIntoParallelPlan<'a, I, T, R, E> for I - where I: Iterator, - T: 'a + Debug, - R: 'a + Debug, - E: 'a + Debug { - fn into_parallel_plan(self, empty: R, mut step: S, merge: M) -> Box + 'a> - where - S: FnMut(I::Item) -> Box + 'a>, - M: 'a + FnMut(R, T) -> R + Clone { - let plan: Box + 'a> = self - .fold(Box::new(StepResult::ret(empty)), - |plan, step_result| { - let mut merge = merge.clone(); - Box::new(SequencePlan::new( - ParallelPlan { - first: plan, - second: step(step_result), - }, - OperatorPlan::new(move |(plan_res, step_res)| - StepResult::ret(merge(plan_res, step_res)), - "merge_results"), - )) - } - ); - plan - } -} - -/// Plan which ignores error and returns optional result -pub struct NoErrorPlan<'a, T, R, E> { - delegate: Box + 'a>, -} - -impl<'a, T, R, E> NoErrorPlan<'a, T, R, E> { - pub fn new

(delegate: P) -> Self - where P: 'a + Plan<'a, T, R, E> { - Self{ delegate: Box::new(delegate) } - } -} - -impl<'a, T, R: 'a, E: 'a> Plan<'a, T, Option, E> for NoErrorPlan<'a, T, R, E> { - fn step(self: Box, arg: T) -> StepResult<'a, Option, E> { - match self.delegate.step(arg) { - StepResult::Execute(next) => StepResult::execute( - NoErrorPlan{ delegate: next }), - StepResult::Return(result) => StepResult::Return(Some(result)), - StepResult::Error(_) => StepResult::Return(None), - } - } -} - -impl Debug for NoErrorPlan<'_, T, R, E> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{:?}", self.delegate) - } -} - -/// Plan returns the first plan which returned non-error result -pub struct OrPlan<'a, R, E> { - first: Box + 'a>, - second: Box + 'a>, -} - -impl<'a, R, E> OrPlan<'a, R, E> { - pub fn new(first: P1, second: P2) -> Self - where P1: 'a + Plan<'a, (), R, E>, - P2: 'a + Plan<'a, (), R, E> { - Self{ - first: Box::new(first), - second: Box::new(second) - } - } -} - -impl<'a, R: 'a, E: 'a + Debug> Plan<'a, (), R, E> for OrPlan<'a, R, E> { - fn step(self: Box, _: ()) -> StepResult<'a, R, E> { - match self.first.step(()) { - StepResult::Execute(next) => StepResult::execute(OrPlan{ - first: next, - second: self.second, - }), - StepResult::Return(first_result) => StepResult::ret(first_result), - StepResult::Error(err) => { - log::debug!("OrPlan: returned second path: {:?} because of error: {:?}", self.second, err); - StepResult::execute(self.second) - }, - } - } -} - -impl Debug for OrPlan<'_, R, E> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{:?} (or {:?})", self.first, self.second) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - /// Execute the plan using given input value and return result - fn execute_plan<'a, T: Debug + 'a, R: 'a, P>(plan: P, arg: T) -> Result where P: 'a + Plan<'a, T, R, String> { - let mut step: Box> = Box::new(ApplyPlan::new(plan, arg)); - loop { - log::debug!("current plan:\n{:?}", step); - match step.step(()) { - StepResult::Execute(next) => step = next, - StepResult::Return(result) => return Ok(result), - StepResult::Error(error) => return Err(error), - } - } - } - - #[test] - fn parallel_plan() { - let mul = SequencePlan::new( - ParallelPlan::new( - StepResult::ret(7), - StepResult::ret(6)), - OperatorPlan::new(|(a, b)| StepResult::ret(a * b), "*"), - ); - assert_eq!(execute_plan(mul, ()), Ok(42)); - } - - #[test] - fn iterator_into_parallel_plan() { - let step_counter = &mut 0; - let args = vec!["1", "2", "3", "4"]; - let plan = args.iter().into_parallel_plan(Vec::new(), - |n| { - *step_counter += 1; - Box::new(ApplyPlan::new(OperatorPlan::new(|n: &str| StepResult::ret(n.parse::().unwrap() + 1), format!("* {}", n)), *n)) - }, - |mut a, b| {a.push(b); a}); - assert_eq!(execute_plan(StepResult::Execute(plan), ()), Ok(vec![2, 3, 4, 5])); - assert_eq!(*step_counter, 4); - } - - #[test] - fn step_result_plan() { - let plan = Box::new(StepResult::execute(OperatorPlan::new( - |_| StepResult::<&str, String>::ret("Successful"), "return string"))); - - let result = plan.step(()); - - if let StepResult::Return(result) = result { - assert_eq!(result, "Successful"); - } else { - assert!(false, "Immediate result is expected"); - } - - } -} - diff --git a/lib/src/metta/mod.rs b/lib/src/metta/mod.rs index bf089a947..9fd87623d 100644 --- a/lib/src/metta/mod.rs +++ b/lib/src/metta/mod.rs @@ -1,13 +1,7 @@ //! Contains MeTTa specific types, constants and functions. pub mod text; -#[cfg(feature = "old_interpreter")] -pub mod old_interpreter; -#[cfg(feature = "old_interpreter")] -pub use old_interpreter as interpreter; -#[cfg(not(feature = "old_interpreter"))] pub mod interpreter_minimal; -#[cfg(not(feature = "old_interpreter"))] pub use interpreter_minimal as interpreter; pub mod types; pub mod runner; diff --git a/lib/src/metta/old_interpreter.rs b/lib/src/metta/old_interpreter.rs deleted file mode 100644 index c451d2700..000000000 --- a/lib/src/metta/old_interpreter.rs +++ /dev/null @@ -1,1140 +0,0 @@ -//! MeTTa interpreter implementation. -//! -//! # Algorithm -//! -//! For an atom and type on input (when type is not set `Undefined` is used): -//! * [Atom::Variable] is returned as is. -//! * [Atom::Symbol] and [Atom::Grounded] are type checked; each applicable -//! type of the atom is checked vs expected type: -//! * If type is correct then atom is returned as is. -//! * If type is incorrect then error result is returned. -//! * Note: cast may return as many results as many types were casted -//! successfully, each result may have its own variable bindings if -//! types are parameterized. -//! * First atom (operation) of [Atom::Expression] is extracted and plan to -//! calculate its type is returned. When type is calculated the expression -//! is interpreted according its operation type. Note: few alternative -//! interpretations may be found here one for each type of the operation. -//! -//! For and expression atom and its operation type: -//! * If expected type is `Atom` or `Expression` then expression is returned as is. -//! * If operation type is a function: -//! * Check arity and return type of the function, if check fails then return -//! error result. -//! * Return a sequence plan which interprets each argument one by one using -//! corresponding types and calls resulting expression. If any argument -//! cannot be casted to type then error result is returned. If argument's -//! bindings are not compatible with bindings of the expression such -//! result is skipped if no options to interpret argument left then error -//! is returned. If argument returns empty value after interpretation -//! then the whole expression is not interpreted further. -//! * Note: this step may return more than one result because each -//! argument can be interpreted by more than one way. -//! * If operation type is not function: -//! * Return a sequence plan which interprets each member using -//! `Undefined` type and calls resulting expression. If member's -//! bindings are not compatible with bindings of the expression such -//! result is skipped if no options to interpret member left then error -//! is returned. -//! -//! Call the expression: -//! * If there is a cached result for this expression then return it -//! * If operation is instance of [Atom::Grounded] then operation is executed: -//! * If result is error then error is returned -//! * If result is empty then it is returned as is -//! * If result is not empty plan to interpret each alternative further is -//! returned. Note: if each alternative returns error then the result -//! of execution is also error. -//! * If operation is not [Atom::Grounded] then expression is matched. -//! Atomspace is queried for `(= $X)` and expression is replaced by $X. -//! * If no results returned then error is returned -//! * If result is not empty plan to interpret each alternative further is -//! returned. Note: if each alternative returns error then the result -//! of execution is also error. -//! * If one of previous steps returned error then original expression is -//! returned. Otherwise the result of the interpretation is returned. -//! It may be empty if one of expression is grounded expression which -//! returns empty result. -//! -//! Summary on possible results: -//! * empty result for expression is returned when grounded operation -//! returns empty result, or one of the arguments returned empty result -//! * error is returned when atom cannot be casted to the type expected -//! or all alternative interpretations are errors; the overall result includes -//! successfully interpreted alternatives only -//! * call of the expression returns either successful result or original expression - -use crate::*; -use crate::common::plan::*; -use crate::atom::subexpr::*; -use crate::atom::matcher::*; -use crate::space::*; -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 std::ops::Deref; -use std::rc::Rc; -use std::fmt::{Debug, Display, Formatter}; -use std::collections::HashSet; - -/// Wrapper, So the old interpreter can present the same public interface as the new intperpreter -pub struct InterpreterState<'a, T: SpaceRef<'a>> { - step_result: StepResult<'a, Results, InterpreterError>, - phantom: core::marker::PhantomData -} - -impl<'a, T: SpaceRef<'a>> InterpreterState<'a, T> { - - /// INTERNAL USE ONLY. Create an InterpreterState that is ready to yield results - #[cfg(feature = "old_interpreter")] - pub(crate) fn new_finished(_space: T, results: Vec) -> Self { - Self { - step_result: StepResult::Return(results.into_iter().map(|atom| InterpretedAtom(atom, Bindings::new())).collect()), - phantom: <_>::default(), - } - } - - pub fn has_next(&self) -> bool { - self.step_result.has_next() - } - pub fn into_result(self) -> Result, String> { - match self.step_result { - StepResult::Return(mut res) => { - let res = res.drain(0..).map(|res| res.into_tuple().0).collect(); - Ok(res) - }, - StepResult::Error((atom, err)) => Ok(vec![Atom::expr([ERROR_SYMBOL, atom, err])]), - StepResult::Execute(_) => Err("Evaluation is not finished".into()) - } - } -} - -impl<'a, T: SpaceRef<'a>> Debug for InterpreterState<'a, T> { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - Debug::fmt(&self.step_result, f) - } -} - -/// Result of atom interpretation plus variable bindings found -#[derive(Clone)] -#[cfg_attr(test, derive(PartialEq))] -pub struct InterpretedAtom(Atom, Bindings); - -impl InterpretedAtom { - fn atom(&self) -> &Atom { - &self.0 - } - - fn bindings(&self) -> &Bindings { - &self.1 - } - - /// Convert the instance into tuple of [Atom] and [Bindings] - pub fn into_tuple(self) -> (Atom, Bindings) { - (self.0, self.1) - } -} - -impl Display for InterpretedAtom { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - if self.1.is_empty() { - write!(f, "{}", self.0) - } else { - // TODO: it is possible to cleanup all bindings for nested - // expressions which were introduced by matching when all - // sub-expressions are interpreted. This will simplify - // textual representation. For example in test_air_humidity_regulator - // (make air wet) leads to (start kettle), {$y: kettle}) result - // but $y is not present in the expression after interpreting - // (make air wet) and can be removed. - write!(f, "{}|{}", self.0, self.1) - } - } -} - -impl Debug for InterpretedAtom { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - Display::fmt(self, f) - } -} - -type Results = Vec; -type InterpreterError = (Atom, Atom); -type NoInputPlan<'a> = Box + 'a>; - -/// Initialize interpreter and returns the result of the zero step. -/// It can be error, immediate result or interpretation plan to be executed. -/// See [crate::metta::interpreter] for algorithm explanation. -/// -/// # Arguments -/// * `space` - atomspace to query for interpretation -/// * `expr` - atom to interpret -pub fn interpret_init<'a, T: Space + 'a>(space: T, expr: &Atom) -> InterpreterState<'a, T> { - let step_result = interpret_init_internal(space, expr); - InterpreterState { step_result: step_result, phantom: <_>::default() } -} - -fn interpret_init_internal<'a, T: Space + 'a>(space: T, expr: &Atom) -> StepResult<'a, Results, InterpreterError> { - let expr = match <&[Atom]>::try_from(expr).ok() { - Some([op, atom, _typ, _space]) if *op == METTA_SYMBOL => atom, - _ => expr, - }; - let context = InterpreterContextRef::new(space); - interpret_as_type_plan(context, - InterpretedAtom(expr.clone(), Bindings::new()), - ATOM_TYPE_UNDEFINED) -} - -/// Perform next step of the interpretation plan and return the result. Panics -/// when [StepResult::Return] or [StepResult::Error] are passed as input. -/// See [crate::metta::interpreter] for algorithm explanation. -/// -/// # Arguments -/// * `step` - [StepResult::Execute] result from the previous step. -pub fn interpret_step<'a, T: Space + 'a>(step: InterpreterState<'a, T>) -> InterpreterState<'a, T> { - log::debug!("current plan:\n{:?}", step); - match step.step_result { - StepResult::Execute(plan) => InterpreterState { step_result: plan.step(()), phantom: <_>::default() }, - StepResult::Return(_) => panic!("Plan execution is finished already"), - StepResult::Error(_) => panic!("Plan execution is finished with error"), - } -} - -/// Interpret passed atom and return a new plan, result or error. This function -/// blocks until result is calculated. For step by step interpretation one -/// should use [interpret_init] and [interpret_step] functions. -/// # Arguments -/// * `space` - atomspace to query for interpretation -/// * `expr` - atom to interpret -pub fn interpret(space: T, expr: &Atom) -> Result, String> { - let mut step = interpret_init(space, expr); - while step.step_result.has_next() { - step = interpret_step(step); - } - match step.step_result { - StepResult::Return(mut result) => Ok(result.drain(0..) - .map(|InterpretedAtom(atom, _)| atom).collect()), - // TODO: return (Error atom err) expression - StepResult::Error((atom, err)) => Ok(vec![Atom::expr([ERROR_SYMBOL, atom, err])]), - _ => panic!("Not expected step result: {:?}", step), - } -} - -// TODO: ListMap is not effective but we cannot use HashMap here without -// requiring hash functions for the grounded atoms. -#[derive(Debug)] -struct InterpreterCache(ListMap); - -impl InterpreterCache { - fn new() -> Self { - Self(ListMap::new()) - } - - fn get(&self, key: &Atom) -> Option { - 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()); }); - - self.0.get(key).map(|results| { - let mut var_mapper = var_mapper.clone(); - let mut result = Vec::new(); - for res in results { - let mut atom = res.atom().clone(); - atom.iter_mut().filter_type::<&mut VariableAtom>() - .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)); - } - result - }) - } - - fn insert(&mut self, key: Atom, mut value: Results) { - value.iter_mut().for_each(|res| { - let vars: HashSet<&VariableAtom> = key.iter().filter_type::<&VariableAtom>().collect(); - res.1.apply_and_retain(&mut res.0, |v| vars.contains(v)); - }); - self.0.insert(key, value) - } - - fn reset(&mut self) { - self.0.clear(); - } -} - -impl SpaceObserver for InterpreterCache { - fn notify(&mut self, _event: &SpaceEvent) { - // TODO: implement more specific cache cleanup for each event - self.reset(); - } -} - -use std::marker::PhantomData; - -pub trait SpaceRef<'a> : Space + 'a {} -impl<'a, T: Space + 'a> SpaceRef<'a> for T {} - -struct InterpreterContext<'a, T: SpaceRef<'a>> { - space: T, - cache: SpaceObserverRef, - phantom: PhantomData<&'a T>, -} - -struct InterpreterContextRef<'a, T: SpaceRef<'a>>(Rc>); - -impl<'a, T: SpaceRef<'a>> InterpreterContextRef<'a, T> { - fn new(space: T) -> Self { - let cache = space.common().register_observer(InterpreterCache::new()); - - Self(Rc::new(InterpreterContext{ space, cache, phantom: PhantomData })) - } -} - -impl<'a, T: SpaceRef<'a>> Deref for InterpreterContextRef<'a, T> { - type Target = InterpreterContext<'a, T>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'a, T: SpaceRef<'a>> Clone for InterpreterContextRef<'a, T> { - fn clone(&self) -> Self { - Self(Rc::clone(&self.0)) - } -} - -fn is_grounded_op(expr: &ExpressionAtom) -> bool { - match expr.children().get(0) { - Some(Atom::Grounded(op)) if is_func(&op.type_()) - || op.type_() == ATOM_TYPE_UNDEFINED => true, - _ => false, - } -} - -fn is_variable_op(expr: &ExpressionAtom) -> bool { - match expr.children().get(0) { - Some(Atom::Variable(_)) => true, - Some(Atom::Expression(expr)) => is_variable_op(expr), - _ => 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>, - input: InterpretedAtom, typ: Atom) -> StepResult<'a, Results, InterpreterError> { - log::debug!("interpret_as_type_plan: input: {}, type: {}", input, typ); - match input.atom() { - - _ if typ == ATOM_TYPE_ATOM => StepResult::ret(vec![input]), - Atom::Symbol(_) if typ == ATOM_TYPE_SYMBOL => StepResult::ret(vec![input]), - Atom::Variable(_) if typ == ATOM_TYPE_VARIABLE => StepResult::ret(vec![input]), - Atom::Expression(_) if typ == ATOM_TYPE_EXPRESSION => StepResult::ret(vec![input]), - Atom::Grounded(_) if typ == ATOM_TYPE_GROUNDED => StepResult::ret(vec![input]), - - Atom::Symbol(_) | Atom::Grounded(_) => - cast_atom_to_type_plan(context, input, typ), - - Atom::Expression(ref expr) if expr.children().is_empty() => - cast_atom_to_type_plan(context, input, typ), - - Atom::Expression(ref expr) => { - let op = &expr.children()[0]; - StepResult::execute(SequencePlan::new( - get_type_of_atom_plan(context.clone(), op.clone()), - interpret_expression_as_type_plan(context, input, typ) - )) - }, - - Atom::Variable(_) => { - StepResult::ret(vec![input]) - }, - } -} - -fn cast_atom_to_type_plan<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, - input: InterpretedAtom, typ: Atom) -> StepResult<'a, Results, InterpreterError> { - // TODO: implement this via interpreting of the (:cast atom typ) expression - let typ = apply_bindings_to_atom_move(typ, input.bindings()); - let mut results = get_type_bindings(&context.space, input.atom(), &typ); - log::debug!("cast_atom_to_type_plan: type check results: {:?}", results); - if !results.is_empty() { - log::debug!("cast_atom_to_type_plan: input: {} is casted to type: {}", input, typ); - StepResult::ret(results.drain(0..).map(|(_match_typ, typ_bindings)| { - let InterpretedAtom(atom, bindings) = input.clone(); - // TODO: need to understand if it is needed to apply bindings - // should we apply bindings to bindings? - let bindings = Bindings::merge(&bindings, &typ_bindings); - if let Some(bindings) = bindings { - let atom = apply_bindings_to_atom_move(atom, &bindings); - Some(InterpretedAtom(atom, bindings)) - } else { - None - } - }).filter(Option::is_some).map(Option::unwrap).collect()) - } else { - log::debug!("cast_atom_to_type_plan: input: {} cannot be casted to type: {}", input, typ); - StepResult::err((input.0, BAD_TYPE_SYMBOL)) - } -} - -fn get_type_of_atom_plan<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, atom: Atom) -> StepResult<'a, Vec, InterpreterError> { - // TODO: implement this via interpreting of the (:? atom) - StepResult::ret(get_atom_types(&context.space, &atom)) -} - -fn interpret_expression_as_type_plan<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, - input: InterpretedAtom, typ: Atom) -> OperatorPlan<'a, Vec, Results, InterpreterError> { - let descr = format!("form alternative plans for expression {} using types", input); - OperatorPlan::new(move |op_types: Vec| { - make_alternives_plan(input.0.clone(), op_types, move |op_typ| { - interpret_expression_as_type_op(context.clone(), - input.clone(), op_typ, typ.clone()) - }) - }, descr) -} - -fn get_expr(atom: &Atom) -> &ExpressionAtom { - match atom { - Atom::Expression(expr) => expr, - _ => panic!("Atom::Expression is expected, recieved: {}", atom), - } -} - -fn get_expr_mut(atom: &mut Atom) -> &mut ExpressionAtom { - match atom { - Atom::Expression(expr) => expr, - _ => panic!("Atom::Expression is expected, recieved: {}", atom), - } -} - -fn interpret_expression_as_type_op<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, - input: InterpretedAtom, op_typ: Atom, ret_typ: Atom) -> NoInputPlan<'a> { - log::debug!("interpret_expression_as_type_op: input: {}, operation type: {}, expected return type: {}", input, op_typ, ret_typ); - if ret_typ == ATOM_TYPE_ATOM || ret_typ == ATOM_TYPE_EXPRESSION { - Box::new(StepResult::ret(vec![input])) - } else if is_func(&op_typ) { - let InterpretedAtom(input_atom, mut input_bindings) = input; - let expr = get_expr(&input_atom); - let (op_arg_types, op_ret_typ) = get_arg_types(&op_typ); - // TODO: supertypes should be checked as well - if !match_reducted_types(op_ret_typ, &ret_typ, &mut input_bindings) { - Box::new(StepResult::err((input_atom, BAD_TYPE_SYMBOL))) - } else if op_arg_types.len() != (expr.children().len() - 1) { - Box::new(StepResult::err((input_atom, INCORRECT_NUMBER_OF_ARGUMENTS_SYMBOL))) - } else { - let input = InterpretedAtom(input_atom, input_bindings); - let expr = get_expr(input.atom()); - assert!(!expr.children().is_empty(), "Empty expression is not expected"); - let mut plan: NoInputPlan = Box::new(StepResult::ret(vec![input.clone()])); - for expr_idx in 0..(expr.children().len()) { - let arg = expr.children()[expr_idx].clone(); - let arg_typ = if expr_idx > 0 { - op_arg_types[expr_idx - 1].clone() - } else { - op_typ.clone() - }; - let context = context.clone(); - plan = Box::new(SequencePlan::new( - plan, - OperatorPlan::new(move |results: Results| { - make_alternives_plan(arg.clone(), results, move |result| -> NoInputPlan { - let arg_typ = apply_bindings_to_atom_move(arg_typ.clone(), result.bindings()); - Box::new(SequencePlan::new( - interpret_as_type_plan(context.clone(), - InterpretedAtom(arg.clone(), result.bindings().clone()), - arg_typ), - insert_reducted_arg_plan(result, expr_idx))) - }) - }, format!("Interpret {} argument", expr_idx)) - )) - } - call_alternatives_plan(plan, context, input) - } - } else { - let expr = get_expr(input.atom()); - let mut plan: NoInputPlan = Box::new(StepResult::ret(vec![input.clone()])); - for expr_idx in 0..(expr.children().len()) { - let arg = expr.children()[expr_idx].clone(); - let context = context.clone(); - plan = Box::new(SequencePlan::new( - plan, - OperatorPlan::new(move |results: Results| { - make_alternives_plan(arg.clone(), results, move |result| -> NoInputPlan { - Box::new(SequencePlan::new( - interpret_as_type_plan(context.clone(), - InterpretedAtom(arg.clone(), result.bindings().clone()), - ATOM_TYPE_UNDEFINED), - insert_reducted_arg_plan(result, expr_idx))) - }) - }, format!("Interpret {} argument", expr_idx)) - )) - } - call_alternatives_plan(plan, context, input) - } -} - -fn call_alternatives_plan<'a, T: SpaceRef<'a>>(plan: NoInputPlan<'a>, context: InterpreterContextRef<'a, T>, - input: InterpretedAtom) -> NoInputPlan<'a> { - Box::new(SequencePlan::new(plan, OperatorPlan::new(move |results: Results| { - make_alternives_plan(input.0, results, move |result| { - call_plan(context.clone(), result) - }) - }, "interpret each alternative"))) -} - -fn insert_reducted_arg_plan<'a>(expr: InterpretedAtom, atom_idx: usize) -> OperatorPlan<'a, Results, Results, InterpreterError> { - let descr = format!("insert right element as child {} of left element", atom_idx); - OperatorPlan::new(move |arg_variants| insert_reducted_arg_op(expr, atom_idx, arg_variants), descr) -} - -fn insert_reducted_arg_op<'a>(expr: InterpretedAtom, atom_idx: usize, mut arg_variants: Results) -> StepResult<'a, Results, InterpreterError> { - let result = arg_variants.drain(0..).map(|arg| { - let InterpretedAtom(arg, bindings) = arg; - let mut expr_with_arg = expr.atom().clone(); - get_expr_mut(&mut expr_with_arg).children_mut()[atom_idx] = arg; - InterpretedAtom(apply_bindings_to_atom_move(expr_with_arg, &bindings), bindings) - }).collect(); - log::debug!("insert_reducted_arg_op: result: {:?}", result); - StepResult::ret(result) -} - -fn call_plan<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, input: InterpretedAtom) -> NoInputPlan<'a> { - let descr = format!("call {}", input); - Box::new(OperatorPlan::new(|_| call_op(context, input), descr)) -} - -fn call_op<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, input: InterpretedAtom) -> StepResult<'a, Results, InterpreterError> { - log::debug!("call_op: {}", input); - - let cached = context.cache.borrow().get(input.atom()); - if let Some(result) = cached { - let result = result.into_iter().flat_map(|InterpretedAtom(atom, bindings)| { - bindings.merge_v2(input.bindings()).into_iter() - .map(move |b| InterpretedAtom(atom.clone(), b)) - }).collect(); - return_cached_result_plan(result) - } else { - if let Atom::Expression(expr) = input.atom() { - if !has_grounded_sub_expr(expr) { - let key = input.atom().clone(); - StepResult::execute(SequencePlan::new( - OrPlan::new( - interpret_reducted_plan(context.clone(), input.clone()), - StepResult::ret(vec![input])), - save_result_in_cache_plan(context, key) - )) - } else { - StepResult::execute(OrPlan::new( - interpret_reducted_plan(context.clone(), input.clone()), - StepResult::ret(vec![input]))) - } - } else { - panic!("Only expressions are expected to be called"); - } - } -} - -fn return_cached_result_plan<'a>(results: Results) -> StepResult<'a, Results, InterpreterError> { - let descr = format!("return cached results {:?}", results); - StepResult::execute(OperatorPlan::new(|_| StepResult::ret(results), descr)) -} - -fn save_result_in_cache_plan<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, key: Atom) -> OperatorPlan<'a, Results, Results, InterpreterError> { - let descr = format!("save results in cache for key {}", key); - OperatorPlan::new(move |results: Results| { - context.cache.borrow_mut().insert(key, results.clone()); - StepResult::ret(results) - }, descr) -} - -fn interpret_reducted_plan<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, - input: InterpretedAtom) -> NoInputPlan<'a> { - 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)) - } - } else { - panic!("Only expression is expected, received: {}", input); - } -} - - -fn execute_plan<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, input: InterpretedAtom) -> OperatorPlan<'a, (), Results, InterpreterError> { - let descr = format!("execute {}", input); - OperatorPlan::new(|_| execute_op(context, input), descr) -} - -fn execute_op<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, input: InterpretedAtom) -> StepResult<'a, Results, InterpreterError> { - log::debug!("execute_op: {}", input); - match input { - InterpretedAtom(Atom::Expression(ref expr), ref bindings) => { - let op = expr.children().get(0); - if let Some(Atom::Grounded(op)) = op { - let args = expr.children(); - match op.as_grounded().as_execute() { - None => StepResult::err((input.0, NOT_REDUCIBLE_SYMBOL)), - Some(executable) => { - match executable.execute(&args[1..]) { - Ok(mut vec) => { - let results: Vec = vec.drain(0..) - .map(|atom| InterpretedAtom(atom, bindings.clone())) - .collect(); - if results.is_empty() { - StepResult::ret(results) - } else { - make_alternives_plan(input.0, results, move |result| { - interpret_as_type_plan(context.clone(), - result, ATOM_TYPE_UNDEFINED) - }) - } - }, - Err(ExecError::Runtime(msg)) => StepResult::ret(vec![InterpretedAtom( - Atom::expr([ERROR_SYMBOL, input.0, Atom::sym(msg)]), input.1)]), - Err(ExecError::NoReduce) => StepResult::err((input.0, NOT_REDUCIBLE_SYMBOL)), - } - }, - } - } else { - panic!("Trying to execute non grounded atom: {}", expr) - } - }, - _ => panic!("Unexpected non expression argument: {}", input), - } -} - -fn match_plan<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, input: InterpretedAtom) -> OperatorPlan<'a, (), Results, InterpreterError> { - let descr = format!("match {}", input); - OperatorPlan::new(|_| match_op(context, input), descr) -} - -fn match_op<'a, T: SpaceRef<'a>>(context: InterpreterContextRef<'a, T>, input: InterpretedAtom) -> StepResult<'a, Results, InterpreterError> { - log::debug!("match_op: {}", input); - let var_x = VariableAtom::new("X").make_unique(); - let query = Atom::expr(vec![EQUAL_SYMBOL, input.atom().clone(), Atom::Variable(var_x.clone())]); - let mut query_bindings = context.space.query(&query); - let results: Vec = query_bindings - .drain(0..) - .map(|query_binding| { - let result = apply_bindings_to_atom_move(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. - let bindings = apply_bindings_to_bindings(&query_binding, input.bindings()); - let bindings = bindings.and_then(|bindings| { - Bindings::merge(&query_binding, &bindings).ok_or(()) - }); - log::debug!("match_op: query: {}, bindings: {:?}, result: {}", input, bindings, result); - (result, bindings) - }) - .filter(|(_, bindings)| bindings.is_ok()) - .map(|(result, bindings)| InterpretedAtom(result, bindings.unwrap())) - .collect(); - make_alternives_plan(input.0, results, move |result| { - interpret_as_type_plan(context.clone(), result, ATOM_TYPE_UNDEFINED) - }) -} - -fn make_alternives_plan<'a, T: Debug, F, P>(input: Atom, mut results: Vec, - plan: F) -> StepResult<'a, Results, InterpreterError> -where - F: 'a + Fn(T) -> P, - P: 'a + Plan<'a, (), Results, InterpreterError> -{ - log::debug!("make_alternives_plan: input: {:?}, alternatives: {:?}", input, results); - match results.len() { - 0 => StepResult::err((input, NO_VALID_ALTERNATIVES)), - 1 => StepResult::execute(plan(results.pop().unwrap())), - _ => { - StepResult::execute(AlternativeInterpretationsPlan::new( - input, - results.drain(0..) - .map(|result| -> NoInputPlan { Box::new(plan(result)) }) - .collect())) - }, - } -} - -use std::collections::VecDeque; - -/// Plan which interprets in parallel alternatives of the expression. -/// Each successful result is appended to the overall result of the plan. -/// If no alternatives returned successful result the plan returns error. -pub struct AlternativeInterpretationsPlan<'a, T> { - atom: Atom, - plans: VecDeque, InterpreterError> + 'a>>, - results: Vec, - success: bool, -} - -impl<'a, T> AlternativeInterpretationsPlan<'a, T> { - /// Create new instance of [AlternativeInterpretationsPlan]. - /// - /// # Arguments - /// `atom` - atom to be printed as root of the alternative interpretations - /// `plan` - altenative plans for the atom - pub fn new(atom: Atom, plans: Vec, InterpreterError> + 'a>>) -> Self { - Self{ atom, plans: plans.into(), results: Vec::new(), success: false } - } -} - -impl<'a, T: Debug> Plan<'a, (), Vec, InterpreterError> for AlternativeInterpretationsPlan<'a, T> { - fn step(mut self: Box, _: ()) -> StepResult<'a, Vec, InterpreterError> { - log::debug!("AlternativeInterpretationsPlan::step: {} alternatives left", self.plans.len()); - if self.plans.len() == 0 { - if self.success { - StepResult::ret(self.results) - } else { - StepResult::err((self.atom, NO_VALID_ALTERNATIVES)) - } - } else { - let plan = self.plans.pop_front().unwrap(); - match plan.step(()) { - StepResult::Execute(next) => { - self.plans.push_front(next); - StepResult::Execute(self) - }, - StepResult::Return(mut result) => { - self.results.append(&mut result); - self.success = true; - StepResult::Execute(self) - }, - StepResult::Error(err) => { - log::debug!("skip alternative because of error returned: {:?}", err); - StepResult::Execute(self) - }, - } - } - } -} - -impl Debug for AlternativeInterpretationsPlan<'_, T> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let mut res = write!(f, "interpret alternatives for {} (current results: {:?}):\n", self.atom, self.results); - for (i, plan) in self.plans.iter().enumerate() { - let plan_str = format!("{:?}", plan); - let mut lines = plan_str.lines(); - res = res.and_then(|_| write!(f, " {} {}\n", - if i == 0 { ">" } else { "-" }, lines.next().unwrap())); - for line in lines { - res = res.and_then(|_| write!(f, " {}\n", line)); - } - } - res - } -} - - -#[cfg(test)] -mod tests { - use super::*; - use crate::common::*; - use crate::common::test_utils::*; - - #[test] - fn test_match_all() { - let mut space = GroundingSpace::new(); - space.add(expr!("=" ("color") "blue")); - space.add(expr!("=" ("color") "red")); - space.add(expr!("=" ("color") "green")); - let expr = expr!(("color")); - - assert_eq_no_order!(interpret(&space, &expr).unwrap(), - vec![expr!("blue"), expr!("red"), expr!("green")]); - } - - #[test] - fn test_frog_reasoning() { - let mut space = GroundingSpace::new(); - 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!("=" ("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!("=" ("frog" "Fritz") "True")])); - } - - fn results_are_equivalent(actual: &Result, String>, - expected: &Result, String>) -> bool { - match (actual, expected) { - (Ok(actual), Ok(expected)) => - actual.len() == expected.len() && - actual.iter().zip(expected.iter()).all(|(actual, expected)| { - atoms_are_equivalent(actual, expected) }), - (Err(actual), Err(expected)) => actual == expected, - _ => false, - } - } - - #[test] - fn test_variable_keeps_value_in_different_sub_expressions() { - let mut space = GroundingSpace::new(); - space.add(expr!("=" ("eq" x x) "True")); - space.add(expr!("=" ("plus" "Z" y) y)); - space.add(expr!("=" ("plus" ("S" k) y) ("S" ("plus" k y)))); - - assert_eq!(interpret(&space, &expr!("eq" ("plus" "Z" n) n)), - Ok(vec![expr!("True")])); - let actual = interpret(&space, &expr!("eq" ("plus" ("S" "Z") n) n)); - let expected = Ok(vec![expr!("eq" ("S" y) y)]); - assert!(results_are_equivalent(&actual, &expected), - "actual: {:?} and expected: {:?} are not equivalent", actual, expected); - } - - fn test_interpret<'a, T, R: 'a, P: Plan<'a, T, R, InterpreterError> + 'a>(plan: P, arg: T) -> Result { - let mut step = Box::new(plan).step(arg); - loop { - match step { - StepResult::Execute(plan) => step = plan.step(()), - StepResult::Return(result) => return Ok(result), - StepResult::Error(err) => return Err(err), - } - } - } - - #[test] - fn test_make_alternatives_plan_no_alternative() { - let plan = make_alternives_plan(sym!("Test"), - vec![], |_res: InterpretedAtom| StepResult::ret(vec![])); - - let result = test_interpret(plan, ()); - - assert_eq!(Err((sym!("Test"), NO_VALID_ALTERNATIVES)), result); - } - - #[test] - fn test_alternatives_plan_single_alternative() { - let plan = AlternativeInterpretationsPlan::new(sym!("Test"), - vec![Box::new(StepResult::ret(vec!["A", "B"]))]); - - let result = test_interpret(plan, ()); - - assert_eq!(Ok(vec!["A", "B"]), result); - } - - #[test] - fn test_alternatives_plan_few_alternatives() { - let plan = AlternativeInterpretationsPlan::new(sym!("Test"), - vec![Box::new(StepResult::ret(vec!["A", "B"])), - Box::new(StepResult::ret(vec!["C", "D"]))]); - - let result = test_interpret(plan, ()); - - assert_eq!(Ok(vec!["A", "B", "C", "D"]), result); - } - - #[test] - fn test_alternatives_plan_error_present() { - let plan = AlternativeInterpretationsPlan::new(sym!("Test"), - vec![Box::new(StepResult::err((sym!("Test"), BAD_TYPE_SYMBOL))), - Box::new(StepResult::ret(vec!["C", "D"]))]); - - let result = test_interpret(plan, ()); - - assert_eq!(Ok(vec!["C", "D"]), result); - } - - #[test] - fn test_alternatives_plan_only_errors() { - let plan: AlternativeInterpretationsPlan<&'static str> = - AlternativeInterpretationsPlan::new(sym!("Test"), - vec![Box::new(StepResult::err((sym!("Test"), sym!("Expected error")))), - Box::new(StepResult::err((sym!("Test"), sym!("Another expected error"))))]); - - let result = test_interpret(plan, ()); - - assert_eq!(Err((sym!("Test"), NO_VALID_ALTERNATIVES)), result); - } - - #[test] - fn test_variable_defined_via_variable() { - let mut space = GroundingSpace::new(); - space.add(expr!("=" ("if" "True" y) y)); - space.add(expr!("=" ("not" "False") "True")); - space.add(expr!("=" ("a" z) ("not" ("b" z)))); - space.add(expr!("=" ("b" "d") "False")); - let expr = expr!("if" ("a" x) x); - - assert_eq!(interpret(&space, &expr), Ok(vec![expr!("d")])); - } - - #[test] - fn test_variable_name_conflict() { - let mut space = GroundingSpace::new(); - space.add(expr!("=" ("a" (W)) {true})); - let expr = expr!("a" W); - - assert_eq!(interpret(&space, &expr), Ok(vec![expr!({true})])); - } - - #[test] - fn test_variable_name_conflict_renaming() { - let space = metta_space(" - (= (b ($x $y)) (c $x $y)) - "); - let expr = metta_atom("(a (b $a) $x $y)"); - - let result = interpret(&space, &expr); - - assert!(results_are_equivalent(&result, - &Ok(vec![metta_atom("(a (c $a $b) $c $d)")]))); - } - - #[derive(PartialEq, Clone, Debug)] - struct ThrowError(); - - impl Grounded for ThrowError { - fn type_(&self) -> Atom { - expr!("->" "&str" "Error") - } - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for ThrowError { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - Err((*args[0].as_gnd::<&str>().unwrap()).into()) - } - } - - impl Display for ThrowError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "throw-error") - } - } - - #[test] - fn test_return_runtime_error_from_grounded_atom() { - let space = GroundingSpace::new(); - let expr = Atom::expr([Atom::gnd(ThrowError()), Atom::value("Runtime test error")]); - - assert_eq!(interpret(&space, &expr), - Ok(vec![Atom::expr([ERROR_SYMBOL, expr, Atom::sym("Runtime test error")])])); - } - - #[derive(PartialEq, Clone, Debug)] - struct NonReducible(); - - impl Grounded for NonReducible { - fn type_(&self) -> Atom { - expr!("->" "&str" "u32") - } - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for NonReducible { - fn execute(&self, _args: &[Atom]) -> Result, ExecError> { - Err(ExecError::NoReduce) - } - } - - impl Display for NonReducible { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "non-reducible") - } - } - - #[test] - fn test_execute_non_reducible_atom() { - let space = GroundingSpace::new(); - let expr = Atom::expr([Atom::gnd(NonReducible()), Atom::value("32")]); - - assert_eq!(interpret(&space, &expr), Ok(vec![expr])); - } - - #[test] - fn test_interpret_empty_expression() { - let space = GroundingSpace::new(); - let expr = Atom::expr([]); - - assert_eq!(interpret(&space, &expr), Ok(vec![expr])); - } - - #[test] - fn test_interpret_non_executable_grounded_atom() { - let space = GroundingSpace::new(); - let expr = Atom::expr([Atom::value(1)]); - - assert_eq!(interpret(&space, &expr), Ok(vec![expr])); - } - - #[derive(PartialEq, Clone, Debug)] - struct MulXUndefinedType(i32); - - impl Grounded for MulXUndefinedType { - fn type_(&self) -> Atom { - ATOM_TYPE_UNDEFINED - } - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for MulXUndefinedType { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - Ok(vec![Atom::value(self.0 * args.get(0).unwrap().as_gnd::().unwrap())]) - } - } - - impl Display for MulXUndefinedType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "x{}", self.0) - } - } - - #[test] - fn test_interpret_undefined_grounded_atom() { - let space = GroundingSpace::new(); - let expr = expr!({MulXUndefinedType(3)} {2}); - - assert_eq!(interpret(&space, &expr), Ok(vec![Atom::value(6)])); - } - - static ID_NUM: &Operation = &Operation{ - name: "id_num", - execute: |_, args| { - let arg_error = || ExecError::from("id_num expects one argument: number"); - let num = args.get(0).ok_or_else(arg_error)?; - Ok(vec![num.clone()]) - }, - typ: "(-> Number Number)", - }; - - #[test] - fn return_bad_type_error() { - let mut space = GroundingSpace::new(); - space.add(expr!(":" "myAtom" "myType")); - space.add(expr!(":" "id_a" ("->" "A" "A"))); - space.add(expr!("=" ("id_a" a) a)); - - assert_eq!(interpret(&space, &expr!({ID_NUM} "myAtom")), - Ok(vec![Atom::expr([ERROR_SYMBOL, sym!("myAtom"), BAD_TYPE_SYMBOL])])); - assert_eq!(interpret(&space, &expr!("id_a" "myAtom")), - Ok(vec![Atom::expr([ERROR_SYMBOL, sym!("myAtom"), BAD_TYPE_SYMBOL])])); - } - - #[test] - fn operation_is_expression() { - let mut space = GroundingSpace::new(); - space.add(expr!(":" "foo" ("->" ("->" "A" "A")))); - space.add(expr!(":" "a" "A")); - space.add(expr!("=" ("foo") "bar")); - space.add(expr!("=" ("bar" x) x)); - - assert_eq!(interpret(&space, &expr!(("foo") "a")), Ok(vec![expr!("a")])); - } - - #[test] - fn interpreter_cache_variables_are_not_changed_when_atom_was_not_transformed() { - let mut cache = InterpreterCache::new(); - cache.insert(expr!("P" x), vec![InterpretedAtom(expr!("P" x), bind!{})]); - assert_eq!(cache.get(&expr!("P" x)), Some(vec![InterpretedAtom(expr!("P" x), bind!{})])); - } - - #[test] - fn interpreter_cache_only_same_variables_are_matched() { - let mut cache = InterpreterCache::new(); - cache.insert(expr!("P" x), vec![InterpretedAtom(expr!("P" x), bind!{})]); - assert_eq!(cache.get(&expr!("P" y)), None); - } - - #[test] - fn interpreter_cache_variables_from_result_are_applied() { - let mut cache = InterpreterCache::new(); - cache.insert(expr!("foo" "a"), vec![InterpretedAtom(expr!("P" x), bind!{ x: expr!("a") })]); - assert_eq!(cache.get(&expr!("foo" "a")), Some(vec![InterpretedAtom(expr!("P" "a"), bind!{})])); - } - - #[test] - fn interpreter_cache_variables_from_key_are_kept_unique() { - let mut cache = InterpreterCache::new(); - cache.insert(expr!("bar" x), vec![InterpretedAtom(expr!("P" x), bind!{})]); - assert_eq!(cache.get(&expr!("bar" x)), Some(vec![InterpretedAtom(expr!("P" x), bind!{})])); - } - - #[test] - fn interpreter_cache_variables_absent_in_key_are_removed() { - let mut cache = InterpreterCache::new(); - cache.insert(expr!("foo" x), vec![InterpretedAtom(expr!("bar"), bind!{ x: expr!("a"), y: expr!("Y") })]); - assert_eq!(cache.get(&expr!("foo" x)), Some(vec![InterpretedAtom(expr!("bar"), bind!{ x: expr!("a") })])); - } - - #[test] - fn interpreter_cache_variables_from_result_becom_unique() { - let mut cache = InterpreterCache::new(); - cache.insert(expr!(("bar")), vec![InterpretedAtom(expr!("P" x), bind!{})]); - if let Some(results) = cache.get(&expr!(("bar"))) { - assert_eq!(results.len(), 1); - assert!(atoms_are_equivalent(results[0].atom(), &expr!("P" x))); - assert_eq!(*results[0].bindings(), bind!{}); - } else { - panic!("Non-empty result is expected"); - } - } - - #[test] - fn interpreter_cache_returns_variable_from_bindings() { - let mut cache = InterpreterCache::new(); - cache.insert(expr!("bar" x), vec![InterpretedAtom(expr!(y), bind!{ x: expr!("A" y)})]); - if let Some(mut results) = cache.get(&expr!("bar" x)) { - let InterpretedAtom(atom, bindings) = results.pop().unwrap(); - let value = bindings.resolve(&VariableAtom::new("x")).unwrap(); - assert_eq!(Atom::expr([sym!("A"), atom]), value); - } else { - 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")]); - } - - #[cfg(not(feature = "variable_operation"))] - #[test] - fn interpret_match_variable_operation_nested() { - let mut space = GroundingSpace::new(); - space.add(expr!("=" (("baz" x) y) ("baz result" x y))); - let actual = interpret(&space, &expr!((op "arg1") "arg2")).unwrap(); - assert_eq!(actual, vec![expr!((op "arg1") "arg2")]); - } -} - diff --git a/lib/src/metta/runner/builtin_mods/catalog_mods.rs b/lib/src/metta/runner/builtin_mods/catalog_mods.rs index 0a1f92d6c..e46a8b211 100644 --- a/lib/src/metta/runner/builtin_mods/catalog_mods.rs +++ b/lib/src/metta/runner/builtin_mods/catalog_mods.rs @@ -3,7 +3,7 @@ use crate::space::grounding::GroundingSpace; use crate::metta::{ARROW_SYMBOL, ATOM_TYPE_SYMBOL, UNIT_TYPE}; use crate::metta::runner::{Metta, ModuleLoader, RunContext, DynSpace}; use crate::metta::runner::pkg_mgmt::{UpdateMode, ManagedCatalog}; -use crate::metta::runner::stdlib::{regex, unit_result}; +use crate::metta::runner::stdlib_minimal::{regex, unit_result}; //DISCUSSION: We want to expose more of the pkg_mgmt / catalog system to MeTTa through programmatic // interfaces, but the details are unclear. Most importantly, the use cases are unclear, and those diff --git a/lib/src/metta/runner/mod.rs b/lib/src/metta/runner/mod.rs index 842718cdf..587784242 100644 --- a/lib/src/metta/runner/mod.rs +++ b/lib/src/metta/runner/mod.rs @@ -86,15 +86,13 @@ mod environment; pub use environment::{Environment, EnvBuilder}; #[macro_use] -pub mod stdlib; +pub mod stdlib_old; use super::interpreter::{interpret, interpret_init, interpret_step, InterpreterState}; -#[cfg(not(feature = "old_interpreter"))] pub mod stdlib_minimal; -#[cfg(not(feature = "old_interpreter"))] use stdlib_minimal::*; -use stdlib::CoreLibLoader; +use stdlib_old::CoreLibLoader; mod builtin_mods; use builtin_mods::*; @@ -440,7 +438,6 @@ impl Metta { } pub fn evaluate_atom(&self, atom: Atom) -> Result, String> { - #[cfg(not(feature = "old_interpreter"))] let atom = if is_bare_minimal_interpreter(self) { atom } else { @@ -1061,7 +1058,6 @@ impl<'input> RunContext<'_, '_, 'input> { let type_err_exp = Atom::expr([ERROR_SYMBOL, atom, BAD_TYPE_SYMBOL]); self.i_wrapper.interpreter_state = Some(InterpreterState::new_finished(self.module().space().clone(), vec![type_err_exp])); } else { - #[cfg(not(feature = "old_interpreter"))] let atom = if is_bare_minimal_interpreter(self.metta) { atom } else { @@ -1087,7 +1083,6 @@ impl<'input> RunContext<'_, '_, 'input> { } -#[cfg(not(feature = "old_interpreter"))] fn is_bare_minimal_interpreter(metta: &Metta) -> bool { metta.get_setting_string("interpreter") == Some("bare-minimal".into()) } @@ -1180,7 +1175,6 @@ impl<'i> InputStream<'i> { } } -#[cfg(not(feature = "old_interpreter"))] fn wrap_atom_by_metta_interpreter(space: DynSpace, atom: Atom) -> Atom { let space = Atom::gnd(space); let interpret = Atom::expr([METTA_SYMBOL, atom, ATOM_TYPE_UNDEFINED, space]); diff --git a/lib/src/metta/runner/modules/mod.rs b/lib/src/metta/runner/modules/mod.rs index e52295140..f17628526 100644 --- a/lib/src/metta/runner/modules/mod.rs +++ b/lib/src/metta/runner/modules/mod.rs @@ -7,12 +7,7 @@ use crate::metta::runner::*; use regex::Regex; -#[cfg(feature = "old_interpreter")] -use super::stdlib::*; - -#[cfg(not(feature = "old_interpreter"))] use super::interpreter_minimal::interpret; -#[cfg(not(feature = "old_interpreter"))] use super::stdlib_minimal::*; mod mod_names; diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta deleted file mode 100644 index c68b8022b..000000000 --- a/lib/src/metta/runner/stdlib.metta +++ /dev/null @@ -1,771 +0,0 @@ -(@doc = - (@desc "A symbol used to define reduction rules for expressions.") - (@params ( - (@param "Pattern to be matched against expression to be reduced") - (@param "Result of reduction or transformation of the first pattern"))) - (@return "Not reduced itself unless custom equalities over equalities are added") ) -(: = (-> $t $t Atom)) - -(@doc if - (@desc "Replace itself by one of the arguments depending on condition.") - (@params ( - (@param "Boolean condition") - (@param "Result when condition is True") - (@param "Result when condition is False"))) - (@return "Second or third argument") ) -(: if (-> Bool Atom Atom $t)) -(= (if True $then $else) $then) -(= (if False $then $else) $else) - -(@doc ErrorType (@desc "Type of the atom which contains error")) -(: ErrorType Type) - -(@doc Error - (@desc "Error constructor") - (@params ( - (@param "Atom which contains error") - (@param "Error message, can be one of the reserved symbols: BadType, IncorrectNumberOfArguments"))) - (@return "Error atom")) -(: Error (-> Atom Atom ErrorType)) - -(@doc add-reduct - (@desc "Adds atom into the atomspace reducing it first") - (@params ( - (@param "Atomspace to add atom into") - (@param "Atom to add"))) - (@return "Unit atom")) -(: add-reduct (-> hyperon::space::DynSpace %Undefined% (->))) -(= (add-reduct $dst $atom) (add-atom $dst $atom)) - -(@doc quote - (@desc "Prevents atom from being reduced") - (@params ( - (@param "Atom"))) - (@return "Quoted atom")) -(: quote (-> Atom Atom)) - -(@doc unify - (@desc "Matches two first arguments and returns third argument if they are matched and forth argument otherwise") - (@params ( - (@param "First atom to unify with") - (@param "Second atom to unify with") - (@param "Result if two atoms unified successfully") - (@param "Result otherwise"))) - (@return "Third argument when first two atoms are matched of forth one otherwise")) -(: unify (-> Atom Atom Atom Atom %Undefined%)) -(= (unify $a $a $then $else) $then) -(= (unify $a $b $then $else) - (case (unify-or-empty $a $b) ((Empty $else))) ) -(: unify-or-empty (-> Atom Atom Atom)) -(= (unify-or-empty $a $a) unified) -(= (unify-or-empty $a $b) (empty)) - -(@doc empty - (@desc "Cuts evaluation of the non-deterministic branch and removes it from the result") - (@params ()) - (@return "Nothing")) -(: empty (-> %Undefined%)) -(= (empty) (let a b never-happens)) - -(@doc unique - (@desc "Function takes non-deterministic input (first argument) and returns only unique entities. E.g. (unique (superpose (a b c d d))) -> [a, b, c, d]") - (@params ( - (@param "Non-deterministic set of values"))) - (@return "Unique values from input set")) -(: unique (-> Atom Atom)) -(= (unique $arg) (let $c (collapse $arg) (let $u (unique-atom $c) (superpose $u)))) - -(@doc union - (@desc "Function takes two non-deterministic inputs (first and second argument) and returns their union. E.g. (union (superpose (a b b c)) (superpose (b c c d))) -> [a, b, b, c, b, c, c, d]") - (@params ( - (@param "Non-deterministic set of values") - (@param "Another non-deterministic set of values"))) - (@return "Union of sets")) -(: union (-> Atom Atom Atom)) -(= (union $arg1 $arg2) - (let $c1 (collapse $arg1) (let $c2 (collapse $arg2) - (let $u (union-atom $c1 $c2) (superpose $u))))) - -(@doc intersection - (@desc "Function takes two non-deterministic inputs (first and second argument) and returns their intersection. E.g. (intersection (superpose (a b c c)) (superpose (b c c c d))) -> [b, c, c]") - (@params ( - (@param "Non-deterministic set of values") - (@param "Another non-deterministic set of values"))) - (@return "Intersection of sets")) -(: intersection (-> Atom Atom Atom)) -(= (intersection $arg1 $arg2) - (let $c1 (collapse $arg1) (let $c2 (collapse $arg2) - (let $u (intersection-atom $c1 $c2) (superpose $u))))) - -(@doc subtraction - (@desc "Function takes two non-deterministic inputs (first and second argument) and returns their subtraction. E.g. !(subtraction (superpose (a b b c)) (superpose (b c c d))) -> [a, b]") - (@params ( - (@param "Non-deterministic set of values") - (@param "Another non-deterministic set of values"))) - (@return "Subtraction of sets")) -(: subtraction (-> Atom Atom Atom)) -(= (subtraction $arg1 $arg2) - (let $c1 (collapse $arg1) (let $c2 (collapse $arg2) - (let $u (subtraction-atom $c1 $c2) (superpose $u))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Documentation formatting functions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(@doc @doc - (@desc "Used for documentation purposes. Function documentation starts with @doc") - (@params ( - (@param "Function name") - (@param "Function description. Starts with @desc") - (@param "(Optional) parameters description starting with @params which should contain one or more @param symbols") - (@param "(Optional) description of what function will return. Starts with @return"))) - (@return "Function documentation using @doc-formal")) -(: @doc (-> Atom DocDescription DocInformal)) -(: @doc (-> Atom DocDescription DocParameters DocReturnInformal DocInformal)) - -(@doc @desc - (@desc "Used for documentation purposes. Description of function starts with @desc as a part of @doc") - (@params ( - (@param "String containing function description"))) - (@return "Function description")) -(: @desc (-> String DocDescription)) - -; TODO: help! for @param and @return returns duplicating result -;(@doc @param -; (@desc "Used for documentation purposes. Description of function parameter starts with @param as a part of @params which is a part of @doc") -; (@params ( -; (@param "String containing parameter description"))) -; (@return "Parameter description")) -(: @param (-> String DocParameterInformal)) -(: @param (-> DocType DocDescription DocParameter)) - -;(@doc @return -; (@desc "Used for documentation purposes. Description of function return value starts with @return as a part of @doc") -; (@params ( -; (@param "String containing return value description"))) -; (@return "Return value description")) -(: @return (-> String DocReturnInformal)) -(: @return (-> DocType DocDescription DocReturn)) - -(@doc @doc-formal - (@desc "Used for documentation purposes. get-doc returns documentation starting with @doc-formal symbol. @doc-formal contains 6 or 4 parameters depending on the entity being described (functions being described using 6 parameters, atoms - 4 parameters)") - (@params ( - (@param "Function/Atom name for which documentation is to be displayed. Format (@item name)") - (@param "Contains (@kind function) or (@kind atom) depends on entity which documentation is displayed") - (@param "Contains type notation of function/atom") - (@param "Function/atom description") - (@param "(Functions only). Description of function parameters") - (@param "(Functions only). Description of function's return value"))) - (@return "Expression containing full documentation on function")) -(: @doc-formal (-> DocItem DocKindFunction DocType DocDescription DocParameters DocReturn DocFormal)) -(: @doc-formal (-> DocItem DocKindAtom DocType DocDescription DocFormal)) - -(@doc @item - (@desc "Used for documentation purposes. Converts atom/function's name to DocItem") - (@params ( - (@param "Atom/Function name to be documented"))) - (@return "(@item Atom) entity")) -(: @item (-> Atom DocItem)) - -(@doc (@kind function) - (@desc "Used for documentation purposes. Shows type of entity to be documented. (@kind function) in this case")) -(: (@kind function) DocKindFunction) - -(@doc (@kind atom) - (@desc "Used for documentation purposes. Shows type of entity to be documented. (@kind atom) in this case")) -(: (@kind atom) DocKindAtom) - -(@doc @type - (@desc "Used for documentation purposes. Converts atom/function's type to DocType") - (@params ( - (@param "Atom/Function type to be documented"))) - (@return "(@type Type) entity")) -(: @type (-> Type DocType)) - -(@doc @params - (@desc "Used for function documentation purposes. Contains several @param entities with description of each @param") - (@params ( - (@param "Several (@param ...) entities"))) - (@return "DocParameters containing description of all parameters of function in form of (@params ((@param ...) (@param ...) ...))")) -(: @params (-> Expression DocParameters)) - -(@doc get-doc - (@desc "Returns documentation for the given Atom/Function") - (@params ( - (@param "Atom/Function name for which documentation is needed"))) - (@return "Documentation for the given atom/function")) -(: get-doc (-> Atom Atom)) -(= (get-doc $atom) - (let $meta-type (get-metatype $atom) - (case $meta-type ( - (Expression (get-doc-atom $atom)) - ($_ (get-doc-single-atom $atom)) )))) - -(@doc get-doc-single-atom - (@desc "Function used by get-doc to get documentation on either function or atom. It checks if input name is the name of function or atom and calls correspondent function") - (@params ( - (@param "Atom/Function name for which documentation is needed"))) - (@return "Documentation for the given atom/function")) -(: get-doc-single-atom (-> Atom Atom)) -(= (get-doc-single-atom $atom) - (let $top-space (mod-space! top) - (let $type (get-type-space $top-space $atom) - (if (is-function-type $type) - (get-doc-function $atom $type) - (get-doc-atom $atom))))) - -(@doc get-doc-function - (@desc "Function used by get-doc-single-atom to get documentation on a function. It returns documentation on a function if it exists or default documentation with no description otherwise") - (@params ( - (@param "Function name for which documentation is needed") - (@param "Type notation for this function"))) - (@return "Documentation for the given function")) -(: get-doc-function (-> Atom Type Atom)) -(= (get-doc-function $name $type) - (let $top-space (mod-space! top) - (unify $top-space (@doc $name $desc (@params $params) $ret) - (let $type' (if (== $type %Undefined%) (undefined-doc-function-type $params) (cdr-atom $type)) - (let ($params' $ret') (get-doc-params $params $ret $type') - (@doc-formal (@item $name) (@kind function) (@type $type) $desc (@params $params') $ret'))) - (@doc-formal (@item $name) (@kind function) (@type $type) (@desc "No documentation")) ))) - -(@doc undefined-doc-function-type - (@desc "Function used by get-doc-single-atom in case of absence of function's type notation") - (@params ( - (@param "List of parameters for the function we want to get documentation for"))) - (@return "List of %Undefined% number of which depends on input list size. So for two parameters function will return (%Undefined% %Undefined% %Undefined%)")) -(: undefined-doc-function-type (-> Expression Type)) -(= (undefined-doc-function-type $params) - (if (== () $params) (%Undefined%) - (let $params-tail (cdr-atom $params) - (let $tail (undefined-doc-function-type $params-tail) - (cons-atom %Undefined% $tail) )))) - -(@doc get-doc-params - (@desc "Function used by get-doc-function to get function's parameters documentation (including return value)") - (@params ( - (@param "List of parameters in form of ((@param Description) (@param Description)...)") - (@param "Return value's description in form of (@return Description)") - (@param "Type notation without -> starting symbol e.g. (Atom Atom Atom)"))) - (@return "United list of params and return value each augmented with its type. E.g. (((@param (@type Atom) (@desc Description)) (@param (@type Atom) (@desc Description2))) (@return (@type Atom) (@desc Description)))")) -(: get-doc-params (-> Expression Atom Expression (Expression Atom))) -(= (get-doc-params $params $ret $types) - (let $head-type (car-atom $types) - (let $tail-types (cdr-atom $types) - (if (== () $params) - (let (@return $ret-desc) $ret - (() (@return (@type $head-type) (@desc $ret-desc))) ) - (let (@param $param-desc) (car-atom $params) - (let $tail-params (cdr-atom $params) - (let ($params' $result-ret) (get-doc-params $tail-params $ret $tail-types) - (let $result-params (cons-atom (@param (@type $head-type) (@desc $param-desc)) $params') - ($result-params $result-ret) )))))))) - -(@doc get-doc-atom - (@desc "Function used by get-doc (in case of input type Expression) and get-doc-single-atom (in case input value is not a function) to get documentation on input value") - (@params ( - (@param "Atom's name to get documentation for"))) - (@return "Documentation on input Atom")) -(: get-doc-atom (-> Atom Atom)) -(= (get-doc-atom $atom) - (let $top-space (mod-space! top) - (let $type (get-type-space $top-space $atom) - (unify $top-space (@doc $atom $desc) - (@doc-formal (@item $atom) (@kind atom) (@type $type) $desc) - (unify $top-space (@doc $atom $desc' (@params $params) $ret) - (get-doc-function $atom %Undefined%) - (@doc-formal (@item $atom) (@kind atom) (@type $type) (@desc "No documentation")) ))))) - -; TODO: Type is used here, but there is no definition for the -> type -; constructor for instance, thus in practice it matches because -> has -; %Undefined% type. We need to assign proper type to -> and other type -; constructors but it is not possible until we support vararg types. -(@doc is-function-type - (@desc "Function checks if input type is a function type") - (@params ( - (@param "Type notation"))) - (@return "True if input type notation is a function type, False - otherwise")) -(: is-function-type (-> Type Bool)) -(= (is-function-type $type) - (let $type-meta (get-metatype $type) - (case $type-meta ( - (Expression - (let $first (car-atom $type) - (if (== $first ->) True False) )) - ($_ False) )))) - -(@doc help! - (@desc "Function prints documentation for the input atom.") - (@params ( - (@param "Input to get documentation for"))) - (@return "Unit atom")) -(: help! (-> Atom (->))) -(= (help! $atom) - (case (get-doc $atom) ( - ((@doc-formal (@item $item) (@kind function) (@type $type) (@desc $descr) - (@params $params) - (@return (@type $ret-type) (@desc $ret-desc))) - (let () (println! (format-args "Function {}: {} {}" ($item $type $descr))) - (let () (println! (format-args "Parameters:" ())) - (let () (for-each-in-atom $params help-param!) - (let () (println! (format-args "Return: (type {}) {}" ($ret-type $ret-desc))) - () ))))) - ((@doc-formal (@item $item) (@kind function) (@type $type) (@desc $descr)) - (let () (println! (format-args "Function {} (type {}) {}" ($item $type $descr))) - () )) - ((@doc-formal (@item $item) (@kind atom) (@type $type) (@desc $descr)) - (let () (println! (format-args "Atom {}: {} {}" ($item $type $descr))) - () )) - ($other (Error $other "Cannot match @doc-formal structure") )))) - -(@doc help-param! - (@desc "Function used by function help! to output parameters using println!") - (@params ( - (@param "Parameters list"))) - (@return "Unit atom")) -(: help-param! (-> Atom (->))) -(= (help-param! $param) - (let (@param (@type $type) (@desc $desc)) $param - (println! (format-args " {} {}" ((type $type) $desc))) )) - -(@doc for-each-in-atom - (@desc "Applies function passed as a second argument to each atom inside first argument") - (@params ( - (@param "Expression to each atom in which function will be applied") - (@param "Function to apply"))) - (@return "Unit atom")) -(: for-each-in-atom (-> Expression Atom (->))) -(= (for-each-in-atom $expr $func) - (if (noreduce-eq $expr ()) - () - (let $head (car-atom $expr) - (let $tail (cdr-atom $expr) - (let $_ ($func $head) - (for-each-in-atom $tail $func) ))))) - -(@doc noreduce-eq - (@desc "Checks equality of two atoms without reducing them") - (@params ( - (@param "First atom") - (@param "Second atom"))) - (@return "True if not reduced atoms are equal, False - otherwise")) -(: noreduce-eq (-> Atom Atom Bool)) -(= (noreduce-eq $a $b) (== (quote $a) (quote $b))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Grounded function's documentation -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(@doc add-atom - (@desc "Adds atom into the atomspace without reducing it") - (@params ( - (@param "Atomspace to add atom into") - (@param "Atom to add"))) - (@return "Unit atom")) - -(@doc match - (@desc "Searches for all declared atoms corresponding to the given pattern (second argument) and produces the output pattern (third argument)") - (@params ( - (@param "A grounded atom referencing a Space") - (@param "Pattern atom to be matched") - (@param "Output pattern typically containing variables from the input pattern"))) - (@return "If match was successfull it outputs pattern (third argument) with filled variables (if any were present in pattern) using matched pattern (second argument). Nothing - otherwise")) - -(@doc bind! - (@desc "Registers a new token which is replaced with an atom during the parsing of the rest of the program") - (@params ( - (@param "Token name") - (@param "Atom, which is associated with the token after reduction"))) - (@return "Unit atom")) - -(@doc new-space - (@desc "Creates new Atomspace which could be used further in the program as a separate from &self Atomspace") - (@params ()) - (@return "Reference to a new space")) - -(@doc remove-atom - (@desc "Removes atom from the input Atomspace") - (@params ( - (@param "Reference to the space from which the Atom needs to be removed") - (@param "Atom to be removed"))) - (@return "Unit atom")) - -(@doc get-atoms - (@desc "Shows all atoms in the input Atomspace") - (@params ( - (@param "Reference to the space"))) - (@return "List of all atoms in the input space")) - -(@doc car-atom - (@desc "Extracts the first atom of an expression as a tuple") - (@params ( - (@param "Expression"))) - (@return "First atom of an expression")) - -(@doc cdr-atom - (@desc "Extracts the tail of an expression (all except first atom)") - (@params ( - (@param "Expression"))) - (@return "Tail of an expression")) - -(@doc cons-atom - (@desc "Constructs an expression using two arguments") - (@params ( - (@param "Head of an expression") - (@param "Tail of an expression"))) - (@return "New expression consists of two input arguments")) - -(@doc min-atom - (@desc "Returns atom with min value in the expression (first argument). Only numbers allowed") - (@params ( - (@param "Expression which contains atoms of Number type"))) - (@return "Min value in the expression. Error if expression contains non-numeric value or is empty")) - -(@doc max-atom - (@desc "Returns atom with max value in the expression (first argument). Only numbers allowed") - (@params ( - (@param "Expression which contains atoms of Number type"))) - (@return "Max value in the expression. Error if expression contains non-numeric value or is empty")) - -(@doc size-atom - (@desc "Returns size of an expression (first argument)") - (@params ( - (@param "Expression"))) - (@return "Size of an expression")) - -(@doc index-atom - (@desc "Returns atom from an expression (first argument) using index (second argument) or error if index is out of bounds") - (@params ( - (@param "Expression") - (@param "Index"))) - (@return "Atom from an expression in the place defined by index. Error if index is out of bounds of an expression")) - -(@doc random-int - (@desc "Returns random int number from range defined by two numbers (first and second argument)") - (@params ( - (@param "Range start") - (@param "Range end"))) - (@return "Random int number from defined range")) - -(@doc random-float - (@desc "Returns random float number from range defined by two numbers (first and second argument)") - (@params ( - (@param "Range start") - (@param "Range end"))) - (@return "Random float number from defined range")) - -(@doc println! - (@desc "Prints a line of text to the console") - (@params ( - (@param "Expression/atom to be printed out"))) - (@return "Unit atom")) - -(@doc format-args - (@desc "Fills {} symbols in the input expression with atoms from the second expression. E.g. (format-args (Probability of {} is {}%) (head 50)) gives [(Probability of head is 50%)]. Atoms in the second input value could be variables") - (@params ( - (@param "Expression with {} symbols to be replaced") - (@param "Atoms to be placed inside expression instead of {}"))) - (@return "Expression with replaced {} with atoms")) - -(@doc trace! - (@desc "Prints its first argument and returns second. Both arguments will be evaluated before processing") - (@params ( - (@param "Atom to print") - (@param "Atom to return"))) - (@return "Evaluated second input")) - -(@doc nop - (@desc "Outputs unit atom for any input") - (@params ( - (@param "Anything"))) - (@return "Unit atom")) - -(@doc let - (@desc "Let function is utilized to establish temporary variable bindings within an expression. It allows introducing variables (first argument), assign values to them (second argument), and then use these values within the scope of the let block") - (@params ( - (@param "Variable name (or several variables inside brackets ())") - (@param "Expression to be bound to variable (it is being reduced before bind)") - (@param "Expression which will be reduced and in which variable (first argument) could be used"))) - (@return "Result of third argument's evaluation")) - -(@doc let* - (@desc "Same as let, but first argument is a tuple containing tuples of variables and their bindings, e.g. (($v (+ 1 2)) ($v2 (* 5 6)))") - (@params ( - (@param "Tuple of tuples with variables and their bindings") - (@param "Expression which will be reduced and in which variable (first argument) could be used"))) - (@return "Result of second argument's evaluation")) - -(@doc new-state - (@desc "Creates a new state atom wrapping its argument") - (@params ( - (@param "Atom to be wrapped"))) - (@return "Returns (State $value) where $value is an argument to a new-state")) - -(@doc change-state! - (@desc "Changes input state's wrapped atom to another value (second argument). E.g. (change-state! (State 5) 6) -> (State 6)") - (@params ( - (@param "State created by new-state function") - (@param "Atom which will replace wrapped atom in the input state"))) - (@return "State with replaced wrapped atom")) - -(@doc get-state - (@desc "Gets a state as an argument and returns its wrapped atom. E.g. (get-state (State 5)) -> 5") - (@params ( - (@param "State"))) - (@return "Atom wrapped by state")) - -(@doc get-metatype - (@desc "Returns metatype of the input atom") - (@params ( - (@param "Atom to get metatype for"))) - (@return "Metatype of input atom")) - -(@doc register-module! - (@desc "Takes a file system path (first argument) and loads the module into the runner") - (@params ( - (@param "File system path"))) - (@return "Unit atom")) - -(@doc mod-space! - (@desc "Returns the space of the module (first argument) and tries to load the module if it is not loaded into the module system") - (@params ( - (@param "Module name"))) - (@return "Space name")) - -(@doc print-mods! - (@desc "Prints all modules with their correspondent spaces") - (@params ()) - (@return "Unit atom")) - -(@doc sealed - (@desc "Replaces all occurrences of any var from var list (first argument) inside atom (second argument) by unique variable. Can be used to create a locally scoped variables") - (@params ( - (@param "Variable list e.g. ($x $y)") - (@param "Atom which uses those variables"))) - (@return "Second argument but with variables being replaced with unique variables")) - -(@doc capture - (@desc "Wraps an atom and capture the current space") - (@params ( - (@param "Function name which space need to be captured"))) - (@return "Function")) - -(@doc case - (@desc "Subsequently tests multiple pattern-matching conditions (second argument) for the given value (first argument)") - (@params ( - (@param "Atom (it will be evaluated)") - (@param "Tuple of pairs mapping condition patterns to results"))) - (@return "Result of evaluating of Atom bound to met condition")) - -(@doc assertEqual - (@desc "Compares (sets of) results of evaluation of two expressions") - (@params ( - (@param "First expression") - (@param "Second expression"))) - (@return "Unit atom if both expression after evaluation is equal, error - otherwise")) - -(@doc assertEqualToResult - (@desc "Same as assertEqual but it doesn't evaluate second argument. Second argument is considered as a set of values of the first argument's evaluation") - (@params ( - (@param "First expression (it will be evaluated)") - (@param "Second expression (it won't be evaluated)"))) - (@return "Unit atom if both expression after evaluation is equal, error - otherwise")) - -(@doc collapse - (@desc "Converts a nondeterministic result into a tuple") - (@params ( - (@param "Atom which will be evaluated"))) - (@return "Tuple")) - -(@doc superpose - (@desc "Turns a tuple (first argument) into a nondeterministic result") - (@params ( - (@param "Tuple to be converted"))) - (@return "Argument converted to nondeterministic result")) - -(@doc get-type - (@desc "Returns type notation of input atom") - (@params ( - (@param "Atom to get type for"))) - (@return "Type notation or %Undefined% if there is no type for input Atom")) - -(@doc get-type-space - (@desc "Returns type notation of input Atom (second argument) relative to a specified atomspace (first argument)") - (@params ( - (@param "Atomspace where type notation for input atom will be searched") - (@param "Atom to get type for"))) - (@return "Type notation or %Undefined% if there is no type for input Atom in provided atomspace")) - -(@doc import! - (@desc "Imports module using its relative path (second argument) and binds it to the token (first argument) which will represent imported atomspace. If first argument is &self then everything will be imported to current atomspace") - (@params ( - (@param "Symbol, which is turned into the token for accessing the imported module") - (@param "Module name"))) - (@return "Unit atom")) - -(@doc include - (@desc "Works just like import! but with &self as a first argument. So everything from input file will be included in the current atomspace and evaluated") - (@params ( - (@param "Name of metta script to import"))) - (@return "Unit atom")) - -(@doc pragma! - (@desc "Changes global key's (first argument) value to a new one (second argument)") - (@params ( - (@param "Key's name") - (@param "New value"))) - (@return "Unit atom")) - -; TODO: Segmentation fault (core dumped) when calling !(help &self) -;(@doc &self -; (@desc "Returns reference to the current atomspace") -; (@params ()) -; (@return "Reference to the current atomspace")) - -; TODO: get-doc/help! not working for + -(@doc + - (@desc "Sums two numbers") - (@params ( - (@param "Addend") - (@param "Augend"))) - (@return "Sum")) - -; TODO: get-doc/help! not working for - -(@doc - - (@desc "Subtracts second argument from first one") - (@params ( - (@param "Minuend") - (@param "Deductible"))) - (@return "Difference")) - -; TODO: get-doc/help! not working for * -(@doc * - (@desc "Multiplies two numbers") - (@params ( - (@param "Multiplier") - (@param "Multiplicand"))) - (@return "Product")) - -; TODO: get-doc/help! not working for / -(@doc / - (@desc "Divides first argument by second one") - (@params ( - (@param "Dividend") - (@param "Divisor"))) - (@return "Fraction")) - -; TODO: get-doc/help! not working for % -(@doc % - (@desc "Modulo operator. It returns remainder of dividing first argument by second argument") - (@params ( - (@param "Dividend") - (@param "Divisor"))) - (@return "Remainder")) - -; TODO: get-doc/help! not working for < -(@doc < - (@desc "Less than. Checks if first argument is less than second one") - (@params ( - (@param "First number") - (@param "Second number"))) - (@return "True if first argument is less than second, False - otherwise")) - -; TODO: get-doc/help! not working for > -(@doc > - (@desc "Greater than. Checks if first argument is greater than second one") - (@params ( - (@param "First number") - (@param "Second number"))) - (@return "True if first argument is greater than second, False - otherwise")) - -; TODO: get-doc/help! not working for <= -(@doc <= - (@desc "Less than or equal. Checks if first argument is less than or equal to second one") - (@params ( - (@param "First number") - (@param "Second number"))) - (@return "True if first argument is less than or equal to second, False - otherwise")) - -; TODO: get-doc/help! not working for >= -(@doc >= - (@desc "Greater than or equal. Checks if first argument is greater than or equal to second one") - (@params ( - (@param "First number") - (@param "Second number"))) - (@return "True if first argument is greater than or equal to second, False - otherwise")) - -; TODO: get-doc/help! not working for == -(@doc == - (@desc "Checks equality for two arguments of the same type") - (@params ( - (@param "First argument") - (@param "Second argument"))) - (@return "Returns True if two arguments are equal, False - otherwise. If arguments are of different type function returns Error currently")) - -; TODO: get-doc/help! not working for and -(@doc and - (@desc "Logical conjunction of two arguments") - (@params ( - (@param "First argument") - (@param "Second argument"))) - (@return "Returns True if both arguments are True, False - otherwise")) - -; TODO: get-doc/help! not working for or -(@doc or - (@desc "Logical disjunction of two arguments") - (@params ( - (@param "First argument") - (@param "Second argument"))) - (@return "True if any of input arguments is True, False - otherwise")) - -; TODO: get-doc/help! not working for not -(@doc not - (@desc "Negation") - (@params ( - (@param "Argument"))) - (@return "Negates boolean input argument (False -> True, True -> False)")) - -(@doc xor - (@desc "Exclusive disjunction of two arguments") - (@params ( - (@param "First argument") - (@param "Second argument"))) - (@return "Return values are the same as logical disjunction, but when both arguments are True xor will return False")) - -(@doc flip - (@desc "Produces random boolean value") - (@params ()) - (@return "Random boolean value")) - -(@doc unique-atom - (@desc "Function takes tuple and returns only unique entities. E.g. (unique-atom (a b c d d)) -> (a b c d)") - (@params ( - (@param "List of values"))) - (@return "Unique values from input set")) - -(@doc union-atom - (@desc "Function takes two tuples and returns their union. E.g. (union-atom (a b b c) (b c c d)) -> (a b b c b c c d)") - (@params ( - (@param "List of values") - (@param "List of values"))) - (@return "Union of sets")) - -(@doc intersection-atom - (@desc "Function takes two tuples and returns their intersection. E.g. (intersection-atom (a b c c) (b c c c d)) -> (b c c)") - (@params ( - (@param "List of values") - (@param "List of values"))) - (@return "Intersection of sets")) - -(@doc subtraction-atom - (@desc "Function takes two tuples and returns their subtraction. E.g. !(subtraction-atom (a b b c) (b c c d)) -> (a b)") - (@params ( - (@param "List of values") - (@param "List of values"))) - (@return "Subtraction of sets")) - -(@doc git-module! - (@desc "Provides access to module in a remote git repo, from within MeTTa code. Similar to `register-module!`, this op will bypass the catalog search") - (@params ( - (@param "URL to github repo"))) - (@return "Unit atom")) diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 043bebbcb..53ec170be 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -6,18 +6,22 @@ use crate::metta::runner::Metta; use crate::metta::types::get_atom_types; use crate::common::assert::vec_eq_no_order; use crate::common::shared::Shared; -use crate::metta::runner::stdlib; -use crate::metta::runner::stdlib::regex; +use crate::metta::runner::stdlib_old; use std::convert::TryInto; +use regex::Regex; use super::arithmetics::*; use super::string::*; -fn unit_result() -> Result, ExecError> { +pub(crate) fn unit_result() -> Result, ExecError> { Ok(vec![UNIT_ATOM()]) } +pub(crate) fn regex(regex: &str) -> Regex { + Regex::new(regex).unwrap() +} + #[derive(Clone, Debug)] pub struct PrintAlternativesOp {} @@ -145,7 +149,7 @@ fn interpret_no_error(space: DynSpace, expr: &Atom) -> Result, String> fn interpret(space: DynSpace, expr: &Atom) -> Result, String> { let expr = Atom::expr([METTA_SYMBOL, expr.clone(), ATOM_TYPE_UNDEFINED, Atom::gnd(space.clone())]); - let result = crate::metta::interpreter::interpret(space, &expr); + let result = crate::metta::interpreter_minimal::interpret(space, &expr); result } @@ -408,57 +412,57 @@ pub fn register_common_tokens(tref: &mut Tokenizer, _tokenizer: Shared tref.register_token(regex(r"case"), move |_| { case_op.clone() }); let capture_op = Atom::gnd(CaptureOp::new(space.clone())); tref.register_token(regex(r"capture"), move |_| { capture_op.clone() }); - let pragma_op = Atom::gnd(stdlib::PragmaOp::new(metta.settings().clone())); + let pragma_op = Atom::gnd(stdlib_old::PragmaOp::new(metta.settings().clone())); tref.register_token(regex(r"pragma!"), move |_| { pragma_op.clone() }); - let import_op = Atom::gnd(stdlib::ImportOp::new(metta.clone())); + let import_op = Atom::gnd(stdlib_old::ImportOp::new(metta.clone())); tref.register_token(regex(r"import!"), move |_| { import_op.clone() }); - let include_op = Atom::gnd(stdlib::IncludeOp::new(metta.clone())); + let include_op = Atom::gnd(stdlib_old::IncludeOp::new(metta.clone())); tref.register_token(regex(r"include"), move |_| { include_op.clone() }); - let bind_op = Atom::gnd(stdlib::BindOp::new(tokenizer.clone())); + let bind_op = Atom::gnd(stdlib_old::BindOp::new(tokenizer.clone())); tref.register_token(regex(r"bind!"), move |_| { bind_op.clone() }); - let trace_op = Atom::gnd(stdlib::TraceOp{}); + let trace_op = Atom::gnd(stdlib_old::TraceOp{}); tref.register_token(regex(r"trace!"), move |_| { trace_op.clone() }); - let println_op = Atom::gnd(stdlib::PrintlnOp{}); + let println_op = Atom::gnd(stdlib_old::PrintlnOp{}); tref.register_token(regex(r"println!"), move |_| { println_op.clone() }); - let format_args_op = Atom::gnd(stdlib::FormatArgsOp{}); + let format_args_op = Atom::gnd(stdlib_old::FormatArgsOp{}); tref.register_token(regex(r"format-args"), move |_| { format_args_op.clone() }); let print_alternatives_op = Atom::gnd(PrintAlternativesOp{}); tref.register_token(regex(r"print-alternatives!"), move |_| { print_alternatives_op.clone() }); - let sealed_op = Atom::gnd(stdlib::SealedOp{}); + let sealed_op = Atom::gnd(stdlib_old::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. @@ -538,7 +542,7 @@ pub fn register_rust_stdlib_tokens(target: &mut Tokenizer) { tref.register_token(regex(r"<="), move |_| { le_op.clone() }); let ge_op = Atom::gnd(GreaterEqOp{}); tref.register_token(regex(r">="), move |_| { ge_op.clone() }); - let eq_op = Atom::gnd(stdlib::EqualOp{}); + let eq_op = Atom::gnd(stdlib_old::EqualOp{}); tref.register_token(regex(r"=="), move |_| { eq_op.clone() }); let and_op = Atom::gnd(AndOp{}); tref.register_token(regex(r"and"), move |_| { and_op.clone() }); @@ -636,29 +640,29 @@ mod tests { #[test] fn metta_min_atom() { assert_eq!(run_program(&format!("!(min-atom (5 4 5.5))")), Ok(vec![vec![expr!({Number::Integer(4)})]])); - assert_eq!(run_program(&format!("!(min-atom ())")), Ok(vec![vec![expr!("Error" ({ stdlib::MinAtomOp{} } ()) "Empty expression")]])); - assert_eq!(run_program(&format!("!(min-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ stdlib::MinAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers are allowed in expression")]])); + assert_eq!(run_program(&format!("!(min-atom ())")), Ok(vec![vec![expr!("Error" ({ stdlib_old::MinAtomOp{} } ()) "Empty expression")]])); + assert_eq!(run_program(&format!("!(min-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ stdlib_old::MinAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers are allowed in expression")]])); } #[test] fn metta_max_atom() { assert_eq!(run_program(&format!("!(max-atom (5 4 5.5))")), Ok(vec![vec![expr!({Number::Float(5.5)})]])); - assert_eq!(run_program(&format!("!(max-atom ())")), Ok(vec![vec![expr!("Error" ({ stdlib::MaxAtomOp{} } ()) "Empty expression")]])); - assert_eq!(run_program(&format!("!(max-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ stdlib::MaxAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers are allowed in expression")]])); + assert_eq!(run_program(&format!("!(max-atom ())")), Ok(vec![vec![expr!("Error" ({ stdlib_old::MaxAtomOp{} } ()) "Empty expression")]])); + assert_eq!(run_program(&format!("!(max-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ stdlib_old::MaxAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers are allowed in expression")]])); } #[test] fn metta_index_atom() { assert_eq!(run_program(&format!("!(index-atom (5 4 3 2 1) 2)")), Ok(vec![vec![expr!({Number::Integer(3)})]])); - assert_eq!(run_program(&format!("!(index-atom (A B C D E) 5)")), Ok(vec![vec![expr!("Error" ({ stdlib::IndexAtomOp{} } ("A" "B" "C" "D" "E") {Number::Integer(5)}) "Index is out of bounds")]])); + assert_eq!(run_program(&format!("!(index-atom (A B C D E) 5)")), Ok(vec![vec![expr!("Error" ({ stdlib_old::IndexAtomOp{} } ("A" "B" "C" "D" "E") {Number::Integer(5)}) "Index is out of bounds")]])); } #[test] fn metta_random() { assert_eq!(run_program(&format!("!(chain (eval (random-int 0 5)) $rint (and (>= $rint 0) (< $rint 5)))")), Ok(vec![vec![expr!({Bool(true)})]])); - assert_eq!(run_program(&format!("!(random-int 0 0)")), Ok(vec![vec![expr!("Error" ({ stdlib::RandomIntOp{} } {Number::Integer(0)} {Number::Integer(0)}) "Range is empty")]])); + assert_eq!(run_program(&format!("!(random-int 0 0)")), Ok(vec![vec![expr!("Error" ({ stdlib_old::RandomIntOp{} } {Number::Integer(0)} {Number::Integer(0)}) "Range is empty")]])); assert_eq!(run_program(&format!("!(chain (eval (random-float 0.0 5.0)) $rfloat (and (>= $rfloat 0.0) (< $rfloat 5.0)))")), Ok(vec![vec![expr!({Bool(true)})]])); - assert_eq!(run_program(&format!("!(random-float 0 -5)")), Ok(vec![vec![expr!("Error" ({ stdlib::RandomFloatOp{} } {Number::Integer(0)} {Number::Integer(-5)}) "Range is empty")]])); + assert_eq!(run_program(&format!("!(random-float 0 -5)")), Ok(vec![vec![expr!("Error" ({ stdlib_old::RandomFloatOp{} } {Number::Integer(0)} {Number::Integer(-5)}) "Range is empty")]])); } #[test] diff --git a/lib/src/metta/runner/stdlib.rs b/lib/src/metta/runner/stdlib_old.rs similarity index 67% rename from lib/src/metta/runner/stdlib.rs rename to lib/src/metta/runner/stdlib_old.rs index 89bceebe2..aa5910bbb 100644 --- a/lib/src/metta/runner/stdlib.rs +++ b/lib/src/metta/runner/stdlib_old.rs @@ -6,8 +6,6 @@ use crate::metta::text::SExprParser; use crate::metta::runner::{Metta, RunContext, ModuleLoader, ResourceKey}; use crate::metta::runner::string::Str; use crate::metta::types::{get_atom_types, get_meta_type}; -#[cfg(feature = "old_interpreter")] -use crate::metta::interpreter::interpret; use crate::common::shared::Shared; use crate::common::CachingMapper; use crate::common::multitrie::MultiTrie; @@ -42,26 +40,6 @@ macro_rules! grounded_op { } } -pub(crate) fn unit_result() -> Result, ExecError> { - Ok(vec![UNIT_ATOM()]) -} - -pub(crate) fn regex(regex: &str) -> Regex { - Regex::new(regex).unwrap() -} - -// TODO: remove hiding errors completely after making it possible passing -// them to the user -#[cfg(feature = "old_interpreter")] -fn interpret_no_error(space: DynSpace, expr: &Atom) -> Result, String> { - let result = interpret(space, expr); - log::debug!("interpret_no_error: interpretation expr: {}, result {:?}", expr, result); - match result { - Ok(result) => Ok(result), - Err(_) => Ok(vec![]), - } -} - #[derive(Clone, Debug)] pub struct ImportOp { //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP @@ -1344,7 +1322,6 @@ impl CustomExecute for SubtractionAtomOp { } } - //TODO: In the current version of rand it is possible for rust to hang if range end's value is too // big. In future releases (0.9+) of rand signature of sample_single will be changed and it will be // possible to use match construction to cover overflow and other errors. So after library will be @@ -1408,667 +1385,7 @@ impl CustomExecute for RandomFloatOp { } } -/// The internal `non_minimal_only_stdlib` module contains code that is never used by the minimal stdlib -#[cfg(feature = "old_interpreter")] -mod non_minimal_only_stdlib { - use std::collections::HashSet; - use super::*; - use crate::common::assert::vec_eq_no_order; - - // TODO: move it into hyperon::atom module? - pub(crate) fn atom_as_expr(atom: &Atom) -> Option<&ExpressionAtom> { - match atom { - Atom::Expression(expr) => Some(expr), - _ => None, - } - } - - #[derive(Clone, Debug)] - pub struct CarAtomOp {} - - grounded_op!(CarAtomOp, "car-atom"); - - impl Grounded for CarAtomOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_UNDEFINED]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for CarAtomOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("car-atom expects one argument: expression"); - let expr = args.get(0).ok_or_else(arg_error)?; - let chld = atom_as_expr(expr).ok_or_else(arg_error)?.children(); - let car = chld.get(0).ok_or("car-atom expects non-empty expression")?; - Ok(vec![car.clone()]) - } - } - - #[derive(Clone, Debug)] - pub struct CdrAtomOp {} - - grounded_op!(CdrAtomOp, "cdr-atom"); - - impl Grounded for CdrAtomOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_UNDEFINED]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for CdrAtomOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("cdr-atom expects one argument: expression"); - let expr = args.get(0).ok_or_else(arg_error)?; - let chld = atom_as_expr(expr).ok_or_else(arg_error)?.children(); - if chld.len() == 0 { - Err(ExecError::Runtime("cdr-atom expects non-empty expression".into())) - } else { - let cdr = Vec::from_iter(chld[1..].iter().cloned()); - Ok(vec![Atom::expr(cdr)]) - } - } - } - - #[derive(Clone, Debug)] - pub struct ConsAtomOp {} - - grounded_op!(ConsAtomOp, "cons-atom"); - - impl Grounded for ConsAtomOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for ConsAtomOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("cons-atom expects two arguments: atom and expression"); - let atom = args.get(0).ok_or_else(arg_error)?; - let expr = args.get(1).ok_or_else(arg_error)?; - let chld = atom_as_expr(expr).ok_or_else(arg_error)?.children(); - let mut res = vec![atom.clone()]; - res.extend(chld.clone()); - Ok(vec![Atom::expr(res)]) - } - } - - #[derive(Clone, Debug)] - pub struct CaptureOp { - space: DynSpace, - } - - grounded_op!(CaptureOp, "capture"); - - impl CaptureOp { - pub fn new(space: DynSpace) -> Self { - Self{ space } - } - } - - impl Grounded for CaptureOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for CaptureOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("capture expects one argument"); - let atom = args.get(0).ok_or_else(arg_error)?; - interpret_no_error(self.space.clone(), &atom).map_err(|e| ExecError::from(e)) - } - } - - #[derive(Clone, Debug)] - pub struct CaseOp { - space: DynSpace, - } - - grounded_op!(CaseOp, "case"); - - impl CaseOp { - pub fn new(space: DynSpace) -> Self { - Self{ space } - } - - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("case expects two arguments: atom and expression of cases"); - let cases = args.get(1).ok_or_else(arg_error)?; - let atom = args.get(0).ok_or_else(arg_error)?; - let cases = CaseOp::parse_cases(atom, cases.clone())?; - log::debug!("CaseOp::execute: atom: {}, cases: {:?}", atom, cases); - - let result = interpret_no_error(self.space.clone(), &atom); - log::debug!("CaseOp::execute: interpretation result {:?}", result); - - match result { - Ok(result) if result.is_empty() => { - cases.into_iter() - .find_map(|(pattern, template, _external_vars)| { - if pattern == EMPTY_SYMBOL { - Some(template) - } else { - None - } - }) - .map_or(Ok(vec![]), |result| Ok(vec![result])) - }, - Ok(result) => { - let triggered = result.into_iter() - .flat_map(|atom| CaseOp::return_first_matched(&atom, &cases)) - .collect(); - Ok(triggered) - }, - Err(message) => Err(format!("Error: {}", message).into()), - } - } - - fn parse_cases(atom: &Atom, cases: Atom) -> Result)>, ExecError> { - let cases = match cases { - Atom::Expression(expr) => Ok(expr), - _ => Err("case expects expression of cases as a second argument"), - }?; - - let mut atom_vars = HashSet::new(); - collect_vars(&atom, &mut atom_vars); - - let mut result = Vec::new(); - for next_case in cases.into_children() { - let mut next_case = match next_case { - Atom::Expression(next_case) if next_case.children().len() == 2 => Ok(next_case.into_children()), - _ => Err("case expects expression of pairs as a second argument"), - }?; - let mut template = next_case.pop().unwrap(); - let mut pattern = next_case.pop().unwrap(); - - let mut external_vars = atom_vars.clone(); - collect_vars(&template, &mut external_vars); - make_conflicting_vars_unique(&mut pattern, &mut template, &external_vars); - - result.push((pattern, template, external_vars)); - } - - Ok(result) - } - - fn return_first_matched(atom: &Atom, cases: &Vec<(Atom, Atom, HashSet)>) -> Vec { - for (pattern, template, external_vars) in cases { - let bindings = matcher::match_atoms(atom, &pattern) - .map(|b| b.convert_var_equalities_to_bindings(&external_vars)); - let result: Vec = bindings.map(|b| matcher::apply_bindings_to_atom_move(template.clone(), &b)).collect(); - if !result.is_empty() { - return result - } - } - return vec![] - } - } - - impl Grounded for CaseOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for CaseOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - CaseOp::execute(self, args) - } - } - - fn assert_results_equal(actual: &Vec, expected: &Vec, atom: &Atom) -> Result, ExecError> { - log::debug!("assert_results_equal: actual: {:?}, expected: {:?}, actual atom: {:?}", actual, expected, atom); - let report = format!("\nExpected: {:?}\nGot: {:?}", expected, actual); - match vec_eq_no_order(actual.iter(), expected.iter()) { - Ok(()) => unit_result(), - Err(diff) => Err(ExecError::Runtime(format!("{}\n{}", report, diff))) - } - } - - #[derive(Clone, Debug)] - pub struct AssertEqualOp { - space: DynSpace, - } - - grounded_op!(AssertEqualOp, "assertEqual"); - - impl AssertEqualOp { - pub fn new(space: DynSpace) -> Self { - Self{ space } - } - } - - impl Grounded for AssertEqualOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for AssertEqualOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("assertEqual expects two atoms as arguments: actual and expected"); - let actual_atom = args.get(0).ok_or_else(arg_error)?; - let expected_atom = args.get(1).ok_or_else(arg_error)?; - - let actual = interpret_no_error(self.space.clone(), actual_atom)?; - let expected = interpret_no_error(self.space.clone(), expected_atom)?; - - assert_results_equal(&actual, &expected, actual_atom) - } - } - - #[derive(Clone, Debug)] - pub struct AssertEqualToResultOp { - space: DynSpace, - } - - grounded_op!(AssertEqualToResultOp, "assertEqualToResult"); - - impl AssertEqualToResultOp { - pub fn new(space: DynSpace) -> Self { - Self{ space } - } - } - - impl Grounded for AssertEqualToResultOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for AssertEqualToResultOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("assertEqualToResult expects two atoms as arguments: actual and expected"); - let actual_atom = args.get(0).ok_or_else(arg_error)?; - let expected = atom_as_expr(args.get(1).ok_or_else(arg_error)?) - .ok_or("assertEqualToResult expects expression of results as a second argument")? - .children(); - - let actual = interpret_no_error(self.space.clone(), actual_atom)?; - - assert_results_equal(&actual, expected, actual_atom) - } - } - - #[derive(Clone, Debug)] - pub struct CollapseOp { - space: DynSpace, - } - - grounded_op!(CollapseOp, "collapse"); - - impl CollapseOp { - pub fn new(space: DynSpace) -> Self { - Self{ space } - } - } - - impl Grounded for CollapseOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for CollapseOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("collapse expects single executable atom as an argument"); - let atom = args.get(0).ok_or_else(arg_error)?; - - // TODO: Calling interpreter inside the operation is not too good - // Could it be done via StepResult? - let result = interpret_no_error(self.space.clone(), atom)?; - - Ok(vec![Atom::expr(result)]) - } - } - - #[derive(Clone, Debug)] - pub struct SuperposeOp { - pub(crate) space: DynSpace, - } - - grounded_op!(SuperposeOp, "superpose"); - - impl SuperposeOp { - pub fn new(space: DynSpace) -> Self { - Self{ space } - } - } - - impl Grounded for SuperposeOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_UNDEFINED]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for SuperposeOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("superpose expects single expression as an argument"); - let atom = args.get(0).ok_or_else(arg_error)?; - let expr = atom_as_expr(&atom).ok_or(arg_error())?; - - let mut superposed = Vec::new(); - for atom in expr.children() { - match interpret_no_error(self.space.clone(), atom) { - Ok(results) => { superposed.extend(results); }, - Err(message) => { return Err(format!("Error: {}", message).into()) }, - } - } - Ok(superposed) - } - } - - #[derive(Clone, Debug)] - pub struct LetOp {} - - grounded_op!(LetOp, "let"); - - impl Grounded for LetOp { - fn type_(&self) -> Atom { - // TODO: Undefined for the argument is necessary to make argument reductable. - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_UNDEFINED, ATOM_TYPE_ATOM, ATOM_TYPE_UNDEFINED]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for LetOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("let expects three arguments: pattern, atom and template"); - let mut template = args.get(2).ok_or_else(arg_error)?.clone(); - let atom = args.get(1).ok_or_else(arg_error)?; - let mut pattern = args.get(0).ok_or_else(arg_error)?.clone(); - - let external_vars = resolve_var_conflicts(&atom, &mut pattern, &mut template); - - let bindings = matcher::match_atoms(&pattern, &atom) - .map(|b| b.convert_var_equalities_to_bindings(&external_vars)); - let result = bindings.map(|b| { matcher::apply_bindings_to_atom_move(template.clone(), &b) }).collect(); - log::debug!("LetOp::execute: pattern: {}, atom: {}, template: {}, result: {:?}", pattern, atom, template, result); - Ok(result) - } - } - - fn resolve_var_conflicts(atom: &Atom, pattern: &mut Atom, template: &mut Atom) -> HashSet { - let mut external_vars = HashSet::new(); - collect_vars(&atom, &mut external_vars); - collect_vars(&template, &mut external_vars); - make_conflicting_vars_unique(pattern, template, &external_vars); - external_vars - } - - fn collect_vars(atom: &Atom, vars: &mut HashSet) { - atom.iter().filter_type::<&VariableAtom>().cloned().for_each(|var| { vars.insert(var); }); - } - - fn make_conflicting_vars_unique(pattern: &mut Atom, template: &mut Atom, external_vars: &HashSet) { - 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| *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) { - Some(v) => *var = v.clone(), - None => {}, - }); - } - - #[derive(Clone, Debug)] - pub struct LetVarOp { } - - grounded_op!(LetVarOp, "let*"); - - impl Grounded for LetVarOp { - fn type_(&self) -> Atom { - // The first argument is an Atom, because it has to be evaluated iteratively - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_UNDEFINED]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for LetVarOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("let* list of couples and template as arguments"); - let expr = atom_as_expr(args.get(0).ok_or_else(arg_error)?).ok_or(arg_error())?; - let template = args.get(1).ok_or_else(arg_error)?.clone(); - log::debug!("LetVarOp::execute: expr: {}, template: {}", expr, template); - - let children = expr.children().as_slice(); - match children { - [] => Ok(vec![template]), - [couple] => { - let couple = atom_as_expr(couple).ok_or_else(arg_error)?.children(); - let pattern = couple.get(0).ok_or_else(arg_error)?.clone(); - let atom = couple.get(1).ok_or_else(arg_error)?.clone(); - Ok(vec![Atom::expr([Atom::gnd(LetOp{}), pattern, atom, template])]) - }, - [couple, tail @ ..] => { - let couple = atom_as_expr(couple).ok_or_else(arg_error)?.children(); - let pattern = couple.get(0).ok_or_else(arg_error)?.clone(); - let atom = couple.get(1).ok_or_else(arg_error)?.clone(); - Ok(vec![Atom::expr([Atom::gnd(LetOp{}), pattern, atom, - Atom::expr([Atom::gnd(LetVarOp{}), Atom::expr(tail), template])])]) - }, - } - } - } - - //TODO: The additional arguments are a temporary hack on account of the way the operation atoms store references - // to the runner & module state. https://github.com/trueagi-io/hyperon-experimental/issues/410 - #[cfg(feature = "old_interpreter")] - pub fn register_common_tokens(tref: &mut Tokenizer, tokenizer: Shared, _space: &DynSpace, metta: &Metta) { - - let match_op = Atom::gnd(MatchOp{}); - tref.register_token(regex(r"match"), move |_| { match_op.clone() }); - let bind_op = Atom::gnd(BindOp::new(tokenizer)); - tref.register_token(regex(r"bind!"), move |_| { bind_op.clone() }); - let new_space_op = Atom::gnd(NewSpaceOp{}); - tref.register_token(regex(r"new-space"), move |_| { new_space_op.clone() }); - let add_atom_op = Atom::gnd(AddAtomOp{}); - tref.register_token(regex(r"add-atom"), move |_| { add_atom_op.clone() }); - let remove_atom_op = Atom::gnd(RemoveAtomOp{}); - tref.register_token(regex(r"remove-atom"), move |_| { remove_atom_op.clone() }); - let get_atoms_op = Atom::gnd(GetAtomsOp{}); - tref.register_token(regex(r"get-atoms"), move |_| { get_atoms_op.clone() }); - let car_atom_op = Atom::gnd(CarAtomOp{}); - tref.register_token(regex(r"car-atom"), move |_| { car_atom_op.clone() }); - let cdr_atom_op = Atom::gnd(CdrAtomOp{}); - tref.register_token(regex(r"cdr-atom"), move |_| { cdr_atom_op.clone() }); - let cons_atom_op = Atom::gnd(ConsAtomOp{}); - tref.register_token(regex(r"cons-atom"), move |_| { cons_atom_op.clone() }); - let max_atom_op = Atom::gnd(MaxAtomOp{}); - tref.register_token(regex(r"max-atom"), move |_| { max_atom_op.clone() }); - let min_atom_op = Atom::gnd(MinAtomOp{}); - tref.register_token(regex(r"min-atom"), move |_| { min_atom_op.clone() }); - let size_atom_op = Atom::gnd(SizeAtomOp{}); - tref.register_token(regex(r"size-atom"), move |_| { size_atom_op.clone() }); - let index_atom_op = Atom::gnd(IndexAtomOp{}); - tref.register_token(regex(r"index-atom"), move |_| { index_atom_op.clone() }); - let random_int_op = Atom::gnd(RandomIntOp{}); - tref.register_token(regex(r"random-int"), move |_| { random_int_op.clone() }); - let random_float_op = Atom::gnd(RandomFloatOp{}); - tref.register_token(regex(r"random-float"), move |_| { random_float_op.clone() }); - let println_op = Atom::gnd(PrintlnOp{}); - tref.register_token(regex(r"println!"), move |_| { println_op.clone() }); - let format_args_op = Atom::gnd(FormatArgsOp{}); - tref.register_token(regex(r"format-args"), move |_| { format_args_op.clone() }); - let trace_op = Atom::gnd(TraceOp{}); - tref.register_token(regex(r"trace!"), move |_| { trace_op.clone() }); - let nop_op = Atom::gnd(NopOp{}); - tref.register_token(regex(r"nop"), move |_| { nop_op.clone() }); - let let_op = Atom::gnd(LetOp{}); - tref.register_token(regex(r"let"), move |_| { let_op.clone() }); - let let_var_op = Atom::gnd(LetVarOp{}); - tref.register_token(regex(r"let\*"), move |_| { let_var_op.clone() }); - let new_state_op = Atom::gnd(NewStateOp{}); - tref.register_token(regex(r"new-state"), move |_| { new_state_op.clone() }); - let change_state_op = Atom::gnd(ChangeStateOp{}); - tref.register_token(regex(r"change-state!"), move |_| { change_state_op.clone() }); - let get_state_op = Atom::gnd(GetStateOp{}); - tref.register_token(regex(r"get-state"), move |_| { get_state_op.clone() }); - let get_meta_type_op = Atom::gnd(GetMetaTypeOp{}); - tref.register_token(regex(r"get-metatype"), move |_| { get_meta_type_op.clone() }); - let mod_space_op = Atom::gnd(ModSpaceOp::new(metta.clone())); - 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() }); - let unique_op = Atom::gnd(UniqueAtomOp{}); - tref.register_token(regex(r"unique-atom"), move |_| { unique_op.clone() }); - let subtraction_op = Atom::gnd(SubtractionAtomOp{}); - tref.register_token(regex(r"subtraction-atom"), move |_| { subtraction_op.clone() }); - let intersection_op = Atom::gnd(IntersectionAtomOp{}); - tref.register_token(regex(r"intersection-atom"), move |_| { intersection_op.clone() }); - let union_op = Atom::gnd(UnionAtomOp{}); - tref.register_token(regex(r"union-atom"), move |_| { union_op.clone() }); - - #[cfg(feature = "pkg_mgmt")] - pkg_mgmt_ops::register_pkg_mgmt_tokens(tref, metta); - } - - //TODO: The metta argument is a temporary hack on account of the way the operation atoms store references - // to the runner & module state. https://github.com/trueagi-io/hyperon-experimental/issues/410 - #[cfg(feature = "old_interpreter")] - pub fn register_runner_tokens(tref: &mut Tokenizer, _tokenizer: Shared, space: &DynSpace, metta: &Metta) { - - let capture_op = Atom::gnd(CaptureOp::new(space.clone())); - tref.register_token(regex(r"capture"), move |_| { capture_op.clone() }); - let case_op = Atom::gnd(CaseOp::new(space.clone())); - tref.register_token(regex(r"case"), move |_| { case_op.clone() }); - let assert_equal_op = Atom::gnd(AssertEqualOp::new(space.clone())); - tref.register_token(regex(r"assertEqual"), move |_| { assert_equal_op.clone() }); - let assert_equal_to_result_op = Atom::gnd(AssertEqualToResultOp::new(space.clone())); - tref.register_token(regex(r"assertEqualToResult"), move |_| { assert_equal_to_result_op.clone() }); - let collapse_op = Atom::gnd(CollapseOp::new(space.clone())); - tref.register_token(regex(r"collapse"), move |_| { collapse_op.clone() }); - let superpose_op = Atom::gnd(SuperposeOp::new(space.clone())); - tref.register_token(regex(r"superpose"), move |_| { superpose_op.clone() }); - let get_type_op = Atom::gnd(GetTypeOp::new(space.clone())); - tref.register_token(regex(r"get-type"), move |_| { get_type_op.clone() }); - let get_type_space_op = Atom::gnd(GetTypeSpaceOp{}); - tref.register_token(regex(r"get-type-space"), move |_| { get_type_space_op.clone() }); - let import_op = Atom::gnd(ImportOp::new(metta.clone())); - tref.register_token(regex(r"import!"), move |_| { import_op.clone() }); - let include_op = Atom::gnd(IncludeOp::new(metta.clone())); - tref.register_token(regex(r"include"), move |_| { include_op.clone() }); - let pragma_op = Atom::gnd(PragmaOp::new(metta.settings().clone())); - tref.register_token(regex(r"pragma!"), move |_| { pragma_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 - // from being freed. There are two options to eliminate this. (1) use weak - // pointer and somehow use the same type to represent weak and strong - // pointers to the atomspace. (2) resolve &self in GroundingSpace::query - // method without adding it into container. - let self_atom = Atom::gnd(space.clone()); - tref.register_token(regex(r"&self"), move |_| { self_atom.clone() }); - } - - #[cfg(feature = "old_interpreter")] - pub fn register_rust_stdlib_tokens(target: &mut Tokenizer) { - let mut rust_tokens = Tokenizer::new(); - let tref = &mut rust_tokens; - - tref.register_fallible_token(regex(r"[\-\+]?\d+"), - |token| { Ok(Atom::gnd(Number::from_int_str(token)?)) }); - tref.register_fallible_token(regex(r"[\-\+]?\d+\.\d+"), - |token| { Ok(Atom::gnd(Number::from_float_str(token)?)) }); - tref.register_fallible_token(regex(r"[\-\+]?\d+(\.\d+)?[eE][\-\+]?\d+"), - |token| { Ok(Atom::gnd(Number::from_float_str(token)?)) }); - tref.register_token(regex(r"True|False"), - |token| { Atom::gnd(Bool::from_str(token)) }); - tref.register_token(regex(r#"^".*"$"#), - |token| { let mut s = String::from(token); s.remove(0); s.pop(); Atom::gnd(Str::from_string(s)) }); - let sum_op = Atom::gnd(SumOp{}); - tref.register_token(regex(r"\+"), move |_| { sum_op.clone() }); - let sub_op = Atom::gnd(SubOp{}); - tref.register_token(regex(r"\-"), move |_| { sub_op.clone() }); - let mul_op = Atom::gnd(MulOp{}); - tref.register_token(regex(r"\*"), move |_| { mul_op.clone() }); - let div_op = Atom::gnd(DivOp{}); - tref.register_token(regex(r"/"), move |_| { div_op.clone() }); - let mod_op = Atom::gnd(ModOp{}); - tref.register_token(regex(r"%"), move |_| { mod_op.clone() }); - let lt_op = Atom::gnd(LessOp{}); - tref.register_token(regex(r"<"), move |_| { lt_op.clone() }); - let gt_op = Atom::gnd(GreaterOp{}); - tref.register_token(regex(r">"), move |_| { gt_op.clone() }); - let le_op = Atom::gnd(LessEqOp{}); - tref.register_token(regex(r"<="), move |_| { le_op.clone() }); - let ge_op = Atom::gnd(GreaterEqOp{}); - tref.register_token(regex(r">="), move |_| { ge_op.clone() }); - let eq_op = Atom::gnd(EqualOp{}); - tref.register_token(regex(r"=="), move |_| { eq_op.clone() }); - let and_op = Atom::gnd(AndOp{}); - tref.register_token(regex(r"and"), move |_| { and_op.clone() }); - let or_op = Atom::gnd(OrOp{}); - tref.register_token(regex(r"or"), move |_| { or_op.clone() }); - let not_op = Atom::gnd(NotOp{}); - tref.register_token(regex(r"not"), move |_| { not_op.clone() }); - // NOTE: xor and flip are absent in Python intentionally for conversion testing - let xor_op = Atom::gnd(XorOp{}); - tref.register_token(regex(r"xor"), move |_| { xor_op.clone() }); - let flip_op = Atom::gnd(FlipOp{}); - tref.register_token(regex(r"flip"), move |_| { flip_op.clone() }); - - target.move_front(&mut rust_tokens); - } - - pub static METTA_CODE: &'static str = include_str!("stdlib.metta"); -} - -#[cfg(feature = "old_interpreter")] -pub use non_minimal_only_stdlib::*; - -#[cfg(not(feature = "old_interpreter"))] use super::stdlib_minimal::*; - -#[cfg(not(feature = "old_interpreter"))] use crate::metta::runner::METTA_CODE; /// Loader to Initialize the corelib module @@ -2113,14 +1430,13 @@ fn mod_space_op() { assert_eq!(result[2], vec![Atom::gnd(stdlib_space)]); } -#[cfg(all(test, feature = "old_interpreter"))] +#[cfg(test)] mod tests { use super::*; use crate::atom::matcher::atoms_are_equivalent; use crate::metta::text::*; use crate::metta::runner::EnvBuilder; use crate::metta::runner::string::Str; - use crate::metta::types::validate_atom; use crate::common::test_utils::*; @@ -2194,30 +1510,6 @@ mod tests { assert_eq_no_order!(res, vec![expr!(("foo" "bar")), expr!(("bar" "foo"))]); } - #[test] - fn car_atom_op() { - let res = CarAtomOp{}.execute(&mut vec![expr!(("A" "C") "B")]).expect("No result returned"); - assert_eq!(res, vec![expr!("A" "C")]); - } - - #[test] - fn cdr_atom_op() { - let res = CdrAtomOp{}.execute(&mut vec![expr!(("A"))]).expect("No result returned"); - assert_eq!(res, vec![expr!()]); - let res = CdrAtomOp{}.execute(&mut vec![expr!(("A" "C") ("D" "E") "B")]).expect("No result returned"); - assert_eq!(res, vec![expr!(("D" "E") "B")]); - let res = CdrAtomOp{}.execute(&mut vec![expr!()]); - assert_eq!(res, Err(ExecError::Runtime("cdr-atom expects non-empty expression".into()))); - } - - #[test] - fn cons_atom_op() { - let res = ConsAtomOp{}.execute(&mut vec![expr!("A"), expr!()]).expect("No result returned"); - assert_eq!(res, vec![expr!(("A"))]); - let res = ConsAtomOp{}.execute(&mut vec![expr!("A" "F"), expr!(("B" "C") "D")]).expect("No result returned"); - assert_eq!(res, vec![expr!(("A" "F") ("B" "C") "D")]); - } - #[test] fn size_atom_op() { let res = SizeAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)})]).expect("No result returned"); @@ -2284,42 +1576,6 @@ mod tests { assert_eq!(constr.unwrap()("&my"), Ok(sym!("definition"))); } - #[test] - fn case_op() { - let space = DynSpace::new(metta_space(" - (= (foo) (A B)) - ")); - - let case_op = CaseOp::new(space.clone()); - - assert_eq!(case_op.execute(&mut vec![expr!(("foo")), - expr!(((n "B") n) ("Empty" "D"))]), - Ok(vec![Atom::sym("A")])); - assert_eq!(case_op.execute(&mut vec![expr!({MatchOp{}} {space} ("B" "C") ("C" "B")), - expr!(((n "C") n) ("Empty" "D"))]), - Ok(vec![Atom::sym("D")])); - } - - #[test] - fn case_op_external_vars_at_right_are_kept_untouched() { - let space = DynSpace::new(GroundingSpace::new()); - let case_op = CaseOp::new(space.clone()); - - assert_eq!(case_op.execute(&mut vec![expr!(ext), expr!(((t t)))]), - Ok(vec![expr!(ext)])); - assert_eq!(case_op.execute(&mut vec![expr!(ext "A"), expr!(((t t)))]), - Ok(vec![expr!(ext "A")])); - } - - #[test] - fn case_op_internal_variables_has_priority_in_template() { - let space = DynSpace::new(GroundingSpace::new()); - let case_op = CaseOp::new(space.clone()); - - assert_eq!(case_op.execute(&mut vec![expr!(x "A"), expr!(((x x)))]), - Ok(vec![expr!(x "A")])); - } - fn assert_runtime_error(actual: Result, ExecError>, expected: Regex) { match actual { Err(ExecError::Runtime(msg)) => assert!(expected.is_match(msg.as_str()), @@ -2364,29 +1620,6 @@ mod tests { unit_result()); } - #[test] - fn collapse_op() { - let space = DynSpace::new(metta_space(" - (= (foo) (A B)) - (= (foo) (B C)) - ")); - let collapse_op = CollapseOp::new(space); - - let actual = collapse_op.execute(&mut vec![expr!(("foo"))]).unwrap(); - assert_eq!(actual.len(), 1); - assert_eq_no_order!( - *atom_as_expr(&actual[0]).unwrap().children(), - vec![expr!("B" "C"), expr!("A" "B")]); - } - - #[test] - fn superpose_op() { - let space = DynSpace::new(GroundingSpace::new()); - let superpose_op = SuperposeOp::new(space); - assert_eq!(superpose_op.execute(&mut vec![expr!("A" ("B" "C"))]), - Ok(vec![sym!("A"), expr!("B" "C")])); - } - #[test] fn unique_op() { let unique_op = UniqueAtomOp{}; @@ -2485,62 +1718,6 @@ mod tests { vec![expr!(("A" ("B" "C")) ("f" "g") ("f" "g") ("P" "b"))]); } - #[test] - fn superpose_op_type() { - let space = DynSpace::new(GroundingSpace::new()); - assert!(validate_atom(space.borrow().as_space(), &expr!({SumOp{}} - ({SuperposeOp::new(space.clone())} ({Number::Integer(1)} {Number::Integer(2)} {Number::Integer(3)})) - {Number::Integer(1)}))); - } - - #[test] - fn superpose_op_multiple_interpretations() { - let metta = Metta::new(Some(EnvBuilder::test_env())); - let parser = SExprParser::new(" - (= (f) A) - (= (f) B) - (= (g) C) - (= (g) D) - - !(superpose ((f) (g))) - "); - - assert_eq_metta_results!(metta.run(parser), - Ok(vec![vec![expr!("A"), expr!("B"), expr!("C"), expr!("D")]])); - } - - #[test] - fn superpose_op_superposed_with_collapse() { - let metta = Metta::new(Some(EnvBuilder::test_env())); - let parser = SExprParser::new(" - (= (f) A) - (= (f) B) - - !(let $x (collapse (f)) (superpose $x)) - "); - - assert_eq_metta_results!(metta.run(parser), - Ok(vec![vec![expr!("A"), expr!("B")]])); - } - - #[test] - fn superpose_op_consumes_interpreter_errors() { - let metta = Metta::new(Some(EnvBuilder::test_env())); - let parser = SExprParser::new(" - (: f (-> A B)) - (= (f $x) $x) - - (: a A) - (: b B) - - !(superpose ((f (superpose ())) (f a) (f b))) - "); - - assert_eq!(metta.run(parser), Ok(vec![vec![ - expr!("Error" ("f" ({SuperposeOp{space:metta.space().clone()}} ())) "NoValidAlternatives"), - expr!("a"), expr!("Error" "b" "BadType")]])); - } - #[test] fn get_type_op() { let space = DynSpace::new(metta_space(" @@ -2586,26 +1763,6 @@ mod tests { assert_eq!(NopOp{}.execute(&mut vec![]), unit_result()); } - #[test] - fn let_op() { - assert_eq!(LetOp{}.execute(&mut vec![expr!(a b), expr!("A" "B"), expr!(b a)]), - Ok(vec![expr!("B" "A")])); - } - - #[test] - fn let_op_external_vars_at_right_are_kept_untouched() { - assert_eq!(LetOp{}.execute(&mut vec![expr!(t), expr!(ext), expr!(t)]), - Ok(vec![expr!(ext)])); - assert_eq!(LetOp{}.execute(&mut vec![expr!(t), expr!(ext "A"), expr!(t)]), - Ok(vec![expr!(ext "A")])); - } - - #[test] - fn let_op_internal_variables_has_priority_in_template() { - assert_eq!(LetOp{}.execute(&mut vec![expr!(x), expr!(x "A"), expr!(x)]), - Ok(vec![expr!(x "A")])); - } - #[test] fn let_op_keep_variables_equalities_issue290() { assert_eq_metta_results!(run_program("!(let* (($f f) ($f $x)) $x)"), Ok(vec![vec![expr!("f")]])); @@ -2641,16 +1798,6 @@ mod tests { assert_eq_metta_results!(run_program(program), Ok(vec![vec![expr!("→" "P" "R")]])); } - #[test] - fn let_var_op() { - assert_eq!(LetVarOp{}.execute(&mut vec![expr!(), sym!("B")]), - Ok(vec![sym!("B")])); - assert_eq!(LetVarOp{}.execute(&mut vec![expr!(((a "A"))), expr!(a)]), - Ok(vec![expr!({LetOp{}} a "A" a)])); - assert_eq!(LetVarOp{}.execute(&mut vec![expr!((a "A") (b "B")), expr!(b a)]), - Ok(vec![expr!({LetOp{}} a "A" ({LetVarOp{}} ((b "B")) (b a)))])); - } - #[test] fn state_ops() { let result = NewStateOp{}.execute(&mut vec![expr!("A" "B")]).unwrap(); @@ -2742,7 +1889,8 @@ mod tests { #[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 $x (input $x) (output $x))"), Ok(vec![vec![]])); + assert_eq!(run_program("!(let $x (input $y) (output $x))"), Ok(vec![vec![expr!("output" ("input" y))]])); assert_eq!(run_program("!(let (quote ($sv $st)) (sealed ($x) (quote ($x (output $x)))) (let $sv (input $x) $st))"), Ok(vec![vec![expr!("output" ("input" x))]])); } @@ -2917,23 +2065,4 @@ mod tests { ("@return" ("@type" "%Undefined%") ("@desc" {Str::from_str("Return value")})) )], ])); } - - #[test] - fn test_string_parsing() { - let metta = Metta::new(Some(EnvBuilder::test_env())); - let parser = SExprParser::new(r#" - (= (id $x) $x) - !(id "test") - !(id "te st") - !(id "te\"st") - !(id "") - "#); - - assert_eq_metta_results!(metta.run(parser), Ok(vec![ - vec![expr!({Str::from_str("test")})], - vec![expr!({Str::from_str("te st")})], - vec![expr!({Str::from_str("te\"st")})], - vec![expr!({Str::from_str("")})], - ])); - } } diff --git a/lib/src/metta/types.rs b/lib/src/metta/types.rs index 0305c39d7..05c3915e4 100644 --- a/lib/src/metta/types.rs +++ b/lib/src/metta/types.rs @@ -185,7 +185,6 @@ fn get_args(expr: &ExpressionAtom) -> &[Atom] { /// assert_eq_no_order!(get_atom_types(&space, &expr!("f" "a")), vec![expr!("B")]); /// assert_eq_no_order!(get_atom_types(&space, &expr!("f" "b")), Vec::::new()); /// ``` -#[cfg(not(feature = "old_interpreter"))] pub fn get_atom_types(space: &dyn Space, atom: &Atom) -> Vec { log::trace!("get_atom_types: atom: {}", atom); let types = match atom { @@ -224,75 +223,6 @@ pub fn get_atom_types(space: &dyn Space, atom: &Atom) -> Vec { types } -/// Returns vector of the types for the given `atom` in context of the given -/// `space`. Returns `%Undefined%` if atom has no type assigned. Returns empty -/// vector if atom is a function call but expected types of arguments are not -/// compatible with passed values. -/// -/// # Examples -/// -/// ``` -/// use hyperon::{Atom, expr, assert_eq_no_order}; -/// use hyperon::metta::ATOM_TYPE_UNDEFINED; -/// use hyperon::metta::runner::*; -/// use hyperon::metta::text::SExprParser; -/// use hyperon::metta::types::get_atom_types; -/// -/// let metta = Metta::new(None); -/// metta.run(SExprParser::new(" -/// (: f (-> A B)) -/// (: a A) -/// (: a B) -/// (: b B) -/// ")).unwrap(); -/// -/// let space = metta.space(); -/// assert_eq_no_order!(get_atom_types(&space, &expr!(x)), vec![ATOM_TYPE_UNDEFINED]); -/// assert_eq_no_order!(get_atom_types(&space, &expr!({1})), vec![expr!("i32")]); -/// assert_eq_no_order!(get_atom_types(&space, &expr!("na")), vec![ATOM_TYPE_UNDEFINED]); -/// assert_eq_no_order!(get_atom_types(&space, &expr!("a")), vec![expr!("A"), expr!("B")]); -/// assert_eq_no_order!(get_atom_types(&space, &expr!("a" "b")), vec![expr!("A" "B"), expr!("B" "B")]); -/// assert_eq_no_order!(get_atom_types(&space, &expr!("f" "a")), vec![expr!("B")]); -/// assert_eq_no_order!(get_atom_types(&space, &expr!("f" "b")), Vec::::new()); -/// ``` -#[cfg(feature = "old_interpreter")] -pub fn get_atom_types(space: &dyn Space, atom: &Atom) -> Vec { - log::trace!("get_atom_types: atom: {}", atom); - let types = match atom { - // TODO: type of the variable could be actually a type variable, - // in this case inside each variant of type for the atom we should - // also keep bindings for the type variables. For example, - // we have an expression `(let $n (foo) (+ $n $n))`, where - // `(: let (-> $t $t $r $r))`, `(: foo (-> $tt))`, - // and `(: + (-> Num Num Num))`then type checker can find that - // `{ $r = $t = $tt = Num }`. - Atom::Variable(_) => vec![ATOM_TYPE_UNDEFINED], - Atom::Grounded(gnd) => vec![make_variables_unique(gnd.type_())], - Atom::Symbol(_) => { - let mut types = query_types(space, atom); - if types.is_empty() { - types.push(ATOM_TYPE_UNDEFINED) - } - types - }, - Atom::Expression(expr) => { - let tuples = get_tuple_types(space, atom, expr); - let applications = get_application_types(space, atom, expr); - - let mut types = Vec::new(); - if tuples.is_empty() && applications == None { - types.push(ATOM_TYPE_UNDEFINED); - } else { - types.extend(tuples); - applications.into_iter().for_each(|t| types.extend(t)); - } - types - }, - }; - log::debug!("get_atom_types: return atom {} types {:?}", atom, types); - types -} - fn get_tuple_types(space: &dyn Space, atom: &Atom, expr: &ExpressionAtom) -> Vec { let mut tuples = vec![vec![]]; for (i, child) in expr.children().iter().enumerate() { @@ -904,18 +834,10 @@ mod tests { (: b B) (: b BB) "); - #[cfg(not(feature = "old_interpreter"))] assert_eq_no_order!(get_atom_types(&space, &atom("(a b)")), vec![atom("(A B)"), atom("(AA B)"), atom("(A BB)"), atom("(AA BB)"), ATOM_TYPE_UNDEFINED]); - #[cfg(feature = "old_interpreter")] - assert_eq_no_order!(get_atom_types(&space, &atom("(a b)")), - vec![atom("(A B)"), atom("(AA B)"), atom("(A BB)"), atom("(AA BB)")]); - #[cfg(not(feature = "old_interpreter"))] assert_eq_no_order!(get_atom_types(&space, &atom("(a c)")), vec![atom("(A %Undefined%)"), atom("(AA %Undefined%)"), ATOM_TYPE_UNDEFINED]); - #[cfg(feature = "old_interpreter")] - assert_eq_no_order!(get_atom_types(&space, &atom("(a c)")), - vec![atom("(A %Undefined%)"), atom("(AA %Undefined%)")]); assert_eq_no_order!(get_atom_types(&space, &atom("(c d)")), vec![ATOM_TYPE_UNDEFINED]); } @@ -971,19 +893,10 @@ mod tests { // Here and below: when interpreter cannot find a function type for // expression it evaluates it. Thus any argument expression without // a function type can potentially suit as a legal argument. - #[cfg(not(feature = "old_interpreter"))] assert_eq!(get_atom_types(&space, &expr!("f_sym" ("b"))), vec![atom("D")]); - #[cfg(feature = "old_interpreter")] - assert_eq!(get_atom_types(&space, &expr!("f_sym" ("b"))), vec![]); assert_eq!(get_atom_types(&space, &expr!("f_expr" ("b"))), vec![atom("D")]); - #[cfg(not(feature = "old_interpreter"))] assert_eq!(get_atom_types(&space, &expr!("f_var" ("b"))), vec![atom("D")]); - #[cfg(feature = "old_interpreter")] - assert_eq!(get_atom_types(&space, &expr!("f_var" ("b"))), vec![]); - #[cfg(not(feature = "old_interpreter"))] assert_eq!(get_atom_types(&space, &expr!("f_gnd" ("b"))), vec![atom("D")]); - #[cfg(feature = "old_interpreter")] - assert_eq!(get_atom_types(&space, &expr!("f_gnd" ("b"))), vec![]); assert_eq!(get_atom_types(&space, &expr!("f_atom" {1})), vec![atom("D")]); assert_eq!(get_atom_types(&space, &expr!("f_sym" {1})), vec![]); diff --git a/python/tests/scripts/c1_grounded_basic.metta b/python/tests/scripts/c1_grounded_basic.metta index e1334fad2..de35c1f66 100644 --- a/python/tests/scripts/c1_grounded_basic.metta +++ b/python/tests/scripts/c1_grounded_basic.metta @@ -94,12 +94,9 @@ ; Custom symbols as arguments of grounded operations ; work similarly (: ln LN) -; TODO: This test has different behavior in old_interpreter and minimal interpreter. -; In first case it returns (Error ln BadType). In second case it returns -; (Error (+ ln 2) BadType). Uncomment when old_interpreter feature is removed -;!(assertEqualToResult -; (== 4 (+ ln 2)) -; ((Error (+ ln 2) BadType))) +!(assertEqualToResult + (== 4 (+ ln 2)) + ((Error (+ ln 2) BadType))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/python/tests/scripts/d5_auto_types.metta b/python/tests/scripts/d5_auto_types.metta index f62a8181e..e84d227c8 100644 --- a/python/tests/scripts/d5_auto_types.metta +++ b/python/tests/scripts/d5_auto_types.metta @@ -61,9 +61,6 @@ Auto type-checking can be enabled (let $x (+ 5 "S") $x) (: f (-> $t Number)) -; TODO: This test has different behavior in old_interpreter and minimal interpreter. -; In first case it returns (Error "S" BadType). In second case it returns -; (Error (+ 5 "S") BadType). Uncomment when old_interpreter feature is removed -;!(assertEqualToResult -; (f (+ 5 "S")) -; ((Error (+ 5 "S") BadType))) +!(assertEqualToResult + (f (+ 5 "S")) + ((Error (+ 5 "S") BadType))) diff --git a/repl/Cargo.toml b/repl/Cargo.toml index b641645aa..f47ffa235 100644 --- a/repl/Cargo.toml +++ b/repl/Cargo.toml @@ -22,5 +22,4 @@ path = "src/main.rs" [features] default = ["hyperon"] python = ["pyo3", "pep440_rs"] -old_interpreter = ["hyperon/old_interpreter"] git = ["hyperon/git"] From 7b2074fcc9696002ce9603d1ecc13f1db148d807 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 8 Nov 2024 17:28:56 +0300 Subject: [PATCH 2/5] Move CoreLibLoader into stdlib_minimal --- lib/src/metta/runner/mod.rs | 4 +-- lib/src/metta/runner/stdlib_minimal.rs | 44 ++++++++++++++++++++++- lib/src/metta/runner/stdlib_old.rs | 49 ++------------------------ 3 files changed, 46 insertions(+), 51 deletions(-) diff --git a/lib/src/metta/runner/mod.rs b/lib/src/metta/runner/mod.rs index 587784242..e39d05eb3 100644 --- a/lib/src/metta/runner/mod.rs +++ b/lib/src/metta/runner/mod.rs @@ -90,9 +90,7 @@ pub mod stdlib_old; use super::interpreter::{interpret, interpret_init, interpret_step, InterpreterState}; pub mod stdlib_minimal; -use stdlib_minimal::*; - -use stdlib_old::CoreLibLoader; +use stdlib_minimal::CoreLibLoader; mod builtin_mods; use builtin_mods::*; diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 53ec170be..35dd77bfe 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -2,11 +2,12 @@ use crate::*; use crate::space::*; use crate::metta::*; use crate::metta::text::Tokenizer; -use crate::metta::runner::Metta; use crate::metta::types::get_atom_types; use crate::common::assert::vec_eq_no_order; use crate::common::shared::Shared; use crate::metta::runner::stdlib_old; +use crate::metta::text::SExprParser; +use crate::metta::runner::{Metta, RunContext, ModuleLoader}; use std::convert::TryInto; use regex::Regex; @@ -561,6 +562,33 @@ pub fn register_rust_stdlib_tokens(target: &mut Tokenizer) { pub static METTA_CODE: &'static str = include_str!("stdlib_minimal.metta"); +/// Loader to Initialize the corelib module +/// +/// NOTE: the corelib will be loaded automatically if the runner is initialized with one of the high-level +/// init functions such as [Metta::new] and [Metta::new_with_stdlib_loader] +#[derive(Debug)] +pub(crate) struct CoreLibLoader; + +impl Default for CoreLibLoader { + fn default() -> Self { + CoreLibLoader + } +} + +impl ModuleLoader for CoreLibLoader { + fn load(&self, context: &mut RunContext) -> Result<(), String> { + let space = DynSpace::new(GroundingSpace::new()); + context.init_self_module(space, None); + + register_rust_stdlib_tokens(&mut *context.module().tokenizer().borrow_mut()); + + let parser = SExprParser::new(METTA_CODE); + context.push_parser(Box::new(parser)); + + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -1348,4 +1376,18 @@ mod tests { vec![expr!({Str::from_str("te\nst")} "test")], ])); } + + #[test] + fn mod_space_op() { + let program = r#" + !(bind! &new_space (new-space)) + !(add-atom &new_space (mod-space! stdlib)) + !(get-atoms &new_space) + "#; + let runner = Metta::new(Some(runner::environment::EnvBuilder::test_env())); + let result = runner.run(SExprParser::new(program)).unwrap(); + + let stdlib_space = runner.module_space(runner.get_module_by_name("stdlib").unwrap()); + assert_eq!(result[2], vec![Atom::gnd(stdlib_space)]); + } } diff --git a/lib/src/metta/runner/stdlib_old.rs b/lib/src/metta/runner/stdlib_old.rs index aa5910bbb..0fbcc31d9 100644 --- a/lib/src/metta/runner/stdlib_old.rs +++ b/lib/src/metta/runner/stdlib_old.rs @@ -2,8 +2,7 @@ use crate::*; use crate::space::*; use crate::metta::*; use crate::metta::text::Tokenizer; -use crate::metta::text::SExprParser; -use crate::metta::runner::{Metta, RunContext, ModuleLoader, ResourceKey}; +use crate::metta::runner::{Metta, RunContext, ResourceKey}; use crate::metta::runner::string::Str; use crate::metta::types::{get_atom_types, get_meta_type}; use crate::common::shared::Shared; @@ -23,6 +22,7 @@ use rand::Rng; use super::arithmetics::*; use super::string::*; +use super::stdlib_minimal::*; macro_rules! grounded_op { ($name:ident, $disp:literal) => { @@ -1385,51 +1385,6 @@ impl CustomExecute for RandomFloatOp { } } -use super::stdlib_minimal::*; -use crate::metta::runner::METTA_CODE; - -/// Loader to Initialize the corelib module -/// -/// NOTE: the corelib will be loaded automatically if the runner is initialized with one of the high-level -/// init functions such as [Metta::new] and [Metta::new_with_stdlib_loader] -#[derive(Debug)] -pub(crate) struct CoreLibLoader; - -impl Default for CoreLibLoader { - fn default() -> Self { - CoreLibLoader - } -} - -impl ModuleLoader for CoreLibLoader { - fn load(&self, context: &mut RunContext) -> Result<(), String> { - let space = DynSpace::new(GroundingSpace::new()); - context.init_self_module(space, None); - - register_rust_stdlib_tokens(&mut *context.module().tokenizer().borrow_mut()); - - let parser = SExprParser::new(METTA_CODE); - context.push_parser(Box::new(parser)); - - Ok(()) - } -} - - -#[test] -fn mod_space_op() { - let program = r#" - !(bind! &new_space (new-space)) - !(add-atom &new_space (mod-space! stdlib)) - !(get-atoms &new_space) - "#; - let runner = Metta::new(Some(runner::environment::EnvBuilder::test_env())); - let result = runner.run(SExprParser::new(program)).unwrap(); - - let stdlib_space = runner.module_space(runner.get_module_by_name("stdlib").unwrap()); - assert_eq!(result[2], vec![Atom::gnd(stdlib_space)]); -} - #[cfg(test)] mod tests { use super::*; From cb8311619b81cd764838bfd2bb7760eac67ae81b Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 8 Nov 2024 18:45:56 +0300 Subject: [PATCH 3/5] Move all stdlib atoms into stdlib_minimal --- lib/src/metta/runner/mod.rs | 3 +- lib/src/metta/runner/stdlib_minimal.rs | 1826 ++++++++++++++++++++- lib/src/metta/runner/stdlib_old.rs | 2023 ------------------------ 3 files changed, 1784 insertions(+), 2068 deletions(-) delete mode 100644 lib/src/metta/runner/stdlib_old.rs diff --git a/lib/src/metta/runner/mod.rs b/lib/src/metta/runner/mod.rs index e39d05eb3..d35be742c 100644 --- a/lib/src/metta/runner/mod.rs +++ b/lib/src/metta/runner/mod.rs @@ -85,10 +85,9 @@ use std::sync::{Arc, Mutex, OnceLock}; mod environment; pub use environment::{Environment, EnvBuilder}; -#[macro_use] -pub mod stdlib_old; use super::interpreter::{interpret, interpret_init, interpret_step, InterpreterState}; +#[macro_use] pub mod stdlib_minimal; use stdlib_minimal::CoreLibLoader; diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 35dd77bfe..1055bd247 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -2,15 +2,25 @@ use crate::*; use crate::space::*; use crate::metta::*; use crate::metta::text::Tokenizer; -use crate::metta::types::get_atom_types; +use crate::metta::types::{get_atom_types, get_meta_type}; use crate::common::assert::vec_eq_no_order; use crate::common::shared::Shared; -use crate::metta::runner::stdlib_old; use crate::metta::text::SExprParser; -use crate::metta::runner::{Metta, RunContext, ModuleLoader}; +use crate::metta::runner::{Metta, RunContext, ModuleLoader, ResourceKey}; +use crate::metta::runner::string::Str; +use crate::common::CachingMapper; +use crate::common::multitrie::MultiTrie; +use crate::space::grounding::atom_to_trie_key; +#[cfg(feature = "pkg_mgmt")] +use crate::metta::runner::{git_catalog::ModuleGitLocation, mod_name_from_url, pkg_mgmt::UpdateMode}; use std::convert::TryInto; +use std::rc::Rc; +use std::cell::RefCell; +use std::fmt::Display; +use std::collections::HashMap; use regex::Regex; +use rand::Rng; use super::arithmetics::*; use super::string::*; @@ -23,6 +33,1322 @@ pub(crate) fn regex(regex: &str) -> Regex { Regex::new(regex).unwrap() } +macro_rules! grounded_op { + ($name:ident, $disp:literal) => { + impl PartialEq for $name { + fn eq(&self, _other: &Self) -> bool { + true + } + } + + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, $disp) + } + } + } +} + +#[derive(Clone, Debug)] +pub struct ImportOp { + //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP + context: std::sync::Arc>>>>>, +} + +grounded_op!(ImportOp, "import!"); + +impl ImportOp { + pub fn new(metta: Metta) -> Self { + Self{ context: metta.0.context.clone() } + } +} + +impl Grounded for ImportOp { + fn type_(&self) -> Atom { + //TODO: Ideally the "import as" / "import into" part would be optional + //A deeper discussion on arg semantics as it relates to import! is here: + // https://github.com/trueagi-io/hyperon-experimental/pull/580#discussion_r1491332304 + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, UNIT_TYPE()]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for ImportOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + //QUESTION: "Import" can mean several (3) different things. In Python parlance, it can mean + //1. "import module" opt. ("as bar") + //2. "from module import foo" opt. ("as bar") + //3. "from module import *" + // + //Do we want one MeTTa operation with multiple ways of invoking it? Or do we want different + // implementations for different versions of the import operation? (since we don't have key-words) + // like "from" and "as" (unless we want to add them) + // + //The old version of this operation supported 1. or 3., depending on whether a "space" argument + // mapped to an atom that already existed or not. If the space atom existed and was a Space, then + // the operation would perform behavior 3 (by importing only the space atom and no token). + // Otherwise it would perform behavior 1, by adding a token, but not adding the child space atom to + // the parent space. + // + //For now, in order to not lose functionality, I have kept this behavior. + // + // ** TO SUMMARIZE ** + // If the destination argument is the &self Space atom, the behavior is (3) ie "from module import *", + // and if the destination argument is a Symbol atom, the behavior is (1) ie "import module as foo" + // + //The Underlying functionality for behavior 2 exists in MettaMod::import_item_from_dependency_as, + // but it isn't called yet because I wanted to discuss the way to expose it as a MeTTa op. + //For behavior 3, there are deeper questions about desired behavior around tokenizer entries, + // transitive imports, etc. I have summarized those concerns in the discussion comments above + // MettaMod::import_all_from_dependency + // + + let arg_error = || ExecError::from("import! expects a destination &space and a module name argument"); + let dest_arg = args.get(0).ok_or_else(arg_error)?; + let mod_name_atom = args.get(1).ok_or_else(arg_error)?; + + // TODO: replace Symbol by grounded String? + let mod_name = match mod_name_atom { + Atom::Symbol(mod_name) => mod_name.name(), + _ => return Err("import! expects a module name as the first argument".into()) + }; + let mod_name = strip_quotes(mod_name); + + // Load the module into the runner, or get the ModId if it's already loaded + //TODO: Remove this hack to access the RunContext, when it's part of the arguments to `execute` + let ctx_ref = self.context.lock().unwrap().last().unwrap().clone(); + let mut context = ctx_ref.lock().unwrap(); + let mod_id = context.load_module(mod_name)?; + + // Import the module, as per the behavior described above + match dest_arg { + Atom::Symbol(dest_sym) => { + context.import_dependency_as(mod_id, Some(dest_sym.name().to_string()))?; + } + other_atom => { + match &other_atom { + Atom::Grounded(_) if Atom::as_gnd::(other_atom) == Some(context.module().space()) => { + context.import_all_from_dependency(mod_id)?; + }, + _ => { + return Err(format!("import! destination argument must be a symbol atom naming a new space, or &self. Found: {other_atom:?}").into()); + } + } + } + // None => { + // //TODO: Currently this pattern is unreachable on account of arity-checking in the MeTTa + // // interpreter, but I have the code path in here for when it is possible + // context.module().import_dependency_as(&context.metta, mod_id, None)?; + // }, + } + + unit_result() + } +} + +#[derive(Clone, Debug)] +pub struct IncludeOp { + //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP + context: std::sync::Arc>>>>>, +} + +grounded_op!(IncludeOp, "include"); + +impl IncludeOp { + pub fn new(metta: Metta) -> Self { + Self{ context: metta.0.context.clone() } + } +} + +impl Grounded for IncludeOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for IncludeOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("include expects a module name argument"); + let mod_name_atom = args.get(0).ok_or_else(arg_error)?; + + // TODO: replace Symbol by grounded String? + let mod_name = match mod_name_atom { + Atom::Symbol(mod_name) => mod_name.name(), + _ => return Err(arg_error()) + }; + let mod_name = strip_quotes(mod_name); + + //TODO: Remove this hack to access the RunContext, when it's part of the arguments to `execute` + let ctx_ref = self.context.lock().unwrap().last().unwrap().clone(); + let mut context = ctx_ref.lock().unwrap(); + let program_buf = context.load_resource_from_module(mod_name, ResourceKey::MainMettaSrc)?; + + // Interpret the loaded MeTTa S-Expression text + let program_text = String::from_utf8(program_buf) + .map_err(|e| e.to_string())?; + let parser = crate::metta::text::OwnedSExprParser::new(program_text); + let eval_result = context.run_inline(|context| { + context.push_parser(Box::new(parser)); + Ok(()) + })?; + + //NOTE: Current behavior returns the result of the last sub-eval to match the old + // `import!` before before module isolation. However that means the results prior to + // the last are dropped. I don't know how to fix this or if it's even wrong, but it's + // different from the way "eval-type" APIs work when called from host code, e.g. Rust + Ok(eval_result.into_iter().last().unwrap_or_else(|| vec![])) + } +} + +/// mod-space! returns the space of a specified module, loading the module if it's not loaded already +//NOTE: The "impure" '!' denoted in the op atom name is due to the side effect of loading the module. If +// we want a side-effect-free version, it could be implemented by calling `RunContext::get_module_by_name` +// instead of `RunContext::load_module`, but then the user would need to use `register-module!`, `import!`, +// or some other mechanism to make sure the module is loaded in advance. +#[derive(Clone, Debug)] +pub struct ModSpaceOp { + //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP + context: std::sync::Arc>>>>>, +} + +grounded_op!(ModSpaceOp, "mod-space!"); + +impl ModSpaceOp { + pub fn new(metta: Metta) -> Self { + Self{ context: metta.0.context.clone() } + } +} + +impl Grounded for ModSpaceOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, rust_type_atom::()]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for ModSpaceOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = "mod-space! expects a module name argument"; + let mod_name_atom = args.get(0).ok_or_else(|| ExecError::from(arg_error))?; + + // TODO: replace Symbol by grounded String? + let mod_name = match mod_name_atom { + Atom::Symbol(mod_name) => mod_name.name(), + _ => {return Err(ExecError::from(arg_error))} + }; + let mod_name = strip_quotes(mod_name); + + // Load the module into the runner, or get the ModId if it's already loaded + //TODO: Remove this hack to access the RunContext, when it's part of the arguments to `execute` + let ctx_ref = self.context.lock().unwrap().last().unwrap().clone(); + let mut context = ctx_ref.lock().unwrap(); + let mod_id = context.load_module(mod_name)?; + + let space = Atom::gnd(context.metta().module_space(mod_id)); + Ok(vec![space]) + } +} + +/// This operation prints the modules loaded from the top of the runner +/// +/// NOTE: This is a temporary stop-gap to help MeTTa users inspect which modules they have loaded and +/// debug module import issues. Ultimately it probably makes sense to make this information accessible +/// as a special kind of Space, so that it would be possible to work with it programmatically. +#[derive(Clone, Debug)] +pub struct PrintModsOp { + metta: Metta +} + +grounded_op!(PrintModsOp, "print-mods!"); + +impl PrintModsOp { + pub fn new(metta: Metta) -> Self { + Self{ metta } + } +} + +impl Grounded for PrintModsOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, UNIT_TYPE()]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for PrintModsOp { + fn execute(&self, _args: &[Atom]) -> Result, ExecError> { + self.metta.display_loaded_modules(); + unit_result() + } +} + +#[derive(Clone, Debug)] +pub struct BindOp { + tokenizer: Shared, +} + +grounded_op!(BindOp, "bind!"); + +impl BindOp { + pub fn new(tokenizer: Shared) -> Self { + Self{ tokenizer } + } +} + +impl Grounded for BindOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_SYMBOL, ATOM_TYPE_UNDEFINED, UNIT_TYPE()]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for BindOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("bind! expects two arguments: token and atom"); + let token = <&SymbolAtom>::try_from(args.get(0).ok_or_else(arg_error)?).map_err(|_| "bind! expects symbol atom as a token")?.name(); + let atom = args.get(1).ok_or_else(arg_error)?.clone(); + + let token_regex = Regex::new(token).map_err(|err| format!("Could convert token {} into regex: {}", token, err))?; + self.tokenizer.borrow_mut().register_token(token_regex, move |_| { atom.clone() }); + unit_result() + } +} + +#[derive(Clone, Debug)] +pub struct NewSpaceOp {} + +grounded_op!(NewSpaceOp, "new-space"); + +impl Grounded for NewSpaceOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, rust_type_atom::()]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for NewSpaceOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + if args.len() == 0 { + let space = Atom::gnd(DynSpace::new(GroundingSpace::new())); + Ok(vec![space]) + } else { + Err("new-space doesn't expect arguments".into()) + } + } +} + +#[derive(Clone, Debug)] +pub struct AddAtomOp {} + +grounded_op!(AddAtomOp, "add-atom"); + +impl Grounded for AddAtomOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, rust_type_atom::(), + ATOM_TYPE_ATOM, UNIT_TYPE()]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for AddAtomOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("add-atom expects two arguments: space and atom"); + let space = args.get(0).ok_or_else(arg_error)?; + let atom = args.get(1).ok_or_else(arg_error)?; + let space = Atom::as_gnd::(space).ok_or("add-atom expects a space as the first argument")?; + space.borrow_mut().add(atom.clone()); + unit_result() + } +} + +#[derive(Clone, Debug)] +pub struct RemoveAtomOp {} + +grounded_op!(RemoveAtomOp, "remove-atom"); + +impl Grounded for RemoveAtomOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, rust_type_atom::(), + ATOM_TYPE_ATOM, UNIT_TYPE()]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for RemoveAtomOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("remove-atom expects two arguments: space and atom"); + let space = args.get(0).ok_or_else(arg_error)?; + let atom = args.get(1).ok_or_else(arg_error)?; + let space = Atom::as_gnd::(space).ok_or("remove-atom expects a space as the first argument")?; + space.borrow_mut().remove(atom); + // TODO? Is it necessary to distinguish whether the atom was removed or not? + unit_result() + } +} + +#[derive(Clone, Debug)] +pub struct GetAtomsOp {} + +grounded_op!(GetAtomsOp, "get-atoms"); + +impl Grounded for GetAtomsOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, rust_type_atom::(), + ATOM_TYPE_ATOM]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for GetAtomsOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("get-atoms expects one argument: space"); + let space = args.get(0).ok_or_else(arg_error)?; + let space = Atom::as_gnd::(space).ok_or("get-atoms expects a space as its argument")?; + space.borrow().as_space().atom_iter() + .map(|iter| iter.cloned().map(|a| make_variables_unique(a)).collect()) + .ok_or(ExecError::Runtime("Unsupported Operation. Can't traverse atoms in this space".to_string())) + } +} + +#[derive(Clone, Debug)] +pub struct PragmaOp { + settings: Shared>, +} + +grounded_op!(PragmaOp, "pragma!"); + +impl PragmaOp { + pub fn new(settings: Shared>) -> Self { + Self{ settings } + } +} + +impl Grounded for PragmaOp { + fn type_(&self) -> Atom { + ATOM_TYPE_UNDEFINED + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for PragmaOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("pragma! expects key and value as arguments"); + let key = <&SymbolAtom>::try_from(args.get(0).ok_or_else(arg_error)?).map_err(|_| "pragma! expects symbol atom as a key")?.name(); + let value = args.get(1).ok_or_else(arg_error)?; + self.settings.borrow_mut().insert(key.into(), value.clone()); + unit_result() + } +} + +#[derive(Clone, Debug)] +pub struct GetTypeSpaceOp {} + +grounded_op!(GetTypeSpaceOp, "get-type-space"); + +impl Grounded for GetTypeSpaceOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, rust_type_atom::(), ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for GetTypeSpaceOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("get-type-space expects two arguments: space and atom"); + let space = args.get(0).ok_or_else(arg_error)?; + let space = Atom::as_gnd::(space).ok_or("get-type-space expects a space as the first argument")?; + let atom = args.get(1).ok_or_else(arg_error)?; + log::debug!("GetTypeSpaceOp::execute: space: {}, atom: {}", space, atom); + + Ok(get_atom_types(space, atom)) + } +} + +#[derive(Clone, Debug)] +pub struct GetMetaTypeOp { } + +grounded_op!(GetMetaTypeOp, "get-metatype"); + +impl Grounded for GetMetaTypeOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for GetMetaTypeOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("get-metatype expects single atom as an argument"); + let atom = args.get(0).ok_or_else(arg_error)?; + + Ok(vec![get_meta_type(&atom)]) + } +} + + +#[derive(Clone, Debug)] +pub struct PrintlnOp {} + +grounded_op!(PrintlnOp, "println!"); + +impl Grounded for PrintlnOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_UNDEFINED, UNIT_TYPE()]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for PrintlnOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("println! expects single atom as an argument"); + let atom = args.get(0).ok_or_else(arg_error)?; + println!("{}", atom_to_string(atom)); + unit_result() + } +} + +#[derive(Clone, Debug)] +pub struct FormatArgsOp {} + +grounded_op!(FormatArgsOp, "format-args"); + +use dyn_fmt::AsStrFormatExt; + +impl Grounded for FormatArgsOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for FormatArgsOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("format-args expects format string as a first argument and expression as a second argument"); + let format = atom_to_string(args.get(0).ok_or_else(arg_error)?); + let args = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?)?; + let args: Vec = args.children().iter() + .map(|atom| atom_to_string(atom)) + .collect(); + let res = format.format(args.as_slice()); + Ok(vec![Atom::gnd(Str::from_string(res))]) + } +} + +/// Implement trace! built-in. +/// +/// It is equivalent to Idris or Haskell Trace, that is, it prints a +/// message to stderr and pass a value along. +/// +/// For instance +/// ```metta +/// !(trace! "Here?" 42) +/// ``` +/// prints to stderr +/// ```stderr +/// Here? +/// ``` +/// and returns +/// ```metta +/// [42] +/// ``` +/// +/// Note that the first argument does not need to be a string, which +/// makes `trace!` actually quite capable on its own. For instance +/// ```metta +/// !(trace! ("Hello world!" (if True A B) 1 2 3) 42) +/// ``` +/// prints to stderr +/// ```stderr +/// (Hello world! A 1 2 3) +/// ``` +/// and returns +/// ```metta +/// [42] +/// ``` + +#[derive(Clone, Debug)] +pub struct TraceOp {} + +grounded_op!(TraceOp, "trace!"); + +impl Grounded for TraceOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_UNDEFINED, Atom::var("a"), Atom::var("a")]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for TraceOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("trace! expects two atoms as arguments"); + let val = args.get(1).ok_or_else(arg_error)?; + let msg = args.get(0).ok_or_else(arg_error)?; + eprintln!("{}", msg); + Ok(vec![val.clone()]) + } +} + +#[derive(Clone, Debug)] +pub struct NopOp {} + +grounded_op!(NopOp, "nop"); + +impl Grounded for NopOp { + fn type_(&self) -> Atom { + ATOM_TYPE_UNDEFINED + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for NopOp { + fn execute(&self, _args: &[Atom]) -> Result, ExecError> { + unit_result() + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct StateAtom { + state: Rc> +} + +impl StateAtom { + pub fn new(atom: Atom) -> Self { + Self{ state: Rc::new(RefCell::new(atom)) } + } +} + +impl Display for StateAtom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "(State {})", self.state.borrow()) + } +} + +impl Grounded for StateAtom { + fn type_(&self) -> Atom { + // TODO? Wrap metatypes for non-grounded atoms + // rust_type_atom::() instead of StateMonad symbol might be used + let atom = &*self.state.borrow(); + let typ = match atom { + Atom::Symbol(_) => ATOM_TYPE_SYMBOL, + Atom::Expression(_) => ATOM_TYPE_EXPRESSION, + Atom::Variable(_) => ATOM_TYPE_VARIABLE, + Atom::Grounded(a) => a.type_(), + }; + Atom::expr([expr!("StateMonad"), typ]) + } +} + +#[derive(Clone, Debug)] +pub struct NewStateOp { } + +grounded_op!(NewStateOp, "new-state"); + +impl Grounded for NewStateOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, expr!(tnso), expr!("StateMonad" tnso)]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for NewStateOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = "new-state expects single atom as an argument"; + let atom = args.get(0).ok_or(arg_error)?; + Ok(vec![Atom::gnd(StateAtom::new(atom.clone()))]) + } +} + +#[derive(Clone, Debug)] +pub struct GetStateOp { } + +grounded_op!(GetStateOp, "get-state"); + +impl Grounded for GetStateOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, expr!("StateMonad" tgso), expr!(tgso)]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for GetStateOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = "get-state expects single state atom as an argument"; + let state = args.get(0).ok_or(arg_error)?; + let atom = Atom::as_gnd::(state).ok_or(arg_error)?; + Ok(vec![atom.state.borrow().clone()]) + } +} + +#[derive(Clone, Debug)] +pub struct ChangeStateOp { } + +grounded_op!(ChangeStateOp, "change-state!"); + +impl Grounded for ChangeStateOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, expr!("StateMonad" tcso), expr!(tcso), expr!("StateMonad" tcso)]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for ChangeStateOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = "change-state! expects a state atom and its new value as arguments"; + let atom = args.get(0).ok_or(arg_error)?; + let state = Atom::as_gnd::(atom).ok_or("change-state! expects a state as the first argument")?; + let new_value = args.get(1).ok_or(arg_error)?; + *state.state.borrow_mut() = new_value.clone(); + Ok(vec![atom.clone()]) + } +} + +#[derive(Clone, Debug)] +pub struct SealedOp {} + +grounded_op!(SealedOp, "sealed"); + +impl Grounded for SealedOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for SealedOp { + 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) + } +} + +#[derive(Clone, Debug)] +pub struct EqualOp {} + +grounded_op!(EqualOp, "=="); + +impl Grounded for EqualOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, expr!(t), expr!(t), ATOM_TYPE_BOOL]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for EqualOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from(concat!(stringify!($op), " expects two arguments")); + let a = args.get(0).ok_or_else(arg_error)?; + let b = args.get(1).ok_or_else(arg_error)?; + + Ok(vec![Atom::gnd(Bool(a == b))]) + } +} + +#[derive(Clone, Debug)] +pub struct MatchOp {} + +grounded_op!(MatchOp, "match"); + +impl Grounded for MatchOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, rust_type_atom::(), ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_UNDEFINED]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for MatchOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("match expects three arguments: space, pattern and template"); + let space = args.get(0).ok_or_else(arg_error)?; + let pattern = args.get(1).ok_or_else(arg_error)?; + let template = args.get(2).ok_or_else(arg_error)?; + log::debug!("MatchOp::execute: space: {:?}, pattern: {:?}, template: {:?}", space, pattern, template); + let space = Atom::as_gnd::(space).ok_or("match expects a space as the first argument")?; + Ok(space.borrow().subst(&pattern, &template)) + } +} + +/// The op atoms that depend on the pkg_mgmt feature +#[cfg(feature = "pkg_mgmt")] +pub(crate) mod pkg_mgmt_ops { + use super::*; + + /// Provides a way to access [Metta::load_module_at_path] from within MeTTa code + #[derive(Clone, Debug)] + pub struct RegisterModuleOp { + metta: Metta + } + + grounded_op!(RegisterModuleOp, "register-module!"); + + impl RegisterModuleOp { + pub fn new(metta: Metta) -> Self { + Self{ metta } + } + } + + impl Grounded for RegisterModuleOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, UNIT_TYPE()]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } + } + + impl CustomExecute for RegisterModuleOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = "register-module! expects a file system path; use quotes if needed"; + let path_arg_atom = args.get(0).ok_or_else(|| ExecError::from(arg_error))?; + + let path = match path_arg_atom { + Atom::Symbol(path_arg) => path_arg.name(), + Atom::Grounded(g) => g.downcast_ref::().ok_or_else(|| ExecError::from(arg_error))?.as_str(), + _ => return Err(arg_error.into()), + }; + let path = strip_quotes(path); + let path = std::path::PathBuf::from(path); + + // Load the module from the path + // QUESTION: Do we want to expose the ability to give the module a different name and/ or + // load it into a different part of the namespace hierarchy? For now I was just thinking + // it is better to keep the args simple. IMO this is a place for optional var-args when we + // decide on the best way to handle them language-wide. + self.metta.load_module_at_path(path, None).map_err(|e| ExecError::from(e))?; + + unit_result() + } + } + + /// Provides access to module in a remote git repo, from within MeTTa code + /// Similar to `register-module!`, this op will bypass the catalog search + /// + /// NOTE: Even if Hyperon is build without git support, this operation may still be used to + /// load existing modules from a git cache. That situation may occur if modules were fetched + /// earlier or by another tool that manages the module cache. However this operation requres + /// git support to actually clone or pull from a git repository. + #[derive(Clone, Debug)] + pub struct GitModuleOp { + //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP + context: std::sync::Arc>>>>>, + } + + grounded_op!(GitModuleOp, "git-module!"); + + impl GitModuleOp { + pub fn new(metta: Metta) -> Self { + Self{ context: metta.0.context.clone() } + } + } + + impl Grounded for GitModuleOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, UNIT_TYPE()]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } + } + + impl CustomExecute for GitModuleOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = "git-module! expects a URL; use quotes if needed"; + let url_arg_atom = args.get(0).ok_or_else(|| ExecError::from(arg_error))?; + // TODO: When we figure out how to address varargs, it will be nice to take an optional branch name + + let url = match url_arg_atom { + Atom::Symbol(url_arg) => url_arg.name(), + Atom::Grounded(g) => g.downcast_ref::().ok_or_else(|| ExecError::from(arg_error))?.as_str(), + _ => return Err(arg_error.into()), + }; + let url = strip_quotes(url); + + // TODO: Depending on what we do with `register-module!`, we might want to let the + // caller provide an optional mod_name here too, rather than extracting it from the url + let mod_name = match mod_name_from_url(url) { + Some(mod_name) => mod_name, + None => return Err(ExecError::from("git-module! error extracting module name from URL")) + }; + + let ctx_ref = self.context.lock().unwrap().last().unwrap().clone(); + let mut context = ctx_ref.lock().unwrap(); + + let git_mod_location = ModuleGitLocation::new(url.to_string()); + + match context.metta.environment().specified_mods.as_ref() { + Some(specified_mods) => if let Some((loader, descriptor)) = specified_mods.loader_for_explicit_git_module(&mod_name, UpdateMode::TryFetchLatest, &git_mod_location)? { + context.get_or_init_module_with_descriptor(&mod_name, descriptor, loader).map_err(|e| ExecError::from(e))?; + }, + None => return Err(ExecError::from(format!("Unable to pull module \"{mod_name}\" from git; no local \"caches\" directory available"))) + } + + unit_result() + } + } + + pub fn register_pkg_mgmt_tokens(tref: &mut Tokenizer, metta: &Metta) { + let register_module_op = Atom::gnd(RegisterModuleOp::new(metta.clone())); + tref.register_token(regex(r"register-module!"), move |_| { register_module_op.clone() }); + let git_module_op = Atom::gnd(GitModuleOp::new(metta.clone())); + tref.register_token(regex(r"git-module!"), move |_| { git_module_op.clone() }); + } +} + +#[derive(Clone, Debug)] +pub struct UniqueAtomOp {} + +grounded_op!(UniqueAtomOp, "unique-atom"); + +impl Grounded for UniqueAtomOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for UniqueAtomOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("unique expects single expression atom as an argument"); + let expr = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?; + + let mut atoms = expr.children().clone(); + let mut set = GroundingSpace::new(); + atoms.retain(|x| { + let not_contained = set.query(x).is_empty(); + if not_contained { set.add(x.clone()) }; + not_contained + }); + Ok(vec![Atom::expr(atoms)]) + } +} + +#[derive(Clone, Debug)] +pub struct UnionAtomOp {} + +grounded_op!(UnionAtomOp, "union-atom"); + +impl Grounded for UnionAtomOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for UnionAtomOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("union expects and executable LHS and RHS atom"); + let mut lhs = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children().clone(); + let rhs = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?)?.children().clone(); + + lhs.extend(rhs); + + Ok(vec![Atom::expr(lhs)]) + } +} + +#[derive(Clone, Debug)] +pub struct IntersectionAtomOp {} + +grounded_op!(IntersectionAtomOp, "intersection-atom"); + +impl Grounded for IntersectionAtomOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for IntersectionAtomOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("intersection expects and executable LHS and RHS atom"); + let mut lhs = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children().clone(); + let rhs = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?)?.children().clone(); + + let mut rhs_index: MultiTrie> = MultiTrie::new(); + for (index, rhs_item) in rhs.iter().enumerate() { + let k = atom_to_trie_key(&rhs_item); + // FIXME this should + // a) use a mutable value endpoint which the MultiTrie does not support atm + // b) use a linked list, which Rust barely supports atm + let r = rhs_index.get(&k).next(); + match r.cloned() { + Some(bucket) => { + rhs_index.remove(&k, &bucket); + let mut nbucket = bucket; + nbucket.push(index); + let nbucket = nbucket; + rhs_index.insert(k, nbucket); + } + None => { rhs_index.insert(k, vec![index]) } + } + } + + lhs.retain(|candidate| { + let k = atom_to_trie_key(candidate); + let r = rhs_index.get(&k).next(); + match r.cloned() { + None => { false } + Some(bucket) => { + match bucket.iter().position(|item| &rhs[*item] == candidate) { + None => { false } + Some(i) => { + rhs_index.remove(&k, &bucket); + if bucket.len() > 1 { + let mut nbucket = bucket; + nbucket.remove(i); + rhs_index.insert(k, nbucket); + } + true + } + } + } + } + }); + + Ok(vec![Atom::expr(lhs)]) + } +} + +#[derive(Clone, Debug)] +pub struct MaxAtomOp {} + +grounded_op!(MaxAtomOp, "max-atom"); + +impl Grounded for MaxAtomOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for MaxAtomOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("max-atom expects one argument: expression"); + let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children(); + if children.is_empty() { + Err(ExecError::from("Empty expression")) + } else { + children.into_iter().fold(Ok(f64::NEG_INFINITY), |res, x| { + match (res, AsPrimitive::from_atom(x).as_number()) { + (res @ Err(_), _) => res, + (_, None) => Err(ExecError::from("Only numbers are allowed in expression")), + (Ok(max), Some(x)) => Ok(f64::max(max, x.into())), + } + }) + }.map(|max| vec![Atom::gnd(Number::Float(max))]) + } +} + +#[derive(Clone, Debug)] +pub struct MinAtomOp {} + +grounded_op!(MinAtomOp, "min-atom"); + +impl Grounded for MinAtomOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for MinAtomOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("min-atom expects one argument: expression"); + let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children(); + if children.is_empty() { + Err(ExecError::from("Empty expression")) + } else { + children.into_iter().fold(Ok(f64::INFINITY), |res, x| { + match (res, AsPrimitive::from_atom(x).as_number()) { + (res @ Err(_), _) => res, + (_, None) => Err(ExecError::from("Only numbers are allowed in expression")), + (Ok(min), Some(x)) => Ok(f64::min(min, x.into())), + } + }) + }.map(|min| vec![Atom::gnd(Number::Float(min))]) + } +} + +#[derive(Clone, Debug)] +pub struct SizeAtomOp {} + +grounded_op!(SizeAtomOp, "size-atom"); + +impl Grounded for SizeAtomOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for SizeAtomOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("size-atom expects one argument: expression"); + let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children(); + let size = children.len(); + Ok(vec![Atom::gnd(Number::Integer(size as i64))]) + } +} + +#[derive(Clone, Debug)] +pub struct IndexAtomOp {} + +grounded_op!(IndexAtomOp, "index-atom"); + +impl Grounded for IndexAtomOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER, ATOM_TYPE_ATOM]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for IndexAtomOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("index-atom expects two arguments: expression and atom"); + let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children(); + let index = AsPrimitive::from_atom(args.get(1).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?; + match children.get(Into::::into(index) as usize) { + Some(atom) => Ok(vec![atom.clone()]), + None => Err(ExecError::from("Index is out of bounds")), + } + } +} + +#[derive(Clone, Debug)] +pub struct SubtractionAtomOp {} + +grounded_op!(SubtractionAtomOp, "subtraction-atom"); + +impl Grounded for SubtractionAtomOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for SubtractionAtomOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("subtraction expects and executable LHS and RHS atom"); + let mut lhs = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children().clone(); + let rhs = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?)?.children().clone(); + + let mut rhs_index: MultiTrie> = MultiTrie::new(); + for (index, rhs_item) in rhs.iter().enumerate() { + let k = atom_to_trie_key(&rhs_item); + // FIXME this should + // a) use a mutable value endpoint which the MultiTrie does not support atm + // b) use a linked list, which Rust barely supports atm + let r = rhs_index.get(&k).next(); + match r.cloned() { + Some(bucket) => { + rhs_index.remove(&k, &bucket); + let mut nbucket = bucket; + nbucket.push(index); + let nbucket = nbucket; + rhs_index.insert(k, nbucket); + } + None => { rhs_index.insert(k, vec![index]) } + } + } + + lhs.retain(|candidate| { + let k = atom_to_trie_key(candidate); + let r = rhs_index.get(&k).next(); + match r.cloned() { + None => { true } + Some(bucket) => { + match bucket.iter().position(|item| &rhs[*item] == candidate) { + None => { true } + Some(i) => { + rhs_index.remove(&k, &bucket); + if bucket.len() > 1 { + let mut nbucket = bucket; + nbucket.remove(i); + rhs_index.insert(k, nbucket); + } + false + } + } + } + } + }); + + Ok(vec![Atom::expr(lhs)]) + } +} + +//TODO: In the current version of rand it is possible for rust to hang if range end's value is too +// big. In future releases (0.9+) of rand signature of sample_single will be changed and it will be +// possible to use match construction to cover overflow and other errors. So after library will be +// upgraded RandomInt and RandomFloat codes should be altered. +// see comment https://github.com/trueagi-io/hyperon-experimental/pull/791#discussion_r1824355414 +#[derive(Clone, Debug)] +pub struct RandomIntOp {} + +grounded_op!(RandomIntOp, "random-int"); + +impl Grounded for RandomIntOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for RandomIntOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("random-int expects two arguments: number (start) and number (end)"); + let start: i64 = AsPrimitive::from_atom(args.get(0).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?.into(); + let end: i64 = AsPrimitive::from_atom(args.get(1).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?.into(); + let range = start..end; + if range.is_empty() { + return Err(ExecError::from("Range is empty")); + } + let mut rng = rand::thread_rng(); + Ok(vec![Atom::gnd(Number::Integer(rng.gen_range(range)))]) + } +} + +#[derive(Clone, Debug)] +pub struct RandomFloatOp {} + +grounded_op!(RandomFloatOp, "random-float"); + +impl Grounded for RandomFloatOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for RandomFloatOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("random-float expects two arguments: number (start) and number (end)"); + let start: f64 = AsPrimitive::from_atom(args.get(0).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?.into(); + let end: f64 = AsPrimitive::from_atom(args.get(1).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?.into(); + let range = start..end; + if range.is_empty() { + return Err(ExecError::from("Range is empty")); + } + let mut rng = rand::thread_rng(); + Ok(vec![Atom::gnd(Number::Float(rng.gen_range(range)))]) + } +} + #[derive(Clone, Debug)] pub struct PrintAlternativesOp {} @@ -413,57 +1739,57 @@ pub fn register_common_tokens(tref: &mut Tokenizer, _tokenizer: Shared tref.register_token(regex(r"case"), move |_| { case_op.clone() }); let capture_op = Atom::gnd(CaptureOp::new(space.clone())); tref.register_token(regex(r"capture"), move |_| { capture_op.clone() }); - let pragma_op = Atom::gnd(stdlib_old::PragmaOp::new(metta.settings().clone())); + let pragma_op = Atom::gnd(PragmaOp::new(metta.settings().clone())); tref.register_token(regex(r"pragma!"), move |_| { pragma_op.clone() }); - let import_op = Atom::gnd(stdlib_old::ImportOp::new(metta.clone())); + let import_op = Atom::gnd(ImportOp::new(metta.clone())); tref.register_token(regex(r"import!"), move |_| { import_op.clone() }); - let include_op = Atom::gnd(stdlib_old::IncludeOp::new(metta.clone())); + let include_op = Atom::gnd(IncludeOp::new(metta.clone())); tref.register_token(regex(r"include"), move |_| { include_op.clone() }); - let bind_op = Atom::gnd(stdlib_old::BindOp::new(tokenizer.clone())); + let bind_op = Atom::gnd(BindOp::new(tokenizer.clone())); tref.register_token(regex(r"bind!"), move |_| { bind_op.clone() }); - let trace_op = Atom::gnd(stdlib_old::TraceOp{}); + let trace_op = Atom::gnd(TraceOp{}); tref.register_token(regex(r"trace!"), move |_| { trace_op.clone() }); - let println_op = Atom::gnd(stdlib_old::PrintlnOp{}); + let println_op = Atom::gnd(PrintlnOp{}); tref.register_token(regex(r"println!"), move |_| { println_op.clone() }); - let format_args_op = Atom::gnd(stdlib_old::FormatArgsOp{}); + let format_args_op = Atom::gnd(FormatArgsOp{}); tref.register_token(regex(r"format-args"), move |_| { format_args_op.clone() }); let print_alternatives_op = Atom::gnd(PrintAlternativesOp{}); tref.register_token(regex(r"print-alternatives!"), move |_| { print_alternatives_op.clone() }); - let sealed_op = Atom::gnd(stdlib_old::SealedOp{}); + let sealed_op = Atom::gnd(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. @@ -543,7 +1869,7 @@ pub fn register_rust_stdlib_tokens(target: &mut Tokenizer) { tref.register_token(regex(r"<="), move |_| { le_op.clone() }); let ge_op = Atom::gnd(GreaterEqOp{}); tref.register_token(regex(r">="), move |_| { ge_op.clone() }); - let eq_op = Atom::gnd(stdlib_old::EqualOp{}); + let eq_op = Atom::gnd(EqualOp{}); tref.register_token(regex(r"=="), move |_| { eq_op.clone() }); let and_op = Atom::gnd(AndOp{}); tref.register_token(regex(r"and"), move |_| { and_op.clone() }); @@ -668,29 +1994,29 @@ mod tests { #[test] fn metta_min_atom() { assert_eq!(run_program(&format!("!(min-atom (5 4 5.5))")), Ok(vec![vec![expr!({Number::Integer(4)})]])); - assert_eq!(run_program(&format!("!(min-atom ())")), Ok(vec![vec![expr!("Error" ({ stdlib_old::MinAtomOp{} } ()) "Empty expression")]])); - assert_eq!(run_program(&format!("!(min-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ stdlib_old::MinAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers are allowed in expression")]])); + assert_eq!(run_program(&format!("!(min-atom ())")), Ok(vec![vec![expr!("Error" ({ MinAtomOp{} } ()) "Empty expression")]])); + assert_eq!(run_program(&format!("!(min-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ MinAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers are allowed in expression")]])); } #[test] fn metta_max_atom() { assert_eq!(run_program(&format!("!(max-atom (5 4 5.5))")), Ok(vec![vec![expr!({Number::Float(5.5)})]])); - assert_eq!(run_program(&format!("!(max-atom ())")), Ok(vec![vec![expr!("Error" ({ stdlib_old::MaxAtomOp{} } ()) "Empty expression")]])); - assert_eq!(run_program(&format!("!(max-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ stdlib_old::MaxAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers are allowed in expression")]])); + assert_eq!(run_program(&format!("!(max-atom ())")), Ok(vec![vec![expr!("Error" ({ MaxAtomOp{} } ()) "Empty expression")]])); + assert_eq!(run_program(&format!("!(max-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ MaxAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers are allowed in expression")]])); } #[test] fn metta_index_atom() { assert_eq!(run_program(&format!("!(index-atom (5 4 3 2 1) 2)")), Ok(vec![vec![expr!({Number::Integer(3)})]])); - assert_eq!(run_program(&format!("!(index-atom (A B C D E) 5)")), Ok(vec![vec![expr!("Error" ({ stdlib_old::IndexAtomOp{} } ("A" "B" "C" "D" "E") {Number::Integer(5)}) "Index is out of bounds")]])); + assert_eq!(run_program(&format!("!(index-atom (A B C D E) 5)")), Ok(vec![vec![expr!("Error" ({ IndexAtomOp{} } ("A" "B" "C" "D" "E") {Number::Integer(5)}) "Index is out of bounds")]])); } #[test] fn metta_random() { assert_eq!(run_program(&format!("!(chain (eval (random-int 0 5)) $rint (and (>= $rint 0) (< $rint 5)))")), Ok(vec![vec![expr!({Bool(true)})]])); - assert_eq!(run_program(&format!("!(random-int 0 0)")), Ok(vec![vec![expr!("Error" ({ stdlib_old::RandomIntOp{} } {Number::Integer(0)} {Number::Integer(0)}) "Range is empty")]])); + assert_eq!(run_program(&format!("!(random-int 0 0)")), Ok(vec![vec![expr!("Error" ({ RandomIntOp{} } {Number::Integer(0)} {Number::Integer(0)}) "Range is empty")]])); assert_eq!(run_program(&format!("!(chain (eval (random-float 0.0 5.0)) $rfloat (and (>= $rfloat 0.0) (< $rfloat 5.0)))")), Ok(vec![vec![expr!({Bool(true)})]])); - assert_eq!(run_program(&format!("!(random-float 0 -5)")), Ok(vec![vec![expr!("Error" ({ stdlib_old::RandomFloatOp{} } {Number::Integer(0)} {Number::Integer(-5)}) "Range is empty")]])); + assert_eq!(run_program(&format!("!(random-float 0 -5)")), Ok(vec![vec![expr!("Error" ({ RandomFloatOp{} } {Number::Integer(0)} {Number::Integer(-5)}) "Range is empty")]])); } #[test] @@ -1139,9 +2465,25 @@ mod tests { Ok(vec![vec![expr!("Error" ("foo" "a" "b" "c") "IncorrectNumberOfArguments")]])); } + #[test] + fn sealed_op_runner() { + let nested = run_program("!(sealed ($x) (sealed ($a $b) (quote (= ($a $x $c) ($b)))))"); + let simple_replace = run_program("!(sealed ($x $y) (quote (= ($y $z))))"); + + assert!(crate::atom::matcher::atoms_are_equivalent(&nested.unwrap()[0][0], &expr!("quote" ("=" (a b c) (z))))); + assert!(crate::atom::matcher::atoms_are_equivalent(&simple_replace.unwrap()[0][0], &expr!("quote" ("=" (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![]])); + assert_eq!(run_program("!(let $x (input $y) (output $x))"), Ok(vec![vec![expr!("output" ("input" y))]])); assert_eq!(run_program("!(let (quote ($sv $st)) (sealed ($x) (quote ($x (output $x)))) (let $sv (input $x) $st))"), Ok(vec![vec![expr!("output" ("input" x))]])); } @@ -1390,4 +2732,402 @@ mod tests { let stdlib_space = runner.module_space(runner.get_module_by_name("stdlib").unwrap()); assert_eq!(result[2], vec![Atom::gnd(stdlib_space)]); } + + #[test] + fn match_op() { + let space = DynSpace::new(metta_space("(A B)")); + let match_op = MatchOp{}; + assert_eq!(match_op.execute(&mut vec![expr!({space}), expr!("A" "B"), expr!("B" "A")]), + Ok(vec![expr!("B" "A")])); + } + + #[test] + fn match_op_issue_530() { + let space = DynSpace::new(metta_space("(A $a $a)")); + let match_op = MatchOp{}; + let result = match_op.execute(&mut vec![expr!({space}), expr!("A" x y), expr!("A" x y)]).unwrap(); + assert_eq!(result.len(), 1); + assert!(atoms_are_equivalent(&result[0], &expr!("A" x x)), + "atoms are not equivalent: expected: {}, actual: {}", expr!("A" x x), result[0]); + } + + + #[test] + fn new_space_op() { + let res = NewSpaceOp{}.execute(&mut vec![]).expect("No result returned"); + let space = res.get(0).expect("Result is empty"); + let space = space.as_gnd::().expect("Result is not space"); + let space_atoms: Vec = space.borrow().as_space().atom_iter().unwrap().cloned().collect(); + assert_eq_no_order!(space_atoms, Vec::::new()); + } + + #[test] + fn add_atom_op() { + let space = DynSpace::new(GroundingSpace::new()); + let satom = Atom::gnd(space.clone()); + let res = AddAtomOp{}.execute(&mut vec![satom, expr!(("foo" "bar"))]).expect("No result returned"); + assert_eq!(res, vec![UNIT_ATOM()]); + let space_atoms: Vec = space.borrow().as_space().atom_iter().unwrap().cloned().collect(); + assert_eq_no_order!(space_atoms, vec![expr!(("foo" "bar"))]); + } + + #[test] + fn remove_atom_op() { + let space = DynSpace::new(metta_space(" + (foo bar) + (bar foo) + ")); + let satom = Atom::gnd(space.clone()); + let res = RemoveAtomOp{}.execute(&mut vec![satom, expr!(("foo" "bar"))]).expect("No result returned"); + // REM: can return Bool in future + assert_eq!(res, vec![UNIT_ATOM()]); + let space_atoms: Vec = space.borrow().as_space().atom_iter().unwrap().cloned().collect(); + assert_eq_no_order!(space_atoms, vec![expr!(("bar" "foo"))]); + } + + #[test] + fn get_atoms_op() { + let space = DynSpace::new(metta_space(" + (foo bar) + (bar foo) + ")); + let satom = Atom::gnd(space.clone()); + let res = GetAtomsOp{}.execute(&mut vec![satom]).expect("No result returned"); + let space_atoms: Vec = space.borrow().as_space().atom_iter().unwrap().cloned().collect(); + assert_eq_no_order!(res, space_atoms); + assert_eq_no_order!(res, vec![expr!(("foo" "bar")), expr!(("bar" "foo"))]); + } + + #[test] + fn bind_new_space_op() { + let tokenizer = Shared::new(Tokenizer::new()); + + let bind_op = BindOp::new(tokenizer.clone()); + + assert_eq!(bind_op.execute(&mut vec![sym!("&my"), sym!("definition")]), unit_result()); + let borrowed = tokenizer.borrow(); + let constr = borrowed.find_token("&my"); + assert!(constr.is_some()); + assert_eq!(constr.unwrap()("&my"), Ok(sym!("definition"))); + } + + fn assert_runtime_error(actual: Result, ExecError>, expected: Regex) { + match actual { + Err(ExecError::Runtime(msg)) => assert!(expected.is_match(msg.as_str()), + "Incorrect error message:\nexpected: {:?}\n actual: {:?}", expected.to_string(), msg), + _ => assert!(false, "Error is expected as result, {:?} returned", actual), + } + } + + #[test] + fn assert_equal_op() { + let space = DynSpace::new(metta_space(" + (= (foo) (A B)) + (= (foo) (B C)) + (= (bar) (B C)) + (= (bar) (A B)) + (= (err) (A B)) + ")); + + let assert_equal_op = AssertEqualOp::new(space); + + assert_eq!(assert_equal_op.execute(&mut vec![expr!(("foo")), expr!(("bar"))]), unit_result()); + + let actual = assert_equal_op.execute(&mut vec![expr!(("foo")), expr!(("err"))]); + let expected = Regex::new("\nExpected: \\[(A B)\\]\nGot: \\[\\((B C)|, |(A B)\\){3}\\]\nExcessive result: (B C)").unwrap(); + assert_runtime_error(actual, expected); + + let actual = assert_equal_op.execute(&mut vec![expr!(("err")), expr!(("foo"))]); + let expected = Regex::new("\nExpected: \\[\\((B C)|, |(A B)\\){3}\\]\nGot: \\[(A B)\\]\nMissed result: (B C)").unwrap(); + assert_runtime_error(actual, expected); + } + + #[test] + fn assert_equal_to_result_op() { + let space = DynSpace::new(metta_space(" + (= (foo) (A B)) + (= (foo) (B C)) + ")); + let assert_equal_to_result_op = AssertEqualToResultOp::new(space); + + assert_eq!(assert_equal_to_result_op.execute(&mut vec![ + expr!(("foo")), expr!(("B" "C") ("A" "B"))]), + unit_result()); + } + + #[test] + fn unique_op() { + let unique_op = UniqueAtomOp{}; + let actual = unique_op.execute(&mut vec![expr!( + ("A" ("B" "C")) + ("A" ("B" "C")) + ("f" "g") + ("f" "g") + ("f" "g") + "Z" + )]).unwrap(); + assert_eq_no_order!(actual, + vec![expr!(("A" ("B" "C")) ("f" "g") "Z")]); + } + + #[test] + fn union_op() { + let union_op = UnionAtomOp{}; + let actual = union_op.execute(&mut vec![expr!( + ("A" ("B" "C")) + ("A" ("B" "C")) + ("f" "g") + ("f" "g") + ("f" "g") + "Z" + ), expr!( + ("A" ("B" "C")) + "p" + "p" + ("Q" "a") + )]).unwrap(); + assert_eq_no_order!(actual, + vec![expr!(("A" ("B" "C")) ("A" ("B" "C")) + ("f" "g") ("f" "g") ("f" "g") "Z" + ("A" ("B" "C")) "p" "p" ("Q" "a"))]); + } + + #[test] + fn intersection_op() { + let intersection_op = IntersectionAtomOp{}; + let actual = intersection_op.execute(&mut vec![expr!( + "Z" + ("A" ("B" "C")) + ("A" ("B" "C")) + ("f" "g") + ("f" "g") + ("f" "g") + ("P" "b") + ), expr!( + ("f" "g") + ("f" "g") + ("A" ("B" "C")) + "p" + "p" + ("Q" "a") + "Z" + )]).unwrap(); + assert_eq_no_order!(actual, vec![expr!("Z" ("A" ("B" "C")) ("f" "g") ("f" "g"))]); + + assert_eq_no_order!(intersection_op.execute(&mut vec![expr!( + { Number::Integer(5) } + { Number::Integer(4) } + { Number::Integer(3) } + { Number::Integer(2) } + ), expr!( + { Number::Integer(5) } + { Number::Integer(3) } + )]).unwrap(), vec![expr!({Number::Integer(5)} {Number::Integer(3)})]); + } + + #[test] + fn subtraction_op() { + let subtraction_op = SubtractionAtomOp{}; + let actual = subtraction_op.execute(&mut vec![expr!( + "Z" + "S" + "S" + ("A" ("B" "C")) + ("A" ("B" "C")) + ("f" "g") + ("f" "g") + ("f" "g") + ("P" "b") + ), expr!( + ("f" "g") + ("A" ("B" "C")) + "p" + "P" + ("Q" "a") + "Z" + "S" + "S" + "S" + )]).unwrap(); + assert_eq_no_order!(actual, + vec![expr!(("A" ("B" "C")) ("f" "g") ("f" "g") ("P" "b"))]); + } + + #[test] + fn println_op() { + assert_eq!(PrintlnOp{}.execute(&mut vec![sym!("A")]), unit_result()); + } + + #[test] + fn trace_op() { + assert_eq!(TraceOp{}.execute(&mut vec![sym!("\"Here?\""), sym!("42")]), + Ok(vec![sym!("42")])); + } + + #[test] + fn nop_op() { + assert_eq!(NopOp{}.execute(&mut vec![]), unit_result()); + } + + #[test] + fn let_op_keep_variables_equalities_issue290() { + assert_eq_metta_results!(run_program("!(let* (($f f) ($f $x)) $x)"), Ok(vec![vec![expr!("f")]])); + assert_eq_metta_results!(run_program("!(let* (($f $x) ($f f)) $x)"), Ok(vec![vec![expr!("f")]])); + assert_eq_metta_results!(run_program("!(let (quote ($x $x)) (quote ($z $y)) (let $y A ($z $y)))"), Ok(vec![vec![expr!("A" "A")]])); + assert_eq_metta_results!(run_program("!(let (quote ($x $x)) (quote ($z $y)) (let $z A ($z $y)))"), Ok(vec![vec![expr!("A" "A")]])); + } + + #[test] + fn let_op_variables_visibility_pr262() { + let program = " + ;; Knowledge + (→ P Q) + (→ Q R) + + ;; Rule + (= (rule (→ $p $q) (→ $q $r)) (→ $p $r)) + + ;; Query (does not work as expected) + (= (query $kb) + (let* (($pq (→ $p $q)) + ($qr (→ $q $r))) + (match $kb + ;; Premises + (, $pq $qr) + ;; Conclusion + (rule $pq $qr)))) + + ;; Call + !(query &self) + ;; [(→ P R)] + "; + assert_eq_metta_results!(run_program(program), Ok(vec![vec![expr!("→" "P" "R")]])); + } + + #[test] + fn state_ops() { + let result = NewStateOp{}.execute(&mut vec![expr!("A" "B")]).unwrap(); + let old_state = result.get(0).ok_or("error").unwrap(); + assert_eq!(old_state, &Atom::gnd(StateAtom::new(expr!("A" "B")))); + let result = ChangeStateOp{}.execute(&mut vec!(old_state.clone(), expr!("C" "D"))).unwrap(); + let new_state = result.get(0).ok_or("error").unwrap(); + assert_eq!(old_state, new_state); + assert_eq!(new_state, &Atom::gnd(StateAtom::new(expr!("C" "D")))); + let result = GetStateOp{}.execute(&mut vec![new_state.clone()]); + assert_eq!(result, Ok(vec![expr!("C" "D")])) + } + + #[test] + fn test_stdlib_uses_rust_grounded_tokens() { + assert_eq!(run_program("!(if True ok nok)"), Ok(vec![vec![Atom::sym("ok")]])); + } + + #[test] + fn test_let_op_inside_other_operation() { + assert_eq!(run_program("!(and True (let $x False $x))"), Ok(vec![vec![expr!({Bool(false)})]])); + } + + #[test] + fn test_quote() { + let metta = Metta::new(Some(EnvBuilder::test_env())); + let parser = SExprParser::new(" + (= (foo) a) + (= (foo) b) + !(foo) + !(quote (foo)) + "); + + assert_eq_metta_results!(metta.run(parser), + Ok(vec![ + vec![expr!("a"), expr!("b")], + vec![expr!("quote" ("foo"))], + ])); + } + + #[test] + fn test_unify() { + let metta = Metta::new(Some(EnvBuilder::test_env())); + let parser = SExprParser::new(" + !(unify (a $b 1 (d)) (a $a 1 (d)) ok nok) + !(unify (a $b c) (a b $c) (ok $b $c) nok) + !(unify $a (a b c) (ok $a) nok) + !(unify (a b c) $a (ok $a) nok) + !(unify (a b c) (a b d) ok nok) + !(unify ($x a) (b $x) ok nok) + "); + + assert_eq_metta_results!(metta.run(parser), + Ok(vec![ + vec![expr!("ok")], + vec![expr!("ok" "b" "c")], + vec![expr!("ok" ("a" "b" "c"))], + vec![expr!("ok" ("a" "b" "c"))], + vec![expr!("nok")], + vec![expr!("nok")] + ])); + } + + #[test] + fn test_empty() { + let metta = Metta::new(Some(EnvBuilder::test_env())); + let parser = SExprParser::new(" + !(empty) + "); + + assert_eq_metta_results!(metta.run(parser), + Ok(vec![vec![]])); + } + + #[test] + fn random_op() { + let res = RandomIntOp{}.execute(&mut vec![expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]); + let range = 0..5; + let res_i64: i64 = AsPrimitive::from_atom(res.unwrap().get(0).unwrap()).as_number().unwrap().into(); + assert!(range.contains(&res_i64)); + let res = RandomIntOp{}.execute(&mut vec![expr!({Number::Integer(2)}), expr!({Number::Integer(-2)})]); + assert_eq!(res, Err(ExecError::from("Range is empty"))); + + let res = RandomFloatOp{}.execute(&mut vec![expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]); + let range = 0.0..5.0; + let res_f64: f64 = AsPrimitive::from_atom(res.unwrap().get(0).unwrap()).as_number().unwrap().into(); + assert!(range.contains(&res_f64)); + let res = RandomFloatOp{}.execute(&mut vec![expr!({Number::Integer(0)}), expr!({Number::Integer(0)})]); + assert_eq!(res, Err(ExecError::from("Range is empty"))); + } + + #[test] + fn size_atom_op() { + let res = SizeAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)})]).expect("No result returned"); + assert_eq!(res, vec![expr!({Number::Integer(5)})]); + let res = SizeAtomOp{}.execute(&mut vec![expr!()]).expect("No result returned"); + assert_eq!(res, vec![expr!({Number::Integer(0)})]); + } + + #[test] + fn min_atom_op() { + let res = MinAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Float(5.5)})]).expect("No result returned"); + assert_eq!(res, vec![expr!({Number::Integer(4)})]); + let res = MinAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} "A")]); + assert_eq!(res, Err(ExecError::from("Only numbers are allowed in expression"))); + let res = MinAtomOp{}.execute(&mut vec![expr!()]); + assert_eq!(res, Err(ExecError::from("Empty expression"))); + } + + #[test] + fn max_atom_op() { + let res = MaxAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Float(5.5)})]).expect("No result returned"); + assert_eq!(res, vec![expr!({Number::Float(5.5)})]); + let res = MaxAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} "A")]); + assert_eq!(res, Err(ExecError::from("Only numbers are allowed in expression"))); + let res = MaxAtomOp{}.execute(&mut vec![expr!()]); + assert_eq!(res, Err(ExecError::from("Empty expression"))); + } + + #[test] + fn index_atom_op() { + let res = IndexAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)}), expr!({Number::Integer(2)})]).expect("No result returned"); + assert_eq!(res, vec![expr!({Number::Integer(3)})]); + let res = IndexAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)}), expr!({Number::Integer(5)})]); + assert_eq!(res, Err(ExecError::from("Index is out of bounds"))); + } } diff --git a/lib/src/metta/runner/stdlib_old.rs b/lib/src/metta/runner/stdlib_old.rs deleted file mode 100644 index 0fbcc31d9..000000000 --- a/lib/src/metta/runner/stdlib_old.rs +++ /dev/null @@ -1,2023 +0,0 @@ -use crate::*; -use crate::space::*; -use crate::metta::*; -use crate::metta::text::Tokenizer; -use crate::metta::runner::{Metta, RunContext, ResourceKey}; -use crate::metta::runner::string::Str; -use crate::metta::types::{get_atom_types, get_meta_type}; -use crate::common::shared::Shared; -use crate::common::CachingMapper; -use crate::common::multitrie::MultiTrie; -use crate::space::grounding::atom_to_trie_key; - -#[cfg(feature = "pkg_mgmt")] -use crate::metta::runner::{git_catalog::ModuleGitLocation, mod_name_from_url, pkg_mgmt::UpdateMode}; - -use std::rc::Rc; -use std::cell::RefCell; -use std::fmt::Display; -use std::collections::HashMap; -use regex::Regex; -use rand::Rng; - -use super::arithmetics::*; -use super::string::*; -use super::stdlib_minimal::*; - -macro_rules! grounded_op { - ($name:ident, $disp:literal) => { - impl PartialEq for $name { - fn eq(&self, _other: &Self) -> bool { - true - } - } - - impl std::fmt::Display for $name { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, $disp) - } - } - } -} - -#[derive(Clone, Debug)] -pub struct ImportOp { - //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP - context: std::sync::Arc>>>>>, -} - -grounded_op!(ImportOp, "import!"); - -impl ImportOp { - pub fn new(metta: Metta) -> Self { - Self{ context: metta.0.context.clone() } - } -} - -impl Grounded for ImportOp { - fn type_(&self) -> Atom { - //TODO: Ideally the "import as" / "import into" part would be optional - //A deeper discussion on arg semantics as it relates to import! is here: - // https://github.com/trueagi-io/hyperon-experimental/pull/580#discussion_r1491332304 - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, UNIT_TYPE()]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for ImportOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - //QUESTION: "Import" can mean several (3) different things. In Python parlance, it can mean - //1. "import module" opt. ("as bar") - //2. "from module import foo" opt. ("as bar") - //3. "from module import *" - // - //Do we want one MeTTa operation with multiple ways of invoking it? Or do we want different - // implementations for different versions of the import operation? (since we don't have key-words) - // like "from" and "as" (unless we want to add them) - // - //The old version of this operation supported 1. or 3., depending on whether a "space" argument - // mapped to an atom that already existed or not. If the space atom existed and was a Space, then - // the operation would perform behavior 3 (by importing only the space atom and no token). - // Otherwise it would perform behavior 1, by adding a token, but not adding the child space atom to - // the parent space. - // - //For now, in order to not lose functionality, I have kept this behavior. - // - // ** TO SUMMARIZE ** - // If the destination argument is the &self Space atom, the behavior is (3) ie "from module import *", - // and if the destination argument is a Symbol atom, the behavior is (1) ie "import module as foo" - // - //The Underlying functionality for behavior 2 exists in MettaMod::import_item_from_dependency_as, - // but it isn't called yet because I wanted to discuss the way to expose it as a MeTTa op. - //For behavior 3, there are deeper questions about desired behavior around tokenizer entries, - // transitive imports, etc. I have summarized those concerns in the discussion comments above - // MettaMod::import_all_from_dependency - // - - let arg_error = || ExecError::from("import! expects a destination &space and a module name argument"); - let dest_arg = args.get(0).ok_or_else(arg_error)?; - let mod_name_atom = args.get(1).ok_or_else(arg_error)?; - - // TODO: replace Symbol by grounded String? - let mod_name = match mod_name_atom { - Atom::Symbol(mod_name) => mod_name.name(), - _ => return Err("import! expects a module name as the first argument".into()) - }; - let mod_name = strip_quotes(mod_name); - - // Load the module into the runner, or get the ModId if it's already loaded - //TODO: Remove this hack to access the RunContext, when it's part of the arguments to `execute` - let ctx_ref = self.context.lock().unwrap().last().unwrap().clone(); - let mut context = ctx_ref.lock().unwrap(); - let mod_id = context.load_module(mod_name)?; - - // Import the module, as per the behavior described above - match dest_arg { - Atom::Symbol(dest_sym) => { - context.import_dependency_as(mod_id, Some(dest_sym.name().to_string()))?; - } - other_atom => { - match &other_atom { - Atom::Grounded(_) if Atom::as_gnd::(other_atom) == Some(context.module().space()) => { - context.import_all_from_dependency(mod_id)?; - }, - _ => { - return Err(format!("import! destination argument must be a symbol atom naming a new space, or &self. Found: {other_atom:?}").into()); - } - } - } - // None => { - // //TODO: Currently this pattern is unreachable on account of arity-checking in the MeTTa - // // interpreter, but I have the code path in here for when it is possible - // context.module().import_dependency_as(&context.metta, mod_id, None)?; - // }, - } - - unit_result() - } -} - -#[derive(Clone, Debug)] -pub struct IncludeOp { - //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP - context: std::sync::Arc>>>>>, -} - -grounded_op!(IncludeOp, "include"); - -impl IncludeOp { - pub fn new(metta: Metta) -> Self { - Self{ context: metta.0.context.clone() } - } -} - -impl Grounded for IncludeOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for IncludeOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("include expects a module name argument"); - let mod_name_atom = args.get(0).ok_or_else(arg_error)?; - - // TODO: replace Symbol by grounded String? - let mod_name = match mod_name_atom { - Atom::Symbol(mod_name) => mod_name.name(), - _ => return Err(arg_error()) - }; - let mod_name = strip_quotes(mod_name); - - //TODO: Remove this hack to access the RunContext, when it's part of the arguments to `execute` - let ctx_ref = self.context.lock().unwrap().last().unwrap().clone(); - let mut context = ctx_ref.lock().unwrap(); - let program_buf = context.load_resource_from_module(mod_name, ResourceKey::MainMettaSrc)?; - - // Interpret the loaded MeTTa S-Expression text - let program_text = String::from_utf8(program_buf) - .map_err(|e| e.to_string())?; - let parser = crate::metta::text::OwnedSExprParser::new(program_text); - let eval_result = context.run_inline(|context| { - context.push_parser(Box::new(parser)); - Ok(()) - })?; - - //NOTE: Current behavior returns the result of the last sub-eval to match the old - // `import!` before before module isolation. However that means the results prior to - // the last are dropped. I don't know how to fix this or if it's even wrong, but it's - // different from the way "eval-type" APIs work when called from host code, e.g. Rust - Ok(eval_result.into_iter().last().unwrap_or_else(|| vec![])) - } -} - -/// mod-space! returns the space of a specified module, loading the module if it's not loaded already -//NOTE: The "impure" '!' denoted in the op atom name is due to the side effect of loading the module. If -// we want a side-effect-free version, it could be implemented by calling `RunContext::get_module_by_name` -// instead of `RunContext::load_module`, but then the user would need to use `register-module!`, `import!`, -// or some other mechanism to make sure the module is loaded in advance. -#[derive(Clone, Debug)] -pub struct ModSpaceOp { - //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP - context: std::sync::Arc>>>>>, -} - -grounded_op!(ModSpaceOp, "mod-space!"); - -impl ModSpaceOp { - pub fn new(metta: Metta) -> Self { - Self{ context: metta.0.context.clone() } - } -} - -impl Grounded for ModSpaceOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, rust_type_atom::()]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for ModSpaceOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = "mod-space! expects a module name argument"; - let mod_name_atom = args.get(0).ok_or_else(|| ExecError::from(arg_error))?; - - // TODO: replace Symbol by grounded String? - let mod_name = match mod_name_atom { - Atom::Symbol(mod_name) => mod_name.name(), - _ => {return Err(ExecError::from(arg_error))} - }; - let mod_name = strip_quotes(mod_name); - - // Load the module into the runner, or get the ModId if it's already loaded - //TODO: Remove this hack to access the RunContext, when it's part of the arguments to `execute` - let ctx_ref = self.context.lock().unwrap().last().unwrap().clone(); - let mut context = ctx_ref.lock().unwrap(); - let mod_id = context.load_module(mod_name)?; - - let space = Atom::gnd(context.metta().module_space(mod_id)); - Ok(vec![space]) - } -} - -/// This operation prints the modules loaded from the top of the runner -/// -/// NOTE: This is a temporary stop-gap to help MeTTa users inspect which modules they have loaded and -/// debug module import issues. Ultimately it probably makes sense to make this information accessible -/// as a special kind of Space, so that it would be possible to work with it programmatically. -#[derive(Clone, Debug)] -pub struct PrintModsOp { - metta: Metta -} - -grounded_op!(PrintModsOp, "print-mods!"); - -impl PrintModsOp { - pub fn new(metta: Metta) -> Self { - Self{ metta } - } -} - -impl Grounded for PrintModsOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, UNIT_TYPE()]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for PrintModsOp { - fn execute(&self, _args: &[Atom]) -> Result, ExecError> { - self.metta.display_loaded_modules(); - unit_result() - } -} - -#[derive(Clone, Debug)] -pub struct BindOp { - tokenizer: Shared, -} - -grounded_op!(BindOp, "bind!"); - -impl BindOp { - pub fn new(tokenizer: Shared) -> Self { - Self{ tokenizer } - } -} - -impl Grounded for BindOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_SYMBOL, ATOM_TYPE_UNDEFINED, UNIT_TYPE()]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for BindOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("bind! expects two arguments: token and atom"); - let token = <&SymbolAtom>::try_from(args.get(0).ok_or_else(arg_error)?).map_err(|_| "bind! expects symbol atom as a token")?.name(); - let atom = args.get(1).ok_or_else(arg_error)?.clone(); - - let token_regex = Regex::new(token).map_err(|err| format!("Could convert token {} into regex: {}", token, err))?; - self.tokenizer.borrow_mut().register_token(token_regex, move |_| { atom.clone() }); - unit_result() - } -} - -#[derive(Clone, Debug)] -pub struct NewSpaceOp {} - -grounded_op!(NewSpaceOp, "new-space"); - -impl Grounded for NewSpaceOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, rust_type_atom::()]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for NewSpaceOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - if args.len() == 0 { - let space = Atom::gnd(DynSpace::new(GroundingSpace::new())); - Ok(vec![space]) - } else { - Err("new-space doesn't expect arguments".into()) - } - } -} - -#[derive(Clone, Debug)] -pub struct AddAtomOp {} - -grounded_op!(AddAtomOp, "add-atom"); - -impl Grounded for AddAtomOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, rust_type_atom::(), - ATOM_TYPE_ATOM, UNIT_TYPE()]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for AddAtomOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("add-atom expects two arguments: space and atom"); - let space = args.get(0).ok_or_else(arg_error)?; - let atom = args.get(1).ok_or_else(arg_error)?; - let space = Atom::as_gnd::(space).ok_or("add-atom expects a space as the first argument")?; - space.borrow_mut().add(atom.clone()); - unit_result() - } -} - -#[derive(Clone, Debug)] -pub struct RemoveAtomOp {} - -grounded_op!(RemoveAtomOp, "remove-atom"); - -impl Grounded for RemoveAtomOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, rust_type_atom::(), - ATOM_TYPE_ATOM, UNIT_TYPE()]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for RemoveAtomOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("remove-atom expects two arguments: space and atom"); - let space = args.get(0).ok_or_else(arg_error)?; - let atom = args.get(1).ok_or_else(arg_error)?; - let space = Atom::as_gnd::(space).ok_or("remove-atom expects a space as the first argument")?; - space.borrow_mut().remove(atom); - // TODO? Is it necessary to distinguish whether the atom was removed or not? - unit_result() - } -} - -#[derive(Clone, Debug)] -pub struct GetAtomsOp {} - -grounded_op!(GetAtomsOp, "get-atoms"); - -impl Grounded for GetAtomsOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, rust_type_atom::(), - ATOM_TYPE_ATOM]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for GetAtomsOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("get-atoms expects one argument: space"); - let space = args.get(0).ok_or_else(arg_error)?; - let space = Atom::as_gnd::(space).ok_or("get-atoms expects a space as its argument")?; - space.borrow().as_space().atom_iter() - .map(|iter| iter.cloned().map(|a| make_variables_unique(a)).collect()) - .ok_or(ExecError::Runtime("Unsupported Operation. Can't traverse atoms in this space".to_string())) - } -} - -#[derive(Clone, Debug)] -pub struct PragmaOp { - settings: Shared>, -} - -grounded_op!(PragmaOp, "pragma!"); - -impl PragmaOp { - pub fn new(settings: Shared>) -> Self { - Self{ settings } - } -} - -impl Grounded for PragmaOp { - fn type_(&self) -> Atom { - ATOM_TYPE_UNDEFINED - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for PragmaOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("pragma! expects key and value as arguments"); - let key = <&SymbolAtom>::try_from(args.get(0).ok_or_else(arg_error)?).map_err(|_| "pragma! expects symbol atom as a key")?.name(); - let value = args.get(1).ok_or_else(arg_error)?; - self.settings.borrow_mut().insert(key.into(), value.clone()); - unit_result() - } -} - -#[derive(Clone, Debug)] -pub struct GetTypeOp { - space: DynSpace, -} - -grounded_op!(GetTypeOp, "get-type"); - -impl GetTypeOp { - pub fn new(space: DynSpace) -> Self { - Self{ space } - } -} - -impl Grounded for GetTypeOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for GetTypeOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("get-type expects single atom as an argument"); - let atom = args.get(0).ok_or_else(arg_error)?; - - Ok(get_atom_types(self.space.borrow().as_space(), atom)) - } -} - -#[derive(Clone, Debug)] -pub struct GetTypeSpaceOp {} - -grounded_op!(GetTypeSpaceOp, "get-type-space"); - -impl Grounded for GetTypeSpaceOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, rust_type_atom::(), ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for GetTypeSpaceOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("get-type-space expects two arguments: space and atom"); - let space = args.get(0).ok_or_else(arg_error)?; - let space = Atom::as_gnd::(space).ok_or("get-type-space expects a space as the first argument")?; - let atom = args.get(1).ok_or_else(arg_error)?; - log::debug!("GetTypeSpaceOp::execute: space: {}, atom: {}", space, atom); - - Ok(get_atom_types(space, atom)) - } -} - -#[derive(Clone, Debug)] -pub struct GetMetaTypeOp { } - -grounded_op!(GetMetaTypeOp, "get-metatype"); - -impl Grounded for GetMetaTypeOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for GetMetaTypeOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("get-metatype expects single atom as an argument"); - let atom = args.get(0).ok_or_else(arg_error)?; - - Ok(vec![get_meta_type(&atom)]) - } -} - - -#[derive(Clone, Debug)] -pub struct PrintlnOp {} - -grounded_op!(PrintlnOp, "println!"); - -impl Grounded for PrintlnOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_UNDEFINED, UNIT_TYPE()]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for PrintlnOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("println! expects single atom as an argument"); - let atom = args.get(0).ok_or_else(arg_error)?; - println!("{}", atom_to_string(atom)); - unit_result() - } -} - -#[derive(Clone, Debug)] -pub struct FormatArgsOp {} - -grounded_op!(FormatArgsOp, "format-args"); - -use dyn_fmt::AsStrFormatExt; - -impl Grounded for FormatArgsOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for FormatArgsOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("format-args expects format string as a first argument and expression as a second argument"); - let format = atom_to_string(args.get(0).ok_or_else(arg_error)?); - let args = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?)?; - let args: Vec = args.children().iter() - .map(|atom| atom_to_string(atom)) - .collect(); - let res = format.format(args.as_slice()); - Ok(vec![Atom::gnd(Str::from_string(res))]) - } -} - -fn atom_to_string(atom: &Atom) -> String { - match atom { - Atom::Grounded(gnd) if gnd.type_() == ATOM_TYPE_STRING => { - let mut s = gnd.to_string(); - s.remove(0); - s.pop(); - s - }, - _ => atom.to_string(), - } -} - - -/// Implement trace! built-in. -/// -/// It is equivalent to Idris or Haskell Trace, that is, it prints a -/// message to stderr and pass a value along. -/// -/// For instance -/// ```metta -/// !(trace! "Here?" 42) -/// ``` -/// prints to stderr -/// ```stderr -/// Here? -/// ``` -/// and returns -/// ```metta -/// [42] -/// ``` -/// -/// Note that the first argument does not need to be a string, which -/// makes `trace!` actually quite capable on its own. For instance -/// ```metta -/// !(trace! ("Hello world!" (if True A B) 1 2 3) 42) -/// ``` -/// prints to stderr -/// ```stderr -/// (Hello world! A 1 2 3) -/// ``` -/// and returns -/// ```metta -/// [42] -/// ``` - -#[derive(Clone, Debug)] -pub struct TraceOp {} - -grounded_op!(TraceOp, "trace!"); - -impl Grounded for TraceOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_UNDEFINED, Atom::var("a"), Atom::var("a")]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for TraceOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("trace! expects two atoms as arguments"); - let val = args.get(1).ok_or_else(arg_error)?; - let msg = args.get(0).ok_or_else(arg_error)?; - eprintln!("{}", msg); - Ok(vec![val.clone()]) - } -} - -#[derive(Clone, Debug)] -pub struct NopOp {} - -grounded_op!(NopOp, "nop"); - -impl Grounded for NopOp { - fn type_(&self) -> Atom { - ATOM_TYPE_UNDEFINED - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for NopOp { - fn execute(&self, _args: &[Atom]) -> Result, ExecError> { - unit_result() - } -} - -#[derive(Clone, PartialEq, Debug)] -pub struct StateAtom { - state: Rc> -} - -impl StateAtom { - pub fn new(atom: Atom) -> Self { - Self{ state: Rc::new(RefCell::new(atom)) } - } -} - -impl Display for StateAtom { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "(State {})", self.state.borrow()) - } -} - -impl Grounded for StateAtom { - fn type_(&self) -> Atom { - // TODO? Wrap metatypes for non-grounded atoms - // rust_type_atom::() instead of StateMonad symbol might be used - let atom = &*self.state.borrow(); - let typ = match atom { - Atom::Symbol(_) => ATOM_TYPE_SYMBOL, - Atom::Expression(_) => ATOM_TYPE_EXPRESSION, - Atom::Variable(_) => ATOM_TYPE_VARIABLE, - Atom::Grounded(a) => a.type_(), - }; - Atom::expr([expr!("StateMonad"), typ]) - } -} - -#[derive(Clone, Debug)] -pub struct NewStateOp { } - -grounded_op!(NewStateOp, "new-state"); - -impl Grounded for NewStateOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, expr!(tnso), expr!("StateMonad" tnso)]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for NewStateOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = "new-state expects single atom as an argument"; - let atom = args.get(0).ok_or(arg_error)?; - Ok(vec![Atom::gnd(StateAtom::new(atom.clone()))]) - } -} - -#[derive(Clone, Debug)] -pub struct GetStateOp { } - -grounded_op!(GetStateOp, "get-state"); - -impl Grounded for GetStateOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, expr!("StateMonad" tgso), expr!(tgso)]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for GetStateOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = "get-state expects single state atom as an argument"; - let state = args.get(0).ok_or(arg_error)?; - let atom = Atom::as_gnd::(state).ok_or(arg_error)?; - Ok(vec![atom.state.borrow().clone()]) - } -} - -#[derive(Clone, Debug)] -pub struct ChangeStateOp { } - -grounded_op!(ChangeStateOp, "change-state!"); - -impl Grounded for ChangeStateOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, expr!("StateMonad" tcso), expr!(tcso), expr!("StateMonad" tcso)]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for ChangeStateOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = "change-state! expects a state atom and its new value as arguments"; - let atom = args.get(0).ok_or(arg_error)?; - let state = Atom::as_gnd::(atom).ok_or("change-state! expects a state as the first argument")?; - let new_value = args.get(1).ok_or(arg_error)?; - *state.state.borrow_mut() = new_value.clone(); - Ok(vec![atom.clone()]) - } -} - -#[derive(Clone, Debug)] -pub struct SealedOp {} - -grounded_op!(SealedOp, "sealed"); - -impl Grounded for SealedOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for SealedOp { - 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) - } -} - -#[derive(Clone, Debug)] -pub struct EqualOp {} - -grounded_op!(EqualOp, "=="); - -impl Grounded for EqualOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, expr!(t), expr!(t), ATOM_TYPE_BOOL]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for EqualOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from(concat!(stringify!($op), " expects two arguments")); - let a = args.get(0).ok_or_else(arg_error)?; - let b = args.get(1).ok_or_else(arg_error)?; - - Ok(vec![Atom::gnd(Bool(a == b))]) - } -} - -#[derive(Clone, Debug)] -pub struct MatchOp {} - -grounded_op!(MatchOp, "match"); - -impl Grounded for MatchOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, rust_type_atom::(), ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_UNDEFINED]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for MatchOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("match expects three arguments: space, pattern and template"); - let space = args.get(0).ok_or_else(arg_error)?; - let pattern = args.get(1).ok_or_else(arg_error)?; - let template = args.get(2).ok_or_else(arg_error)?; - log::debug!("MatchOp::execute: space: {:?}, pattern: {:?}, template: {:?}", space, pattern, template); - let space = Atom::as_gnd::(space).ok_or("match expects a space as the first argument")?; - Ok(space.borrow().subst(&pattern, &template)) - } -} - -/// The op atoms that depend on the pkg_mgmt feature -#[cfg(feature = "pkg_mgmt")] -pub(crate) mod pkg_mgmt_ops { - use super::*; - - /// Provides a way to access [Metta::load_module_at_path] from within MeTTa code - #[derive(Clone, Debug)] - pub struct RegisterModuleOp { - metta: Metta - } - - grounded_op!(RegisterModuleOp, "register-module!"); - - impl RegisterModuleOp { - pub fn new(metta: Metta) -> Self { - Self{ metta } - } - } - - impl Grounded for RegisterModuleOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, UNIT_TYPE()]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for RegisterModuleOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = "register-module! expects a file system path; use quotes if needed"; - let path_arg_atom = args.get(0).ok_or_else(|| ExecError::from(arg_error))?; - - let path = match path_arg_atom { - Atom::Symbol(path_arg) => path_arg.name(), - Atom::Grounded(g) => g.downcast_ref::().ok_or_else(|| ExecError::from(arg_error))?.as_str(), - _ => return Err(arg_error.into()), - }; - let path = strip_quotes(path); - let path = std::path::PathBuf::from(path); - - // Load the module from the path - // QUESTION: Do we want to expose the ability to give the module a different name and/ or - // load it into a different part of the namespace hierarchy? For now I was just thinking - // it is better to keep the args simple. IMO this is a place for optional var-args when we - // decide on the best way to handle them language-wide. - self.metta.load_module_at_path(path, None).map_err(|e| ExecError::from(e))?; - - unit_result() - } - } - - /// Provides access to module in a remote git repo, from within MeTTa code - /// Similar to `register-module!`, this op will bypass the catalog search - /// - /// NOTE: Even if Hyperon is build without git support, this operation may still be used to - /// load existing modules from a git cache. That situation may occur if modules were fetched - /// earlier or by another tool that manages the module cache. However this operation requres - /// git support to actually clone or pull from a git repository. - #[derive(Clone, Debug)] - pub struct GitModuleOp { - //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP - context: std::sync::Arc>>>>>, - } - - grounded_op!(GitModuleOp, "git-module!"); - - impl GitModuleOp { - pub fn new(metta: Metta) -> Self { - Self{ context: metta.0.context.clone() } - } - } - - impl Grounded for GitModuleOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, UNIT_TYPE()]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } - } - - impl CustomExecute for GitModuleOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = "git-module! expects a URL; use quotes if needed"; - let url_arg_atom = args.get(0).ok_or_else(|| ExecError::from(arg_error))?; - // TODO: When we figure out how to address varargs, it will be nice to take an optional branch name - - let url = match url_arg_atom { - Atom::Symbol(url_arg) => url_arg.name(), - Atom::Grounded(g) => g.downcast_ref::().ok_or_else(|| ExecError::from(arg_error))?.as_str(), - _ => return Err(arg_error.into()), - }; - let url = strip_quotes(url); - - // TODO: Depending on what we do with `register-module!`, we might want to let the - // caller provide an optional mod_name here too, rather than extracting it from the url - let mod_name = match mod_name_from_url(url) { - Some(mod_name) => mod_name, - None => return Err(ExecError::from("git-module! error extracting module name from URL")) - }; - - let ctx_ref = self.context.lock().unwrap().last().unwrap().clone(); - let mut context = ctx_ref.lock().unwrap(); - - let git_mod_location = ModuleGitLocation::new(url.to_string()); - - match context.metta.environment().specified_mods.as_ref() { - Some(specified_mods) => if let Some((loader, descriptor)) = specified_mods.loader_for_explicit_git_module(&mod_name, UpdateMode::TryFetchLatest, &git_mod_location)? { - context.get_or_init_module_with_descriptor(&mod_name, descriptor, loader).map_err(|e| ExecError::from(e))?; - }, - None => return Err(ExecError::from(format!("Unable to pull module \"{mod_name}\" from git; no local \"caches\" directory available"))) - } - - unit_result() - } - } - - pub fn register_pkg_mgmt_tokens(tref: &mut Tokenizer, metta: &Metta) { - let register_module_op = Atom::gnd(RegisterModuleOp::new(metta.clone())); - tref.register_token(regex(r"register-module!"), move |_| { register_module_op.clone() }); - let git_module_op = Atom::gnd(GitModuleOp::new(metta.clone())); - tref.register_token(regex(r"git-module!"), move |_| { git_module_op.clone() }); - } -} - -#[derive(Clone, Debug)] -pub struct UniqueAtomOp {} - -grounded_op!(UniqueAtomOp, "unique-atom"); - -impl Grounded for UniqueAtomOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for UniqueAtomOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("unique expects single expression atom as an argument"); - let expr = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?; - - let mut atoms = expr.children().clone(); - let mut set = GroundingSpace::new(); - atoms.retain(|x| { - let not_contained = set.query(x).is_empty(); - if not_contained { set.add(x.clone()) }; - not_contained - }); - Ok(vec![Atom::expr(atoms)]) - } -} - -#[derive(Clone, Debug)] -pub struct UnionAtomOp {} - -grounded_op!(UnionAtomOp, "union-atom"); - -impl Grounded for UnionAtomOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for UnionAtomOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("union expects and executable LHS and RHS atom"); - let mut lhs = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children().clone(); - let rhs = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?)?.children().clone(); - - lhs.extend(rhs); - - Ok(vec![Atom::expr(lhs)]) - } -} - -#[derive(Clone, Debug)] -pub struct IntersectionAtomOp {} - -grounded_op!(IntersectionAtomOp, "intersection-atom"); - -impl Grounded for IntersectionAtomOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for IntersectionAtomOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("intersection expects and executable LHS and RHS atom"); - let mut lhs = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children().clone(); - let rhs = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?)?.children().clone(); - - let mut rhs_index: MultiTrie> = MultiTrie::new(); - for (index, rhs_item) in rhs.iter().enumerate() { - let k = atom_to_trie_key(&rhs_item); - // FIXME this should - // a) use a mutable value endpoint which the MultiTrie does not support atm - // b) use a linked list, which Rust barely supports atm - let r = rhs_index.get(&k).next(); - match r.cloned() { - Some(bucket) => { - rhs_index.remove(&k, &bucket); - let mut nbucket = bucket; - nbucket.push(index); - let nbucket = nbucket; - rhs_index.insert(k, nbucket); - } - None => { rhs_index.insert(k, vec![index]) } - } - } - - lhs.retain(|candidate| { - let k = atom_to_trie_key(candidate); - let r = rhs_index.get(&k).next(); - match r.cloned() { - None => { false } - Some(bucket) => { - match bucket.iter().position(|item| &rhs[*item] == candidate) { - None => { false } - Some(i) => { - rhs_index.remove(&k, &bucket); - if bucket.len() > 1 { - let mut nbucket = bucket; - nbucket.remove(i); - rhs_index.insert(k, nbucket); - } - true - } - } - } - } - }); - - Ok(vec![Atom::expr(lhs)]) - } -} - -#[derive(Clone, Debug)] -pub struct MaxAtomOp {} - -grounded_op!(MaxAtomOp, "max-atom"); - -impl Grounded for MaxAtomOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for MaxAtomOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("max-atom expects one argument: expression"); - let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children(); - if children.is_empty() { - Err(ExecError::from("Empty expression")) - } else { - children.into_iter().fold(Ok(f64::NEG_INFINITY), |res, x| { - match (res, AsPrimitive::from_atom(x).as_number()) { - (res @ Err(_), _) => res, - (_, None) => Err(ExecError::from("Only numbers are allowed in expression")), - (Ok(max), Some(x)) => Ok(f64::max(max, x.into())), - } - }) - }.map(|max| vec![Atom::gnd(Number::Float(max))]) - } -} - -#[derive(Clone, Debug)] -pub struct MinAtomOp {} - -grounded_op!(MinAtomOp, "min-atom"); - -impl Grounded for MinAtomOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for MinAtomOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("min-atom expects one argument: expression"); - let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children(); - if children.is_empty() { - Err(ExecError::from("Empty expression")) - } else { - children.into_iter().fold(Ok(f64::INFINITY), |res, x| { - match (res, AsPrimitive::from_atom(x).as_number()) { - (res @ Err(_), _) => res, - (_, None) => Err(ExecError::from("Only numbers are allowed in expression")), - (Ok(min), Some(x)) => Ok(f64::min(min, x.into())), - } - }) - }.map(|min| vec![Atom::gnd(Number::Float(min))]) - } -} - -#[derive(Clone, Debug)] -pub struct SizeAtomOp {} - -grounded_op!(SizeAtomOp, "size-atom"); - -impl Grounded for SizeAtomOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for SizeAtomOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("size-atom expects one argument: expression"); - let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children(); - let size = children.len(); - Ok(vec![Atom::gnd(Number::Integer(size as i64))]) - } -} - -#[derive(Clone, Debug)] -pub struct IndexAtomOp {} - -grounded_op!(IndexAtomOp, "index-atom"); - -impl Grounded for IndexAtomOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER, ATOM_TYPE_ATOM]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for IndexAtomOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("index-atom expects two arguments: expression and atom"); - let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children(); - let index = AsPrimitive::from_atom(args.get(1).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?; - match children.get(Into::::into(index) as usize) { - Some(atom) => Ok(vec![atom.clone()]), - None => Err(ExecError::from("Index is out of bounds")), - } - } -} - -#[derive(Clone, Debug)] -pub struct SubtractionAtomOp {} - -grounded_op!(SubtractionAtomOp, "subtraction-atom"); - -impl Grounded for SubtractionAtomOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION, ATOM_TYPE_EXPRESSION]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for SubtractionAtomOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("subtraction expects and executable LHS and RHS atom"); - let mut lhs = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children().clone(); - let rhs = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?)?.children().clone(); - - let mut rhs_index: MultiTrie> = MultiTrie::new(); - for (index, rhs_item) in rhs.iter().enumerate() { - let k = atom_to_trie_key(&rhs_item); - // FIXME this should - // a) use a mutable value endpoint which the MultiTrie does not support atm - // b) use a linked list, which Rust barely supports atm - let r = rhs_index.get(&k).next(); - match r.cloned() { - Some(bucket) => { - rhs_index.remove(&k, &bucket); - let mut nbucket = bucket; - nbucket.push(index); - let nbucket = nbucket; - rhs_index.insert(k, nbucket); - } - None => { rhs_index.insert(k, vec![index]) } - } - } - - lhs.retain(|candidate| { - let k = atom_to_trie_key(candidate); - let r = rhs_index.get(&k).next(); - match r.cloned() { - None => { true } - Some(bucket) => { - match bucket.iter().position(|item| &rhs[*item] == candidate) { - None => { true } - Some(i) => { - rhs_index.remove(&k, &bucket); - if bucket.len() > 1 { - let mut nbucket = bucket; - nbucket.remove(i); - rhs_index.insert(k, nbucket); - } - false - } - } - } - } - }); - - Ok(vec![Atom::expr(lhs)]) - } -} - -//TODO: In the current version of rand it is possible for rust to hang if range end's value is too -// big. In future releases (0.9+) of rand signature of sample_single will be changed and it will be -// possible to use match construction to cover overflow and other errors. So after library will be -// upgraded RandomInt and RandomFloat codes should be altered. -// see comment https://github.com/trueagi-io/hyperon-experimental/pull/791#discussion_r1824355414 -#[derive(Clone, Debug)] -pub struct RandomIntOp {} - -grounded_op!(RandomIntOp, "random-int"); - -impl Grounded for RandomIntOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for RandomIntOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("random-int expects two arguments: number (start) and number (end)"); - let start: i64 = AsPrimitive::from_atom(args.get(0).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?.into(); - let end: i64 = AsPrimitive::from_atom(args.get(1).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?.into(); - let range = start..end; - if range.is_empty() { - return Err(ExecError::from("Range is empty")); - } - let mut rng = rand::thread_rng(); - Ok(vec![Atom::gnd(Number::Integer(rng.gen_range(range)))]) - } -} - -#[derive(Clone, Debug)] -pub struct RandomFloatOp {} - -grounded_op!(RandomFloatOp, "random-float"); - -impl Grounded for RandomFloatOp { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER]) - } - - fn as_execute(&self) -> Option<&dyn CustomExecute> { - Some(self) - } -} - -impl CustomExecute for RandomFloatOp { - fn execute(&self, args: &[Atom]) -> Result, ExecError> { - let arg_error = || ExecError::from("random-float expects two arguments: number (start) and number (end)"); - let start: f64 = AsPrimitive::from_atom(args.get(0).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?.into(); - let end: f64 = AsPrimitive::from_atom(args.get(1).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?.into(); - let range = start..end; - if range.is_empty() { - return Err(ExecError::from("Range is empty")); - } - let mut rng = rand::thread_rng(); - Ok(vec![Atom::gnd(Number::Float(rng.gen_range(range)))]) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::atom::matcher::atoms_are_equivalent; - use crate::metta::text::*; - use crate::metta::runner::EnvBuilder; - use crate::metta::runner::string::Str; - use crate::common::test_utils::*; - - - fn run_program(program: &str) -> Result>, String> { - let metta = Metta::new(Some(EnvBuilder::test_env())); - metta.run(SExprParser::new(program)) - } - - #[test] - fn match_op() { - let space = DynSpace::new(metta_space("(A B)")); - let match_op = MatchOp{}; - assert_eq!(match_op.execute(&mut vec![expr!({space}), expr!("A" "B"), expr!("B" "A")]), - Ok(vec![expr!("B" "A")])); - } - - #[test] - fn match_op_issue_530() { - let space = DynSpace::new(metta_space("(A $a $a)")); - let match_op = MatchOp{}; - let result = match_op.execute(&mut vec![expr!({space}), expr!("A" x y), expr!("A" x y)]).unwrap(); - assert_eq!(result.len(), 1); - assert!(atoms_are_equivalent(&result[0], &expr!("A" x x)), - "atoms are not equivalent: expected: {}, actual: {}", expr!("A" x x), result[0]); - } - - - #[test] - fn new_space_op() { - let res = NewSpaceOp{}.execute(&mut vec![]).expect("No result returned"); - let space = res.get(0).expect("Result is empty"); - let space = space.as_gnd::().expect("Result is not space"); - let space_atoms: Vec = space.borrow().as_space().atom_iter().unwrap().cloned().collect(); - assert_eq_no_order!(space_atoms, Vec::::new()); - } - - #[test] - fn add_atom_op() { - let space = DynSpace::new(GroundingSpace::new()); - let satom = Atom::gnd(space.clone()); - let res = AddAtomOp{}.execute(&mut vec![satom, expr!(("foo" "bar"))]).expect("No result returned"); - assert_eq!(res, vec![UNIT_ATOM()]); - let space_atoms: Vec = space.borrow().as_space().atom_iter().unwrap().cloned().collect(); - assert_eq_no_order!(space_atoms, vec![expr!(("foo" "bar"))]); - } - - #[test] - fn remove_atom_op() { - let space = DynSpace::new(metta_space(" - (foo bar) - (bar foo) - ")); - let satom = Atom::gnd(space.clone()); - let res = RemoveAtomOp{}.execute(&mut vec![satom, expr!(("foo" "bar"))]).expect("No result returned"); - // REM: can return Bool in future - assert_eq!(res, vec![UNIT_ATOM()]); - let space_atoms: Vec = space.borrow().as_space().atom_iter().unwrap().cloned().collect(); - assert_eq_no_order!(space_atoms, vec![expr!(("bar" "foo"))]); - } - - #[test] - fn get_atoms_op() { - let space = DynSpace::new(metta_space(" - (foo bar) - (bar foo) - ")); - let satom = Atom::gnd(space.clone()); - let res = GetAtomsOp{}.execute(&mut vec![satom]).expect("No result returned"); - let space_atoms: Vec = space.borrow().as_space().atom_iter().unwrap().cloned().collect(); - assert_eq_no_order!(res, space_atoms); - assert_eq_no_order!(res, vec![expr!(("foo" "bar")), expr!(("bar" "foo"))]); - } - - #[test] - fn size_atom_op() { - let res = SizeAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)})]).expect("No result returned"); - assert_eq!(res, vec![expr!({Number::Integer(5)})]); - let res = SizeAtomOp{}.execute(&mut vec![expr!()]).expect("No result returned"); - assert_eq!(res, vec![expr!({Number::Integer(0)})]); - } - - #[test] - fn min_atom_op() { - let res = MinAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Float(5.5)})]).expect("No result returned"); - assert_eq!(res, vec![expr!({Number::Integer(4)})]); - let res = MinAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} "A")]); - assert_eq!(res, Err(ExecError::from("Only numbers are allowed in expression"))); - let res = MinAtomOp{}.execute(&mut vec![expr!()]); - assert_eq!(res, Err(ExecError::from("Empty expression"))); - } - - #[test] - fn max_atom_op() { - let res = MaxAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Float(5.5)})]).expect("No result returned"); - assert_eq!(res, vec![expr!({Number::Float(5.5)})]); - let res = MaxAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} "A")]); - assert_eq!(res, Err(ExecError::from("Only numbers are allowed in expression"))); - let res = MaxAtomOp{}.execute(&mut vec![expr!()]); - assert_eq!(res, Err(ExecError::from("Empty expression"))); - } - - #[test] - fn index_atom_op() { - let res = IndexAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)}), expr!({Number::Integer(2)})]).expect("No result returned"); - assert_eq!(res, vec![expr!({Number::Integer(3)})]); - let res = IndexAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)}), expr!({Number::Integer(5)})]); - assert_eq!(res, Err(ExecError::from("Index is out of bounds"))); - } - - #[test] - fn random_op() { - let res = RandomIntOp{}.execute(&mut vec![expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]); - let range = 0..5; - let res_i64: i64 = AsPrimitive::from_atom(res.unwrap().get(0).unwrap()).as_number().unwrap().into(); - assert!(range.contains(&res_i64)); - let res = RandomIntOp{}.execute(&mut vec![expr!({Number::Integer(2)}), expr!({Number::Integer(-2)})]); - assert_eq!(res, Err(ExecError::from("Range is empty"))); - - let res = RandomFloatOp{}.execute(&mut vec![expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]); - let range = 0.0..5.0; - let res_f64: f64 = AsPrimitive::from_atom(res.unwrap().get(0).unwrap()).as_number().unwrap().into(); - assert!(range.contains(&res_f64)); - let res = RandomFloatOp{}.execute(&mut vec![expr!({Number::Integer(0)}), expr!({Number::Integer(0)})]); - assert_eq!(res, Err(ExecError::from("Range is empty"))); - } - - #[test] - fn bind_new_space_op() { - let tokenizer = Shared::new(Tokenizer::new()); - - let bind_op = BindOp::new(tokenizer.clone()); - - assert_eq!(bind_op.execute(&mut vec![sym!("&my"), sym!("definition")]), unit_result()); - let borrowed = tokenizer.borrow(); - let constr = borrowed.find_token("&my"); - assert!(constr.is_some()); - assert_eq!(constr.unwrap()("&my"), Ok(sym!("definition"))); - } - - fn assert_runtime_error(actual: Result, ExecError>, expected: Regex) { - match actual { - Err(ExecError::Runtime(msg)) => assert!(expected.is_match(msg.as_str()), - "Incorrect error message:\nexpected: {:?}\n actual: {:?}", expected.to_string(), msg), - _ => assert!(false, "Error is expected as result, {:?} returned", actual), - } - } - - #[test] - fn assert_equal_op() { - let space = DynSpace::new(metta_space(" - (= (foo) (A B)) - (= (foo) (B C)) - (= (bar) (B C)) - (= (bar) (A B)) - (= (err) (A B)) - ")); - - let assert_equal_op = AssertEqualOp::new(space); - - assert_eq!(assert_equal_op.execute(&mut vec![expr!(("foo")), expr!(("bar"))]), unit_result()); - - let actual = assert_equal_op.execute(&mut vec![expr!(("foo")), expr!(("err"))]); - let expected = Regex::new("\nExpected: \\[(A B)\\]\nGot: \\[\\((B C)|, |(A B)\\){3}\\]\nExcessive result: (B C)").unwrap(); - assert_runtime_error(actual, expected); - - let actual = assert_equal_op.execute(&mut vec![expr!(("err")), expr!(("foo"))]); - let expected = Regex::new("\nExpected: \\[\\((B C)|, |(A B)\\){3}\\]\nGot: \\[(A B)\\]\nMissed result: (B C)").unwrap(); - assert_runtime_error(actual, expected); - } - - #[test] - fn assert_equal_to_result_op() { - let space = DynSpace::new(metta_space(" - (= (foo) (A B)) - (= (foo) (B C)) - ")); - let assert_equal_to_result_op = AssertEqualToResultOp::new(space); - - assert_eq!(assert_equal_to_result_op.execute(&mut vec![ - expr!(("foo")), expr!(("B" "C") ("A" "B"))]), - unit_result()); - } - - #[test] - fn unique_op() { - let unique_op = UniqueAtomOp{}; - let actual = unique_op.execute(&mut vec![expr!( - ("A" ("B" "C")) - ("A" ("B" "C")) - ("f" "g") - ("f" "g") - ("f" "g") - "Z" - )]).unwrap(); - assert_eq_no_order!(actual, - vec![expr!(("A" ("B" "C")) ("f" "g") "Z")]); - } - - #[test] - fn union_op() { - let union_op = UnionAtomOp{}; - let actual = union_op.execute(&mut vec![expr!( - ("A" ("B" "C")) - ("A" ("B" "C")) - ("f" "g") - ("f" "g") - ("f" "g") - "Z" - ), expr!( - ("A" ("B" "C")) - "p" - "p" - ("Q" "a") - )]).unwrap(); - assert_eq_no_order!(actual, - vec![expr!(("A" ("B" "C")) ("A" ("B" "C")) - ("f" "g") ("f" "g") ("f" "g") "Z" - ("A" ("B" "C")) "p" "p" ("Q" "a"))]); - } - - #[test] - fn intersection_op() { - let intersection_op = IntersectionAtomOp{}; - let actual = intersection_op.execute(&mut vec![expr!( - "Z" - ("A" ("B" "C")) - ("A" ("B" "C")) - ("f" "g") - ("f" "g") - ("f" "g") - ("P" "b") - ), expr!( - ("f" "g") - ("f" "g") - ("A" ("B" "C")) - "p" - "p" - ("Q" "a") - "Z" - )]).unwrap(); - assert_eq_no_order!(actual, vec![expr!("Z" ("A" ("B" "C")) ("f" "g") ("f" "g"))]); - - assert_eq_no_order!(intersection_op.execute(&mut vec![expr!( - { Number::Integer(5) } - { Number::Integer(4) } - { Number::Integer(3) } - { Number::Integer(2) } - ), expr!( - { Number::Integer(5) } - { Number::Integer(3) } - )]).unwrap(), vec![expr!({Number::Integer(5)} {Number::Integer(3)})]); - } - - #[test] - fn subtraction_op() { - let subtraction_op = SubtractionAtomOp{}; - let actual = subtraction_op.execute(&mut vec![expr!( - "Z" - "S" - "S" - ("A" ("B" "C")) - ("A" ("B" "C")) - ("f" "g") - ("f" "g") - ("f" "g") - ("P" "b") - ), expr!( - ("f" "g") - ("A" ("B" "C")) - "p" - "P" - ("Q" "a") - "Z" - "S" - "S" - "S" - )]).unwrap(); - assert_eq_no_order!(actual, - vec![expr!(("A" ("B" "C")) ("f" "g") ("f" "g") ("P" "b"))]); - } - - #[test] - fn get_type_op() { - let space = DynSpace::new(metta_space(" - (: B Type) - (: C Type) - (: A B) - (: A C) - ")); - - let get_type_op = GetTypeOp::new(space); - assert_eq_no_order!(get_type_op.execute(&mut vec![sym!("A")]).unwrap(), - vec![sym!("B"), sym!("C")]); - } - - #[test] - fn get_type_op_non_valid_atom() { - let space = DynSpace::new(metta_space(" - (: f (-> Number String)) - (: 42 Number) - (: \"test\" String) - ")); - - let get_type_op = GetTypeOp::new(space); - assert_eq_no_order!(get_type_op.execute(&mut vec![expr!("f" "42")]).unwrap(), - vec![sym!("String")]); - assert_eq_no_order!(get_type_op.execute(&mut vec![expr!("f" "\"test\"")]).unwrap(), - Vec::::new()); - } - - #[test] - fn println_op() { - assert_eq!(PrintlnOp{}.execute(&mut vec![sym!("A")]), unit_result()); - } - - #[test] - fn trace_op() { - assert_eq!(TraceOp{}.execute(&mut vec![sym!("\"Here?\""), sym!("42")]), - Ok(vec![sym!("42")])); - } - - #[test] - fn nop_op() { - assert_eq!(NopOp{}.execute(&mut vec![]), unit_result()); - } - - #[test] - fn let_op_keep_variables_equalities_issue290() { - assert_eq_metta_results!(run_program("!(let* (($f f) ($f $x)) $x)"), Ok(vec![vec![expr!("f")]])); - assert_eq_metta_results!(run_program("!(let* (($f $x) ($f f)) $x)"), Ok(vec![vec![expr!("f")]])); - assert_eq_metta_results!(run_program("!(let (quote ($x $x)) (quote ($z $y)) (let $y A ($z $y)))"), Ok(vec![vec![expr!("A" "A")]])); - assert_eq_metta_results!(run_program("!(let (quote ($x $x)) (quote ($z $y)) (let $z A ($z $y)))"), Ok(vec![vec![expr!("A" "A")]])); - } - - #[test] - fn let_op_variables_visibility_pr262() { - let program = " - ;; Knowledge - (→ P Q) - (→ Q R) - - ;; Rule - (= (rule (→ $p $q) (→ $q $r)) (→ $p $r)) - - ;; Query (does not work as expected) - (= (query $kb) - (let* (($pq (→ $p $q)) - ($qr (→ $q $r))) - (match $kb - ;; Premises - (, $pq $qr) - ;; Conclusion - (rule $pq $qr)))) - - ;; Call - !(query &self) - ;; [(→ P R)] - "; - assert_eq_metta_results!(run_program(program), Ok(vec![vec![expr!("→" "P" "R")]])); - } - - #[test] - fn state_ops() { - let result = NewStateOp{}.execute(&mut vec![expr!("A" "B")]).unwrap(); - let old_state = result.get(0).ok_or("error").unwrap(); - assert_eq!(old_state, &Atom::gnd(StateAtom::new(expr!("A" "B")))); - let result = ChangeStateOp{}.execute(&mut vec!(old_state.clone(), expr!("C" "D"))).unwrap(); - let new_state = result.get(0).ok_or("error").unwrap(); - assert_eq!(old_state, new_state); - assert_eq!(new_state, &Atom::gnd(StateAtom::new(expr!("C" "D")))); - let result = GetStateOp{}.execute(&mut vec![new_state.clone()]); - assert_eq!(result, Ok(vec![expr!("C" "D")])) - } - - #[test] - fn test_stdlib_uses_rust_grounded_tokens() { - assert_eq!(run_program("!(if True ok nok)"), Ok(vec![vec![Atom::sym("ok")]])); - } - - #[test] - fn test_let_op_inside_other_operation() { - assert_eq!(run_program("!(and True (let $x False $x))"), Ok(vec![vec![expr!({Bool(false)})]])); - } - - #[test] - fn test_quote() { - let metta = Metta::new(Some(EnvBuilder::test_env())); - let parser = SExprParser::new(" - (= (foo) a) - (= (foo) b) - !(foo) - !(quote (foo)) - "); - - assert_eq_metta_results!(metta.run(parser), - Ok(vec![ - vec![expr!("a"), expr!("b")], - vec![expr!("quote" ("foo"))], - ])); - } - - #[test] - fn test_unify() { - let metta = Metta::new(Some(EnvBuilder::test_env())); - let parser = SExprParser::new(" - !(unify (a $b 1 (d)) (a $a 1 (d)) ok nok) - !(unify (a $b c) (a b $c) (ok $b $c) nok) - !(unify $a (a b c) (ok $a) nok) - !(unify (a b c) $a (ok $a) nok) - !(unify (a b c) (a b d) ok nok) - !(unify ($x a) (b $x) ok nok) - "); - - assert_eq_metta_results!(metta.run(parser), - Ok(vec![ - vec![expr!("ok")], - vec![expr!("ok" "b" "c")], - vec![expr!("ok" ("a" "b" "c"))], - vec![expr!("ok" ("a" "b" "c"))], - vec![expr!("nok")], - vec![expr!("nok")] - ])); - } - - #[test] - fn test_empty() { - let metta = Metta::new(Some(EnvBuilder::test_env())); - let parser = SExprParser::new(" - !(empty) - "); - - assert_eq_metta_results!(metta.run(parser), - Ok(vec![vec![]])); - } - - #[test] - fn sealed_op_runner() { - let nested = run_program("!(sealed ($x) (sealed ($a $b) (quote (= ($a $x $c) ($b)))))"); - let simple_replace = run_program("!(sealed ($x $y) (quote (= ($y $z))))"); - - assert!(crate::atom::matcher::atoms_are_equivalent(&nested.unwrap()[0][0], &expr!("quote" ("=" (a b c) (z))))); - assert!(crate::atom::matcher::atoms_are_equivalent(&simple_replace.unwrap()[0][0], &expr!("quote" ("=" (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![]])); - assert_eq!(run_program("!(let $x (input $y) (output $x))"), Ok(vec![vec![expr!("output" ("input" y))]])); - assert_eq!(run_program("!(let (quote ($sv $st)) (sealed ($x) (quote ($x (output $x)))) - (let $sv (input $x) $st))"), Ok(vec![vec![expr!("output" ("input" x))]])); - } - - #[derive(Clone, PartialEq, Debug)] - pub struct SomeGndAtom { } - - impl Display for SomeGndAtom { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "some-gnd-atom") - } - } - - impl Grounded for SomeGndAtom { - fn type_(&self) -> Atom { - Atom::expr([ARROW_SYMBOL, sym!("Arg1Type"), sym!("Arg2Type"), sym!("ReturnType")]) - } - } - - #[test] - fn test_get_doc_func() { - let metta = Metta::new(Some(EnvBuilder::test_env())); - let parser = SExprParser::new(r#" - (: Arg1Type Type) - (: Arg2Type Type) - (: ReturnType Type) - (: some-func (-> Arg1Type Arg2Type ReturnType)) - (@doc some-func - (@desc "Test function") - (@params ( - (@param "First argument") - (@param "Second argument") - )) - (@return "Return value") - ) - - !(get-doc some-func) - "#); - - assert_eq_metta_results!(metta.run(parser), Ok(vec![ - vec![expr!("@doc-formal" - ("@item" "some-func") - ("@kind" "function") - ("@type" ("->" "Arg1Type" "Arg2Type" "ReturnType")) - ("@desc" {Str::from_str("Test function")}) - ("@params" ( - ("@param" ("@type" "Arg1Type") ("@desc" {Str::from_str("First argument")})) - ("@param" ("@type" "Arg2Type") ("@desc" {Str::from_str("Second argument")})) )) - ("@return" ("@type" "ReturnType") ("@desc" {Str::from_str("Return value")})) )], - ])); - } - - #[test] - fn test_get_doc_atom() { - let metta = Metta::new(Some(EnvBuilder::test_env())); - let parser = SExprParser::new(r#" - (: SomeAtom SomeType) - (@doc SomeAtom (@desc "Test symbol atom having specific type")) - - !(get-doc SomeAtom) - "#); - - assert_eq_metta_results!(metta.run(parser), Ok(vec![ - vec![expr!("@doc-formal" - ("@item" "SomeAtom") - ("@kind" "atom") - ("@type" "SomeType") - ("@desc" {Str::from_str("Test symbol atom having specific type")}) )], - ])); - } - - #[test] - fn test_get_doc_gnd_func() { - let metta = Metta::new(Some(EnvBuilder::test_env())); - metta.tokenizer().borrow_mut() - .register_token(regex::Regex::new(r"some-gnd-atom").unwrap(), |_| Atom::gnd(SomeGndAtom{})); - let parser = SExprParser::new(r#" - (@doc some-gnd-atom - (@desc "Test function") - (@params ( - (@param "First argument") - (@param "Second argument") - )) - (@return "Return value") - ) - !(get-doc some-gnd-atom) - "#); - - assert_eq_metta_results!(metta.run(parser), Ok(vec![ - vec![expr!("@doc-formal" - ("@item" {SomeGndAtom{}}) - ("@kind" "function") - ("@type" ("->" "Arg1Type" "Arg2Type" "ReturnType")) - ("@desc" {Str::from_str("Test function")}) - ("@params" ( - ("@param" ("@type" "Arg1Type") ("@desc" {Str::from_str("First argument")})) - ("@param" ("@type" "Arg2Type") ("@desc" {Str::from_str("Second argument")})) )) - ("@return" ("@type" "ReturnType") ("@desc" {Str::from_str("Return value")})) )], - ])); - } - - #[test] - fn test_get_doc_no_doc() { - let metta = Metta::new(Some(EnvBuilder::test_env())); - let parser = SExprParser::new(r#" - !(get-doc NoSuchAtom) - "#); - - assert_eq_metta_results!(metta.run(parser), Ok(vec![ - vec![expr!("@doc-formal" - ("@item" "NoSuchAtom") - ("@kind" "atom") - ("@type" "%Undefined%") - ("@desc" {Str::from_str("No documentation")}) )], - ])); - } - - #[test] - fn test_get_doc_function_call() { - let metta = Metta::new(Some(EnvBuilder::test_env())); - let parser = SExprParser::new(r#" - (: Arg1Type Type) - (: Arg2Type Type) - (: ReturnType Type) - (: some-func (-> Arg1Type Arg2Type ReturnType)) - (@doc some-func - (@desc "Test function") - (@params ( - (@param "First argument") - (@param "Second argument") - )) - (@return "Return value") - ) - - !(get-doc (some-func arg1 arg2)) - "#); - - assert_eq_metta_results!(metta.run(parser), Ok(vec![ - vec![expr!("@doc-formal" - ("@item" ("some-func" "arg1" "arg2")) - ("@kind" "atom") - ("@type" "ReturnType") - ("@desc" {Str::from_str("No documentation")}) )], - ])); - } - - #[test] - fn test_get_doc_no_type() { - let metta = Metta::new(Some(EnvBuilder::test_env())); - let parser = SExprParser::new(r#" - (@doc some-func-no-type - (@desc "Test function") - (@params ( - (@param "First argument") - (@param "Second argument") - )) - (@return "Return value") - ) - - !(get-doc some-func-no-type) - "#); - - assert_eq_metta_results!(metta.run(parser), Ok(vec![ - vec![expr!("@doc-formal" - ("@item" "some-func-no-type") - ("@kind" "function") - ("@type" "%Undefined%") - ("@desc" {Str::from_str("Test function")}) - ("@params" ( - ("@param" ("@type" "%Undefined%") ("@desc" {Str::from_str("First argument")})) - ("@param" ("@type" "%Undefined%") ("@desc" {Str::from_str("Second argument")})) )) - ("@return" ("@type" "%Undefined%") ("@desc" {Str::from_str("Return value")})) )], - ])); - } -} From ffaf1e849f0d6cd53fec823fd978d352443cdd9a Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 8 Nov 2024 19:02:30 +0300 Subject: [PATCH 4/5] Simplify InterpreterContext definition Second lifetime was needed only for compatibility with old_interpreter. --- c/src/metta.rs | 22 +++++++++++----------- lib/src/metta/interpreter_minimal.rs | 17 +++++------------ lib/src/metta/runner/mod.rs | 18 +++++++++--------- lib/src/metta/runner/stdlib_minimal.rs | 8 ++++---- 4 files changed, 29 insertions(+), 36 deletions(-) diff --git a/c/src/metta.rs b/c/src/metta.rs index ed925511d..1e59ed170 100644 --- a/c/src/metta.rs +++ b/c/src/metta.rs @@ -720,19 +720,19 @@ pub struct step_result_t { result: *mut RustStepResult, } -struct RustStepResult(InterpreterState<'static, DynSpace>); +struct RustStepResult(InterpreterState); -impl From> for step_result_t { - fn from(state: InterpreterState<'static, DynSpace>) -> Self { +impl From> for step_result_t { + fn from(state: InterpreterState) -> Self { Self{ result: Box::into_raw(Box::new(RustStepResult(state))) } } } impl step_result_t { - fn into_inner(self) -> InterpreterState<'static, DynSpace> { + fn into_inner(self) -> InterpreterState { unsafe{ Box::from_raw(self.result).0 } } - fn borrow(&self) -> &InterpreterState<'static, DynSpace> { + fn borrow(&self) -> &InterpreterState { &unsafe{ &*(&*self).result }.0 } } @@ -1210,22 +1210,22 @@ impl run_context_t { } } -struct RustRunContext(RunContext<'static, 'static, 'static>); +struct RustRunContext(RunContext<'static, 'static>); -impl From<&mut RunContext<'_, '_, '_>> for run_context_t { - fn from(context_ref: &mut RunContext<'_, '_, '_>) -> Self { +impl From<&mut RunContext<'_, '_>> for run_context_t { + fn from(context_ref: &mut RunContext<'_, '_>) -> Self { Self { - context: (context_ref as *mut RunContext<'_, '_, '_>).cast(), + context: (context_ref as *mut RunContext<'_, '_>).cast(), err_string: core::ptr::null_mut(), } } } impl run_context_t { - fn borrow(&self) -> &RunContext<'static, 'static, 'static> { + fn borrow(&self) -> &RunContext<'static, 'static> { &unsafe{ &*self.context.cast::() }.0 } - fn borrow_mut(&mut self) -> &mut RunContext<'static, 'static, 'static> { + fn borrow_mut(&mut self) -> &mut RunContext<'static, 'static> { &mut unsafe{ &mut *self.context.cast::() }.0 } } diff --git a/lib/src/metta/interpreter_minimal.rs b/lib/src/metta/interpreter_minimal.rs index 7835cf790..b682e0b11 100644 --- a/lib/src/metta/interpreter_minimal.rs +++ b/lib/src/metta/interpreter_minimal.rs @@ -156,20 +156,16 @@ impl InterpreterContext { /// This wrapper is to keep interpreter interface compatible with previous /// implementation and will be removed in future. -// TODO: MINIMAL: This wrapper is for compatibility with old_interpreter.rs only -pub trait SpaceRef<'a> : Space + 'a {} -impl<'a, T: Space + 'a> SpaceRef<'a> for T {} /// State of the interpreter which passed between `interpret_step` calls. #[derive(Debug)] -pub struct InterpreterState<'a, T: SpaceRef<'a>> { +pub struct InterpreterState { /// List of the alternatives to evaluate further. plan: Vec, /// List of the completely evaluated results to be returned. finished: Vec, /// Evaluation context. context: InterpreterContext, - phantom: std::marker::PhantomData>, } fn atom_as_slice(atom: &Atom) -> Option<&[Atom]> { @@ -184,16 +180,14 @@ fn atom_into_array(atom: Atom) -> Option<[Atom; N]> { <[Atom; N]>::try_from(atom).ok() } -impl<'a, T: SpaceRef<'a>> InterpreterState<'a, T> { +impl InterpreterState { /// INTERNAL USE ONLY. Create an InterpreterState that is ready to yield results - #[allow(dead_code)] //TODO: MINIMAL only silence the warning until interpreter_minimal replaces interpreter pub(crate) fn new_finished(space: T, results: Vec) -> Self { Self { plan: vec![], finished: results, context: InterpreterContext::new(space), - phantom: std::marker::PhantomData, } } @@ -229,7 +223,7 @@ impl<'a, T: SpaceRef<'a>> InterpreterState<'a, T> { } } -impl<'a, T: SpaceRef<'a>> std::fmt::Display for InterpreterState<'a, T> { +impl std::fmt::Display for InterpreterState { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:?}\n", self.plan) } @@ -241,13 +235,12 @@ impl<'a, T: SpaceRef<'a>> std::fmt::Display for InterpreterState<'a, T> { /// # Arguments /// * `space` - atomspace to query for interpretation /// * `expr` - atom to interpret -pub fn interpret_init<'a, T: Space + 'a>(space: T, expr: &Atom) -> InterpreterState<'a, T> { +pub fn interpret_init(space: T, expr: &Atom) -> InterpreterState { let context = InterpreterContext::new(space); InterpreterState { plan: vec![InterpretedAtom(atom_to_stack(expr.clone(), None), Bindings::new())], finished: vec![], context, - phantom: std::marker::PhantomData, } } @@ -256,7 +249,7 @@ pub fn interpret_init<'a, T: Space + 'a>(space: T, expr: &Atom) -> InterpreterSt /// /// # Arguments /// * `state` - interpreter state from the previous step. -pub fn interpret_step<'a, T: Space + 'a>(mut state: InterpreterState<'a, T>) -> InterpreterState<'a, T> { +pub fn interpret_step(mut state: InterpreterState) -> InterpreterState { let interpreted_atom = state.pop().unwrap(); log::debug!("interpret_step:\n{}", interpreted_atom); let InterpretedAtom(stack, bindings) = interpreted_atom; diff --git a/lib/src/metta/runner/mod.rs b/lib/src/metta/runner/mod.rs index d35be742c..3cec2109b 100644 --- a/lib/src/metta/runner/mod.rs +++ b/lib/src/metta/runner/mod.rs @@ -136,7 +136,7 @@ pub(crate) struct MettaContents { //TODO-HACK: This is a terrible horrible ugly hack that should not be merged. Delete this field // The real context is an interface to the state in a run, and should not live across runs // This hack will fail badly if we end up running code from two different modules in parallel - context: Arc>>>>>, + context: Arc>>>>>, } impl Metta { @@ -489,7 +489,7 @@ pub struct RunnerState<'m, 'i> { mod_id: ModId, mod_ptr: Option>, init_state: ModuleInitState, - i_wrapper: InterpreterWrapper<'m, 'i>, + i_wrapper: InterpreterWrapper<'i>, } impl std::fmt::Debug for RunnerState<'_, '_> { @@ -585,7 +585,7 @@ impl<'m, 'input> RunnerState<'m, 'input> { //TODO: When we eliminate the RunnerState, this method should become a private method of Metta, // and an argument of type `Option` should be added. When this function is used to initialize // modules, the module type can be returned from this function - fn run_in_context) -> Result>(&mut self, f: F) -> Result { + fn run_in_context) -> Result>(&mut self, f: F) -> Result { // Construct the RunContext let mut context = RunContext { @@ -641,22 +641,22 @@ impl<'m, 'input> RunnerState<'m, 'input> { // TODO: I think we may be able to remove the `'interpreter`` lifetime after the minimal MeTTa migration // because the lifetime is separated on account of the inability of the compiler to shorten a lifetime // used as a generic parameter on a trait. In this case, the `Plan` trait. -pub struct RunContext<'a, 'interpreter, 'input> { +pub struct RunContext<'a, 'input> { metta: &'a Metta, mod_id: ModId, mod_ptr: &'a mut Option>, init_state: &'a mut ModuleInitState, - i_wrapper: &'a mut InterpreterWrapper<'interpreter, 'input> + i_wrapper: &'a mut InterpreterWrapper<'input> } -impl std::fmt::Debug for RunContext<'_, '_, '_> { +impl std::fmt::Debug for RunContext<'_, '_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("RunContext") .finish() } } -impl<'input> RunContext<'_, '_, 'input> { +impl<'input> RunContext<'_, 'input> { /// Returns access to the Metta runner that is hosting the context pub fn metta(&self) -> &Metta { &self.metta @@ -1091,10 +1091,10 @@ fn is_bare_minimal_interpreter(metta: &Metta) -> bool { /// Private structure to contain everything associated with an InterpreterState. /// This is basically the part of RunContext that lasts across calls to run_step #[derive(Default)] -struct InterpreterWrapper<'interpreter, 'i> { +struct InterpreterWrapper<'i> { mode: MettaRunnerMode, input_src: InputStream<'i>, - interpreter_state: Option>, + interpreter_state: Option>, results: Vec>, } diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 1055bd247..2d45750ed 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -52,7 +52,7 @@ macro_rules! grounded_op { #[derive(Clone, Debug)] pub struct ImportOp { //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP - context: std::sync::Arc>>>>>, + context: std::sync::Arc>>>>>, } grounded_op!(ImportOp, "import!"); @@ -152,7 +152,7 @@ impl CustomExecute for ImportOp { #[derive(Clone, Debug)] pub struct IncludeOp { //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP - context: std::sync::Arc>>>>>, + context: std::sync::Arc>>>>>, } grounded_op!(IncludeOp, "include"); @@ -215,7 +215,7 @@ impl CustomExecute for IncludeOp { #[derive(Clone, Debug)] pub struct ModSpaceOp { //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP - context: std::sync::Arc>>>>>, + context: std::sync::Arc>>>>>, } grounded_op!(ModSpaceOp, "mod-space!"); @@ -914,7 +914,7 @@ pub(crate) mod pkg_mgmt_ops { #[derive(Clone, Debug)] pub struct GitModuleOp { //TODO-HACK: This is a terrible horrible ugly hack that should be fixed ASAP - context: std::sync::Arc>>>>>, + context: std::sync::Arc>>>>>, } grounded_op!(GitModuleOp, "git-module!"); From ac807742298584b495cbd67671b565c27e7323be Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 8 Nov 2024 19:11:16 +0300 Subject: [PATCH 5/5] Uncomment test, remove legacy comments and pragmas --- lib/src/atom/matcher.rs | 1 - lib/src/metta/runner/stdlib_minimal.metta | 2 +- lib/src/metta/runner/stdlib_minimal.rs | 2 -- python/tests/scripts/b5_types_prelim.metta | 8 +++----- python/tests/scripts/d2_higherfunc.metta | 6 ++++-- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/src/atom/matcher.rs b/lib/src/atom/matcher.rs index 041ab35bf..102bcf58c 100644 --- a/lib/src/atom/matcher.rs +++ b/lib/src/atom/matcher.rs @@ -211,7 +211,6 @@ impl Bindings { self.bindings.remove(from_binding_id); } - #[allow(dead_code)] //TODO: MINIMAL only silence the warning until interpreter2 replaces interpreter pub(crate) fn len(&self) -> usize { self.binding_by_var.len() } diff --git a/lib/src/metta/runner/stdlib_minimal.metta b/lib/src/metta/runner/stdlib_minimal.metta index d73540549..b91adc1de 100644 --- a/lib/src/metta/runner/stdlib_minimal.metta +++ b/lib/src/metta/runner/stdlib_minimal.metta @@ -492,7 +492,7 @@ (= (nop) ()) (= (nop $x) ()) -; TODO: MINIMAL added for compatibility, remove after migration +; TODO: can be replaced by Empty atom and removed, kept for compatibility (@doc empty (@desc "Cuts evaluation of the non-deterministic branch and removes it from the result") (@params ()) diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 2d45750ed..9411caa85 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -1391,8 +1391,6 @@ fn atom_to_string(atom: &Atom) -> String { } #[derive(Clone, Debug)] pub struct GetTypeOp { - // TODO: MINIMAL this is temporary compatibility fix to be removed after - // migration to the minimal MeTTa space: DynSpace, } diff --git a/python/tests/scripts/b5_types_prelim.metta b/python/tests/scripts/b5_types_prelim.metta index b98d7e5ea..275894a34 100644 --- a/python/tests/scripts/b5_types_prelim.metta +++ b/python/tests/scripts/b5_types_prelim.metta @@ -81,12 +81,10 @@ (Cons (S Z) (Cons Z Nil)) ((Cons (S Z) (Cons Z Nil)))) -; TODO: MINIMAL This test has different behavior in old and new versions of the -; interpreter versions. Uncomment it after migration to the minimal MeTTa. ; This list is badly typed, because S and Z are not the same type -;!(assertEqualToResult -; (Cons S (Cons Z Nil)) -; ((Error (Cons Z Nil) BadType))) +!(assertEqualToResult + (Cons S (Cons Z Nil)) + ((Error (Cons Z Nil) BadType))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/python/tests/scripts/d2_higherfunc.metta b/python/tests/scripts/d2_higherfunc.metta index 25f42be1f..40df91294 100644 --- a/python/tests/scripts/d2_higherfunc.metta +++ b/python/tests/scripts/d2_higherfunc.metta @@ -169,8 +169,10 @@ (get-type (fmap (curry-a + 2) (Left "5"))) ()) ; TODO: Two examples below are type-checked successfully because, (UntypedC "5") -; can return result which has an appropriate type. Uncomment when old_interpreter -; feature is removed. +; can return result which has an appropriate type. But type returned contains +; variable, for instance ($F#3287 Number). There is no function to compare such +; atom easily with pattern yet. Uncomment after equivalent-atom operation is +; introduced. ;!(assertEqualToResult ; (get-type (fmap (curry-a + 2) (UntypedC "5"))) ; ())