Mon, 13 Jul 2026 22:37:40 +0200
improve implementation of read_pgn() and parse_pgn()
| src/chess/pgn.c | file | annotate | diff | comparison | revisions |
--- a/src/chess/pgn.c Mon Jul 13 22:23:00 2026 +0200 +++ b/src/chess/pgn.c Mon Jul 13 22:37:40 2026 +0200 @@ -43,6 +43,7 @@ pgn_error_move_syntax, pgn_error_move_semantics, pgn_error_result_syntax, + pgn_error_unexpected_eof, }; static const char* pgn_error_strings[] = { @@ -54,49 +55,16 @@ "Move is syntactically incorrect.", "Move is not valid according to chess rules.", "Result syntax is incorrect. Expected 1-0, 0-1, 1/2-1/2, or *.", + "Unexpected end of data.", }; const char* pgn_error_str(int code) { return pgn_error_strings[code]; } -struct pgn_input { - union { - FILE *f; - const char *s; - } data; - int (*getc)(struct pgn_input *); - void (*backc)(struct pgn_input *); -}; - -static int pgngetc(struct pgn_input *pgn) { - return pgn->getc(pgn); -} - -static void pgnbackc(struct pgn_input *pgn) { - pgn->backc(pgn); -} - -static int pgnfgetc(struct pgn_input *pgn) { - return fgetc(pgn->data.f); -} - -static void pgnfbackc(struct pgn_input *pgn) { - fseek(pgn->data.f, -1, SEEK_CUR); -} - -static void pgnsbackc(struct pgn_input *pgn) { - --(pgn->data.s); -} - -static int pgnsgetc(struct pgn_input *pgn) { - int c = *(unsigned char *) pgn->data.s; - pgn->data.s++; - return c; -} - -static int read_pgn_(struct pgn_input pgndata, GameState *gamestate) { - int c, i; +int parse_pgn(const char *pgndata, GameState *gamestate) { + int i; + char c; char result[8]; @@ -105,32 +73,32 @@ /* read tag pairs */ while (true) { - while (isspace(c = pgngetc(&pgndata))); + while (isspace(c = *(pgndata++))); if (c == '1') { break; } if (c != '[') { return pgn_error_missing_bracket; } - while (isspace(c = pgngetc(&pgndata))); + while (isspace(c = *(pgndata++))); i = 0; do { tagkey[i++] = c; - } while (!isspace(c = pgngetc(&pgndata))); + } while (!isspace(c = *(pgndata++))); tagkey[i] = '\0'; - while (isspace(c = pgngetc(&pgndata))); + while (isspace(c = *(pgndata++))); if (c != '"') { return pgn_error_missing_quote; } i = 0; - while ((c = pgngetc(&pgndata)) != '"') { - if (c == '\n' || c == EOF) { + while ((c = *(pgndata++)) != '"') { + if (c == '\n' || c == 0) { return pgn_error_missing_quote; } tagvalue[i++] = c; } tagvalue[i] = '\0'; - if (pgngetc(&pgndata) != ']') { + if (*(pgndata++) != ']') { return pgn_error_missing_bracket; } @@ -142,7 +110,7 @@ } /* read moves */ - if (pgngetc(&pgndata) != '.') { + if (*(pgndata++) != '.') { return pgn_error_missing_dot; } @@ -152,14 +120,14 @@ while (true) { /* move */ - while (isspace(c = pgngetc(&pgndata))); + while (isspace(c = *(pgndata++))); i = 0; do { movestr[i++] = c; if (i >= 10) { return pgn_error_move_syntax; } - } while (!isspace(c = pgngetc(&pgndata))); + } while (!isspace(c = *(pgndata++))); movestr[i] = '\0'; if (eval_move(gamestate, movestr, &move, curcol) != VALID_MOVE_SYNTAX) { @@ -171,33 +139,33 @@ apply_move(gamestate, &move); /* skip spaces */ - while (isspace(c = pgngetc(&pgndata))); + while (isspace(c = *(pgndata++))); /* parse possible comment */ if (c == '{') { // TODO: interpret comment (may contain clock info) do { - c = pgngetc(&pgndata); - } while (c != '}' && c != EOF); - if (c == EOF) { + c = *(pgndata++); + } while (c != '}' && c != 0); + if (c == 0) { return pgn_error_missing_brace; } /* skip spaces */ - while (isspace(c = pgngetc(&pgndata))); + while (isspace(c = *(pgndata++))); } /* end of game data encountered */ - if (c == EOF) { + if (c == 0) { break; } if (c == '1' || c == '0') { - c = pgngetc(&pgndata); + c = *(pgndata++); // TODO: #842 - allow games to be continued if possible if (c == '-') { if (!gamestate->checkmate) { // TODO: maybe we should not parse this here; // there is an unused result string from the STR - c = pgngetc(&pgndata); + c = *(pgndata++); if (c == '1') { gamestate->wresign = true; } else if (c == '0') { @@ -212,16 +180,16 @@ break; } else { /* oops, it was a move number, go back! */ - pgnbackc(&pgndata); + pgndata--; } } /* we have eaten the next valuable byte, so go back */ - pgnbackc(&pgndata); + pgndata--; /* skip move number after black move */ if (curcol == BLACK) { - while (isdigit(c = pgngetc(&pgndata))); + while (isdigit(c = *(pgndata++))); if (c != '.') { return pgn_error_missing_dot; } @@ -233,21 +201,20 @@ } int read_pgn(FILE *stream, GameState *gamestate) { - return read_pgn_( - (struct pgn_input){ - {.f = stream}, - pgnfgetc, - pgnfbackc - }, gamestate); -} - -int parse_pgn(const char *data, GameState *gamestate) { - return read_pgn_( - (struct pgn_input){ - {.s = data}, - pgnsgetc, - pgnsbackc - }, gamestate); + fseek(stream, 0, SEEK_END); + long int size = ftell(stream); + fseek(stream, 0, SEEK_SET); + char *data = malloc(size + 1); + memset(data, 0, size + 1); + size_t read = 0; + do { + size_t r = fread(data + read, 1, size - read, stream); + if (r == 0) return pgn_error_unexpected_eof; + read += r; + } while (read < size); + int result = parse_pgn(data, gamestate); + free(data); + return result; } static void pgn_insert_newlines(char *block) {