diff --git a/gtsam/discrete/Signature.cpp b/gtsam/discrete/Signature.cpp index bc045e8c2..17f3b52d6 100644 --- a/gtsam/discrete/Signature.cpp +++ b/gtsam/discrete/Signature.cpp @@ -16,56 +16,16 @@ * @date Feb 27, 2011 */ -#include - +#include "gtsam/discrete/SignatureParser.h" #include "Signature.h" -#include // for parsing -#include // for qi::_val +#include +#include namespace gtsam { using namespace std; - namespace qi = boost::spirit::qi; - namespace ph = boost::phoenix; - - // parser for strings of form "99/1 80/20" etc... - namespace parser { - typedef string::const_iterator It; - using boost::phoenix::val; - using boost::phoenix::ref; - using boost::phoenix::push_back; - - // Special rows, true and false - Signature::Row F{1, 0}, T{0, 1}; - - // Special tables (inefficient, but do we care for user input?) - Signature::Table logic(bool ff, bool ft, bool tf, bool tt) { - Signature::Table t(4); - t[0] = ff ? T : F; - t[1] = ft ? T : F; - t[2] = tf ? T : F; - t[3] = tt ? T : F; - return t; - } - - struct Grammar { - qi::rule table, or_, and_, rows; - qi::rule true_, false_, row; - Grammar() { - table = or_ | and_ | rows; - or_ = qi::lit("OR")[qi::_val = logic(false, true, true, true)]; - and_ = qi::lit("AND")[qi::_val = logic(false, false, false, true)]; - rows = +(row | true_ | false_); - row = qi::double_ >> +("/" >> qi::double_); - true_ = qi::lit("T")[qi::_val = T]; - false_ = qi::lit("F")[qi::_val = F]; - } - } grammar; - - } // \namespace parser - ostream& operator <<(ostream &os, const Signature::Row &row) { os << row[0]; for (size_t i = 1; i < row.size(); i++) @@ -139,9 +99,7 @@ namespace gtsam { Signature& Signature::operator=(const string& spec) { spec_ = spec; Table table; - parser::It f = spec.begin(), l = spec.end(); - bool success = - qi::phrase_parse(f, l, parser::grammar.table, qi::space, table); + bool success = gtsam::SignatureParser::parse(spec, table); if (success) { for (Row& row : table) normalize(row); table_ = table; diff --git a/gtsam/discrete/SignatureParser.cpp b/gtsam/discrete/SignatureParser.cpp new file mode 100644 index 000000000..cf073595b --- /dev/null +++ b/gtsam/discrete/SignatureParser.cpp @@ -0,0 +1,112 @@ +#include + +#include +#include +#include + +namespace gtsam { + +inline static std::vector ParseTrueRow() { return {0, 1}; } + +inline static std::vector ParseFalseRow() { return {1, 0}; } + +inline static SignatureParser::Table ParseOr() { + return {ParseFalseRow(), ParseTrueRow(), ParseTrueRow(), ParseTrueRow()}; +} + +inline static SignatureParser::Table ParseAnd() { + return {ParseFalseRow(), ParseFalseRow(), ParseFalseRow(), ParseTrueRow()}; +} + +bool static ParseConditional(const std::string& token, std::vector& row) { + // Expect something like a/b/c + std::istringstream iss2(token); + try { + // if the string has no / then return false + if (std::count(token.begin(), token.end(), '/') == 0) return false; + // split the word on the '/' character + for (std::string s; std::getline(iss2, s, '/');) { + // can throw exception + row.push_back(std::stod(s)); + } + } catch (...) { + return false; + } + return true; +} + +void static ParseConditionalTable(const std::vector& tokens, SignatureParser::Table& table) { + // loop over the words + // for each word, split it into doubles using a stringstream + for (const auto& word : tokens) { + // If the string word is F or T then the row is {0,1} or {1,0} respectively + if (word == "F") { + table.push_back(ParseFalseRow()); + } else if (word == "T") { + table.push_back(ParseTrueRow()); + } else { + // Expect something like a/b/c + std::vector row; + if (!ParseConditional(word, row)) { + // stop parsing if we encounter an error + return; + } + table.push_back(row); + } + } +} + +std::vector static Tokenize(const std::string& str) { + std::istringstream iss(str); + std::vector tokens; + for (std::string s; iss >> s;) { + tokens.push_back(s); + } + return tokens; +} + +bool SignatureParser::parse(const std::string& str, Table& table) { + // check if string is just whitespace + if (std::all_of(str.begin(), str.end(), isspace)) { + return false; + } + + // return false if the string is empty + if (str.empty()) { + return false; + } + + // tokenize the str on whitespace + std::vector tokens = Tokenize(str); + + // if the first token is "OR", return the OR table + if (tokens[0] == "OR") { + // if there are more tokens, return false + if (tokens.size() > 1) { + return false; + } + table = ParseOr(); + return true; + } + + // if the first token is "AND", return the AND table + if (tokens[0] == "AND") { + // if there are more tokens, return false + if (tokens.size() > 1) { + return false; + } + table = ParseAnd(); + return true; + } + + // otherwise then parse the conditional table + ParseConditionalTable(tokens, table); + // return false if the table is empty + if (table.empty()) { + return false; + } + // the boost::phoenix parser did not return an error if we could not fully parse a string + // it just returned whatever it could parse + return true; +} +} // namespace gtsam diff --git a/gtsam/discrete/SignatureParser.h b/gtsam/discrete/SignatureParser.h new file mode 100644 index 000000000..a4a9f05f5 --- /dev/null +++ b/gtsam/discrete/SignatureParser.h @@ -0,0 +1,30 @@ +/** + * This is a simple parser that replaces the boost spirit parser. It is + * meant to parse strings like "1/1 2/3 1/4". Every word of the form "a/b/c/..." + * should be parsed as a row, and the rows should be stored in a table. + * The elements of the row will be doubles. + * The table is a vector of rows. The row is a vector of doubles. + * The parser should be able to parse the following strings: + * "1/1 2/3 1/4": {{1,1},{2,3},{1,4}} + * "1/1 2/3 1/4 1/1 2/3 1/4" : {{1,1},{2,3},{1,4},{1,1},{2,3},{1,4}} + * "1/2/3 2/3/4 1/2/3 2/3/4 1/2/3 2/3/4 1/2/3 2/3/4 1/2/3" : {{1,2,3},{2,3,4},{1,2,3},{2,3,4},{1,2,3},{2,3,4},{1,2,3},{2,3,4},{1,2,3}} + * If the string has unparseable elements, the parser should parse whatever it can + * "1/2 sdf" : {{1,2}} + * It should return false if the string is empty. + * "": false + * We should return false if the rows are not of the same size. + */ + +#pragma once +#include +#include +#include + +namespace gtsam { +namespace SignatureParser { +typedef std::vector Row; +typedef std::vector Table; + +bool parse(const std::string& str, Table& table); +}; // namespace SignatureParser +} // namespace gtsam diff --git a/gtsam/discrete/tests/testSignatureParser.cpp b/gtsam/discrete/tests/testSignatureParser.cpp new file mode 100644 index 000000000..017b8e70c --- /dev/null +++ b/gtsam/discrete/tests/testSignatureParser.cpp @@ -0,0 +1,97 @@ +/** + * Unit tests for the SimpleParser class. + * @file testSimpleParser.cpp + */ + +#include +#include +#include + +bool compare_tables(const gtsam::SignatureParser::Table& table1, + const gtsam::SignatureParser::Table& table2) { + if (table1.size() != table2.size()) { + return false; + } + for (size_t i = 0; i < table1.size(); ++i) { + if (table1[i].size() != table2[i].size()) { + return false; + } + for (size_t j = 0; j < table1[i].size(); ++j) { + if (table1[i][j] != table2[i][j]) { + return false; + } + } + } + return true; +} + +// Simple test case +TEST(SimpleParser, simple) { + gtsam::SignatureParser::Table table, expectedTable; + expectedTable = {{1, 1}, {2, 3}, {1, 4}}; + bool ret = gtsam::SignatureParser::parse("1/1 2/3 1/4", table); + EXPECT(ret); + // compare the tables + EXPECT(compare_tables(table, expectedTable)); +} + +// Test case with each row having 3 elements +TEST(SimpleParser, three_elements) { + gtsam::SignatureParser::Table table, expectedTable; + expectedTable = {{1, 1, 1}, {2, 3, 2}, {1, 4, 3}}; + bool ret = gtsam::SignatureParser::parse("1/1/1 2/3/2 1/4/3", table); + EXPECT(ret); + // compare the tables + EXPECT(compare_tables(table, expectedTable)); +} + +// A test case to check if we can parse a signarue with 'T' and 'F' +TEST(SimpleParser, TandF) { + gtsam::SignatureParser::Table table, expectedTable; + expectedTable = {{1,0}, {1,0}, {1,0}, {0,1}}; + bool ret = gtsam::SignatureParser::parse("F F F T", table); + EXPECT(ret); + // compare the tables + EXPECT(compare_tables(table, expectedTable)); +} + +// A test to parse {F F F 1} +TEST(SimpleParser, FFF1) { + gtsam::SignatureParser::Table table, expectedTable; + expectedTable = {{1,0}, {1,0}, {1,0}}; + // should ignore the last 1 + bool ret = gtsam::SignatureParser::parse("F F F 1", table); + EXPECT(ret); + // compare the tables + EXPECT(compare_tables(table, expectedTable)); +} + +// Expect false if the string is empty +TEST(SimpleParser, empty_string) { + gtsam::SignatureParser::Table table; + bool ret = gtsam::SignatureParser::parse("", table); + EXPECT(!ret); +} + + +// Expect false if jibberish +TEST(SimpleParser, jibberish) { + gtsam::SignatureParser::Table table; + bool ret = gtsam::SignatureParser::parse("sdf 22/3", table); + EXPECT(!ret); +} + +// If jibberish is in the middle, it should still parse the rest +TEST(SimpleParser, jibberish_in_middle) { + gtsam::SignatureParser::Table table, expectedTable; + expectedTable = {{1, 1}, {2, 3}}; + bool ret = gtsam::SignatureParser::parse("1/1 2/3 sdf 1/4", table); + EXPECT(ret); + // compare the tables + EXPECT(compare_tables(table, expectedTable)); +} + +int main() { + TestResult tr; + return TestRegistry::runAllTests(tr); +}