aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLoic Guegan <manzerbredes@mailbox.org>2022-01-25 16:51:37 +0100
committerLoic Guegan <manzerbredes@mailbox.org>2022-01-25 16:51:37 +0100
commite063e1453c611ac0d862ece2d2797582573c801f (patch)
tree5a9cb912724b67d4075cb67acf0b57a3feee6c46
parent773d93b02e078c83b74d2080aafe56828be6a7c8 (diff)
Improve parser
-rw-r--r--README.md8
-rw-r--r--src/PGN.cpp54
-rw-r--r--src/PGN.hpp14
-rw-r--r--tests/tests.cpp21
4 files changed, 77 insertions, 20 deletions
diff --git a/README.md b/README.md
index a479f1b..99fbdb0 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,10 @@
PGNP is a Portable Game Notation (PGN) parser. More details about the
PGN specification can be found [here](https://www.chessclub.com/help/PGN-spec).
+# Features
+- Basic PGN parsing (tags, move, comments, variations etc.)
+- Merged PGN files parsing (several games in one file)
+
# How to use it ?
PGNP can be used as a shared library in your project.
You only need to include `pgnp.hpp` and linking the .so file to your
@@ -17,6 +21,7 @@ Load PGN from file:
pgnp::PGN pgn;
try {
pgn.FromFile("pgn.txt");
+ pgn.ParseNextGame();
}
catch(...){
// Handle exceptions
@@ -24,8 +29,9 @@ Load PGN from file:
Load PGN from string:
pgnp::PGN pgn;
+ pgn.FromString("YOUR PGN CONTENT HERE");
try {
- pgn.FromString("YOUR PGN CONTENT HERE");
+ pgn.ParseNextGame();
}
catch(...){
// Handle exceptions
diff --git a/src/PGN.cpp b/src/PGN.cpp
index 0e7e023..1e26bef 100644
--- a/src/PGN.cpp
+++ b/src/PGN.cpp
@@ -7,7 +7,7 @@
#define IS_DIGIT(c) \
(c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || \
c == '6' || c == '7' || c == '8' || c == '9')
-#define IS_EOF(loc) (loc >= pgn_content.size())
+#define IS_EOF(loc) ((loc) >= pgn_content.size())
#define EOF_CHECK(loc) \
{ \
if (IS_EOF(loc)) \
@@ -16,6 +16,8 @@
namespace pgnp {
+PGN::PGN() : LastGameEndLoc(0), moves(NULL) {}
+
PGN::~PGN() {
if (moves != NULL)
delete moves;
@@ -26,15 +28,30 @@ std::string PGN::GetResult() { return (result); }
void PGN::FromFile(std::string filepath) {
std::ifstream file(filepath);
- std::string content((std::istreambuf_iterator<char>(file)),
+ std::string pgn_content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
- FromString(content);
+ this->pgn_content = pgn_content;
}
void PGN::FromString(std::string pgn_content) {
this->pgn_content = pgn_content;
+}
+
+void PGN::ParseNextGame(){
+ // Clean previous parse
+ if(moves!=NULL){
+ delete moves;
+ }
+ result="";
+ tagkeys.clear();
+ tags.clear();
+
+
moves = new HalfMove();
- int loc = 0;
+ int loc = NextNonBlank(LastGameEndLoc);
+ if(IS_EOF(loc)){
+ throw NoGameFound();
+ }
while (!IS_EOF(loc)) {
char c = pgn_content[loc];
if (!IS_BLANK(c)) {
@@ -42,9 +59,10 @@ void PGN::FromString(std::string pgn_content) {
loc = ParseNextTag(loc);
} else if (IS_DIGIT(c)) {
loc = ParseHalfMove(loc, moves);
+ LastGameEndLoc=loc+1; // Next game start 1 char after the last one
break;
- } else if (c=='{') {
- loc = ParseComment(loc,moves);
+ } else if (c == '{') {
+ loc = ParseComment(loc, moves);
continue; // No need loc++
}
}
@@ -89,20 +107,20 @@ int PGN::ParseComment(int loc, HalfMove *hm) {
loc = NextNonBlank(loc);
EOF_CHECK(loc);
char c = pgn_content[loc];
-
- if(c=='{'){
+
+ if (c == '{') {
loc++;
EOF_CHECK(loc);
c = pgn_content[loc];
- while(c!='}'){
- hm->comment+=c;
+ while (c != '}') {
+ hm->comment += c;
loc++;
EOF_CHECK(loc);
c = pgn_content[loc];
}
loc++; // Skip '}'
}
- return(loc);
+ return (loc);
}
int PGN::ParseHalfMove(int loc, HalfMove *hm) {
@@ -120,11 +138,14 @@ int PGN::ParseHalfMove(int loc, HalfMove *hm) {
} else if (nc == '-') {
if (c == '1') {
result = "1-0";
+ loc+=2;
} else {
result = "0-1";
+ loc+=2;
}
} else {
result = "1/2-1/2";
+ loc+=6;
}
return (loc);
}
@@ -151,8 +172,9 @@ int PGN::ParseHalfMove(int loc, HalfMove *hm) {
hm->isBlack = true;
}
- // Parse comment entries (various comment could appear during HalfMove parsing)
- loc=ParseComment(loc,hm);
+ // Parse comment entries (various comment could appear during HalfMove
+ // parsing)
+ loc = ParseComment(loc, hm);
// Parse the HalfMove
loc = NextNonBlank(loc);
@@ -168,7 +190,7 @@ int PGN::ParseHalfMove(int loc, HalfMove *hm) {
hm->move = move;
// Parse comment
- loc=ParseComment(loc,hm);
+ loc = ParseComment(loc, hm);
// Skip end of variation
if (c == ')') {
@@ -177,7 +199,7 @@ int PGN::ParseHalfMove(int loc, HalfMove *hm) {
}
// Parse comment
- loc=ParseComment(loc,hm);
+ loc = ParseComment(loc, hm);
// Check for variations
loc = NextNonBlank(loc);
@@ -190,7 +212,7 @@ int PGN::ParseHalfMove(int loc, HalfMove *hm) {
}
// Parse comment
- loc=ParseComment(loc,hm);
+ loc = ParseComment(loc, hm);
// Parse next HalfMove
loc = NextNonBlank(loc);
diff --git a/src/PGN.hpp b/src/PGN.hpp
index 0f59bd0..c4e27d0 100644
--- a/src/PGN.hpp
+++ b/src/PGN.hpp
@@ -14,15 +14,23 @@ private:
std::vector<std::string> tagkeys;
/// @brief Contains game result (last PGN word)
std::string result;
- /// @brief COntains the parsed PGN moves
+ /// @brief Contains the parsed PGN moves
HalfMove *moves;
/// @brief Contains the PGN data
std::string pgn_content;
+ /// @brief Contains the location of the end of the last parsed game (1 PGN file may have multiple games)
+ int LastGameEndLoc;
public:
+ PGN();
~PGN();
void FromFile(std::string);
void FromString(std::string);
+ /**
+ * Parse the next available game. Note that it raises a @a NoGameFound exception if no more game is available.
+ * A call to this method flush all the last parsed game data. Be careful.
+ */
+ void ParseNextGame();
/// @brief Check if PGN contains a specific tag
bool HasTag(std::string);
/// @brief Perform a Seven Tag Roster compliance check
@@ -62,6 +70,10 @@ struct InvalidGameResult : public std::exception {
const char *what() const throw() { return "Invalid game result"; }
};
+struct NoGameFound : public std::exception {
+ const char *what() const throw() { return "No game (or more game) found"; }
+};
+
struct UnexpectedCharacter : public std::exception {
std::string msg;
UnexpectedCharacter(char actual, char required, int loc) {
diff --git a/tests/tests.cpp b/tests/tests.cpp
index f376f33..ea79be8 100644
--- a/tests/tests.cpp
+++ b/tests/tests.cpp
@@ -6,6 +6,7 @@ using namespace pgnp;
TEST_CASE("Valid PGN", "[valid/pgn1]") {
PGN pgn;
REQUIRE_NOTHROW(pgn.FromFile("pgn_files/valid/pgn1.pgn"));
+ REQUIRE_NOTHROW(pgn.ParseNextGame());
REQUIRE_THROWS(pgn.STRCheck());
HalfMove *m = new HalfMove();
@@ -33,7 +34,7 @@ TEST_CASE("Valid PGN", "[valid/pgn1]") {
}
SECTION("Main line color checks") {
- m=m_backup;
+ m = m_backup;
CHECK_FALSE(m->isBlack);
m = m->MainLine;
@@ -60,26 +61,42 @@ TEST_CASE("Valid PGN", "[valid/pgn1]") {
CHECK(m_backup->GetHalfMoveAt(4)->move == "c4");
CHECK(pgn.GetResult() == "*");
+ REQUIRE_THROWS_AS(pgn.ParseNextGame(),NoGameFound);
}
TEST_CASE("Valid PGN", "[valid/pgn2]") {
PGN pgn;
REQUIRE_NOTHROW(pgn.FromFile("pgn_files/valid/pgn2.pgn"));
+ REQUIRE_NOTHROW(pgn.ParseNextGame());
+
REQUIRE_THROWS(pgn.STRCheck());
HalfMove *m = new HalfMove();
pgn.GetMoves(m);
REQUIRE(m->GetLength() == 66);
CHECK(pgn.GetResult() == "0-1");
CHECK(m->comment == " A00 Hungarian Opening ");
- CHECK(m->GetHalfMoveAt(7)->comment == " (0.22 → 0.74) Inaccuracy. dxc4 was best. ");
+ CHECK(m->GetHalfMoveAt(65)->comment == " White resigns. ");
+ CHECK(m->GetHalfMoveAt(7)->comment ==
+ " (0.22 → 0.74) Inaccuracy. dxc4 was best. ");
+
+ SECTION("Check Variations") {
+ HalfMove *var = m->GetHalfMoveAt(7)->variations[0];
+ REQUIRE(var->GetLength() == 10);
+ CHECK(var->move == "dxc4");
+ CHECK(var->GetHalfMoveAt(1)->move == "O-O");
+ }
+ REQUIRE_THROWS_AS(pgn.ParseNextGame(),NoGameFound);
}
TEST_CASE("Seven Tag Roster", "[std/pgn1]") {
PGN pgn;
REQUIRE_NOTHROW(pgn.FromFile("pgn_files/str/pgn1.pgn"));
+ REQUIRE_NOTHROW(pgn.ParseNextGame());
+
REQUIRE_NOTHROW(pgn.STRCheck());
HalfMove *m = new HalfMove();
pgn.GetMoves(m);
REQUIRE(m->GetLength() == 85);
CHECK(pgn.GetResult() == "1/2-1/2");
+ REQUIRE_THROWS_AS(pgn.ParseNextGame(),NoGameFound);
}