LCOV - code coverage report
Current view: top level - src - pgnparse.cpp (source / functions) Hit Total Coverage
Test: test_coverage.info Lines: 372 602 61.8 %
Date: 2017-06-21 14:32:49 Functions: 23 29 79.3 %

          Line data    Source code
       1             : //////////////////////////////////////////////////////////////////////
       2             : //
       3             : //  FILE:       pgnparse.cpp
       4             : //              PgnParser class methods
       5             : //
       6             : //  Part of:    Scid (Shane's Chess Information Database)
       7             : //  Version:    3.5
       8             : //
       9             : //  Notice:     Copyright (c) 2001-2003  Shane Hudson.  All rights reserved.
      10             : //              Copyright (C) 2015 Fulvio Benini
      11             : //
      12             : //////////////////////////////////////////////////////////////////////
      13             : 
      14             : 
      15             : #include "pgnparse.h"
      16             : #include "game.h"
      17             : #include <stdio.h>
      18             : 
      19             : #if defined(_MSC_VER) && _MSC_VER <= 1800
      20             :     #define snprintf _snprintf
      21             : #endif
      22             : 
      23             : namespace {
      24             : 
      25             : const uint MAX_COMMENT_SIZE = 16000;
      26             : 
      27             : // ADDCHAR() macro: Adds one character to a buffer and increments the
      28             : //     buffer pointer.
      29             : //
      30             : #define ADDCHAR(buf,ch)  *(buf) = (ch); (buf)++; *(buf) = 0
      31             : 
      32             : /**
      33             :  * charIsSpace() - Checks whether @c is a white-space character.
      34             :  *
      35             :  * Return false for non-breaking spaces (ASCII-160 or A0 hex) because
      36             :  * they can be part of a multi-byte utf-8 character.
      37             :  */
      38             : bool charIsSpace (unsigned char c) {
      39    98960434 :     return (c == ' '    ||
      40    49480217 :             c == '\t'   ||
      41    45547786 :             c == '\n'   ||
      42             :             c == '\v'   ||
      43    94541009 :             c == '\f'   ||
      44             :             c == '\r');
      45             : }
      46             : 
      47             : /**
      48             :  * Convert a string form ISO 8859/1 (Latin1) to UTF-8.
      49             :  * The PGN standard use a subset of ISO 8859/1 (Latin 1):
      50             :  * Code value from 0 to 126 are the standard ASCII character set
      51             :  * Code value from 127 to 191 are not used for PGN data representation.
      52             :  * Code value from 192 to 255 are mostly alphabetic printing characters with
      53             :  * various diacritical marks; their use is encouraged for those languages
      54             :  * that require such characters.
      55             :  * Latin1 chars must be converted because the Tcl/tk framework uses UTF-8.
      56             :  * @param s: the string to be converted.
      57             :  * @returns @e true is the string was modified.
      58             :  */
      59      237130 : bool pgnLatin1_to_UTF8(std::string& s) {
      60      237130 :         bool res = false;
      61    67968172 :         for (std::string::iterator it = s.begin(); it != s.end(); ++it) {
      62    33628391 :                 unsigned char v = *it;
      63    33628391 :                 if (v >= 192) {
      64           0 :                         std::string::iterator next = it + 1;
      65           0 :                         if (next != s.end()) {
      66           0 :                                 unsigned char nextCh = *next;
      67           0 :                                 if ((nextCh >> 6) == 0x02)
      68             :                                         continue;
      69             :                         }
      70             :                         // Not a valid UTF-8 sequence: assume it's a Latin1 char and
      71             :                         // convert it.
      72           0 :                         res = true;
      73           0 :                         it = s.insert(it, char(0xC3));
      74           0 :                         *++it = v & 0xBF;
      75             :                 }
      76             :         }
      77      237130 :         return res;
      78             : }
      79             : 
      80             : } // end of anonymous namespace.
      81             : 
      82             : void
      83           0 : PgnParser::Reset()
      84             : {
      85          36 :     UnGetCount = 0;
      86          36 :     NumErrors = 0;
      87          36 :     BytesSeen = 0;
      88          36 :     LineCounter = 0;
      89          36 :     GameCounter = 0;
      90          36 :     StorePreGameText = true;
      91          36 :     EndOfInputWarnings = true;
      92          36 :     ResultWarnings = true;
      93          36 :     NumIgnoredTags = 0;
      94           0 : }
      95             : 
      96             : void
      97          11 : PgnParser::Reset (MFile * infile)
      98             : {
      99          11 :     Reset();
     100          11 :     InFile = infile;
     101          11 :     InBuffer = InCurrent = NULL;
     102          11 :     EndChar = EOF;
     103          11 : }
     104             : 
     105             : void
     106          18 : PgnParser::Init (const char * inbuffer)
     107             : {
     108          18 :     Reset();
     109          18 :     InFile = NULL;
     110          18 :     InBuffer = InCurrent = inbuffer;
     111          18 :     EndChar = 0;
     112          18 : }
     113             : 
     114             : void
     115           7 : PgnParser::Reset (const char * inbuffer)
     116             : {
     117           7 :     Reset();
     118           7 :     InFile = NULL;
     119           7 :     InBuffer = InCurrent = inbuffer;
     120           7 :     EndChar = 0;
     121           7 : }
     122             : 
     123             : int
     124    49848196 : PgnParser::GetChar ()
     125             : {
     126    49848196 :     int ch = 0;
     127    49848196 :     BytesSeen++;
     128    49848196 :     if (UnGetCount > 0) {
     129     2809932 :         UnGetCount--;
     130     2809932 :         ch = UnGetCh[UnGetCount];
     131    47038264 :     } else if (InFile != NULL) {
     132    47032237 :         ch =  InFile->ReadOneByte();
     133             :     } else {
     134        6027 :         ch = *InCurrent;
     135        6027 :         if (ch != 0) { InCurrent++; }
     136             :     }
     137    49848196 :     if (ch == '\n') { LineCounter++; }
     138    49848196 :     return ch;
     139             : }
     140             : 
     141             : void
     142             : PgnParser::UnGetChar (int ch)
     143             : {
     144     2809932 :     if (UnGetCount == MAX_UNGETCHARS) { return; }
     145     2809932 :     UnGetCh[UnGetCount] = ch;
     146     2809932 :     UnGetCount++;
     147     2809932 :     BytesSeen--;
     148     2809932 :     if (ch == '\n') { LineCounter--; }
     149             : }
     150             : 
     151             : void
     152           0 : PgnParser::AddIgnoredTag (const char * tag)
     153             : {
     154           0 :     if (NumIgnoredTags >= MAX_IGNORED_TAGS) { return; }
     155           0 :     if (tag == NULL  ||  tag[0] == 0) { return; }
     156           0 :     IgnoredTags [NumIgnoredTags] = strDuplicate (tag);
     157           0 :     NumIgnoredTags++;
     158             : }
     159             : 
     160             : void
     161          18 : PgnParser::ClearIgnoredTags ()
     162             : {
     163          18 :     for (uint i = 0; i < NumIgnoredTags; i++) {
     164           0 :         delete[] IgnoredTags[i];
     165             :     }
     166          18 :     NumIgnoredTags = 0;
     167          18 : }
     168             : 
     169             : bool
     170           0 : PgnParser::IsIgnoredTag (const char * tag)
     171             : {
     172           0 :     for (uint i = 0; i < NumIgnoredTags; i++) {
     173           0 :         if (strEqual (tag, IgnoredTags[i])) { return true; }
     174             :     }
     175             :     return false;
     176             : }
     177             : 
     178             : void
     179           7 : PgnParser::LogError (const char * errMessage, const char * text)
     180             : {
     181           7 :     NumErrors++;
     182          35 :     ErrorBuffer += "(game " + to_string(GameCounter);
     183          42 :     ErrorBuffer += ", line " + to_string(LineCounter) + ") ";
     184          14 :     ErrorBuffer += errMessage;
     185          14 :     ErrorBuffer += text;;
     186          14 :     ErrorBuffer += "\n";
     187           7 : }
     188             : 
     189             : void
     190       18005 : PgnParser::GetLine (char * buffer, uint bufSize)
     191             : {
     192       18005 :     ASSERT (bufSize > 0);
     193             :     while (true) {
     194      220005 :         int ch = GetChar();
     195      220005 :         if (ch == EndChar  ||  ch == 10) {
     196             :             break;
     197             :         }
     198      202000 :         if (ch == 13) {
     199             :             // Handle ascii-13 followed by ascii-10 as a single newline:
     200           0 :             ch = GetChar();
     201           0 :             if (ch != 10) {
     202             :                 UnGetChar (ch);
     203             :             }
     204             :             break;
     205             :         }
     206      202000 :         bufSize--;
     207      202000 :         if (bufSize == 0) {
     208             :             break;
     209             :         }
     210      202000 :         *buffer++ = ch;
     211      202000 :     }
     212       18005 :     *buffer = 0;
     213       18005 :     return;
     214             : }
     215             : 
     216             : 
     217             : 
     218             : // If STANDARD_PLAYER_NAMES is defined, then player names are
     219             : // processed with a simple algorithm before adding to the name base,
     220             : // to reduce multiple instances of the same player. First, the
     221             : // number of spaces after a comma is made consistent (the default
     222             : // is one space; see NUM_SPACES_AFTER_COMMA). Second, a dot (".") at
     223             : // the END of the name string is removed.
     224             : 
     225             : #define STANDARD_PLAYER_NAMES
     226             : 
     227             : 
     228             : // NUM_SPACES_AFTER_COMMA: number of spaces to follow every comma in
     229             : // a player name, when standardising. Commom values are 0 and 1.
     230             : 
     231             : #define NUM_SPACES_AFTER_COMMA 1
     232             : 
     233             : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     234             : // standardDutchName(): standardises various combinations of upper
     235             : //       and lower case "v" and "d" in the common Dutch name
     236             : //       prefixes "van der", "van de" and "van den" to a capital
     237             : //       V and small d, for consistency to avoid multiple names.
     238             : //
     239             : static void
     240        4000 : standardDutchName (char * s)
     241             : {
     242        4000 :     if (*s != 'v'  &&  *s != 'V') { return; }
     243           0 :     if (strIsPrefix ("van ", s)) { s[0] = 'V'; }
     244           0 :     if (strIsPrefix ("Van Der ", s) ||
     245           0 :         strIsPrefix ("Van Den ", s) ||
     246           0 :         strIsPrefix ("Van De ", s))
     247             :     {
     248           0 :         s[4] = 'd';
     249             :     }
     250             : }
     251             : 
     252             : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     253             : // standardPlayerName(): standardises player names to reduce the number
     254             : //      of different names referring to the same player. Algorithm:
     255             : //      (1) The number of spaces after a comma is standardised, to
     256             : //          NUM_SPACES_AFTER_COMMA spaces.
     257             : //      (2) Spaces and dots are removed from the END of the name.
     258             : //      So "Anand,V" "Anand, V" and "Anand, V." would all become "Anand, V"
     259             : //
     260             : static void
     261        4000 : standardPlayerName (char * source)
     262             : {
     263             :     char tempName [1024];
     264        4000 :     strCopy (tempName, source);
     265        4000 :     char * from = tempName;
     266        4000 :     char * to = source;
     267        4000 :     int afterComma = 0;
     268        4000 :     bool startSpaces = true;
     269             : 
     270       48000 :     while (*from) {
     271       22000 :         if (*from == ',') {
     272           0 :             *to++ = ',';
     273           0 :             afterComma = 1;
     274           0 :             startSpaces = false;
     275       22000 :         } else if (*from == ' ') {
     276        2000 :             if (!afterComma  &&  !startSpaces) {
     277             :                 // Not after a ',' or at start of name, so add the space:
     278        2000 :                 *to++ = ' ';
     279             :             }
     280             :         } else { // any other character:
     281       20000 :             startSpaces = false;
     282       20000 :             if (afterComma) {
     283             :                 // First, insert spaces after the comma:
     284             :                 uint x = NUM_SPACES_AFTER_COMMA;
     285           0 :                 while (x > 0) { *to++ = ' '; x--; }
     286             :             }
     287       20000 :             *to = *from;
     288       20000 :             to++;
     289       20000 :             afterComma = 0;
     290             :         }
     291       22000 :         from++;
     292             :     }
     293        4000 :     *to = 0;
     294             : 
     295             :     // Now trim any trailing spaces, tabs :
     296        4000 :     strTrimRight (source, " \t");
     297             : 
     298             :     // Now standardise the capital letters of Dutch/etc prefix names:
     299        4000 :     standardDutchName(source);
     300        4000 :     return;
     301             : }
     302             : 
     303             : errorT
     304       14000 : PgnParser::ExtractPgnTag (const char * buffer, Game * game)
     305             : {
     306       14000 :     const uint maxTagLength = 255;
     307             :     char tag [255];
     308             :     char value [512];
     309             : 
     310             :     // Skip any initial whitespace:
     311       28000 :     while (charIsSpace(*buffer)  &&  *buffer != 0) { buffer++; }
     312             :     // Skip the '[' character and any whitespace:
     313       14000 :     ASSERT (*buffer == '[');
     314       14000 :     buffer++;
     315       28000 :     while (charIsSpace(*buffer) && *buffer != 0) { buffer++; }
     316             : 
     317             :     // Now at the start of the tag name:
     318             :     uint length = 0;
     319      164000 :     while (!charIsSpace(*buffer)  &&  *buffer != 0) {
     320       68000 :         tag[length] = *buffer++;
     321       68000 :         length++;
     322       68000 :         if (length == maxTagLength) { return ERROR_PGNTag; }
     323             :     }
     324       14000 :     if (*buffer == 0) { return ERROR_PGNTag; }
     325       14000 :     tag[length] = 0;
     326             : 
     327             :     // Find the start of the tag value:
     328       28000 :     while (*buffer != '"'  &&  *buffer != 0) { buffer++; }
     329       14000 :     if (*buffer != '"') { return ERROR_PGNTag; }
     330       14000 :     buffer++;
     331             :     // Find the end of the tag value: it is the last double-quote (")
     332             :     // on this line.
     333       14000 :     length = 0;
     334       14000 :     uint lastQuoteIndex = 0;
     335       14000 :     bool seenEndQuote = false;
     336      118000 :     while (*buffer != 0) {
     337      104000 :         if (*buffer == '"') {
     338       14000 :             lastQuoteIndex = length;
     339       14000 :             seenEndQuote = true;
     340             :         }
     341      104000 :         value[length] = *buffer;
     342      104000 :         buffer++;
     343      104000 :         length++;
     344      104000 :         if (length == maxTagLength) { return ERROR_PGNTag; }
     345             :     }
     346       14000 :     if (! seenEndQuote) { return ERROR_PGNTag; }
     347       14000 :     value[lastQuoteIndex] = 0;
     348             : 
     349       42000 :     std::string tmpUTF8(value, value + lastQuoteIndex);
     350       14000 :     if (pgnLatin1_to_UTF8(tmpUTF8)) {
     351           0 :         ASSERT(tmpUTF8.length() < sizeof(value));
     352           0 :         std::copy(tmpUTF8.begin(), tmpUTF8.end(), value);
     353           0 :         value[tmpUTF8.length()] = 0;
     354             :     }
     355             : 
     356             :     // Now decide what to add to the game based on this tag:
     357       14000 :     if (strEqual (tag, "White")) {
     358             : #ifdef STANDARD_PLAYER_NAMES
     359        2000 :         standardPlayerName (value);
     360             : #endif
     361             :         // Check for a rating in parentheses at the end of the player name:
     362        2000 :         uint len = strLength (value);
     363        2000 :         if (len > 7  &&  value[len-1] == ')'
     364           0 :             &&  isdigit(static_cast<unsigned char>(value[len-2]))
     365           0 :             &&  isdigit(static_cast<unsigned char>(value[len-3]))
     366           0 :             &&  isdigit(static_cast<unsigned char>(value[len-4]))
     367           0 :             &&  isdigit(static_cast<unsigned char>(value[len-5]))
     368           0 :             &&  value[len-6] == '('  &&  value[len-7] == ' ') {
     369           0 :             uint elo = strGetUnsigned (&(value[len-5]));
     370           0 :             if (elo <= MAX_ELO) {
     371           0 :                 value[len - 7] = 0;
     372           0 :                 game->SetWhiteElo(elo);
     373             :                 game->SetWhiteRatingType(RATING_Elo);
     374             :             }
     375             :         }
     376             :         game->SetWhiteStr (value);
     377             : 
     378       12000 :     } else if (strEqual (tag, "Black")) {
     379             : #ifdef STANDARD_PLAYER_NAMES
     380        2000 :         standardPlayerName (value);
     381             : #endif
     382             :         // Check for a rating in parentheses at the end of the player name:
     383        2000 :         uint len = strLength (value);
     384        2000 :         if (len > 7  &&  value[len-1] == ')'
     385           0 :             &&  isdigit(static_cast<unsigned char>(value[len-2]))
     386           0 :             &&  isdigit(static_cast<unsigned char>(value[len-3]))
     387           0 :             &&  isdigit(static_cast<unsigned char>(value[len-4]))
     388           0 :             &&  isdigit(static_cast<unsigned char>(value[len-5]))
     389           0 :             &&  value[len-6] == '('  &&  value[len-7] == ' ') {
     390           0 :             uint elo = strGetUnsigned (&(value[len-5]));
     391           0 :             if (elo <= MAX_ELO) {
     392           0 :                 value[len - 7] = 0;
     393           0 :                 game->SetBlackElo(elo);
     394             :                 game->SetBlackRatingType(RATING_Elo);
     395             :             }
     396             :         }
     397             :         game->SetBlackStr (value);
     398             : 
     399       10000 :     } else if (strEqual (tag, "Event")) {
     400             :         game->SetEventStr (value);
     401             : 
     402        8000 :     } else if (strEqual (tag, "Site")) {
     403             :         game->SetSiteStr (value);
     404             : 
     405        6000 :     } else if (strEqual (tag, "Round")) {
     406             :         game->SetRoundStr (value);
     407             : 
     408        4000 :     } else if (strEqual (tag, "Result")) {
     409        2000 :         if (strIsPrefix ("0-1", value)) {
     410             :             game->SetResult (RESULT_Black);
     411        2000 :         } else if (strIsPrefix ("1-0", value)) {
     412             :                 game->SetResult (RESULT_White);
     413        2000 :         } else if (strIsPrefix ("1/2", value)) {
     414             :                 game->SetResult (RESULT_Draw);
     415             :         } else {
     416             :             game->SetResult (RESULT_None);
     417             :         }
     418             : 
     419        2000 :     } else if (strEqual (tag, "Date")) {
     420        2000 :         game->SetDate (date_EncodeFromString (value));
     421             : 
     422           0 :     } else if (strEqual (tag, "EventDate")) {
     423           0 :         game->SetEventDate (date_EncodeFromString (value));
     424             : 
     425           0 :     } else if (strEqual (tag, "ECO")) {
     426           0 :         game->SetEco (eco_FromString (value));
     427             : 
     428           0 :     } else if (strEqual (tag, "ScidFlags")) {
     429             :         game->SetScidFlags (value);
     430             : 
     431           0 :     } else if (strEqual (tag, "FEN")) {
     432           0 :         if (game->SetStartFen (value) != OK) {
     433           0 :             LogError ("Error: Invalid FEN: ", value);
     434             :             return ERROR_InvalidFEN;
     435             :         }
     436             : 
     437             :     } else {
     438             :         // Look for Rating Types: only the first Rating type found for
     439             :         // each player is added as the rating. Any extra ratings are
     440             :         // just added as normal tags.
     441             : 
     442             :         bool isRatingType = false;
     443             : 
     444           0 :         if (strIsPrefix ("White", tag)  &&  game->GetWhiteElo() == 0) {
     445             :             char * tagSuffix = tag + 5;
     446             :             uint i = 0;
     447           0 :             while (ratingTypeNames[i] != NULL) {
     448           0 :                 if (strEqual (tagSuffix, ratingTypeNames[i])) {
     449           0 :                     uint elo = strGetUnsigned (value);
     450           0 :                     if (elo > MAX_ELO) {
     451           0 :                         LogError ("Warning: rating too large: ", value);
     452             :                         elo = MAX_ELO;
     453             :                     }
     454           0 :                     game->SetWhiteElo (elo);
     455           0 :                     game->SetWhiteRatingType (i);
     456           0 :                     isRatingType = true;
     457           0 :                     break;
     458             :                 }
     459           0 :                 i++;
     460             :             }
     461             :         }
     462           0 :         if (strIsPrefix ("Black", tag)  &&  game->GetBlackElo() == 0) {
     463             :             char * tagSuffix = tag + 5;
     464             :             uint i = 0;
     465           0 :             while (ratingTypeNames[i] != NULL) {
     466           0 :                 if (strEqual (tagSuffix, ratingTypeNames[i])) {
     467           0 :                     uint elo = strGetUnsigned (value);
     468           0 :                     if (elo > MAX_ELO) {
     469           0 :                         LogError ("Warning: rating too large: ", value);
     470             :                         elo = MAX_ELO;
     471             :                     }
     472           0 :                     game->SetBlackElo (elo);
     473           0 :                     game->SetBlackRatingType (i);
     474           0 :                     isRatingType = true;
     475           0 :                     break;
     476             :                 }
     477           0 :                 i++;
     478             :             }
     479             :         }
     480             : 
     481           0 :         if (! isRatingType  &&  ! IsIgnoredTag (tag)) {
     482           0 :             game->AddPgnTag (tag, value);
     483             :         }
     484             :     }
     485             :     return OK;
     486             : }
     487             : 
     488             : bool
     489       20016 : PgnParser::EndOfInput()
     490             : {
     491       20016 :     if (InFile != NULL) { return InFile->EndOfFile(); }
     492           0 :     int ch = GetChar();
     493           0 :     if (ch == EndChar) { return true; }
     494             :     UnGetChar (ch);
     495             :     return false;
     496             : }
     497             : 
     498             : // Modifies the parameter string in-place, trimming all
     499             : // whitespace at the start and end of the string, and reducing
     500             : // all other sequences of whitespace to a single space.
     501             : //
     502             : // Example: "\t\n   A  \t\n   B   C  "  (where \t and \n are tabs
     503             : // and newlines) becomes "A B C".
     504      223130 : std::string PgnParser::GetComment()
     505             : {
     506      223130 :     std::string res;
     507             : 
     508      223130 :     int ch = GetChar();
     509    68052980 :     for (; ch != EndChar  &&  ch != '}'; ch = GetChar()) {
     510    67829850 :         if (charIsSpace(ch)) {
     511      362765 :             if (res.length() == 0) continue;
     512      339474 :             if (*(res.rbegin()) == ' ') continue;
     513             :             ch = ' ';
     514             :         }
     515    33721897 :         res += ch;
     516             :     }
     517      669390 :     if (res.length() != 0 && *(res.rbegin()) == ' ') {
     518      169506 :         res.resize(res.length() -1);
     519             :     }
     520             : 
     521      223130 :     if (ch == EndChar) {
     522             :         char tempStr[80];
     523           0 :         sprintf (tempStr, "started on line %u\n", LineCounter);
     524           0 :         LogError ("Error: Open Comment at end of input", tempStr);
     525             :     }
     526             : 
     527      223130 :     return res;
     528             : }
     529             : 
     530             : void
     531           0 : PgnParser::GetRestOfSuffix (char * buffer, char firstChar)
     532             : {
     533           0 :     if (firstChar == '!'  ||  firstChar == '?') {
     534           0 :         int ch = GetChar();
     535             :         // Only get successive ! or ? characters, so a complex
     536             :         // annotation like "!!+-" can be parsed as two separate
     537             :         // entities, "!!" and "+-":
     538           0 :         while (ch == '!'  ||  ch == '?') {
     539           0 :             *buffer++ = ch;
     540           0 :             ch = GetChar();
     541             :         }
     542           0 :         UnGetChar (ch);
     543           0 :         *buffer = 0;
     544             :     } else {
     545             :         // Some other Suffix like "+/-" so just get rest of word:
     546           0 :         GetRestOfWord_NoDots (buffer);
     547             :     }
     548           0 : }
     549             : 
     550             : void
     551     1156033 : PgnParser::GetRestOfWord_NoDots (char * buffer)
     552             : {
     553     1156033 :     int ch = GetChar();
     554     6864662 :     while (!charIsSpace (ch) && ch != '.' && ch != ')' && ch != EndChar) {
     555     1517532 :         *buffer++ = ch;
     556     1517532 :         ch = GetChar();
     557             :     }
     558     1156033 :     UnGetChar (ch);
     559     1156033 :     *buffer = 0;
     560     1156033 : }
     561             : 
     562             : void
     563           0 : PgnParser::GetRestOfWord_WithDots (char * buffer)
     564             : {
     565           0 :     int ch = GetChar();
     566           0 :     while (!charIsSpace (ch)  &&  ch != ')'  &&  ch != EndChar) {
     567           0 :         *buffer++ = ch;
     568           0 :         ch = GetChar();
     569             :     }
     570           0 :     UnGetChar (ch);
     571           0 :     *buffer = 0;
     572           0 : }
     573             : 
     574             : void
     575           0 : PgnParser::GetRestOfWord_Letters (char * buffer)
     576             : {
     577           0 :     int ch = GetChar();
     578           0 :     while (isalpha(ch)) {
     579           0 :         *buffer++ = ch;
     580           0 :         ch = GetChar();
     581             :     }
     582           0 :     UnGetChar (ch);
     583           0 :     *buffer = 0;
     584           0 : }
     585             : 
     586             : tokenT
     587         578 : PgnParser::GetRestOfCastling (char * buffer)
     588             : {
     589             :     int ch;
     590         578 :     int numOhsSeen = 1;
     591             :     while (true) {
     592        2156 :         ch = GetChar();
     593        2156 :         if (ch == 'O'  ||  ch == 'o'  ||  ch == '0') {
     594         789 :             numOhsSeen++;
     595         789 :             ADDCHAR (buffer, ch);
     596         789 :             continue;
     597             :         }
     598        1367 :         if (ch == '-') {
     599             :             // Check for "-+" or "-/+" after the move:
     600         789 :             int nextCh = GetChar();
     601         789 :             UnGetChar (nextCh);
     602         789 :             if (nextCh == '+'  ||  nextCh == '/') {
     603             :                 // Seen "-+" or "-/+", e.g. "O-O-+"
     604           0 :                 UnGetChar (ch);
     605             :                 break;
     606             :             }
     607         789 :             ADDCHAR (buffer, ch);
     608         789 :             continue;
     609             :         }
     610        1739 :         if (charIsSpace(ch)  ||  ch == '+'  ||  ch == '#'  ||  ch == '='  ||
     611         578 :               ch == '!'  ||  ch == '?'  ||  ch == ')'  ||  ch == EndChar) {
     612        1156 :             UnGetChar (ch);
     613         578 :             switch (numOhsSeen) {
     614             :             case 2:  return TOKEN_Move_Castle_King;
     615         211 :             case 3:  return TOKEN_Move_Castle_Queen;
     616           0 :             default: return  TOKEN_Invalid;
     617             :             }
     618             :         }
     619             :         break;
     620             :     }
     621             :     // If we reach here, it is not a valid castling move:
     622           0 :     GetRestOfWord_WithDots (buffer);
     623             :     return TOKEN_Invalid;
     624             : }
     625             : 
     626             : tokenT
     627     1362253 : PgnParser::GetRestOfMove (char * buffer)
     628             : {
     629     1362253 :     int moveLength = 1;
     630             :     int ch;
     631             :     while (true) {
     632     4206667 :         ch = GetChar();
     633     8413334 :         if (charIsSpace(ch)) {
     634     2583454 :             UnGetChar (ch);
     635     1291727 :             return (moveLength == 1 ? TOKEN_Suffix : TOKEN_Move_Piece);
     636             :         }
     637     2914940 :         if ((ch >= '1'  &&  ch <= '8')  ||  (ch >= 'a'  &&  ch <= 'h')) {
     638     2744705 :             ADDCHAR (buffer, ch);
     639     2744705 :             moveLength++;
     640     2744705 :             continue;
     641             :         }
     642      170235 :         if (ch == '-') {
     643             :             // Check for "-+" or "-/+" after the move:
     644           0 :             int nextCh = GetChar();
     645           0 :             UnGetChar (nextCh);
     646           0 :             if (nextCh == '+'  ||  nextCh == '/') {
     647             :                 // Seen "-+" or "-/+", e.g. "Bb5-+"
     648           0 :                 UnGetChar (ch);
     649             :                 break;
     650             :             }
     651             :             // Otherwise, just ignore "-" in a move:
     652           0 :             moveLength++;
     653           0 :             continue;
     654             :         }
     655      170235 :         if (ch == 'x'  ||  ch == ':') {
     656             :             // We allow ":" as a capture as well as "x".
     657       99709 :             moveLength++;
     658       99709 :             continue;
     659             :         }
     660       70526 :         if (ch == ')'  ||  ch == '+'  ||  ch == '!'  ||  ch == '='  ||
     661         522 :               ch == '?'  ||  ch == '#'  ||  ch == EndChar) {
     662             :             // Put c back into the infile buffer for next token.
     663      141052 :             UnGetChar (ch);
     664       70526 :             return (moveLength == 1 ? TOKEN_Suffix : TOKEN_Move_Piece);
     665             :         }
     666             :         break;
     667             :     }
     668             : 
     669             :     // If we get here, it is an invalid Move character:
     670             :     return TOKEN_Invalid;
     671             : }
     672             : 
     673             : 
     674             : tokenT
     675      195796 : PgnParser::GetRestOfPawnMove (char * buffer)
     676             : {
     677             :     int ch;
     678      195796 :     bool seenDigit = false;
     679             : 
     680             :     // allows for using lowercase 'b' for bishop promotion
     681             :     // eg. OliThink uses a7b8b for FEN "1q6/P6k/8/5N1K/8/8/8/8 w - - 0 1"
     682      195796 :     bool pawn2seen = false;
     683             : 
     684             :     // First, check for "ep" or "e.p." on its own, not a move at all:
     685      195796 :     if (*(buffer-1) == 'e') {
     686       24606 :         ch = GetChar ();
     687       49212 :         UnGetChar (ch);
     688       24606 :         if (ch == 'p'  ||  ch == '.') {
     689           0 :             GetRestOfWord_WithDots (buffer);
     690             :             return TOKEN_Ignore;
     691             :         }
     692             :     }
     693             : 
     694             :     while (true) {
     695      456478 :         ch = GetChar ();
     696      912956 :         if (charIsSpace (ch)) {
     697      180744 :             UnGetChar (ch);
     698             :             return TOKEN_Move_Pawn;
     699             :         }
     700             :         // Check for "ep" or "e.p." after a digit:
     701      275734 :         if (seenDigit) {
     702       15052 :             if (ch == 'e') {
     703           0 :                 char nextCh = GetChar ();
     704           0 :                 UnGetChar (nextCh);
     705           0 :                 if (nextCh == 'p'  ||  nextCh == 'p') { continue; }
     706             :             }
     707       15052 :             if (ch == 'p'  ||  ch == '.') { continue; }
     708             :         }
     709      275734 :         if (ch >= '1'  &&  ch <= '8') {
     710      195796 :             seenDigit = true;
     711      195796 :             ADDCHAR (buffer, ch);
     712      195796 :             continue;
     713             :         }
     714       79938 :         if (ch >= 'a'  &&  ch <= 'h' && !pawn2seen) {
     715       32443 :             pawn2seen = true;
     716       32443 :             ADDCHAR (buffer, ch);
     717       32443 :             continue;
     718             :         }
     719             : 
     720       47495 :         if (ch == '-') {
     721             :             // Check for "-+" or "-/+" after the move:
     722           0 :             int nextCh = GetChar();
     723           0 :             UnGetChar (nextCh);
     724           0 :             if (nextCh == '+'  ||  nextCh == '/') {
     725             :                 // Seen "-+" or "-/+", e.g. "e4-+"
     726           0 :                 UnGetChar (ch);
     727             :                 return TOKEN_Move_Pawn;
     728             :             }
     729             :             // Otherwise, just ignore "-" in a move:
     730             :             continue;
     731             :         }
     732       47495 :         if (ch == 'x'  ||  ch == ':') {
     733             :             // Omit capture symbols, etc:
     734             :             continue;
     735             :         }
     736       15052 :         if (ch == '=') {   // A promotion!
     737        9864 :             ch = GetChar();
     738             :             // Convert "K" for promoted piece from King to Knight:
     739             :             //if (ch == 'K') { ch = 'N'; }
     740        9864 :             if (ch == 'Q' || ch == 'R' || ch == 'B'  ||  ch == 'N') {
     741        9864 :                 ADDCHAR (buffer, '=');
     742        9864 :                 ADDCHAR (buffer, ch);
     743        9864 :                 return TOKEN_Move_Promote;
     744             :             } else {
     745             :                 // OK, the "=" is NOT a promotion, but may be part of
     746             :                 // a symbol like "e4=" or "e4=+" so put it back:
     747           0 :                 UnGetChar (ch);
     748             :                 UnGetChar ('=');
     749             :                 return TOKEN_Move_Pawn;
     750             :             }
     751             :         }
     752             :         // Convert "K" for promoted piece from King to Knight:
     753             :         //if (ch == 'K') { ch = 'N'; }
     754        5188 :         if (ch == 'q' || ch == 'r' || ch == 'b'  ||  ch == 'n') {
     755             :             // Promotion with the "=" sign missing.
     756             :             // Faile and Spike use lower case letters.. Will this break anything else ? S.A.
     757           0 :             ADDCHAR (buffer, '=');
     758           0 :             ADDCHAR (buffer, toupper(ch));
     759           0 :             return TOKEN_Move_Promote;
     760             :         }
     761        5188 :         if (ch == 'Q' || ch == 'R' || ch == 'B'  ||  ch == 'N') {
     762             :             // Promotion with the "=" sign missing. We insert it.
     763           0 :             ADDCHAR (buffer, '=');
     764           0 :             ADDCHAR (buffer, ch);
     765           0 :             return TOKEN_Move_Promote;
     766             :         }
     767        5188 :         if (ch == ')'  ||  ch == '+'  ||  ch == '!'  ||
     768          16 :             ch == '?'  ||  ch == '#'  ||  ch == EndChar) {
     769        5188 :             UnGetChar (ch);
     770             :             return TOKEN_Move_Pawn;
     771             :         }
     772             :         break;
     773             :     }
     774             :     // If we reach here, it is an invalid move:
     775             :     return TOKEN_Invalid;
     776             : }
     777             : 
     778             : tokenT
     779     3631866 : PgnParser::GetGameToken (char * buffer, uint bufSize)
     780             : {
     781     3631866 :     char * buf = buffer;
     782     3631866 :     int ch = GetChar();
     783     3631866 :     if (ch == EndChar) { return TOKEN_EndOfInput; }
     784             : 
     785             :     // Read past any whitespace, dots and newlines.
     786    16044526 :     while ((charIsSpace(ch)  ||  (ch == '.'))) {
     787     4390404 :         ch = GetChar();
     788     4390404 :         if (ch == EndChar) { return TOKEN_EndOfInput; }
     789             :     }
     790     3631859 :     ADDCHAR (buf, ch);
     791             : 
     792             :     // Now try to figure out what sort of token we have...
     793             : 
     794     3631859 :     if (isdigit(ch)) {   // MoveNumber, or result, or invalid
     795     1153937 :         int allDigits = 1; // Set to zero when a non-digit is found.
     796     1153937 :         GetRestOfWord_NoDots (buf);
     797             :         char *temp = buffer;
     798             :         // Verify if token is all digits, or could be a result:
     799     6496665 :         while (*temp) {
     800     2671364 :             if (! isdigit(static_cast<unsigned char>(*temp))) {
     801             :                 allDigits = 0;
     802             :                 break;
     803             :             }
     804     2671364 :             temp++;
     805             :         }
     806     1153937 :         if (allDigits) { // Token was all digits.
     807             :             // We should just return TOKEN_MoveNum now, unless we
     808             :             // want to check for the ugly "00" and "000" (with zeroes) for
     809             :             // castling. PGN input that bad doesn't deserve to get accepted!
     810             :             return TOKEN_MoveNum;
     811             :         }
     812             : 
     813             :         // Now we check each acceptable result string.
     814             :         // Note that we also check for the awful "0-0" (castling with zeroes
     815             :         // instead of big-Ohs) although it is BAD PGN input.
     816             : 
     817           0 :         if (*buffer == '0') {   // token starts with '0'
     818           0 :             if (strEqual (buffer, "0-1")) { return TOKEN_Result_Black; }
     819           0 :             if (strEqual (buffer, "0:1")) { return TOKEN_Result_Black; }
     820           0 :             if (strIsPrefix ("0-0-0", buffer)) { return TOKEN_Move_Castle_Queen; }
     821           0 :             if (strIsPrefix ("000", buffer)) { return TOKEN_Move_Castle_Queen; }
     822           0 :             if (strIsPrefix ("0-0", buffer)) { return TOKEN_Move_Castle_King; }
     823           0 :             if (strIsPrefix ("00", buffer)) { return TOKEN_Move_Castle_King; }
     824           0 :         } else if (*buffer == '1') {   // token starts with '1'
     825           0 :             if (strEqual (buffer, "1-0")) { return TOKEN_Result_White; }
     826           0 :             if (strEqual (buffer, "1:0")) { return TOKEN_Result_White; }
     827           0 :             if (strEqual (buffer, "1/2")) { return TOKEN_Result_Draw; }
     828           0 :             if (strEqual (buffer, "1/2-1/2")) { return TOKEN_Result_Draw; }
     829           0 :             if (strEqual (buffer, "1/2:1/2")) { return TOKEN_Result_Draw; }
     830             :         }
     831             : 
     832             :         // If we get here, it must be invalid (Not a move number or a result)
     833             :         return TOKEN_Invalid;
     834             :     }
     835             : 
     836             :     // Now we check for Moves.
     837             : 
     838     2477922 :     if (ch >= 'a'  &&  ch <= 'h') {   // Pawn move.
     839      195796 :         return GetRestOfPawnMove (buf);
     840             :     }
     841     2282126 :     if (ch == 'P') {
     842             :         // Treat "P..." as a pawn move, ignoring the initial "P":
     843           0 :         buf = buffer;
     844           0 :         ADDCHAR (buf, GetChar());
     845           0 :         return GetRestOfPawnMove (buf);
     846             :     }
     847     2282126 :     if (ch == 'N'  ||  ch == 'B'  ||  ch == 'R'  ||  ch == 'Q'  ||  ch == 'K') {
     848     1362253 :         return GetRestOfMove (buf);
     849             :     }
     850      919873 :     if (ch == 'O'  ||  ch == 'o') { //letter "O": must be Castling or invalid
     851         578 :         return GetRestOfCastling (buf);
     852             :     }
     853             : 
     854             :     // Check for null move:
     855      919295 :     if (ch == 'n') {
     856           0 :         GetRestOfWord_Letters (buf);
     857           0 :         if (strEqual (buffer, "null")) {
     858             :             return TOKEN_Move_Null;
     859             :         }
     860           0 :         return TOKEN_Invalid;
     861             :     }
     862             : 
     863             :     // Now we check for other tokens.......
     864      919295 :     if (ch == ';'  ||  ch == '%') { // LineComment.
     865             :         // "%" should only mark a comment if at the start of the line, but we allow it anywhere on a line.
     866             :         // S.A - There's a bug here with the parser, but not sure if it's fixable:
     867             :         //       stray ';' before variations and/or comments SPLIT OVER MULTIPLE LINES cause chaos
     868           0 :         GetLine (buf, bufSize-1);
     869             :         return TOKEN_LineComment;
     870             :     }
     871      919295 :     if (ch == '{') {   // regular comment. We let caller read until a "}".
     872             :         return TOKEN_Comment;
     873             :     }
     874             : 
     875      696165 :     if (ch == '}') {   // Close-brace outside a comment. Should not happen.
     876             :         return TOKEN_CommentEnd;
     877             :     }
     878             : 
     879      696165 :     if (ch == '(') {   // variation. We let caller parse it out.
     880             :         return TOKEN_VarStart;
     881             :     }
     882      388001 :     if (ch == ')') { return TOKEN_VarEnd; }
     883             : 
     884       79837 :     if (ch == '!'  ||  ch == '?'  ||  ch == '='  ||  ch == '-') {   // Suffix
     885           0 :         GetRestOfSuffix (buf, ch);
     886             :         // Treat the sequence "--" as a null move:
     887           0 :         if (strEqual (buffer, "--")) {
     888             :             return TOKEN_Move_Null;
     889             :         }
     890           0 :         return TOKEN_Suffix;
     891             :     }
     892       79837 :     if (ch == '$') {   // NAG
     893          96 :         GetRestOfWord_NoDots (buf);
     894             :         return TOKEN_Nag;
     895             :     }
     896             : 
     897       79741 :     if (ch == '+'  ||  ch == '#') {   // Check or mate or invalid
     898       77741 :         tokenT t = (ch == '+' ? TOKEN_Check : TOKEN_Mate);
     899             :         // Can be followed by: space, !, ? or $.  So peek at next input char
     900       77741 :         char nextc = GetChar();
     901             :         // If "+" is followed by another "+", treat it as a double-check:
     902       77741 :         if (ch == '+'  &&  nextc == '+') { return t; }
     903      155482 :         UnGetChar (nextc);
     904      233223 :         if (charIsSpace(nextc)  ||  nextc == '!'  ||  nextc == '?' ||
     905       77741 :                 nextc == '$'  ||  nextc == ')'  ||  nextc == EndChar) {
     906             :             return t;   // Token was a valid "+" or "#".
     907             :         }
     908             :         // If we get here, token looks invalid.
     909             :         // It could be a suffix, e.g. "+=", so return as that:
     910           0 :         GetRestOfSuffix (buf, ch);
     911             :         return TOKEN_Suffix;
     912             :     }
     913             : 
     914        2000 :     if (ch == '*') {   // "*" (Result).  Must be followed by whitespace.
     915        2000 :         GetRestOfWord_NoDots (buf);
     916        2000 :         if (buf[0] != '\0') { // We have a word with more than just "*"
     917             :             return TOKEN_Invalid;
     918             :         }
     919        2000 :         return TOKEN_Result_Star;
     920             :     }
     921             : 
     922           0 :     if (ch == '[') {   // Tag! This shouldn't happen! But return TOKEN_Tag.
     923             :         // Put the '[' back so it can be read as a tag of the next game:
     924           0 :         UnGetChar (ch);
     925             :         return TOKEN_Tag;
     926             :     }
     927             : 
     928           0 :     if (ch == 'D') {  // Diagram symbol:
     929           0 :         GetRestOfWord_NoDots (buf);
     930             :         return TOKEN_Nag;
     931             :     }
     932             : 
     933           0 :     if (ch == '~') {  // "Unclear" annotation symbol:
     934           0 :         GetRestOfSuffix (buf, ch);
     935             :         return TOKEN_Suffix;
     936             :     }
     937             : 
     938             :     // Convert Z0 to Null Move (thanks to Marcin Kasperski)
     939           0 :     if (ch == 'Z') {   // Z0 - nullmove in CA notation
     940           0 :         int nextCh = GetChar();
     941           0 :         if (nextCh == '0')
     942             :             return TOKEN_Move_Null;
     943             :         UnGetChar(nextCh);
     944             :     }
     945             : 
     946             :     // If we get down this far, the first character of our token is invalid.
     947             :     // Probably a letter like C or z, or punctuation or nonprintable.
     948             : 
     949           0 :     GetRestOfWord_WithDots (buf);
     950             :     // Any other null-move notations to be checked for here?
     951             :     return TOKEN_Invalid;
     952             : }
     953             : 
     954             : 
     955             : static inline char *
     956             : firstNonBlank (char * s)
     957             : {
     958        6005 :     char *x = s;
     959        6005 :     while (*x) {
     960        4000 :         if (! charIsSpace(*x))  { return x; }
     961           0 :         x++;
     962             :     }
     963             :     return x;
     964             : }
     965             : 
     966             : tokenT
     967     3651882 : PgnParser::GetNextToken (char * buffer, uint bufSize)
     968             : {
     969     3651882 :     if (ParseMode == PARSE_Header) {
     970       14000 :         if (EndOfInput()) { return TOKEN_EndOfInput; }
     971             : 
     972             :         // We want to read a while line, but first we need to
     973             :         // peek at the first character of the line to see if
     974             :         // we are past the tags and already at the moves.
     975             :         // If this happens, it means there was no blank line
     976             :         // between the tags and the moves which is not good PGN,
     977             :         // but it is very common we need to accept it.
     978             : 
     979       14000 :         char * buf = buffer;
     980       14000 :         int ch = GetChar();
     981       14000 :         ADDCHAR (buf,ch);
     982       14000 :         if (ch == EndChar) { return TOKEN_EndOfInput; }
     983             : 
     984             :         // Read past any whitespace, dots and newlines.
     985             :         // but preserve them in buffer.
     986       32000 :         while ((charIsSpace(ch)  ||  (ch == '.'))) {
     987        2000 :             ch = GetChar();
     988        2000 :             ADDCHAR (buf, ch);
     989        2000 :             if (ch == EndChar) { return TOKEN_EndOfInput; }
     990             :         }
     991             : 
     992       14000 :         if ((ch == '%')||(ch ==';')) {
     993           0 :             GetLine (buf, bufSize-(buf-buffer));
     994             :             return TOKEN_LineComment;
     995             :         }
     996             : 
     997       14000 :         if (ch == '[') {
     998       12000 :             GetLine (buf, bufSize-(buf-buffer));
     999             :             return TOKEN_Tag;
    1000             :         }
    1001             : 
    1002        2000 :         if (ch == '\0') {
    1003             :             return TOKEN_TagEnd;
    1004             :         }
    1005             : 
    1006             :         // We've got the start of the moves.
    1007        2000 :         UnGetChar (ch);
    1008             :         return TOKEN_TagEnd;
    1009             :     }  // End of Header Mode
    1010             : 
    1011     3637882 :     if (ParseMode == PARSE_Searching) {
    1012             :         // Looking for first Header Tag of game.  In this mode, nothing is
    1013             :         // invalid. Lines without a PGN Header Tag are treated as a Line
    1014             :         // Comment, even if they don't start with "%" or ";".
    1015        6016 :         if (EndOfInput())  { return TOKEN_EndOfInput; }
    1016        6005 :         GetLine (buffer, bufSize);
    1017        6005 :         char * s = firstNonBlank (buffer);
    1018        6005 :         if (*s == '[') { return TOKEN_Tag; }
    1019        4005 :         return TOKEN_LineComment;
    1020             :     }   // End of Searching mode.
    1021             : 
    1022             :    // If we reach here, we are in Game mode, the most complex.
    1023     3631866 :     return GetGameToken (buffer, bufSize);
    1024             : }
    1025             : 
    1026             : 
    1027             : errorT
    1028           7 : PgnParser::ParseMoves (Game * game)
    1029             : {
    1030           7 :     char * buffer = new char [MAX_COMMENT_SIZE];
    1031           7 :     errorT err = ParseMoves (game, buffer, MAX_COMMENT_SIZE);
    1032           7 :     delete[] buffer;
    1033           7 :     return err;
    1034             : }
    1035             :    
    1036             : 
    1037             : errorT
    1038        2007 : PgnParser::ParseMoves (Game * game, char * buffer, uint bufSize)
    1039             : {
    1040        2007 :     errorT err = OK;
    1041        2007 :     uint moveErrorCount  = 0;
    1042        2007 :     const uint maxMoveErrorsPerGame = 1;
    1043        2007 :     uint commentErrorCount = 0;
    1044        2007 :     const uint maxCommentErrorsPerGame = 1;
    1045             :     simpleMoveT sm;
    1046             :     byte nag;
    1047             : 
    1048             :     // Uncomment next line to allow castling after King or Rook have moved:
    1049             :     // game->GetCurrentPos()->SetStrictCastling (false);
    1050        2007 :     ParseMode = PARSE_Game;
    1051        2007 :     tokenT token = GetNextToken (buffer, bufSize);
    1052     3631866 :     while (! TOKEN_isResult(token)) {   
    1053     3629866 :         switch (token) {
    1054             :         case TOKEN_Move_Pawn:
    1055             :         case TOKEN_Move_Promote:
    1056             :         case TOKEN_Move_Piece:
    1057             :         case TOKEN_Move_Castle_King:
    1058             :         case TOKEN_Move_Castle_Queen:
    1059             :         case TOKEN_Move_Null:
    1060     1558627 :             err = game->GetCurrentPos()->ReadMove (&sm, buffer, token);
    1061             : 
    1062             :             // If king castling failed, maybe it's OO meaning castle queen-side
    1063     1558627 :             if (err != OK  &&  token == TOKEN_Move_Castle_King) {
    1064           0 :                 err = game->GetCurrentPos()->ReadMove (&sm, buffer, TOKEN_Move_Castle_Queen);
    1065             :             }
    1066             : 
    1067             :             // The most common type of "illegal" move in standard
    1068             :             // chess is castling when the king or rook have already
    1069             :             // moved. So if a castling move failed, turn off
    1070             :             // strict checking of castling rights and try again,
    1071             :             // but still print a warning if that succeeded:
    1072             : 
    1073     1558627 :             if (err != OK  &&  (token == TOKEN_Move_Castle_King  ||
    1074             :                                 token == TOKEN_Move_Castle_Queen)) {
    1075           0 :                 bool prevFlag = game->GetCurrentPos()->GetStrictCastling();
    1076           0 :                 game->GetCurrentPos()->SetStrictCastling (false);
    1077           0 :                 err = game->GetCurrentPos()->ReadMove (&sm, buffer, token);
    1078           0 :                 game->GetCurrentPos()->SetStrictCastling (prevFlag);
    1079             : 
    1080             :                 // If no longer an error, castling without strict checking
    1081             :                 // worked, but still print a warning about it:
    1082           0 :                 if (err == OK) {
    1083             :                     char tempStr[500];
    1084           0 :                     snprintf (tempStr, sizeof(tempStr), "(%s) in game %s - %s, %u",
    1085             :                              buffer, game->GetWhiteStr(), game->GetBlackStr(),
    1086           0 :                              date_GetYear (game->GetDate()));
    1087           0 :                     LogError ("Warning: illegal castling ", tempStr);
    1088             :                 }
    1089             :             }
    1090             : 
    1091     1558627 :             if (err == OK  &&  moveErrorCount == 0) {
    1092     1558627 :                 err = game->AddMove (&sm, NULL);
    1093             :             }
    1094             : 
    1095             :              // Report an error if the move could not be added:
    1096     1558627 :             if (err != OK) {
    1097           0 :                 moveErrorCount++;
    1098           0 :                 if (moveErrorCount <= maxMoveErrorsPerGame) {
    1099             :                     char tempStr [500];
    1100             :                     // Add an error comment to the game:
    1101           0 :                     snprintf (tempStr, sizeof(tempStr), "Error reading move: %s", buffer);
    1102           0 :                     game->SetMoveComment (tempStr);
    1103           0 :                     snprintf (tempStr, sizeof(tempStr), "Error reading move in game %s - %s, %u: ",
    1104             :                              game->GetWhiteStr(), game->GetBlackStr(),
    1105           0 :                              date_GetYear (game->GetDate()));
    1106           0 :                     LogError (tempStr, buffer);
    1107             :                 }
    1108             :             }
    1109             :             break;
    1110             : 
    1111             :         case TOKEN_Ignore:
    1112             :         case TOKEN_MoveNum:
    1113             :         case TOKEN_Check:
    1114             :         case TOKEN_Mate:
    1115             :             break;  // Move numbers, check and made symbols: just ignore.
    1116             : 
    1117             :         case TOKEN_Nag:
    1118          96 :             nag = game_parseNag (buffer);
    1119          96 :             if (moveErrorCount == 0) { game->AddNag (nag); }
    1120             :             break;
    1121             : 
    1122             :         case TOKEN_Suffix:
    1123           0 :             nag = game_parseNag (buffer);
    1124           0 :             if (nag == 0) {
    1125           0 :                 LogError ("Warning: Invalid annotation symbol: ", buffer);
    1126             :             } else {
    1127           0 :                 if (moveErrorCount == 0) {
    1128           0 :                     game->AddNag (nag);
    1129             :                 }
    1130             :             }
    1131             :             break;
    1132             : 
    1133             :         case TOKEN_VarStart:
    1134      308164 :             if (game->AddVariation() != OK) {
    1135           0 :                 LogError ("Error: Unable to add variation", "");
    1136             :                 return ERROR_Game;
    1137             :             }
    1138             :             break;
    1139             : 
    1140             :         case TOKEN_VarEnd:
    1141      308164 :             game->MoveExitVariation();
    1142      308164 :             game->MoveForward();
    1143             :             break;
    1144             : 
    1145             :         case TOKEN_Comment: {
    1146      446260 :             std::string comment = GetComment();
    1147      223130 :             pgnLatin1_to_UTF8(comment);
    1148      223130 :             game->SetMoveComment(comment.c_str());
    1149      223130 :             } break;
    1150             : 
    1151             :         case TOKEN_LineComment:
    1152             :             break;  // Line comments inside a game are just ignored.
    1153             : 
    1154             :         case TOKEN_CommentEnd:
    1155           0 :             if (commentErrorCount < maxCommentErrorsPerGame) {
    1156             :                 char tempStr [500];
    1157           0 :                 snprintf (tempStr, sizeof(tempStr), " in game %s - %s, %u: ",
    1158             :                     game->GetWhiteStr(), game->GetBlackStr(),
    1159           0 :                     date_GetYear (game->GetDate()));
    1160           0 :                 LogError ("Warning: \"}\" seen outside a comment", tempStr);
    1161           0 :                 commentErrorCount++;
    1162             :             }
    1163             :             break;
    1164             : 
    1165             :         case TOKEN_Tag:
    1166             :             // This is often seen when missing TOKEN_Result
    1167           0 :             LogError ("PGN header '[' seen inside game (result missing ?)", "");
    1168             :             return ERROR_Game;
    1169             : 
    1170             :         case TOKEN_EndOfInput:
    1171           7 :             if (EndOfInputWarnings) {
    1172           7 :                 LogError ("End of input reached in game (result missing ?)", "");
    1173             :                 return ERROR_Game;
    1174             :             } else {
    1175             :                 return OK;
    1176             :             }
    1177             : 
    1178             :         default:
    1179           0 :             LogError ("Error: Unexpected symbol: ", buffer);
    1180             :         }
    1181             : 
    1182     3629859 :         token = GetNextToken (buffer, bufSize);
    1183             :     }
    1184             : 
    1185             :     // Now the token value is the game result:
    1186        2000 :     resultT r = RESULT_None;
    1187             :     switch (token) {
    1188             :     case TOKEN_Result_White: r = RESULT_White; break;
    1189             :     case TOKEN_Result_Black: r = RESULT_Black; break;
    1190             :     case TOKEN_Result_Draw:  r = RESULT_Draw;  break;
    1191             :     default:
    1192             :         r = RESULT_None;
    1193             :     }
    1194             : 
    1195             :     // Verify the result matches that from the header:
    1196        2000 :     if (r != game->GetResult()) {
    1197             :         // Use the end-of-game result instead of the header tag result:
    1198           0 :         game->SetResult (r);
    1199           0 :         if (ResultWarnings) {
    1200           0 :             LogError ("Result did not match the header result", "");
    1201             :         }
    1202             :     }
    1203             :     return OK;
    1204             : }
    1205             : 
    1206             : 
    1207             : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    1208             : // PgnParser::ParseGame():
    1209             : //      Parses the next game from the input source.
    1210             : //      Returns: OK if a game was found and free of fatal errors;
    1211             : //               ERROR_NotFound if no game was found;
    1212             : //               or some other appropriate error code upon error.
    1213             : //
    1214             : errorT
    1215        2011 : PgnParser::ParseGame (Game * game)
    1216             : {
    1217        2011 :     char * buffer = new char [MAX_COMMENT_SIZE];
    1218        2011 :     uint preGameTextLength = 0;
    1219             : 
    1220        2011 :     char * preGameTextBuffer = new char [MAX_COMMENT_SIZE];
    1221             : 
    1222        2011 :     GameCounter++;
    1223        2011 :     errorT err = ERROR_NotFound;
    1224        2011 :     ParseMode = PARSE_Searching;
    1225        2011 :     tokenT token = GetNextToken (buffer, MAX_COMMENT_SIZE);
    1226       20016 :     while (token != TOKEN_EndOfInput) {
    1227       20005 :         if (TOKEN_isTag (token)) {
    1228             :             // Found a PGN Header tag, e.g. [Event "..."]
    1229       14000 :             if (ParseMode == PARSE_Searching) {
    1230             :                 // This is the first tag of a new game:
    1231        2000 :                 game->Clear();
    1232        4000 :                 if (StorePreGameText  &&  preGameTextLength > 0
    1233        2000 :                     &&  ! strIsAllWhitespace (preGameTextBuffer)) {
    1234             :                     // Remove last newline and store pre-game comment:
    1235           0 :                     preGameTextBuffer[preGameTextLength-1] = 0;
    1236           0 :                     game->SetMoveComment (preGameTextBuffer);
    1237             :                 }
    1238        2000 :                 ParseMode = PARSE_Header;
    1239             :             }
    1240       14000 :             if (ExtractPgnTag (buffer, game) != OK) {
    1241           0 :                 LogError ("Error reading tag: ", buffer);
    1242             :             }
    1243             : 
    1244        6005 :         } else if (token == TOKEN_LineComment) {
    1245        4005 :             static Position epd;
    1246        4005 :             if (epd.ReadFromFEN(buffer) == OK) {
    1247             :                 //EPD line
    1248           0 :                 game->Clear();
    1249           0 :                 game->SetStartFen(buffer);
    1250           0 :                 uint spaces = 0;
    1251           0 :                 const char* buffer_end = buffer + MAX_COMMENT_SIZE;
    1252           0 :                 for (const char* i = buffer; *i != 0 && i != buffer_end; i++) {
    1253           0 :                     if (*i == ' ') {
    1254           0 :                         spaces++;
    1255           0 :                         continue;
    1256             :                     }
    1257           0 :                     if (spaces >= 4) {
    1258           0 :                         game->SetMoveComment(i);
    1259             :                         break;
    1260             :                     }
    1261             :                 }
    1262           0 :                 ParseMode = PARSE_Game;
    1263           0 :                 err = OK;
    1264           0 :                 break;
    1265             :             }
    1266             :             // Add the line to the pre-game text if necessary:
    1267        4005 :             if (preGameTextLength > 0  ||  buffer[0] != 0) {
    1268           0 :                 uint len = strLength (buffer);
    1269           0 :                 if (preGameTextLength + len < MAX_COMMENT_SIZE) {
    1270           0 :                     strCopy (&(preGameTextBuffer[preGameTextLength]), buffer);
    1271           0 :                     preGameTextLength += len;
    1272           0 :                     preGameTextBuffer[preGameTextLength] = '\n';
    1273           0 :                     preGameTextLength++;
    1274             :                 }
    1275             :             }
    1276             : 
    1277        2000 :         } else if (token == TOKEN_Comment) {
    1278             :             // Get, but ignore, this comment:
    1279           0 :             GetComment();
    1280             : 
    1281        2000 :         } else if (token == TOKEN_TagEnd) {
    1282             :             // A blank line after the PGN header tags:
    1283        2000 :             ParseMode = PARSE_Game;
    1284        2000 :             err = ParseMoves (game, buffer, MAX_COMMENT_SIZE);
    1285             :             break;
    1286             :         } else {
    1287             :             // Any other token is invalid here:
    1288           0 :             LogError ("Warning: Invalid text in PGN game header: ", buffer);
    1289             :         }
    1290             : 
    1291       18005 :         token = GetNextToken (buffer, MAX_COMMENT_SIZE);
    1292             :     }
    1293        2011 :     delete[] buffer;
    1294        2011 :     delete[] preGameTextBuffer;
    1295             : 
    1296        2011 :     if (ParseMode == PARSE_Header) {
    1297           0 :         if (EndOfInputWarnings) {
    1298           0 :             LogError ("Warning: End of input in PGN header tags section", "");
    1299             :             err = ERROR_Game;
    1300             :         } else {
    1301             :             err = OK;
    1302             :         }
    1303             :     }
    1304        2011 :     return err;
    1305           2 : }
    1306             : 
    1307             : 
    1308             : //////////////////////////////////////////////////////////////////////
    1309             : //  EOF: pgnparse.cpp
    1310             : //////////////////////////////////////////////////////////////////////

Generated by: LCOV version 1.12