Commit 4617dc50 authored by Yann Boucher's avatar Yann Boucher
Browse files

Implemented structure readers for Golly's RLE format and a custom JSON format, fixed a few tests

parent f60086b4
Pipeline #76856 passed with stages
in 17 seconds
......@@ -35,4 +35,12 @@ inline bool operator!=(const Coord& lhs, const Coord& rhs)
return !(lhs == rhs);
}
//! \brief Comparaison lexicographique de deux coordonnées (pour std::map et compagnie)
inline bool operator<(const Coord& lhs, const Coord& rhs)
{
if (lhs.x == rhs.x)
return lhs.y < rhs.y;
return lhs.x < rhs.x;
}
#endif // COORD_HPP
......@@ -14,7 +14,7 @@ Fichier contenant la classe Structure, représentant un ensemble de cellules con
#define STRUCTURE_HPP
#include <functional>
#include <vector>
#include <map>
#include <type_traits>
#include "coord.hpp"
......@@ -25,6 +25,7 @@ Fichier contenant la classe Structure, représentant un ensemble de cellules con
Cette classe permet de représenter un ensemble de cellules constituant une structure, comme un oscillateur ou un glider.
**/
// TODO : contrainte d'unicité (utiliser std::unordered_map?)
class Structure
{
public:
......@@ -44,12 +45,19 @@ public:
template <typename It>
void load(It begin, It end)
{
static_assert (std::is_same<typename std::remove_reference<decltype(*begin)>::type, std::pair<Coord, int>>::value,
"Iterator value type must be std::pair<Coord, int>");
static_assert (std::is_same<typename std::remove_reference<decltype(*begin)>::type, std::pair<Coord, unsigned>>::value ||
std::is_same<typename std::remove_reference<decltype(*begin)>::type, std::pair<const Coord, unsigned>>::value,
"Iterator value type must be std::pair<Coord, unsigned>");
m_cells.clear();
// back_inserter permet de redimensionner automatiquement m_cells lors de la copie via les itérateurs begin et end
std::copy(begin, end, std::back_inserter(m_cells));
for (auto it = begin; it != end; ++it)
{
// on ignore les entrées avec des cellules zéro, elles sont implicites (cela permet d'économiser pas mal de mémoire)
if (it->second == 0)
continue;
else
m_cells[it->first] = it->second;
}
}
//! \brief retourne le nombre de cellules présentes dans la structure
......@@ -62,7 +70,7 @@ public:
{
friend class Structure;
private:
iterator(std::vector<std::pair<Coord, int>>::const_iterator it)
iterator(std::map<Coord, unsigned>::const_iterator it)
: m_it(it) {}
public:
......@@ -70,24 +78,26 @@ public:
iterator operator++(int) {iterator retval = *this; ++(*this); return retval;}
bool operator==(iterator other) const {return m_it == other.m_it;}
bool operator!=(iterator other) const {return !(*this == other);}
std::pair<Coord, int> operator*() {return *m_it;}
std::pair<Coord, unsigned> operator*() {return *m_it;}
// iterator traits
using difference_type = long;
using value_type = std::pair<Coord, int>;
using pointer = const std::pair<Coord, int>*;
using reference = const std::pair<Coord, int>&;
using value_type = std::pair<Coord, unsigned>;
using pointer = const std::pair<Coord, unsigned>*;
using reference = const std::pair<Coord, unsigned>&;
using iterator_category = std::forward_iterator_tag;
private:
std::vector<std::pair<Coord, int>>::const_iterator m_it;
std::map<Coord, unsigned>::const_iterator m_it;
};
//! \brief Retourne un itérateur vers le début de la liste de cellules.
//! \post Chaque élément itérable est de coordonnée unique.
iterator begin() const { return iterator(m_cells.begin()); }
//! \brief Retourne un itérateur vers la fin de la liste de cellules.
//! \post Chaque élément itérable est de coordonnée unique.
iterator end() const { return iterator(m_cells.end()); }
private:
std::vector<std::pair<Coord, int>> m_cells;
std::map<Coord, unsigned> m_cells;
};
#endif // STRUCTURE_HPP
#ifndef STRUCTUREREADER_HPP
#define STRUCTUREREADER_HPP
#include <string>
#include <exception>
class Structure;
// TODO : documenter ce fichier
// TODO : adapter cettre classe d'Exception en la faisant hériter à notre propre classe d'Exception
class StructureReaderException : public std::exception
{
public:
StructureReaderException(const std::string& what)
: m_what(what)
{}
const char* what() const noexcept
{ return m_what.c_str(); }
private:
const std::string m_what;
};
class StructureReader
{
public:
StructureReader(const std::string& in_data):
m_idx(0), m_data(in_data)
{}
virtual Structure read_structure() = 0;
protected:
char peek() const;
char read();
bool eof() const;
void expect(const std::string& str);
bool accept(const std::string& str);
std::string read_word();
std::string read_line();
std::string data_left() const;
int read_int();
void read_white();
private:
size_t m_idx;
const std::string& m_data;
};
class RLEStructureReader : public StructureReader
{
public:
RLEStructureReader(const std::string& in_data):
StructureReader(in_data)
{}
virtual Structure read_structure();
private:
unsigned read_state();
};
class JSONStructureReader : public StructureReader
{
public:
JSONStructureReader(const std::string& in_data):
StructureReader(in_data)
{}
virtual Structure read_structure();
};
#endif // STRUCTUREREADER_HPP
......@@ -14,7 +14,8 @@ INCLUDEPATH += ../include
SOURCES += \
main.cpp \
propertyvisitors.cpp \
neighborhood.cpp
neighborhood.cpp \
structurereader.cpp
HEADERS += \
../include/coord.hpp \
......@@ -23,7 +24,8 @@ HEADERS += \
../include/propertyvisitors.hpp \
../include/structure.hpp \
../include/neighborhood.hpp \
../include/factory.hpp
../include/factory.hpp \
../include/structurereader.hpp
# Default rules for deployment.
......
#include "structurereader.hpp"
#include <QByteArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include "structure.hpp"
char StructureReader::peek() const
{
if (m_idx >= m_data.size())
throw StructureReaderException("Fichier invalide, la lecture a dépassé la fin du fichier.");
return m_data[m_idx];
}
char StructureReader::read()
{
char val = peek();
++m_idx;
return val;
}
bool StructureReader::eof() const
{
return m_idx >= m_data.size();
}
void StructureReader::expect(const std::string &str)
{
read_white();
if (m_data.substr(m_idx, str.size()) != str)
throw StructureReaderException("Valeur attendue : " + str);
m_idx += str.size();
read_white();
}
bool StructureReader::accept(const std::string &str)
{
read_white();
if (m_data.substr(m_idx, str.size()) == str)
{
m_idx += str.size();
read_white();
return true;
}
return false;
}
std::string StructureReader::read_word()
{
std::string str;
while (m_idx < m_data.size() && !isspace(m_data[m_idx]))
str += m_data[m_idx++];
return str;
}
std::string StructureReader::read_line()
{
std::string str;
while (m_idx < m_data.size() && m_data[m_idx] != '\n')
str += m_data[m_idx++];
read_white();
return str;
}
std::string StructureReader::data_left() const
{
return m_data.substr(m_idx);
}
int StructureReader::read_int()
{
read_white();
size_t cars_read;
int val;
try
{
val = std::stoi(&m_data.data()[m_idx], &cars_read, 10);
}
catch (std::exception& e)
{
// TODO
throw StructureReaderException("La valeur lue n'est pas un entier !");
}
m_idx += cars_read;
read_white();
return val;
}
void StructureReader::read_white()
{
while (m_idx < m_data.size() && isspace(peek()))
++m_idx;
}
// ref : http://golly.sourceforge.net/Help/formats.html#rle
unsigned RLEStructureReader::read_state()
{
char char_1 = read();
if (char_1 == 'b' || char_1 == 'B' || char_1 == '.')
return 0;
else if (char_1 == 'o')
return 1;
else if (char_1 >= 'A' && char_1 <= 'X')
return char_1 - 'A' + 1;
else if (islower(char_1) && isalpha(char_1) && char_1 >= 'p' && char_1 <= 'q')
{
char char_2 = read();
unsigned offset = (char_1 - 'p')*24 + 49;
return offset + (char_2 - 'A');
}
// unknown chars are zero
return 0;
}
// ref : http://golly.sourceforge.net/Help/formats.html#rle
Structure RLEStructureReader::read_structure()
{
// ignore first comment lines
while (accept("#"))
read_line();
// First Line
{
expect("x");
expect("=");
int x = read_int();
expect(",");
expect("y");
expect("=");
int y = read_int();
std::string rule = "";
if (accept(",") && accept("rule"))
{
expect("=");
rule = read_word();
}
(void)x; (void)y; // ignore warnings
}
std::vector<std::pair<Coord, unsigned>> data;
int x = 0, y = 0;
while (!eof())
{
// comment line
if (accept("#"))
{
//char tag = read();
std::string line = read_line();
}
// end of structure line
else if (accept("$"))
{
x = 0;
++y;
}
// end of data
else if (accept("!"))
{
break;
}
// structure
else
{
unsigned state = 0;
unsigned run_count = 1;
// <run_count> is > 1
if (isdigit(peek()))
run_count = read_int();
state = read_state();
for (unsigned i = 0; i < run_count; ++i)
{
data.push_back({{x, y}, state});
++x;
}
}
}
return Structure(data.begin(), data.end());
}
Structure JSONStructureReader::read_structure()
{
std::vector<std::pair<Coord, unsigned>> data;
QByteArray byteArray = QByteArray::fromStdString(data_left());
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(byteArray, &parseError);
if(parseError.error != QJsonParseError::NoError)
{
throw StructureReaderException("Erreur de parsing JSON à " + std::to_string(parseError.offset) + ":" + parseError.errorString().toStdString());
}
QJsonObject root = jsonDoc.object();
if (!root.contains("cells"))
throw StructureReaderException("Pas de champ 'cells' !");
if (!root["cells"].isArray())
throw StructureReaderException("'cells' doit être un array.");
for (const auto& entry : root["cells"].toArray())
{
const char* msg = "Chaque entrée de 'cells' doit être un objet contenant des entiers x, y, et state.";
if (!entry.isObject())
throw StructureReaderException(msg);
QJsonObject val = entry.toObject();
if (!val.contains("x") || !val.contains("y") || !val.contains("state"))
throw StructureReaderException(msg);
if (!val["x"].isDouble() || !val["y"].isDouble() || !val["state"].isDouble())
throw StructureReaderException(msg);
data.push_back({{val["x"].toInt(), val["y"].toInt()}, val["state"].toInt()});
}
return Structure(data.begin(), data.end());
}
......@@ -12,6 +12,9 @@ private slots:
void test_structure();
void test_factory();
void test_coord();
void test_rle_structurereader();
void test_json_structurereader();
};
#endif // CELLULUT_TESTS_HPP
......@@ -8,9 +8,9 @@
void CellulutTests::test_structure()
{
std::vector<std::pair<Coord, int>> coords;
std::vector<std::pair<Coord, int>> empty;
coords.push_back({{1, 2}, 0});
std::vector<std::pair<Coord, unsigned>> coords;
std::vector<std::pair<Coord, unsigned>> empty;
coords.push_back({{1, 2}, 3});
coords.push_back({{-1, 2}, 1});
{
......@@ -19,18 +19,23 @@ void CellulutTests::test_structure()
s2.load(coords.begin(), coords.end());
QCOMPARE(s1.size(), coords.size());
QCOMPARE(s2.size(), coords.size());
QVERIFY(std::equal(coords.begin(), coords.end(), s1.begin()));
QVERIFY(std::equal(coords.begin(), coords.end(), s2.begin()));
QVERIFY(std::is_permutation(coords.begin(), coords.end(), s1.begin()));
QVERIFY(std::is_permutation(coords.begin(), coords.end(), s2.begin()));
s1.load(empty.begin(), empty.end());
QVERIFY(s1.size() == 0);
}
int cnt = 0;
for (auto val : s2)
{
QVERIFY(val == coords[cnt]);
++cnt;
}
std::vector<std::pair<Coord, unsigned>> coords_with_zero;
coords_with_zero.push_back({{1, 2}, 3});
coords_with_zero.push_back({{-1, 2}, 1});
coords_with_zero.push_back({{-1, 2}, 0});
{
Structure s1(coords_with_zero.begin(), coords_with_zero.end());
QCOMPARE(s1.size(), 2); // 2 cellules non nulles, on ignore les cellules à zéro qui sont toujours implicites
QVERIFY(std::is_permutation(coords.begin(), coords.end(), s1.begin()));
}
}
#include <QtTest/QtTest>
#include <algorithm>
#include "cellulut_tests.hpp"
#include "structure.hpp"
#include "structurereader.hpp"
const char* rle_glider =
"#C This is a glider.\n"
"x = 3, y = 3\n"
"bo$2bo$3o!\n";
const char* rle_glider_gun =
"#N Gosper glider gun\n"
"#C This was the first gun discovered.\n"
"#C As its name suggests, it was discovered by Bill Gosper.\n"
" x = 36, y = 9, rule = B3/S23\n"
"24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4b\n"
"obo$10bo5bo7bo$11bo3bo$12b2o!";
const char* rle_wireworld =
"x = 6, y = 7, rule = WireWorld\n"
".A$.C$4C$C2.3C$4C$.C$.A!";
const char* json_test_1 =
"{\n"
" \"cells\" : [\n"
" { \"x\" : 1, \"y\" : 2, \"state\" : 1},\n"
" { \"x\" : 1, \"y\" : 3, \"state\" : 3}\n"
" ]\n"
"}";
void CellulutTests::test_rle_structurereader()
{
{
RLEStructureReader rle(rle_glider);
try {
Structure stru = rle.read_structure();
QCOMPARE(stru.size(), 5);
QVERIFY(std::count(stru.begin(), stru.end(), std::make_pair<Coord, unsigned>(Coord{1, 0}, 1)) == 1);
}
catch (const std::exception& ex)
{
fprintf(stderr, "Error is %s\n", ex.what());
throw;
}
}
{
RLEStructureReader rle(rle_glider_gun);
try {
Structure stru = rle.read_structure();
QCOMPARE(stru.size(), 36);
}
catch (const std::exception& ex)
{
fprintf(stderr, "Error is %s\n", ex.what());
throw;
}
}
{
RLEStructureReader rle(rle_wireworld);
try {
Structure stru = rle.read_structure();
QCOMPARE(stru.size(), 16);
std::map<int, int> state_count;
for (auto pair : stru)
{
state_count[pair.second]++;
}
QCOMPARE(state_count[1], 2);
QCOMPARE(state_count[2], 0);
QCOMPARE(state_count[3], 14);
}
catch (const std::exception& ex)
{
fprintf(stderr, "Error is %s\n", ex.what());
throw;
}
}
}
void CellulutTests::test_json_structurereader()
{
{
JSONStructureReader rle(json_test_1);
try {
Structure stru = rle.read_structure();
QCOMPARE(stru.size(), 2);
QVERIFY(std::count(stru.begin(), stru.end(), std::make_pair<Coord, unsigned>(Coord{1, 2}, 1)) == 1);
QVERIFY(std::count(stru.begin(), stru.end(), std::make_pair<Coord, unsigned>(Coord{1, 3}, 3)) == 1);
}
catch (const std::exception& ex)
{
fprintf(stderr, "Error is %s\n", ex.what());
throw;
}
}
}
......@@ -15,12 +15,14 @@ INCLUDEPATH += ../include
SOURCES += \
../src/propertyvisitors.cpp \
../src/structurereader.cpp \
coord_tests.cpp \
factory_tests.cpp \
property_test.cpp \
propertyvisitors_test.cpp \
cellulut_tests.cpp \
structure_test.cpp
structure_test.cpp \
structurereader_tests.cpp
HEADERS += \
cellulut_tests.hpp
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment