diff --git a/Findimagine-planner.cmake b/Findimagine-planner.cmake
index eb6d0cfe7686f5a3bc3d25e750f0b55b49d4e0a4..1a0ccf0e5f02818c6c9965077a670c71d5c6f65e 100644
--- a/Findimagine-planner.cmake
+++ b/Findimagine-planner.cmake
@@ -1,5 +1,5 @@
 #edit the following line to add the librarie's header files
-FIND_PATH(imagine-planner_INCLUDE_DIR imagine-planner.h /usr/include/iridrivers /usr/local/include/iridrivers)
+FIND_PATH(imagine-planner_INCLUDE_DIR imagine-planner.h /usr/include/iridrivers/imagine_planner /usr/local/include/iridrivers/imagine_planner)
 
 FIND_LIBRARY(imagine-planner_LIBRARY
     NAMES imagine-planner
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 226d711e2cbedb4ca44518d695becec2b61e8299..595b7f15da77c67130912bc7e02b6bd32d77a2fe 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,23 +1,24 @@
 # driver source files
-SET(sources imagine-planner.cpp types.cpp queries.cpp parsing.cpp)
+SET(sources imagine-planner.cpp types.cpp queries.cpp effects.cpp domains.cpp parsing.cpp)
 # application header files
-SET(headers imagine-planner.h types.h queries.h parsing.h)
+SET(headers imagine-planner.h types.h queries.h effects.h domains.h parsing.h)
+# Ades
+SET(CMAKE_MODULE_PATH /usr/lib/cmake/libades)
+FIND_PACKAGE(libimagine-ades)
 # Boost
-FIND_PACKAGE(Boost COMPONENTS system filesystem unit_test_framework REQUIRED)
-# Swi-pl
-include(FindPkgConfig)
+FIND_PACKAGE(Boost REQUIRED COMPONENTS system serialization filesystem unit_test_framework)
 # locate the necessary dependencies
 # add the necessary include directories
-INCLUDE_DIRECTORIES(. ${Boost_INCLUDE_DIR})
+INCLUDE_DIRECTORIES(. ${Boost_INCLUDE_DIR} ${LIBIMAGINE-ADES_INCLUDE_DIRS})
 # create the shared library
 ADD_LIBRARY(imagine-planner SHARED ${sources})
 # link necessary libraries
-TARGET_LINK_LIBRARIES(imagine-planner)
+TARGET_LINK_LIBRARIES(imagine-planner ${LIBIMAGINE-ADES_LIBRARIES})
 
 INSTALL(TARGETS imagine-planner
         RUNTIME DESTINATION bin
         LIBRARY DESTINATION lib/iridrivers
         ARCHIVE DESTINATION lib/iridrivers)
-INSTALL(FILES ${headers} DESTINATION include/iridrivers)
+INSTALL(FILES ${headers} DESTINATION include/iridrivers/imagine_planner)
 INSTALL(FILES ../Findimagine-planner.cmake DESTINATION ${CMAKE_ROOT}/Modules/)
 ADD_SUBDIRECTORY(examples)
diff --git a/src/domains.cpp b/src/domains.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dda282c1f4bfb8bc11972e8003c2f097936f9388
--- /dev/null
+++ b/src/domains.cpp
@@ -0,0 +1,154 @@
+#include "domains.h"
+#include <sstream>
+#include <libades/libades.h>
+
+namespace imagine_planner
+{
+
+// Implementation of OperatorSpecification's methods
+
+OperatorSpecification::OperatorSpecification(
+    const OperatorSpecification& other)
+  : name_(other.name_), pre_(other.pre_->clone()),
+    post_(clone_effects(other.post_)) {}
+
+OperatorSpecification::OperatorSpecification(const std::string& name,
+    const std::string& pre, const std::string& post) : name_(name), pre_(nullptr)
+{
+  pre_ = parse_query(pre);
+  post_ = parse_effects(post);
+  if (not pre_) throw ImaginePlannerException("Could not parse precondition");
+  if (post_.empty())
+  {
+    throw ImaginePlannerException("Could not parse postcondition");
+  }
+}
+
+std::string OperatorSpecification::to_str() const
+{
+  std::ostringstream oss;
+  oss << name_ << "\n  Pre: " << pre_->to_str() << "\n  Post: ";
+  bool first = true;
+  for (Effect* eff : post_)
+  {
+    if (not first) oss << ',';
+    oss << *eff;
+    first = false;
+  }
+  return oss.str();
+}
+
+std::vector<Operator> OperatorSpecification::instantiate(
+    const PlanState& state) const
+{
+  std::vector<Operator> instantiations;
+  instantiations.reserve(50);
+  Query* query = (*pre_)(state);
+  while (query->next_solution())
+  {
+    instantiations.push_back(Operator(this, query->get_solution()));
+  }
+  delete query;
+  return instantiations;
+}
+
+OperatorSpecification::~OperatorSpecification()
+{
+  delete pre_;
+  for (Effect* eff : post_) delete eff;
+}
+
+// Implementation of Operator's methods
+
+Operator::Operator(const OperatorSpecification* spec,
+    const Substitution& sigma) : spec_(spec), sigma_(sigma) {}
+
+std::string Operator::to_str() const
+{
+  std::ostringstream oss;
+  oss << spec_->get_name() << '(';
+  bool first = true;
+  for (const auto& entry : sigma_)
+  {
+    if (entry.first[0] != '_')
+    {
+      if (not first) oss << ',';
+      oss << entry.first << '=' << entry.second;
+      first = false;
+    }
+  }
+  oss << ')';
+  return oss.str();
+}
+
+PlanState Operator::operator()(const PlanState& state) const
+{
+  PlanState result(state);
+  for (const Effect* eff : spec_->get_post())
+  {
+    (*eff)(result, sigma_);
+  }
+  return result;
+}
+
+// Implementation of Domain's methods
+
+void Domain::load_ades()
+{
+  ades::AdesDB db(ades_db_, version_db_);
+  available_operators_.clear();
+  available_operators_.reserve(db.getAdesNb());
+  for (ades::Ades& action : db.listAdes())
+  {
+    std::string name = action.getName();
+    auto ades_pc = action.getPreconditions();
+    auto ades_ef = action.getEffects();
+    if (ades_pc.empty()) throw ImaginePlannerException("0 preconditions");
+    if (ades_ef.empty()) throw ImaginePlannerException("0 effects");
+    // consider just the tail of the first precondition and the first effect
+    std::string pre = action.getPreconditions().begin()->second;
+    std::string post = action.getEffects().begin()->second;
+    available_operators_.push_back(OperatorSpecification(name, pre, post));
+  }
+}
+
+Domain::Domain() : ades_db_(""), version_db_(0) {}
+
+Domain::Domain(const std::string& ades_db, std::size_t version_db)
+  : ades_db_(ades_db), version_db_(version_db)
+{
+  load_ades();
+}
+
+std::string Domain::to_str() const
+{
+  std::ostringstream oss;
+  oss << "Domain:\nADES_DB: " << ades_db_ << " (v" << version_db_
+      << ")\nOperators:";
+  for (const OperatorSpecification& opspec : available_operators_)
+  {
+    oss << '\n' << opspec;
+  }
+  return oss.str();
+}
+
+void Domain::set_ades_db(const std::string& ades_db, std::size_t version_db)
+{
+  ades_db_ = ades_db;
+  version_db_ = version_db;
+  load_ades();
+}
+
+std::vector<Operator> Domain::instantiate_all(const PlanState& state) const
+{
+  std::vector<Operator> acc;
+  acc.reserve(50);
+  for (const OperatorSpecification& op : available_operators_)
+  {
+    std::vector<Operator> instantiations = op.instantiate(state);
+    acc.insert(acc.end(), instantiations.begin(), instantiations.end());
+  }
+  return acc;
+}
+
+}
diff --git a/src/domains.h b/src/domains.h
new file mode 100644
index 0000000000000000000000000000000000000000..0c0c3bd8deb53db83977ce6d2e7979cf4a965f1c
--- /dev/null
+++ b/src/domains.h
@@ -0,0 +1,89 @@
+#ifndef _LIBIMAGINE_PLANNER_DOMAINS_H_
+#define _LIBIMAGINE_PLANNER_DOMAINS_H_
+
+#include "parsing.h"
+
+namespace imagine_planner
+{
+
+class OperatorSpecification;
+class Operator;
+class Problem;
+
+class OperatorSpecification : public Stringifiable
+{
+  private:
+    std::string name_;
+    QuerySpecification* pre_;
+    EffectV post_;
+
+  public:
+
+    OperatorSpecification(const OperatorSpecification& other);
+
+    OperatorSpecification(const std::string& name, const std::string& pre,
+        const std::string& post);
+
+    std::string to_str() const override;
+
+    const std::string& get_name() const { return name_; }
+
+    const QuerySpecification* get_pre() const { return pre_; }
+
+    const EffectV& get_post() const { return post_; }
+
+    std::vector<Operator> instantiate(const PlanState& state) const;
+
+    ~OperatorSpecification();
+    
+};
+
+
+class Operator : public Stringifiable
+{
+  private:
+    const OperatorSpecification* spec_;
+    Substitution sigma_;
+
+  public:
+
+    Operator(const OperatorSpecification* spec, const Substitution& sigma);
+
+    std::string to_str() const override;
+
+    PlanState operator()(const PlanState& state) const;
+
+};
+
+class Domain : public Stringifiable
+{
+  private:
+    std::string ades_db_;
+    std::size_t version_db_;
+    std::vector<OperatorSpecification> available_operators_;
+
+    void load_ades();
+
+  public:
+
+    Domain();
+
+    Domain(const std::string& ades_db, std::size_t version_db=0);
+
+    std::string to_str() const;
+
+    const std::string& get_ades_db() const { return ades_db_; }
+
+    void set_ades_db(const std::string& ades_db, std::size_t version_db=0);
+
+    const std::vector<OperatorSpecification>& get_available_operators() const
+    {
+      return available_operators_;
+    }
+
+    std::vector<Operator> instantiate_all(const PlanState& state) const;
+};
+
+}
+
+#endif
diff --git a/src/effects.cpp b/src/effects.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..aac731b455c2525da2b5acb11a609b765def1552
--- /dev/null
+++ b/src/effects.cpp
@@ -0,0 +1,64 @@
+#include "effects.h"
+#include <sstream>
+
+namespace imagine_planner
+{
+
+void throw_non_instantiated_error(const Predicate& predicate)
+{
+  std::ostringstream oss;
+  oss << "The following terms are not ground: ";
+  bool first = true;
+  for (const TermWrapper& term : predicate.get_arguments())
+  {
+    if (not term.is_ground())
+    {
+      if (not first) oss << ", ";
+      oss << term;
+      first = false;
+    }
+  }
+  throw ImaginePlannerException(oss.str());
+}
+
+// AddEffect's methods
+
+AddEffect::AddEffect(const Predicate& effect) : effect_(effect) {}
+
+std::string AddEffect::to_str() const
+{
+  return effect_.to_str();
+}
+
+void AddEffect::operator()(PlanState& state, const Substitution& sigma) const
+{
+  Predicate predicate = sigma(effect_);
+  if (not predicate.is_ground()) throw_non_instantiated_error(predicate);
+  state.put(predicate);
+}
+
+// DeleteEffect's methods
+
+DeleteEffect::DeleteEffect(const Predicate& effect) : effect_(effect) {}
+
+std::string DeleteEffect::to_str() const
+{
+  return std::string("\\+") + effect_.to_str();
+}
+
+void DeleteEffect::operator()(PlanState& state, const Substitution& sigma) const
+{
+  Predicate predicate = sigma(effect_);
+  if (not predicate.is_ground()) throw_non_instantiated_error(predicate);
+  state.remove(predicate);
+}
+
+std::vector<Effect*> clone_effects(const std::vector<Effect*>& effects)
+{
+  std::vector<Effect*> clone;
+  clone.reserve(effects.size());
+  for (Effect* eff : effects) clone.push_back(eff->clone());
+  return clone;
+}
+
+} /* end namespace imagine_planner */
diff --git a/src/effects.h b/src/effects.h
new file mode 100644
index 0000000000000000000000000000000000000000..0ac5254b2c9bd59dd5b7c5f58ef8a90eb08d1fde
--- /dev/null
+++ b/src/effects.h
@@ -0,0 +1,72 @@
+#ifndef _LIBIMAGINE_PLANNER_EFFECTS_H_
+#define _LIBIMAGINE_PLANNER_EFFECTS_H_
+
+#define EFFECT_COMMON(Class)\
+    virtual Effect* clone() const override { return new Class(*this); };
+
+#include "types.h"
+
+namespace imagine_planner
+{
+
+class Effect;
+class AddEffect;
+class DeleteEffect;
+
+void throw_non_instantiated_error(const Predicate& predicate);
+
+class Effect : public Stringifiable
+{
+  public:
+
+    virtual void operator()(PlanState& state,
+        const Substitution& sigma) const =0;
+
+    virtual ~Effect() {}
+
+    virtual Effect* clone() const =0;
+
+};
+
+class AddEffect : public Effect
+{
+  private:
+    Predicate effect_;
+
+  public:
+
+    AddEffect(const Predicate& effect);
+
+    std::string to_str() const override;
+
+    virtual void operator()(PlanState& state,
+        const Substitution& sigma) const override;
+
+    EFFECT_COMMON(AddEffect);
+
+};
+
+class DeleteEffect : public Effect
+{
+  private:
+    Predicate effect_;
+
+  public:
+
+    DeleteEffect(const Predicate& effect);
+
+    std::string to_str() const override;
+
+    virtual void operator()(PlanState& state,
+        const Substitution& sigma) const override;
+
+    EFFECT_COMMON(DeleteEffect);
+};
+
+typedef std::vector<Effect*> EffectV;
+
+std::vector<Effect*> clone_effects(const std::vector<Effect*>& effects);
+
+}
+
+#endif
diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt
index dd6dd89c4c842afdb3c2396ee643052fd1d23683..25564458e2d57f4d9a37c896b18d6c540f817b9b 100644
--- a/src/examples/CMakeLists.txt
+++ b/src/examples/CMakeLists.txt
@@ -1,26 +1,34 @@
 # create an example application
 ADD_EXECUTABLE(imagine-planner_test imagine-planner_test.cpp)
 # link necessary libraries
-TARGET_LINK_LIBRARIES(imagine-planner_test imagine-planner)
+TARGET_LINK_LIBRARIES(imagine-planner_test
+                      imagine-planner
+                      ${Boost_LIBRARIES}
+                      ${LIBIMAGINE-ADES_LIBRARIES})
 
 ADD_EXECUTABLE(types_test types_test.cpp)
 TARGET_LINK_LIBRARIES(types_test
                       imagine-planner
-                      ${Boost_FILESYSTEM_LIBRARY}
-                      ${Boost_SYSTEM_LIBRARY}
-                      ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})
+                      ${Boost_LIBRARIES})
 
 ADD_EXECUTABLE(queries_test queries_test.cpp)
 TARGET_LINK_LIBRARIES(queries_test
                       imagine-planner
-                      ${Boost_FILESYSTEM_LIBRARY}
-                      ${Boost_SYSTEM_LIBRARY}
-                      ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})
+                      ${Boost_LIBRARIES})
+
+ADD_EXECUTABLE(effects_test effects_test.cpp)
+TARGET_LINK_LIBRARIES(effects_test
+                      imagine-planner
+                      ${Boost_LIBRARIES})
 
 ADD_EXECUTABLE(parsing_test parsing_test.cpp)
 TARGET_LINK_LIBRARIES(parsing_test
                       imagine-planner
-                      ${Boost_FILESYSTEM_LIBRARY}
-                      ${Boost_SYSTEM_LIBRARY}
-                      ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})
+                      ${Boost_LIBRARIES})
+
+ADD_EXECUTABLE(domains_test domains_test.cpp)
+TARGET_LINK_LIBRARIES(domains_test
+                      imagine-planner
+                      ${Boost_LIBRARIES}
+                      ${LIBIMAGINE-ADES_LIBRARIES})
 
diff --git a/src/examples/domains_test.cpp b/src/examples/domains_test.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2faeb499b9b1d35082079ed715e53aa1051bdbc3
--- /dev/null
+++ b/src/examples/domains_test.cpp
@@ -0,0 +1,218 @@
+#define BOOST_TEST_MODULE Domains Test
+#define BOOST_TEST_DYN_LINK
+#include <boost/test/unit_test.hpp>
+
+#include "domains.h"
+#include <libades/libades.h>
+#include <iostream>
+using namespace imagine_planner;
+
+#if 0 // Set to 1 if __PRETTY_FUNCTION__ macro is not available
+#define __PRETTY_FUNCTION__ __func__
+#endif
+
+#if 1
+#define INFO(x)\
+  std::cout << "\e[1m\e[32m\e[4m" <<  __PRETTY_FUNCTION__ << " [" << __LINE__ << "]" << "\e[24m: " << x << "\e[0m" << std::endl;
+#else
+#define INFO(x)
+#endif
+
+BOOST_AUTO_TEST_CASE(operators_test1)
+{
+  PlanState state({Predicate("at", "lettuce", "east"),
+                   Predicate("at", "wolf", "east"),
+                   Predicate("at", "bunny", "east"),
+                   Predicate("at", "boat", "east"),
+                   Predicate("side", "east"),
+                   Predicate("side", "west"),
+                   Predicate("forbidden", "lettuce", "bunny"),
+                   Predicate("forbidden", "wolf", "bunny")});
+  OperatorSpecification spec("load",
+      "at(boat,Side), at(What,Side), side(Side), !=(What,boat), \\+loaded(_)",
+      "\\+at(What,Side), loaded(What)");
+  INFO(spec);
+  std::vector<Operator> instantiations = spec.instantiate(state);
+  for (const Operator& op : instantiations)
+  {
+    INFO(op);
+  }
+  BOOST_REQUIRE_EQUAL(instantiations.size(), 3);
+  BOOST_CHECK_EQUAL(instantiations[0].to_str(), "load(Side=east,What=bunny)");
+  BOOST_CHECK_EQUAL(instantiations[1].to_str(), "load(Side=east,What=lettuce)");
+  BOOST_CHECK_EQUAL(instantiations[2].to_str(), "load(Side=east,What=wolf)");
+  PlanState after = instantiations[0](state);
+  INFO("Before " << instantiations[0] << ": " << state << ", after: " << after);
+}
+
+BOOST_AUTO_TEST_CASE(operators_test2)
+{
+  PlanState state({Predicate("at", "lettuce", "east"),
+                   Predicate("at", "wolf", "east"),
+                   Predicate("at", "bunny", "east"),
+                   Predicate("at", "boat", "east"),
+                   Predicate("side", "east"),
+                   Predicate("side", "west"),
+                   Predicate("forbidden", "lettuce", "bunny"),
+                   Predicate("forbidden", "wolf", "bunny")});
+  OperatorSpecification spec("switch_sides",
+      "at(boad,From),side(From),side(To),!=(From,To),\\+(forbidden(Obj1, Obj2),at(Obj1,From),at(Obj2,From))",
+      "\\+at(boat,From), at(boat,To)");
+  INFO(spec);
+  std::vector<Operator> instantiations = spec.instantiate(state);
+  for (const Operator& op : instantiations)
+  {
+    INFO(op);
+  }
+  BOOST_REQUIRE_EQUAL(instantiations.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(operators_test3)
+{
+  PlanState state({Predicate("at", "lettuce", "west"),
+                   Predicate("at", "wolf", "west"),
+                   Predicate("at", "bunny", "east"),
+                   Predicate("at", "boat", "east"),
+                   Predicate("side", "east"),
+                   Predicate("side", "west"),
+                   Predicate("forbidden", "lettuce", "bunny"),
+                   Predicate("forbidden", "wolf", "bunny")});
+  OperatorSpecification spec("switch_sides",
+      "at(boat,From),side(From),side(To),!=(From,To),\\+(forbidden(Obj1, Obj2),at(Obj1,From),at(Obj2,From))",
+      "\\+at(boat,From), at(boat,To)");
+  INFO(spec);
+  std::vector<Operator> instantiations = spec.instantiate(state);
+  for (const Operator& op : instantiations)
+  {
+    INFO(op);
+  }
+  BOOST_REQUIRE_EQUAL(instantiations.size(), 1);
+  BOOST_CHECK_EQUAL(instantiations[0].to_str(), "switch_sides(From=east,To=west)");
+}
+
+void head_and_tail(const std::string& rule,
+    std::string& head,
+    std::string& tail)
+{
+  std::size_t separator = rule.find(":-");
+  head = rule.substr(0, separator);
+  tail = separator == std::string::npos? "" : rule.substr(separator + 2);
+  std::cout << "head: " << head << "; tail: " << tail << std::endl;
+}
+
+ades::Ades new_ades(const std::string& name,
+    const std::vector<std::string>& preconditions,
+    const std::vector<std::string>& effects)
+{
+  ades::Ades ret(name);
+  std::map<std::string, std::string> pc_map;
+  for (const std::string& pc : preconditions)
+  {
+    std::string head, tail;
+    head_and_tail(pc, head, tail);
+    pc_map.insert({head, tail});
+  }
+  ret.insertPreconditions(pc_map);
+  std::map<std::string, std::string> eff_map;
+  for (const std::string& eff : effects)
+  {
+    std::string head, tail;
+    head_and_tail(eff, head, tail);
+    eff_map.insert({head, tail});
+  }
+  ret.insertEffects(eff_map);
+  return ret;
+}
+
+void add_basic_actions(ades::AdesDB& db)
+{
+  db.addAdes(new_ades("load",
+        {"load_pre(Side,What):-at(boat,Side), at(What,Side), side(Side), !=(What,boat), \\+loaded(_)"},
+        {"load_post(Side,What):-\\+at(What,Side), loaded(What)"}));
+  db.addAdes(new_ades("switch_sides",
+        {"switch_sides_pre(From,To):-at(boat,From),side(From),side(To),!=(From,To),\\+(forbidden(Obj1, Obj2),at(Obj1,From),at(Obj2,From))"},
+        {"switch_sides_post(From,To):-\\+at(boat,From),at(boat,To)"}));
+  db.addAdes(new_ades("unload",
+        {"unload_pre(Side,What):-at(boat,Side),side(Side),loaded(What)"},
+        {"unload_post(Side,What):-at(What,Side),\\+loaded(What)"}));
+}
+
+BOOST_AUTO_TEST_CASE(operator_loading)
+{
+  std::string dbpath = std::string(std::getenv("HOME")) + "/DOMAINS_TEST";
+  {
+    ades::AdesDB db(dbpath, 0);
+    if (db.getAdesNb() == 0) add_basic_actions(db);
+  }
+  PlanState state({Predicate("at", "lettuce", "east"),
+                   Predicate("at", "wolf", "east"),
+                   Predicate("at", "bunny", "east"),
+                   Predicate("at", "boat", "east"),
+                   Predicate("side", "east"),
+                   Predicate("side", "west"),
+                   Predicate("forbidden", "lettuce", "bunny"),
+                   Predicate("forbidden", "wolf", "bunny")});
+  //GoalSpecification goal({Predicate("at", "lettuce", "west"),
+                          //Predicate("at", "bunny", "west"),
+                          //Predicate("at", "wolf", "west"),
+                          //Predicate("at", "boat", "west")}, {});
+  Domain domain(dbpath);
+  INFO(domain);
+  INFO("state: " << state);
+  std::vector<Operator> instantiations = domain.instantiate_all(state);
+  for (const Operator op : instantiations)
+  {
+    INFO(op);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(instantiations1)
+{
+  std::string dbpath = std::string(std::getenv("HOME")) + "/DOMAINS_TEST";
+  {
+    ades::AdesDB db(dbpath, 0);
+    if (db.getAdesNb() == 0) add_basic_actions(db);
+  }
+  PlanState state({Predicate("at", "lettuce", "east"),
+                   Predicate("at", "wolf", "east"),
+                   Predicate("at", "bunny", "east"),
+                   Predicate("at", "boat", "east"),
+                   Predicate("side", "east"),
+                   Predicate("side", "west"),
+                   Predicate("forbidden", "lettuce", "bunny"),
+                   Predicate("forbidden", "wolf", "bunny")});
+  Domain domain(dbpath);
+  INFO(domain);
+  INFO("state: " << state);
+  std::vector<Operator> instantiations = domain.instantiate_all(state);
+  for (const Operator op : instantiations)
+  {
+    INFO(op);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(instantiations2)
+{
+  std::string dbpath = std::string(std::getenv("HOME")) + "/DOMAINS_TEST";
+  {
+    ades::AdesDB db(dbpath, 0);
+    if (db.getAdesNb() == 0) add_basic_actions(db);
+  }
+  PlanState state({Predicate("at", "lettuce", "west"),
+                   Predicate("at", "wolf", "west"),
+                   Predicate("at", "bunny", "east"),
+                   Predicate("at", "boat", "east"),
+                   Predicate("side", "east"),
+                   Predicate("side", "west"),
+                   Predicate("forbidden", "lettuce", "bunny"),
+                   Predicate("forbidden", "wolf", "bunny")});
+  Domain domain(dbpath);
+  INFO(domain);
+  INFO("state: " << state);
+  std::vector<Operator> instantiations = domain.instantiate_all(state);
+  for (const Operator op : instantiations)
+  {
+    INFO(op);
+  }
+}
+
diff --git a/src/examples/effects_test.cpp b/src/examples/effects_test.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..21c5c11218ee24bc84d5b7d3595c300848585c1f
--- /dev/null
+++ b/src/examples/effects_test.cpp
@@ -0,0 +1,53 @@
+#define BOOST_TEST_MODULE Effects test
+#define BOOST_TEST_DYN_LINK
+#include <boost/test/unit_test.hpp>
+
+#include "effects.h"
+#include <iostream>
+using namespace imagine_planner;
+
+#if 0 // Set to 1 if __PRETTY_FUNCTION__ macro is not available
+#define __PRETTY_FUNCTION__ __func__
+#endif
+
+#if 1
+#define INFO(x)\
+  std::cout << "\e[1m\e[32m\e[4m" <<  __PRETTY_FUNCTION__ << " [" << __LINE__ << "]" << "\e[24m: " << x << "\e[0m" << std::endl;
+#else
+#define INFO(x)
+#endif
+
+BOOST_AUTO_TEST_CASE(simple_add_effect_test1)
+{
+  PlanState state({Predicate("p","a","b"), Predicate("p","a","c"),
+      Predicate("p","d","a"), Predicate("q","w"), Predicate("q", "s")});
+  PlanState goal({Predicate("p","a","b"), Predicate("p","a","c"),
+      Predicate("p","d","a"), Predicate("q","w"), Predicate("q", "s"),
+      Predicate("r", "a", "b", 1.0)});
+  INFO(state);
+  AddEffect ef1(Predicate("r", "X", "Y", "Z"));
+  PlanState state2(state);
+  BOOST_CHECK_THROW(ef1(state2, Substitution({{"X", TermWrapper("a")}})),
+                    ImaginePlannerException);
+  ef1(state2, Substitution({{"X", TermWrapper("a")},
+                            {"Y", TermWrapper("b")},
+                            {"Z", TermWrapper(1.0)}}));
+  BOOST_CHECK_EQUAL(state2, goal);
+}
+
+BOOST_AUTO_TEST_CASE(simple_delete_effect_test1)
+{
+  PlanState state({Predicate("p","a","b"), Predicate("p","a",1.0),
+      Predicate("p","d","a"), Predicate("q","w"), Predicate("q", "s")});
+  PlanState goal({Predicate("p","a","b"), Predicate("p","d","a"),
+      Predicate("q","w"), Predicate("q", "s")});
+  INFO(state);
+  DeleteEffect ef1(Predicate("p", "X", "Y"));
+  PlanState state2(state);
+  BOOST_CHECK_THROW(ef1(state2, Substitution({{"X", TermWrapper("a")}})),
+                    ImaginePlannerException);
+  ef1(state2, Substitution({{"X", TermWrapper("a")},
+                            {"Y", TermWrapper(1.0+5e-10)}}));
+  BOOST_CHECK_EQUAL(state2, goal);
+}
+
diff --git a/src/examples/imagine-planner_test.cpp b/src/examples/imagine-planner_test.cpp
index bb65efe713e047d3c947f29602ca6e032713fc83..1bb23e2855611e6896ee6844c2f900e0d97c9df0 100644
--- a/src/examples/imagine-planner_test.cpp
+++ b/src/examples/imagine-planner_test.cpp
@@ -1,5 +1,193 @@
 #include "imagine-planner.h"
+#include <libades/libades.h>
+#include <iostream>
+#include <unordered_set>
+using namespace imagine_planner;
+
+#if 0 // Set to 1 if __PRETTY_FUNCTION__ macro is not available
+#define __PRETTY_FUNCTION__ __func__
+#endif
+
+#if 1
+#define INFO(x)\
+  std::cout << "\e[1m\e[32m\e[4m" <<  __PRETTY_FUNCTION__ << " [" << __LINE__ << "]" << "\e[24m: " << x << "\e[0m" << std::endl;
+#else
+#define INFO(x)
+#endif
+
+class IDSPlanner : public Planner
+{
+  private:
+
+    typedef std::unordered_set<PlanState, std::hash<Hashable>> ClosedSet;
+
+    const Problem* problem_;
+    ClosedSet seen_;
+    int max_depth_;
+    double elapsed_;
+
+    bool ids(const PlanState& state, Plan& plan, std::size_t depth)
+    {
+      if (problem_->goal(state)) return true;
+      if (depth == 0) return false;
+      seen_.insert(state);
+      std::vector<Operator> instantiations = problem_->domain.instantiate_all(state);
+      for (const Operator& op : instantiations)
+      {
+        PlanState next = op(state);
+        if (seen_.count(next)) continue;
+        plan.push_back({op, next});
+        if (ids(next, plan, depth-1)) return true;
+        plan.pop_back();
+      }
+      seen_.erase(state);
+      return false;
+    }
+
+  public:
+
+    IDSPlanner(int max_depth=100) : max_depth_(100) {}
+
+    virtual Plan operator()(const Problem& problem) override
+    {
+      Plan plan;
+      problem_ = &problem;
+      StopWatch sw;
+      for (int depth = 0; depth < max_depth_; ++depth)
+      {
+        if (ids(problem_->start, plan, depth)) break;
+      }
+      elapsed_ = sw.time();
+      seen_.clear();
+      return plan;
+    }
+
+    double get_elapsed() const { return elapsed_; }
+
+};
+
+void head_and_tail(const std::string& rule,
+    std::string& head,
+    std::string& tail)
+{
+  std::size_t separator = rule.find(":-");
+  head = rule.substr(0, separator);
+  tail = separator == std::string::npos? "" : rule.substr(separator + 2);
+}
+
+ades::Ades new_ades(const std::string& name,
+    const std::vector<std::string>& preconditions,
+    const std::vector<std::string>& effects)
+{
+  ades::Ades ret(name);
+  std::map<std::string, std::string> pc_map;
+  for (const std::string& pc : preconditions)
+  {
+    std::string head, tail;
+    head_and_tail(pc, head, tail);
+    pc_map.insert({head, tail});
+  }
+  ret.insertPreconditions(pc_map);
+  std::map<std::string, std::string> eff_map;
+  for (const std::string& eff : effects)
+  {
+    std::string head, tail;
+    head_and_tail(eff, head, tail);
+    eff_map.insert({head, tail});
+  }
+  ret.insertEffects(eff_map);
+  return ret;
+}
+
+void add_basic_actions(ades::AdesDB& db, const std::string& domain="boat")
+{
+  // domains: {boat, blocks}
+  if (domain == "boat")
+  {
+    db.addAdes(new_ades("load",
+          {"load_pre(Side,What):-at(boat,Side), at(What,Side), side(Side), !=(What,boat), \\+loaded(_)"},
+          {"load_post(Side,What):-\\+at(What,Side), loaded(What)"}));
+    db.addAdes(new_ades("switch_sides",
+          {"switch_sides_pre(From,To):-at(boat,From),side(From),side(To),!=(From,To),\\+(forbidden(Obj1, Obj2),at(Obj1,From),at(Obj2,From))"},
+          {"switch_sides_post(From,To):-\\+at(boat,From),at(boat,To)"}));
+    db.addAdes(new_ades("unload",
+          {"unload_pre(Side,What):-at(boat,Side),side(Side),loaded(What)"},
+          {"unload_post(Side,What):-at(What,Side),\\+loaded(What)"}));
+  }
+  else if (domain == "blocks")
+  {
+    db.addAdes(new_ades("pick_from",
+          {"pick_from_pre(Block,Where):-\\+holding(_),on(Block,Where),\\+on(_,Block)"},
+          {"pick_from_post(Block,Where):-\\+on(Block,Where),holding(Block)"}));
+    db.addAdes(new_ades("leave_on",
+          {"leave_on_pre(Block,Where):-holding(Block),object(Where),\\+(!=(Where,table),on(_,Where))"},
+          {"leave_on_post(Block,Where):-\\+holding(Block),on(Block,Where)"}));
+  }
+  else if (domain == "blocks_traditional")
+  {
+    db.addAdes(new_ades("unstack",
+          {"unstack_pre(X,Y):-\\+holding(_),on(X,Y),!=(Y,table),\\+on(_,X)"},
+          {"unstack_post(X,Y):-holding(X),\\+on(X,Y)"}));
+    db.addAdes(new_ades("pick",
+          {"pick_pre(X):-\\+holding(_),on(X,table),\\+on(_,X)"},
+          {"pick_post(X):-holding(X),\\+on(X,table)"}));
+    db.addAdes(new_ades("stack",
+          {"stack_pre(X,Y):-holding(X),object(Y),!=(Y,table),\\+on(_,Y)"},
+          {"stack_post(X,Y):-\\+holding(X),on(X,Y)"}));
+    db.addAdes(new_ades("leave",
+          {"leave_pre(X):-holding(X)"},
+          {"leave_post(X):-\\+holding(X),on(X,table)"}));
+  }
+}
 
 int main(int argc, char *argv[])
 {
+  std::string domain_name = argc < 2? "boat" : argv[1];
+  std::string dbpath = std::string(std::getenv("HOME")) + "/ADES_DOMAINS/" + domain_name + "_DOMAIN";
+  {
+    ades::AdesDB db(dbpath, 0);
+    if (db.getAdesNb() == 0) add_basic_actions(db, domain_name);
+  }
+  Problem problem;
+  if (domain_name == "boat")
+  {
+    problem.start = PlanState({Predicate("at", "lettuce", "east"),
+                     Predicate("at", "wolf", "east"),
+                     Predicate("at", "bunny", "east"),
+                     Predicate("at", "boat", "east"),
+                     Predicate("side", "east"),
+                     Predicate("side", "west"),
+                     Predicate("forbidden", "lettuce", "bunny"),
+                     Predicate("forbidden", "wolf", "bunny")});
+    problem.goal = GoalSpecification({Predicate("at", "lettuce", "west"),
+                            Predicate("at", "bunny", "west"),
+                            Predicate("at", "wolf", "west"),
+                            Predicate("at", "boat", "west")}, {});
+  }
+  else if (domain_name == "blocks" or domain_name == "blocks_traditional")
+  {
+    problem.start = PlanState({Predicate("object", "a"),
+                               Predicate("object", "b"),
+                               Predicate("object", "c"),
+                               Predicate("object", "d"),
+                               Predicate("object", "table"),
+                               Predicate("on", "b", "d"),
+                               Predicate("on", "d", "c"),
+                               Predicate("on", "c", "table"),
+                               Predicate("on", "a", "table")});
+    problem.goal = GoalSpecification({Predicate("on", "a", "b"),
+                                      Predicate("on", "b", "table"),
+                                      Predicate("on", "c", "d"),
+                                      Predicate("on", "d", "table")}, {});
+  }
+  else
+  {
+    INFO("INVALID DOMAIN: " << domain_name);
+  }
+  problem.domain = Domain(dbpath, 0);
+  IDSPlanner planner(50);
+  Plan plan = planner(problem);
+  INFO("Initial state: " << problem.start);
+  INFO("Plan found:\n" << plan);
+  INFO("Elapsed (ms): " << planner.get_elapsed()*1000);
 }
diff --git a/src/examples/parsing_test.cpp b/src/examples/parsing_test.cpp
index 6d7422c4c8a3aa0dcf4eafece2891ae6dc355192..3abd834c668281192095afb6d015de99b0d9c92a 100644
--- a/src/examples/parsing_test.cpp
+++ b/src/examples/parsing_test.cpp
@@ -31,6 +31,20 @@ std::ostream& operator<<(std::ostream& os, const TokenV& tokens)
   return os;
 }
 
+std::ostream& operator<<(std::ostream& os, const std::vector<Effect*>& effects)
+{
+  bool first = true;
+  os << '[';
+  for (const Effect* effect : effects)
+  {
+    if (not first) os << ", ";
+    os << *effect;
+    first = false;
+  }
+  os << ']';
+  return os;
+}
+
 BOOST_AUTO_TEST_CASE(spaces)
 {
   std::string str("   Hello  ( world) ");
@@ -73,70 +87,55 @@ BOOST_AUTO_TEST_CASE(test_parse_simple_expressions)
   std::string str2("\\+at(X, Y)");
   std::string str3("(at(X, Y))");
   std::string str4("\\+(at(X, Y))");
-  QuerySpecification* q1 = parse_expression(str1);
-  if (q1)
-  {
-    INFO("q1: " << *q1);
-    delete q1;
-  }
-  else INFO("q1: none");
-  QuerySpecification* q2 = parse_expression(str2);
-  if (q2)
-  {
-    INFO("q2: " << *q2);
-    delete q2;
-  }
-  else INFO("q2: none");
-  QuerySpecification* q3 = parse_expression(str3);
-  if (q3)
-  {
-    INFO("q3: " << *q3);
-    delete q3;
-  }
-  else INFO("q3: none");
-  QuerySpecification* q4 = parse_expression(str4);
-  if (q4)
-  {
-    INFO("q4: " << *q4);
-    delete q4;
-  }
-  else INFO("q4: none");
+  QuerySpecification* q1 = parse_query(str1);
+  BOOST_REQUIRE(q1);
+  BOOST_CHECK_EQUAL(q1->to_str(), "at(X,Y)");
+  delete q1;
+  QuerySpecification* q2 = parse_query(str2);
+  BOOST_REQUIRE(q2);
+  BOOST_CHECK_EQUAL(q2->to_str(), "\\+at(X,Y)");
+  delete q2;
+  QuerySpecification* q3 = parse_query(str3);
+  BOOST_REQUIRE(q3);
+  BOOST_CHECK_EQUAL(q3->to_str(), "at(X,Y)");
+  delete q3;
+  QuerySpecification* q4 = parse_query(str4);
+  BOOST_REQUIRE(q4);
+  BOOST_CHECK_EQUAL(q4->to_str(), "\\+at(X,Y)");
+  delete q4;
 }
 
 BOOST_AUTO_TEST_CASE(test_parse_complex_expressions)
 {
   std::string str1("at(X, Y), at(Z, Y)");
-  std::string str2("at(X, Y), at(Z, Y), \\+at(boat, Y)");
+  std::string str2("at(X, Y), at(Z, Y), !=(boat, X)");
   std::string str3("fixes(X, Y), \\+(precedes(_1, X), occluded_by(X, _2))");
   std::string str4("\\+(\\+q(X), \\+q(Y), \\+r())");
-  QuerySpecification* q1 = parse_expression(str1);
-  if (q1)
-  {
-    INFO("q1: " << *q1);
-    delete q1;
-  }
-  else INFO("q1: none");
-  QuerySpecification* q2 = parse_expression(str2);
-  if (q2)
-  {
-    INFO("q2: " << *q2);
-    delete q2;
-  }
-  else INFO("q2: none");
-  QuerySpecification* q3 = parse_expression(str3);
-  if (q3)
-  {
-    INFO("q3: " << *q3);
-    delete q3;
-  }
-  else INFO("q3: none");
-  QuerySpecification* q4 = parse_expression(str4);
-  if (q4)
-  {
-    INFO("q4: " << *q4);
-    delete q4;
-  }
-  else INFO("q4: none");
+  QuerySpecification* q1 = parse_query(str1);
+  BOOST_REQUIRE(q1);
+  BOOST_CHECK_EQUAL(q1->to_str(), "at(X,Y),at(Z,Y)");
+  delete q1;
+  QuerySpecification* q2 = parse_query(str2);
+  BOOST_REQUIRE(q2);
+  BOOST_CHECK_EQUAL(q2->to_str(), "at(X,Y),at(Z,Y),!=(boat,X)");
+  delete q2;
+  QuerySpecification* q3 = parse_query(str3);
+  BOOST_REQUIRE(q3);
+  BOOST_CHECK_EQUAL(q3->to_str(), "fixes(X,Y),\\+(precedes(_1,X),occluded_by(X,_2))");
+  delete q3;
+  QuerySpecification* q4 = parse_query(str4);
+  BOOST_REQUIRE(q4);
+  BOOST_CHECK_EQUAL(q4->to_str(), "\\+(\\+q(X),\\+q(Y),\\+r())");
+  delete q4;
 }
 
+BOOST_AUTO_TEST_CASE(test_parse_effects)
+{
+  std::string str1("\\+at(X, Y), at(X, Z)");
+  std::vector<Effect*> effects = parse_effects(str1);
+  INFO(effects);
+  BOOST_REQUIRE_EQUAL(effects.size(), 2);
+  BOOST_CHECK_EQUAL(effects[0]->to_str(), "\\+at(X,Y)");
+  BOOST_CHECK_EQUAL(effects[1]->to_str(), "at(X,Z)");
+}
 
diff --git a/src/examples/queries_test.cpp b/src/examples/queries_test.cpp
index e86e3c675a2dc180f206eb7df1dfe11f930a8e16..cf50c57870a825bb844bd22c1d7939a216a8eea7 100644
--- a/src/examples/queries_test.cpp
+++ b/src/examples/queries_test.cpp
@@ -64,5 +64,23 @@ BOOST_AUTO_TEST_CASE(and_query_test_sat)
   BOOST_REQUIRE(and1->next_solution());
   BOOST_CHECK_EQUAL(and1->get_solution().to_str(), "{\n  X -> d,\n  Y -> a\n}");
   BOOST_REQUIRE(not and1->next_solution());
+  delete and1;
+}
+
+BOOST_AUTO_TEST_CASE(comparison_query_test_sat)
+{
+  PlanState state({Predicate("p","a","b"), Predicate("p","a","c"),
+      Predicate("p", "e", "d"), Predicate("p","d","a"), Predicate("q","b")});
+  INFO("state: " << state);
+  SimpleQuery* q1 = new SimpleQuery(state, Predicate("p", "X", "Y"));
+  ComparisonQuery* q2 = new ComparisonQuery(state, Predicate("<", "X", "Y"));
+  AndQuery* and1 = new AndQuery(q1, q2);
+  INFO("and1: " << *and1);
+  BOOST_REQUIRE(and1->next_solution());
+  BOOST_CHECK_EQUAL(and1->get_solution().to_str(), "{\n  X -> a,\n  Y -> b\n}");
+  BOOST_REQUIRE(and1->next_solution());
+  BOOST_CHECK_EQUAL(and1->get_solution().to_str(), "{\n  X -> a,\n  Y -> c\n}");
+  BOOST_REQUIRE(not and1->next_solution());
+  delete and1;
 }
 
diff --git a/src/imagine-planner.cpp b/src/imagine-planner.cpp
index 3ec12725d53d977af12b82479d4359ab9507d26b..c30e4cfde3c985552402f208593ccb61815e4466 100644
--- a/src/imagine-planner.cpp
+++ b/src/imagine-planner.cpp
@@ -1,10 +1,31 @@
 #include "imagine-planner.h"
 
-CImaginePlanner::CImaginePlanner()
+namespace imagine_planner
 {
+
+std::ostream& operator<<(std::ostream& os, const Plan& plan)
+{
+  bool first = true;
+  for (const Step& step : plan)
+  {
+    if (not first) os << ",\n";
+    os << step.first << " -> " << step.second;
+    first = false;
+  }
+  return os;
 }
- 
-CImaginePlanner::~CImaginePlanner()
+
+StopWatch::StopWatch() : tic_(std::clock()) {}
+
+void StopWatch::reset()
 {
+  tic_ = std::clock();
+}
+
+double StopWatch::time() const
+{
+  return (std::clock() - tic_)/(double)(CLOCKS_PER_SEC);
+}
+
 }
 
diff --git a/src/imagine-planner.h b/src/imagine-planner.h
index 22c2a6fa6049aeb6874914318805913c2c15124f..3d9327c919f5143f7e9164dbacdffb1ab4c13165 100644
--- a/src/imagine-planner.h
+++ b/src/imagine-planner.h
@@ -1,11 +1,49 @@
-#ifndef _IMAGINE_PLANNER_H_
-#define _IMAGINE_PLANNER_H_
+#ifndef _LIBIMAGINE_PLANNER_H_
+#define _LIBIMAGINE_PLANNER_H_
 
-class CImaginePlanner
+#include "domains.h"
+#include <ctime>
+#include <utility>
+
+namespace imagine_planner
+{
+
+struct Problem
+{
+  PlanState start;
+  GoalSpecification goal;
+  Domain domain;
+};
+
+typedef std::pair<Operator, PlanState> Step;
+typedef std::vector<Step> Plan;
+
+
+class Planner
 {
   public:
-    CImaginePlanner();
-    ~CImaginePlanner();
+
+    virtual Plan operator()(const Problem& problem) =0;
+
+    virtual ~Planner() {};
 };
 
+class StopWatch
+{
+  private:
+    std::clock_t tic_;
+    
+  public:
+
+    StopWatch();
+
+    void reset();
+
+    double time() const;
+};
+
+std::ostream& operator<<(std::ostream& os, const Plan& plan);
+
+} /* end of imagine_planner namespace */
+
 #endif
diff --git a/src/parsing.cpp b/src/parsing.cpp
index 49677a1bf2694abeb28eb451d862d963e4b0f54c..1e6e75988e4cde54b2e7dc98b73026506c972647 100644
--- a/src/parsing.cpp
+++ b/src/parsing.cpp
@@ -1,7 +1,6 @@
 #include "parsing.h"
 #include <cctype>
 #include <sstream>
-#include <iostream>
 
 namespace imagine_planner
 {
@@ -20,6 +19,12 @@ bool is_number(const std::string& str)
   }
 }
 
+bool is_comparison_sign(const std::string& str)
+{
+  return str == "==" or str == ">"  or str == "<" or
+         str == "!=" or str == "=<" or str == ">=";
+}
+
 std::string remove_spaces(const std::string& in)
 {
   std::ostringstream oss;
@@ -144,7 +149,7 @@ Predicate* parse_predicate(const TokenV& tokens, std::size_t& cursor)
   return predicate;
 }
 
-QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor)
+QuerySpecification* parse_query(const TokenV& tokens, std::size_t& cursor)
 {
   enum State {EXPECTING_PRED_OR_PAR, EXPECTING_PAR_EXPR,
     EXPECTING_PAR_CLOSE, EXPECTING_COMMA_OR_END, EXPECTING_END_EXPR, OK, KO};
@@ -155,11 +160,9 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor)
   while (state != OK and state != KO)
   {
     const Token& token = tokens[idx];
-    std::cout << token.first << '(' << token.second << ')' << std::endl;
     switch (state)
     {
       case EXPECTING_PRED_OR_PAR:
-        std::cout << "EXPECTING_PRED_OR_PAR" << std::endl;
         if (token.first == "NOT")
         {
           negated = not negated;
@@ -168,7 +171,9 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor)
         }
         else if (Predicate* predicate = parse_predicate(tokens, idx))
         {
-          query = new SimpleQuerySpecification(*predicate);
+          query = is_comparison_sign(predicate->get_name())?
+            (QuerySpecification*)new ComparisonQuerySpecification(*predicate) :
+            (QuerySpecification*)new SimpleQuerySpecification(*predicate);
           query = negated? new NotQuerySpecification(query) : query;
           state = EXPECTING_COMMA_OR_END;
           delete predicate;
@@ -181,8 +186,7 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor)
         else state = KO;
         break;
       case EXPECTING_PAR_EXPR:
-        std::cout << "EXPECTING_PAR_EXPR" << std::endl;
-        if ((query = parse_expression(tokens, idx)))
+        if ((query = parse_query(tokens, idx)))
         {
           query = negated? new NotQuerySpecification(query) : query;
           state = EXPECTING_PAR_CLOSE;
@@ -190,7 +194,6 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor)
         else state = KO;
         break;
       case EXPECTING_PAR_CLOSE:
-        std::cout << "EXPECTING_PAR_CLOSE" << std::endl;
         if (token.first == "PAR_CLOSE")
         {
           state = EXPECTING_COMMA_OR_END;
@@ -199,7 +202,6 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor)
         else state = KO;
         break;
       case EXPECTING_COMMA_OR_END:
-        std::cout << "EXPECTING_COMMA_OR_END" << std::endl;
         if (token.first == "COMMA")
         {
           state = EXPECTING_END_EXPR;
@@ -208,8 +210,7 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor)
         else state = OK;
         break;
       case EXPECTING_END_EXPR:
-        std::cout << "EXPECTING_END_EXPR" << std::endl;
-        if (QuerySpecification* query_snd = parse_expression(tokens, idx))
+        if (QuerySpecification* query_snd = parse_query(tokens, idx))
         {
           query = new AndQuerySpecification(query, query_snd);
           state = OK;
@@ -222,26 +223,76 @@ QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor)
   }
   if (state == OK)
   {
-    std::cout << "OK" << std::endl;
     cursor = idx;
   }
-  else
+  else if (query)
   {
-    std::cout << "KO" << std::endl;
-    if (query)
-    {
-      delete query;
-      query = nullptr;
-    }
+    delete query;
+    query = nullptr;
   }
   return query;
 }
 
-QuerySpecification* parse_expression(const std::string& str)
+QuerySpecification* parse_query(const std::string& str)
 {
   TokenV tokens = decompose(str);
   std::size_t cursor = 0;
-  return parse_expression(tokens, cursor);
+  return parse_query(tokens, cursor);
+}
+
+Effect* parse_next_effect(const TokenV& tokens, std::size_t& cursor)
+{
+  enum State {EXPECTING_ADD_OR_DEL, OK, KO};
+  State state = EXPECTING_ADD_OR_DEL;
+  bool delete_effect = false;
+  Effect* effect = nullptr;
+  std::size_t idx = cursor;
+  while (state != OK and state != KO)
+  {
+    const Token& token = tokens[idx];
+    switch (state)
+    {
+      case EXPECTING_ADD_OR_DEL:
+        if (token.first == "NOT")
+        {
+          delete_effect = true;
+          ++idx;
+        }
+        else if (token.first == "COMMA") ++idx; // ignore commas
+        else if (Predicate* predicate = parse_predicate(tokens, idx))
+        {
+          effect = delete_effect?
+            (Effect*)new DeleteEffect(*predicate) :
+            (Effect*)new AddEffect(*predicate);
+          delete predicate;
+          state = OK;
+        }
+        else state = KO;
+        break;
+      default:
+        /* do nothing <*/
+        break;
+    }
+  }
+  if (state == OK) cursor = idx;
+  else if (effect)
+  {
+    delete effect;
+    effect = nullptr;
+  }
+  return effect; 
+}
+
+EffectV parse_effects(const std::string& str)
+{ 
+  TokenV tokens = decompose(str);
+  std::size_t cursor = 0;
+  EffectV effects;
+  while (Effect* effect = parse_next_effect(tokens, cursor))
+  {
+    effects.push_back(effect);
+  }
+  return effects;
 }
 
 }
diff --git a/src/parsing.h b/src/parsing.h
index c89d6f2f4fbcfeac0a18afb7bafb6693824e4e39..f3d4f77fce62be79085bea1d654444117f48bed8 100644
--- a/src/parsing.h
+++ b/src/parsing.h
@@ -2,6 +2,7 @@
 #define _LIBIMAGINE_PLANNER_PARSING_H_
 
 #include "queries.h"
+#include "effects.h"
 
 namespace imagine_planner
 {
@@ -11,15 +12,21 @@ typedef std::vector<Token> TokenV;
 
 bool is_number(const std::string& str);
 
+bool is_comparison_sign(const std::string& str);
+
 std::string remove_spaces(const std::string& in);
 
 TokenV decompose(const std::string& in);
 
 Predicate* parse_predicate(const TokenV& tokens, std::size_t& cursor);
 
-QuerySpecification* parse_expression(const TokenV& tokens, std::size_t& cursor);
+QuerySpecification* parse_query(const TokenV& tokens, std::size_t& cursor);
+
+QuerySpecification* parse_query(const std::string& str);
+
+Effect* parse_next_effect(const TokenV& tokens, std::size_t& cursor);
 
-QuerySpecification* parse_expression(const std::string& str);
+std::vector<Effect*> parse_effects(const std::string& str);
 
 } /* end namespace imagine_planner */
 
diff --git a/src/queries.cpp b/src/queries.cpp
index 3566caf868ffff48cab415dce7db43807abde301..69ed107679e452f986c93353e61b58b223cbe771 100644
--- a/src/queries.cpp
+++ b/src/queries.cpp
@@ -3,6 +3,39 @@
 namespace imagine_planner
 {
 
+// ComparisonQuery methods
+
+ComparisonQuery::ComparisonQuery(const PlanState&, const Predicate& op,
+    const Substitution& sigma0) : op_(op), sigma0_(sigma0) {}
+
+std::string ComparisonQuery::to_str() const
+{
+  return op_.to_str();
+}
+
+bool ComparisonQuery::next_solution()
+{
+  if (done_) return false;
+  done_ = true;
+  Predicate op_sub = sigma0_(op_);
+  if (not op_sub.is_ground() or op_sub.arity() != 2) return false;
+  const TermWrapper& arg1 = op_sub.get_arguments()[0];
+  const TermWrapper& arg2 = op_sub.get_arguments()[1];
+  if (op_sub.get_name() == "==") return arg1 == arg2;
+  else if (op_sub.get_name() == "!=") return arg1 != arg2;
+  else if (op_sub.get_name() == "<") return arg1 < arg2;
+  else if (op_sub.get_name() == ">") return arg1 > arg2;
+  else if (op_sub.get_name() == "=<") return arg1 <= arg2;
+  else if (op_sub.get_name() == ">=") return arg1 >= arg2;
+  return false;
+}
+
+void ComparisonQuery::reset(const Substitution& sigma0)
+{
+  sigma0_ = sigma0;
+  done_ = false;
+}
+
 // Simple query methods
 
 bool SimpleQuery::unify(const Predicate& goal)
@@ -74,6 +107,10 @@ NotQuery::NotQuery(Query* q) : query_(q), done_(false) {}
 
 std::string NotQuery::to_str() const
 {
+  if (dynamic_cast<const AndQuery*>(query_))
+  {
+    return std::string("\\+(") + query_->to_str() + ')';
+  }
   return std::string("\\+") + query_->to_str();
 }
 
@@ -102,7 +139,7 @@ AndQuery::AndQuery(Query* q1, Query* q2) :
 
 std::string AndQuery::to_str() const
 {
-  return std::string(1, '(') + q1_->to_str() + ',' + q2_->to_str() + ')';
+  return q1_->to_str() + ',' + q2_->to_str();
 }
 
 bool AndQuery::next_solution()
@@ -130,6 +167,22 @@ AndQuery::~AndQuery()
   delete q2_;
 }
 
+// ComparisonQuerySpecification methods
+
+std::string ComparisonQuerySpecification::to_str() const
+{
+  return op_.to_str();
+}
+
+ComparisonQuerySpecification::ComparisonQuerySpecification(
+    const Predicate& op) : op_(op) {}
+
+Query* ComparisonQuerySpecification::operator()(const PlanState& state,
+    const Substitution& sigma0) const
+{
+  return new ComparisonQuery(state, op_, sigma0);
+}
+
 // SimpleQuerySpecification methods
 
 std::string SimpleQuerySpecification::to_str() const
@@ -149,46 +202,85 @@ Query* SimpleQuerySpecification::operator()(const PlanState& state,
 
 // NotQuerySpecification methods
 
-std::string NotQuerySpecification::to_str() const
+void NotQuerySpecification::copy_other(const NotQuerySpecification& other)
 {
-  return std::string("\\+") + query_->to_str();
+  query_ = other.query_->clone();
 }
 
-NotQuerySpecification::~NotQuerySpecification()
+NotQuerySpecification::NotQuerySpecification(const NotQuerySpecification& other)
 {
-  delete query_;
+  copy_other(other);
 }
 
 NotQuerySpecification::NotQuerySpecification(QuerySpecification* query)
   : query_(query) {}
 
+NotQuerySpecification& NotQuerySpecification::operator=(
+    const NotQuerySpecification& other)
+{
+  copy_other(other);
+  return *this;
+}
+
+std::string NotQuerySpecification::to_str() const
+{
+  if (dynamic_cast<const AndQuerySpecification*>(query_))
+  {
+    return std::string("\\+(") + query_->to_str() + ')';
+  }
+  return std::string("\\+") + query_->to_str();
+}
+
 Query* NotQuerySpecification::operator()(const PlanState& state,
     const Substitution& sigma0) const
 {
   return new NotQuery((*query_)(state, sigma0));
 }
 
+NotQuerySpecification::~NotQuerySpecification()
+{
+  delete query_;
+}
+
 // AndQuerySpecification methods
 
-std::string AndQuerySpecification::to_str() const
+void AndQuerySpecification::copy_other(const AndQuerySpecification& other)
 {
-  return std::string(1,'(') + q1_->to_str() + ',' + q2_->to_str() + ')';
+  q1_ = other.q1_->clone();
+  q2_ = other.q2_->clone();
 }
 
-AndQuerySpecification::~AndQuerySpecification()
+AndQuerySpecification::AndQuerySpecification(const AndQuerySpecification& other)
 {
-  delete q1_;
-  delete q2_;
+  copy_other(other);
+}
+
+AndQuerySpecification& AndQuerySpecification::operator=(
+    const AndQuerySpecification& other)
+{
+  copy_other(other);
+  return *this;
 }
 
 AndQuerySpecification::AndQuerySpecification(QuerySpecification* q1,
     QuerySpecification* q2) : q1_(q1), q2_(q2) {}
 
+std::string AndQuerySpecification::to_str() const
+{
+  return q1_->to_str() + ',' + q2_->to_str();
+}
+
 Query* AndQuerySpecification::operator()(const PlanState& state,
     const Substitution& sigma0) const
 {
   return new AndQuery((*q1_)(state, sigma0), (*q2_)(state, sigma0));
 }
 
+AndQuerySpecification::~AndQuerySpecification()
+{
+  delete q1_;
+  delete q2_;
+}
+
 }
 
diff --git a/src/queries.h b/src/queries.h
index 86f6976174299c996e60387d9e8909777bfb5b69..bc6538499837d6ff927d9fd5b3b016f2d366e5c3 100644
--- a/src/queries.h
+++ b/src/queries.h
@@ -1,11 +1,25 @@
 #ifndef _LIBIMAGINE_PLANNER_QUERIES_H_
 #define _LIBIMAGINE_PLANNER_QUERIES_H_
 
+#define QUERY_SPEC_COMMON(Class)\
+    virtual QuerySpecification* clone() const override { return new Class(*this); };
+
 #include "types.h"
 
 namespace imagine_planner
 {
 
+class Query;
+class ComparisonQuery;
+class SimpleQuery;
+class NotQuery;
+class AndQuery;
+class QuerySpecification;
+class ComparisonQuerySpecification;
+class SimpleQuerySpecification;
+class NotQuerySpecification;
+class AndQuerySpecification;
+
 class Query : public Stringifiable
 {
   public:
@@ -21,6 +35,29 @@ class Query : public Stringifiable
 
 };
 
+class ComparisonQuery : public Query
+{
+  private:
+    Predicate op_;
+    Substitution sigma0_;
+    bool done_;
+
+  public:
+    ComparisonQuery(const PlanState&, const Predicate& op,
+        const Substitution& sigma0=Substitution());
+
+    virtual std::string to_str() const override;
+
+    virtual bool next_solution() override;
+
+    virtual const Substitution& get_solution() const override { return sigma0_; }
+
+    virtual const Substitution& get_sigma0() const override { return sigma0_; }
+
+    virtual void reset(const Substitution& sigma0) override;
+
+};
+
 class SimpleQuery : public Query
 {
   private:
@@ -115,8 +152,30 @@ class QuerySpecification : public Stringifiable
         const Substitution& sigma0=Substitution()) const=0;
 
     virtual ~QuerySpecification() {}
+
+    virtual QuerySpecification* clone() const =0;
+};
+
+class ComparisonQuerySpecification : public QuerySpecification
+{
+  private:
+    Predicate op_;
+
+  public:
+    virtual std::string to_str() const override;
+
+    ComparisonQuerySpecification(const Predicate& op);
+
+    const Predicate& get_op() const { return op_; }
+
+    virtual Query* operator()(const PlanState& state,
+        const Substitution& sigma0=Substitution()) const override;
+
+    QUERY_SPEC_COMMON(ComparisonQuerySpecification);
 };
 
+// Query specifications: they don't need a state to be instantiated
+
 class SimpleQuerySpecification : public QuerySpecification
 {
   private:
@@ -127,8 +186,12 @@ class SimpleQuerySpecification : public QuerySpecification
 
     SimpleQuerySpecification(const Predicate& pattern);
 
+    const Predicate& get_pattern() const { return pattern_; }
+
     virtual Query* operator()(const PlanState& state,
         const Substitution& sigma0=Substitution()) const override;
+
+    QUERY_SPEC_COMMON(SimpleQuerySpecification);
 };
 
 class NotQuerySpecification : public QuerySpecification
@@ -136,15 +199,23 @@ class NotQuerySpecification : public QuerySpecification
   private:
     QuerySpecification* query_;
 
+    void copy_other(const NotQuerySpecification& other);
+
   public:
-    virtual std::string to_str() const override;
+    NotQuerySpecification(const NotQuerySpecification& other);
 
     NotQuerySpecification(QuerySpecification* query);
 
+    NotQuerySpecification& operator=(const NotQuerySpecification& other);
+
+    virtual std::string to_str() const override;
+
     virtual Query* operator()(const PlanState& state,
         const Substitution& sigma0=Substitution()) const override;
 
     virtual ~NotQuerySpecification() override;
+
+    QUERY_SPEC_COMMON(NotQuerySpecification);
 };
 
 class AndQuerySpecification : public QuerySpecification
@@ -152,15 +223,24 @@ class AndQuerySpecification : public QuerySpecification
   private:
     QuerySpecification* q1_;
     QuerySpecification* q2_;
+
+    void copy_other(const AndQuerySpecification& other);
+
   public:
-    virtual std::string to_str() const override;
+    AndQuerySpecification(const AndQuerySpecification& other);
 
     AndQuerySpecification(QuerySpecification* q1, QuerySpecification* q2);
 
+    AndQuerySpecification& operator=(const AndQuerySpecification& other);
+
+    virtual std::string to_str() const override;
+
     virtual Query* operator()(const PlanState& state,
         const Substitution& sigma0=Substitution()) const override;
 
     virtual ~AndQuerySpecification() override;
+
+    QUERY_SPEC_COMMON(AndQuerySpecification);
 };
 
 } /* end namespace imagine-planner */
diff --git a/src/types.cpp b/src/types.cpp
index 704e501ae6cb903faddeaedc979a00b08a37b79f..b1890b1e84ce22d87852e5a9552ab6a723cfaf28 100644
--- a/src/types.cpp
+++ b/src/types.cpp
@@ -318,6 +318,50 @@ bool PlanState::subset_of(const PlanState& other) const
   return true;
 }
 
+// Implementation of GoalSpecification's methods
+
+GoalSpecification::GoalSpecification() {}
+
+GoalSpecification::GoalSpecification(const std::vector<Predicate>& must_appear,
+    const std::vector<Predicate>& cannot_appear)
+  : must_appear_(must_appear), cannot_appear_(cannot_appear) {}
+
+std::string GoalSpecification::to_str() const
+{
+  std::ostringstream oss;
+  bool first = true;
+  oss << "Must appear: [";
+  for (const Predicate& pred : must_appear_)
+  {
+    if (not first) oss << ',';
+    oss << pred;
+    first = false;
+  }
+  oss << "]\nCannot appear: [";
+  first = true;
+  for (const Predicate& pred : cannot_appear_)
+  {
+    if (not first) oss << ',';
+    oss << pred;
+    first = false;
+  }
+  oss << ']';
+  return oss.str();
+}
+
+bool GoalSpecification::operator()(const PlanState& state) const
+{
+  for (const Predicate& pred : must_appear_)
+  {
+    if (not state.has(pred)) return false;
+  }
+  for (const Predicate& pred : cannot_appear_)
+  {
+    if (state.has(pred)) return false;
+  }
+  return true;
+}
+
 // Implementation of stream operator
 
 std::ostream& operator<<(std::ostream& os, const Stringifiable& strable)
diff --git a/src/types.h b/src/types.h
index 24b9f45d8076ab6b543b0782e8185f3e24e7b12d..3235a535f3d709bf90ff904b9b161600258a1053 100644
--- a/src/types.h
+++ b/src/types.h
@@ -7,17 +7,41 @@
     virtual std::string type() const override { return #Class; }\
     virtual Term* clone() const override { return new Class(*this); };
 
+#include <exception>
 #include <functional>
+#include <map> 
 #include <memory>
 #include <ostream>
+#include <set>
 #include <string>
 #include <vector>
-#include <map>
-#include <set>
 
 namespace imagine_planner
 {
 
+class ImaginePlannerException;
+class Stringifiable;
+class Hashable;
+class Term;
+class LiteralTerm;
+class NumberTerm;
+class TermWrapper;
+class Predicate;
+class Substitution;
+class PlanState;
+class GoalSpecification;
+
+class ImaginePlannerException : public std::exception
+{
+  private:
+    std::string msg_;
+
+  public:
+    explicit ImaginePlannerException(const std::string& msg) : msg_(msg) {}
+
+    virtual const char* what() const throw() { return msg_.c_str(); }
+};
+
 /** 
  * @brief Abstract class (interface-like) that simply declares the to_str
  * method (to be implemented by concrete classes).
@@ -330,6 +354,23 @@ class PlanState : public Stringifiable,
 
 };
 
+class GoalSpecification : public Stringifiable
+{
+  private:
+    std::vector<Predicate> must_appear_, cannot_appear_;
+
+  public:
+    GoalSpecification();
+
+    GoalSpecification(const std::vector<Predicate>& must_appear,
+                      const std::vector<Predicate>& cannot_appear);
+
+    std::string to_str() const override;
+
+    bool operator()(const PlanState& state) const;
+
+};
+
 /** 
  * @brief Stream operator that conveniently puts a Stringifiable object into
  * an output stream.