Commit b37e9212 authored by Alejandro Suarez Hernandez's avatar Alejandro Suarez Hernandez
Browse files

Query parser seems to work correctly

parent 2df53a03
# driver source files
SET(sources imagine-planner.cpp types.cpp queries.cpp)
SET(sources imagine-planner.cpp types.cpp queries.cpp parsing.cpp)
# application header files
SET(headers imagine-planner.h types.h queries.h)
SET(headers imagine-planner.h types.h queries.h parsing.h)
# Boost
FIND_PACKAGE(Boost COMPONENTS system filesystem unit_test_framework REQUIRED)
# Swi-pl
......
......@@ -17,3 +17,10 @@ TARGET_LINK_LIBRARIES(queries_test
${Boost_SYSTEM_LIBRARY}
${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})
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})
#define BOOST_TEST_MODULE Parsing Test
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#include "parsing.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
std::ostream& operator<<(std::ostream& os, const TokenV& tokens)
{
bool first = true;
os << '[';
for (const Token& token : tokens)
{
if (not first) os << ", ";
os << token.first << "(" << token.second << ")";
first = false;
}
os << ']';
return os;
}
BOOST_AUTO_TEST_CASE(spaces)
{
std::string str(" Hello ( world) ");
std::string str_clean = remove_spaces(str);
INFO("str_clean: " << str_clean);
BOOST_CHECK_EQUAL(str_clean, "Hello(world)");
}
BOOST_AUTO_TEST_CASE(test_parse_predicate)
{
std::string str("at(lettuce,east), cost(2.5), r(), s(");
TokenV tokens = decompose(str);
INFO(tokens);
std::size_t cursor = 0;
Predicate* predicate = parse_predicate(tokens, cursor);
BOOST_REQUIRE(predicate);
BOOST_CHECK_EQUAL(*predicate, Predicate("at", "lettuce", "east"));
BOOST_CHECK_EQUAL(cursor, 6);
delete predicate;
cursor = 7;
predicate = parse_predicate(tokens, cursor);
BOOST_REQUIRE(predicate);
BOOST_CHECK_EQUAL(*predicate, Predicate("cost", 2.5));
BOOST_CHECK_EQUAL(cursor, 11);
delete predicate;
cursor = 12;
predicate = parse_predicate(tokens, cursor);
BOOST_REQUIRE(predicate);
BOOST_CHECK_EQUAL(*predicate, Predicate("r"));
BOOST_CHECK_EQUAL(cursor, 15);
delete predicate;
cursor = 16;
predicate = parse_predicate(tokens, cursor);
BOOST_REQUIRE(not predicate);
}
BOOST_AUTO_TEST_CASE(test_parse_simple_expressions)
{
std::string str1("at(X, Y)");
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");
}
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 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");
}
#define BOOST_TEST_MODULE Types Test
#define BOOST_TEST_MODULE Queries Test
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
......@@ -24,6 +24,7 @@ BOOST_AUTO_TEST_CASE(simple_query_test_sat)
INFO(state);
SimpleQuery q1(state, Predicate("p", "a", "X"));
SimpleQuery q2(state, Predicate("q", "w"));
INFO("q1: " << q1 << ", " << q2);
BOOST_REQUIRE(q1.next_solution());
BOOST_CHECK_EQUAL(q1.get_solution().to_str(), "{\n X -> b\n}");
BOOST_REQUIRE(q1.next_solution());
......@@ -41,6 +42,7 @@ BOOST_AUTO_TEST_CASE(simple_query_test_unsat)
INFO(state);
SimpleQuery q1(state, Predicate("p", "c", "X"));
SimpleQuery q2(state, Predicate("p", "a", "d"));
INFO("q1: " << q1 << ", " << q2);
BOOST_REQUIRE(not q1.next_solution());
BOOST_REQUIRE(not q2.next_solution());
}
......@@ -48,16 +50,19 @@ BOOST_AUTO_TEST_CASE(simple_query_test_unsat)
BOOST_AUTO_TEST_CASE(and_query_test_sat)
{
PlanState state({Predicate("p","a","b"), Predicate("p","a","c"),
Predicate("p", "b", "d"), Predicate("p","d","a"), Predicate("q","a"),
Predicate("q", "d")});
Predicate("p", "b", "d"), Predicate("p","d","a"), Predicate("q","b")});
INFO("state: " << state);
SimpleQuery q1(state, Predicate("p", "X", "Y"));
SimpleQuery q2(state, Predicate("q", "X"));
//NotQuery not1(q2);
AndQuery and1(q1, q2);
while (and1.next_solution())
{
INFO(and1.get_solution());
}
SimpleQuery* q1 = new SimpleQuery(state, Predicate("p", "X", "Y"));
SimpleQuery* q2 = new SimpleQuery(state, Predicate("q", "X"));
NotQuery* not1 = new NotQuery(q2);
AndQuery* and1 = new AndQuery(q1, not1);
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(and1->next_solution());
BOOST_CHECK_EQUAL(and1->get_solution().to_str(), "{\n X -> d,\n Y -> a\n}");
BOOST_REQUIRE(not and1->next_solution());
}
#include "parsing.h"
#include <cctype>
#include <sstream>
#include <iostream>
namespace imagine_planner
{
bool is_number(const std::string& str)
{
try
{
std::size_t idx = 0;
std::stod(str, &idx);
return idx == str.length(); // no more chars after the number
}
catch (...)
{
return false;
}
}
std::string remove_spaces(const std::string& in)
{
std::ostringstream oss;
for (char c : in)
{
if (not std::isspace(c)) oss << c;
}
return oss.str();
}
TokenV decompose(const std::string& in)
{
TokenV tokens;
std::string in_clean = remove_spaces(in);
std::size_t cursor = 0;
while (cursor < in_clean.length())
{
if (in_clean.compare(cursor, 2, "\\+") == 0)
{
tokens.push_back({"NOT", ""});
cursor += 2;
}
else if (in_clean[cursor] == '(')
{
tokens.push_back({"PAR_OPEN", ""});
++cursor;
}
else if (in_clean[cursor] == ')')
{
tokens.push_back({"PAR_CLOSE", ""});
++cursor;
}
else if (in_clean[cursor] == ',')
{
tokens.push_back({"COMMA", ""});
++cursor;
}
else
{
std::size_t end = in_clean.find_first_of(",()\\", cursor);
if (end == std::string::npos) end = in_clean.length();
std::size_t len = end - cursor;
std::string substr = in_clean.substr(cursor, len);
std::string type = is_number(substr)? "NUMBER" : "SYMBOL";
tokens.push_back({type, substr});
cursor = end;
}
}
tokens.push_back({"EOF", ""});
return tokens;
}
Predicate* parse_predicate(const TokenV& tokens, std::size_t& cursor)
{
enum State {EXPECTING_NAME, EXPECTING_PAR_OPEN, EXPECTING_ARG_OR_END,
EXPECTING_COMMA_OR_END, EXPECTING_ARG, KO, OK};
State state = EXPECTING_NAME;
Predicate* predicate = nullptr;
std::string pred_name;
TermV arguments;
std::size_t idx = cursor;
while (state != OK and state != KO)
{
const Token& token = tokens[idx];
switch (state)
{
case EXPECTING_NAME:
if (token.first == "SYMBOL")
{
pred_name = token.second;
state = EXPECTING_PAR_OPEN;
}
else state = KO;
break;
case EXPECTING_PAR_OPEN:
if (token.first == "PAR_OPEN") state = EXPECTING_ARG_OR_END;
else state = KO;
break;
case EXPECTING_ARG_OR_END:
if (token.first == "PAR_CLOSE") state = OK;
else if (token.first == "SYMBOL")
{
arguments.push_back(TermWrapper(token.second));
state = EXPECTING_COMMA_OR_END;
}
else if (token.first == "NUMBER")
{
arguments.push_back(TermWrapper(std::stod(token.second)));
state = EXPECTING_COMMA_OR_END;
}
else state = KO;
break;
case EXPECTING_COMMA_OR_END:
if (token.first == "COMMA") state = EXPECTING_ARG;
else if (token.first == "PAR_CLOSE") state = OK;
else state = KO;
break;
case EXPECTING_ARG:
if (token.first == "SYMBOL")
{
arguments.push_back(TermWrapper(token.second));
state = EXPECTING_COMMA_OR_END;
}
else if (token.first == "NUMBER")
{
arguments.push_back(TermWrapper(std::stod(token.second)));
state = EXPECTING_COMMA_OR_END;
}
else state = KO;
break;
default:
/* do nothing */
break;
}
++idx;
}
if (state == OK)
{
cursor = idx;
predicate = new Predicate(pred_name, arguments);
}
return predicate;
}
QuerySpecification* parse_expression(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};
State state = EXPECTING_PRED_OR_PAR;
bool negated = false;
QuerySpecification* query = nullptr;
std::size_t idx = 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;
++idx;
/* Stay in the same state */
}
else if (Predicate* predicate = parse_predicate(tokens, idx))
{
query = new SimpleQuerySpecification(*predicate);
query = negated? new NotQuerySpecification(query) : query;
state = EXPECTING_COMMA_OR_END;
delete predicate;
}
else if (token.first == "PAR_OPEN")
{
state = EXPECTING_PAR_EXPR;
++idx;
}
else state = KO;
break;
case EXPECTING_PAR_EXPR:
std::cout << "EXPECTING_PAR_EXPR" << std::endl;
if ((query = parse_expression(tokens, idx)))
{
query = negated? new NotQuerySpecification(query) : query;
state = EXPECTING_PAR_CLOSE;
}
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;
++idx;
}
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;
++idx;
}
else state = OK;
break;
case EXPECTING_END_EXPR:
std::cout << "EXPECTING_END_EXPR" << std::endl;
if (QuerySpecification* query_snd = parse_expression(tokens, idx))
{
query = new AndQuerySpecification(query, query_snd);
state = OK;
}
break;
default:
/* do nothing */
break;
}
}
if (state == OK)
{
std::cout << "OK" << std::endl;
cursor = idx;
}
else
{
std::cout << "KO" << std::endl;
if (query)
{
delete query;
query = nullptr;
}
}
return query;
}
QuerySpecification* parse_expression(const std::string& str)
{
TokenV tokens = decompose(str);
std::size_t cursor = 0;
return parse_expression(tokens, cursor);
}
}
#ifndef _LIBIMAGINE_PLANNER_PARSING_H_
#define _LIBIMAGINE_PLANNER_PARSING_H_
#include "queries.h"
namespace imagine_planner
{
typedef std::pair<std::string, std::string> Token;
typedef std::vector<Token> TokenV;
bool is_number(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_expression(const std::string& str);
} /* end namespace imagine_planner */
#endif
......@@ -7,12 +7,12 @@ namespace imagine_planner
bool SimpleQuery::unify(const Predicate& goal)
{
if (goal.arity() != target_.arity()) return false;
if (goal.get_name() != target_.get_name()) return false;
if (goal.arity() != pattern_.arity()) return false;
if (goal.get_name() != pattern_.get_name()) return false;
sigma_ = sigma0_;
for (int idx = 0; idx < goal.arity(); ++idx)
{
const TermWrapper& term1 = target_.get_arguments()[idx];
const TermWrapper& term1 = pattern_.get_arguments()[idx];
const TermWrapper& term2 = goal.get_arguments()[idx];
if (term1.is_ground())
{
......@@ -30,19 +30,24 @@ bool SimpleQuery::unify(const Predicate& goal)
return true;
}
SimpleQuery::SimpleQuery(const PlanState& state, const Predicate& target,
SimpleQuery::SimpleQuery(const PlanState& state, const Predicate& pattern,
const Substitution& sigma0) :
state_(state), target_(target), sigma0_(sigma0)
state_(state), pattern_(pattern), sigma0_(sigma0)
{
cursor_ = state_.begin();
}
std::string SimpleQuery::to_str() const
{
return pattern_.to_str();
}
bool SimpleQuery::next_solution()
{
if (cursor_ != state_.end() and target_.is_ground())
if (cursor_ != state_.end() and pattern_.is_ground())
{
cursor_ = state_.end();
return state_.has(target_);
return state_.has(pattern_);
}
while (cursor_ != state_.end())
......@@ -65,37 +70,52 @@ void SimpleQuery::reset(const Substitution& sigma0)
// NotQuery methods
NotQuery::NotQuery(Query& q) : query_(q), done_(false) {}
NotQuery::NotQuery(Query* q) : query_(q), done_(false) {}
std::string NotQuery::to_str() const
{
return std::string("\\+") + query_->to_str();
}
bool NotQuery::next_solution()
{
if (done_) return false;
done_ = true;
return not query_.next_solution();
return not query_->next_solution();
}
void NotQuery::reset(const Substitution& sigma0)
{
query_.reset(sigma0);
query_->reset(sigma0);
done_ = false;
}
NotQuery::~NotQuery()
{
delete query_;
}
// AndQuery methods
AndQuery::AndQuery(Query& q1, Query& q2) :
AndQuery::AndQuery(Query* q1, Query* q2) :
q1_(q1), q2_(q2), first_fixed_(false) {}
std::string AndQuery::to_str() const
{
return std::string(1, '(') + q1_->to_str() + ',' + q2_->to_str() + ')';
}
bool AndQuery::next_solution()
{
if (first_fixed_ and q2_.next_solution())
if (first_fixed_ and q2_->next_solution())
{
return true;
}
first_fixed_ = false;
while (q1_.next_solution())
while (q1_->next_solution())
{
q2_.reset(q1_.get_solution());
if (q2_.next_solution())
q2_->reset(q1_->get_solution());
if (q2_->next_solution())
{
first_fixed_ = true;
return true;
......@@ -104,5 +124,71 @@ bool AndQuery::next_solution()
return false;
}
AndQuery::~AndQuery()
{
delete q1_;
delete q2_;
}
// SimpleQuerySpecification methods
std::string SimpleQuerySpecification::to_str() const
{
return pattern_.to_str();
}
SimpleQuerySpecification::SimpleQuerySpecification(const Predicate& pattern)
: pattern_(pattern) {}