replace boost phoenix parser with SignatureParser

release/4.3a0
kartik arcot 2023-01-23 14:52:24 -08:00 committed by Frank Dellaert
parent 2d2504e9ce
commit deafbdb3a7
4 changed files with 243 additions and 46 deletions

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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);
}