improve implementation of read_pgn() and parse_pgn() default tip

Mon, 13 Jul 2026 22:37:40 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 13 Jul 2026 22:37:40 +0200
changeset 143
e9159c70ff9e
parent 142
780337e5cf6a

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) {

mercurial