chess-move-interface/src/PGN.cpp
2023-01-18 18:42:13 +01:00

425 lines
No EOL
10 KiB
C++

#include "pgnp.hpp"
#include <iostream>
#include <string>
#define IS_BLANK(c) (c == ' ' || c == '\n' || c == '\t' || c == '\r')
#define IS_ALPHA(c) \
((c == 'a' || c == 'b' || c == 'c' || c == 'd' || c == 'e' || c == 'f' || \
c == 'g' || c == 'h' || c == 'i' || c == 'j' || c == 'k' || c == 'l' || \
c == 'm' || c == 'n' || c == 'o' || c == 'p' || c == 'q' || c == 'r' || \
c == 's' || c == 't' || c == 'u' || c == 'v' || c == 'w' || c == 'x' || \
c == 'y' || c == 'z') || \
(c == 'A' || c == 'B' || c == 'C' || c == 'D' || c == 'E' || c == 'F' || \
c == 'G' || c == 'H' || c == 'I' || c == 'J' || c == 'K' || c == 'L' || \
c == 'M' || c == 'N' || c == 'O' || c == 'P' || c == 'Q' || c == 'R' || \
c == 'S' || c == 'T' || c == 'U' || c == 'V' || c == 'W' || c == 'X' || \
c == 'Y' || c == 'Z'))
#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 (pgn_content.IsEOF())
#define IS_TOKEN(c) \
(IS_DIGIT(c) || IS_ALPHA(c) || c == '{' || c == '}' || c == '(' || \
c == ')' || c == '[' || c == ']' || c == '$' || c == '"' || c == '*')
#define EOF_CHECK(loc) \
{ \
if (IS_EOF) \
throw UnexpectedEOF(); \
}
namespace pgnp {
PGN::PGN() : LastGameEndLoc(0), moves(NULL) {}
PGN::~PGN() {
if (moves != NULL)
delete moves;
}
std::string PGN::GetResult() { return (result); }
void PGN::FromFile(std::string filepath) { pgn_content.FromFile(filepath); }
void PGN::FromString(std::string pgn_content) {
this->pgn_content.FromString(pgn_content);
}
void PGN::GotoNextGame(){
// Search for new game
if (IS_EOF) {
throw NoGameFound();
}
loctype loc = GotoNextToken(LastGameEndLoc);
if (IS_EOF) {
throw NoGameFound();
}
// First skip current game tags
while (!IS_EOF) {
char c = pgn_content[loc];
if (!IS_BLANK(c)) {
if (c == '[') {
loc = ParseNextTag(loc); // Here we are at ']' after the call to ParseNextTag
} else
break;
}
loc++;
}
// Goto next game '[' by skipping the entire game
while (!IS_EOF) {
char c = pgn_content[loc];
if (!IS_BLANK(c)) {
if (c == '[') {
LastGameEndLoc=loc;
return;
} else if(c== '{'){
// We skip the comments as they can contains '['
while(!IS_EOF && c != '}'){
loc++;
c = pgn_content[loc];
}
}
}
loc++;
}
throw NoGameFound();
}
void PGN::ParseNextGame() {
// Clean previous parse
if (moves != NULL) {
delete moves;
}
result = "";
tagkeys.clear();
tags.clear();
moves = new HalfMove();
// Search for new game
if (IS_EOF) {
throw NoGameFound();
}
loctype loc = GotoNextToken(LastGameEndLoc);
if (IS_EOF) {
throw NoGameFound();
}
// Parse game
while (!IS_EOF) {
char c = pgn_content[loc];
if (!IS_BLANK(c)) {
if (c == '[') {
loc = ParseNextTag(loc);
} else {
LastGameEndLoc = ParseHalfMove(loc, moves);
break;
}
}
loc++;
}
if (result.size() <= 0) {
throw InvalidGameResult();
}
}
void PGN::STRCheck() {
int i = 0;
// Locate Event tag
while (i < tagkeys.size()) {
if (tagkeys[i] == "Event") {
break;
}
i++;
}
// Check tags
if (i + 6 < tagkeys.size()) {
bool valid = (tagkeys[i] == "Event") && (tagkeys[i + 1] == "Site") &&
(tagkeys[i + 2] == "Date") && (tagkeys[i + 3] == "Round") &&
(tagkeys[i + 4] == "White") && (tagkeys[i + 5] == "Black") &&
(tagkeys[i + 6] == "Result");
if (!valid) {
throw STRCheckFailed();
}
} else {
throw STRCheckFailed();
}
}
bool PGN::HasTag(std::string key) {
auto tags = GetTagList();
return (std::find(tags.begin(), tags.end(), key) != tags.end());
}
loctype PGN::ParseComment(loctype loc, HalfMove *hm) {
// Goto next char
loc = GotoNextToken(loc);
EOF_CHECK(loc);
char c = pgn_content[loc];
// Parse a sequence of comment
while (!IS_EOF && c == '{') {
loc++;
EOF_CHECK(loc);
c = pgn_content[loc];
while (c != '}') {
hm->comment += c;
loc++;
EOF_CHECK(loc);
c = pgn_content[loc];
}
loc++; // Skip '}'
// Goto next non blank to look for another comment token
loc = GotoNextToken(loc);
if (!IS_EOF) {
c = pgn_content[loc];
}
}
return (loc);
}
loctype PGN::ParseHalfMove(loctype loc, HalfMove *hm) {
// Goto next char
loc = GotoNextToken(loc);
EOF_CHECK(loc);
char c = pgn_content[loc];
// Check if we reach score entry (* or 1-0 or 0-1 or 1/2-1/2)
if (c == '*') {
result = "*";
return (loc + 1);
}
// Parse comment
if (c == '{') {
loc = ParseComment(loc, hm);
EOF_CHECK(loc);
c = pgn_content[loc];
}
// Parse move number and check if end of game
if (IS_DIGIT(c)) {
std::string move_nb;
char first_digit = c;
while (IS_DIGIT(c)) {
move_nb += c;
loc++;
c = pgn_content[loc];
EOF_CHECK(loc);
if (c == '/' || c == '-') {
if (c == '/') {
result = "1/2-1/2";
return (loc + 6);
} else if (first_digit == '1') {
result = "1-0";
return (loc + 2);
} else {
result = "0-1";
return (loc + 2);
}
}
}
hm->count = std::stoi(move_nb);
loc++;
EOF_CHECK(loc);
if (pgn_content[loc] == '.') {
hm->isBlack = true;
loc += 2; // Skip two dots
EOF_CHECK(loc);
}
} else {
hm->isBlack = true;
}
// Parse the HalfMove
loc = GotoNextToken(loc);
EOF_CHECK(loc);
c = pgn_content[loc];
std::string move;
while (!IS_BLANK(c) && c != ')') {
move += c;
loc++;
c = pgn_content[loc];
EOF_CHECK(loc);
}
hm->move = move;
// Parse Comments, Variations and NAG
loc = GotoNextToken(loc);
EOF_CHECK(loc);
c = pgn_content[loc];
while (c == '{' || c == '$' || c == '(') {
if (c == '{') {
// Parse comment
loc = ParseComment(loc, hm);
} else if (c == '$') {
// Check for NAG
loc = GotoNextToken(loc);
EOF_CHECK(loc);
c = pgn_content[loc];
if (c == '$') {
//hm->NAG += c;
std::string NAG;
loc++;
EOF_CHECK(loc);
c = pgn_content[loc];
while (IS_DIGIT(c)) {
NAG += c;
loc++;
EOF_CHECK(loc);
c = pgn_content[loc];
}
if(NAG.size()>0)
hm->NAG=std::stoi(NAG);
}
} else if (c == '(') {
// Check for variations
loc = GotoNextToken(loc);
while (!IS_EOF && pgn_content[loc] == '(') {
loc++; // Skip '('
HalfMove *var = new HalfMove;
loc = ParseHalfMove(loc, var);
hm->variations.push_back(var);
loc++; // Skip ')'
// Goto next var
loc = GotoNextToken(loc);
EOF_CHECK(loc);
c = pgn_content[loc];
}
}
loc = GotoNextToken(loc);
EOF_CHECK(loc);
c = pgn_content[loc];
}
// Skip end of variation
if (c == ')') {
return (loc);
}
// Parse next HalfMove
loc = GotoNextToken(loc);
if (!IS_EOF) {
HalfMove *next_hm = new HalfMove;
next_hm->count = hm->count;
loc = ParseHalfMove(loc, next_hm);
// Check if move parsed successfuly
if (next_hm->move.size() > 0) {
hm->MainLine = next_hm;
} else {
delete next_hm;
}
}
return (loc);
}
loctype PGN::ParseNextTag(loctype start_loc) {
// Parse key
std::string key;
loctype keyloc = start_loc + 1;
EOF_CHECK(keyloc);
char c = pgn_content[keyloc];
while (!IS_BLANK(c)) {
key += c;
keyloc++;
EOF_CHECK(keyloc);
c = pgn_content[keyloc];
}
// Parse value
std::string value;
loctype valueloc = GotoNextToken(keyloc) + 1;
EOF_CHECK(keyloc);
c = pgn_content[valueloc];
while (c != '"' or IS_EOF) {
value += c;
valueloc++;
EOF_CHECK(keyloc);
c = pgn_content[valueloc];
}
// Add tag
tags[key] = value;
tagkeys.push_back(key);
EOF_CHECK(valueloc + 1);
c = pgn_content[valueloc + 1];
if (c != ']') {
throw UnexpectedCharacter(c, ']', valueloc + 1);
}
return (valueloc + 1); // +1 For the last char of the tag which is ']'
}
void PGN::GetMoves(HalfMove *copy) { moves->Copy(copy); }
std::vector<std::string> PGN::GetTagList() { return tagkeys; }
std::string PGN::GetTagValue(std::string key) {
if (tags.find(key) == tags.end()) {
throw InvalidTagName();
}
return tags[key];
}
std::string PGN::Dump() {
std::stringstream ss;
ss << "---------- PGN DUMP ----------" << std::endl;
ss << "Tags:" << std::endl;
for (auto &tag : GetTagList()) {
ss << " " << tag << "=" << GetTagValue(tag) << std::endl;
}
ss << "Moves:" << std::endl;
if (moves != NULL)
ss << moves->Dump();
return (ss.str());
}
loctype PGN::GotoNextToken(loctype loc) {
char c = pgn_content[loc];
while (IS_BLANK(c) || !IS_TOKEN(c)) {
loc++;
if (IS_EOF) {
return (loc);
}
c = pgn_content[loc];
if (c == '%' || c == ';') {
loc = GotoEOL(loc);
if (!IS_EOF) {
c = pgn_content[loc];
} else {
return (loc);
}
}
}
return (loc);
}
loctype PGN::GotoEOL(loctype loc) {
char c = pgn_content[loc];
while (true) {
loc++;
if (IS_EOF) {
return (loc);
}
c = pgn_content[loc];
if (c == '\n') {
return (loc);
}
}
}
void ParseSANMoves(const std::string &sequence,HalfMove *moves) {
PGN parser;
// Note that PGN need a results (* at the end)
// Otherwise an InvalidGameResult exception is raised
parser.FromString(sequence+" *");
parser.ParseNextGame();
parser.GetMoves(moves);
}
} // namespace pgnp