replace boost phoenix parser with SignatureParser
parent
2d2504e9ce
commit
deafbdb3a7
|
|
@ -16,56 +16,16 @@
|
|||
* @date Feb 27, 2011
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "gtsam/discrete/SignatureParser.h"
|
||||
#include "Signature.h"
|
||||
|
||||
#include <boost/spirit/include/qi.hpp> // for parsing
|
||||
#include <boost/spirit/include/phoenix.hpp> // for qi::_val
|
||||
#include <sstream>
|
||||
#include <cassert>
|
||||
|
||||
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<It, qi::space_type, Signature::Table()> table, or_, and_, rows;
|
||||
qi::rule<It, Signature::Row()> 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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
#include <gtsam/discrete/SignatureParser.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
|
||||
namespace gtsam {
|
||||
|
||||
inline static std::vector<double> ParseTrueRow() { return {0, 1}; }
|
||||
|
||||
inline static std::vector<double> 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<double>& 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<std::string>& 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<double> row;
|
||||
if (!ParseConditional(word, row)) {
|
||||
// stop parsing if we encounter an error
|
||||
return;
|
||||
}
|
||||
table.push_back(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> static Tokenize(const std::string& str) {
|
||||
std::istringstream iss(str);
|
||||
std::vector<std::string> 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<std::string> 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
|
||||
|
|
@ -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 <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gtsam {
|
||||
namespace SignatureParser {
|
||||
typedef std::vector<double> Row;
|
||||
typedef std::vector<Row> Table;
|
||||
|
||||
bool parse(const std::string& str, Table& table);
|
||||
}; // namespace SignatureParser
|
||||
} // namespace gtsam
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* Unit tests for the SimpleParser class.
|
||||
* @file testSimpleParser.cpp
|
||||
*/
|
||||
|
||||
#include <gtsam/base/TestableAssertions.h>
|
||||
#include <gtsam/discrete/SignatureParser.h>
|
||||
#include <CppUnitLite/TestHarness.h>
|
||||
|
||||
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);
|
||||
}
|
||||
Loading…
Reference in New Issue