add parse_pgn function which can later be used for automated testing

Mon, 13 Jul 2026 22:13:34 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 13 Jul 2026 22:13:34 +0200
changeset 140
e92ffe046659
parent 139
fa10bee331ac
child 141
0d6bb8b21193

add parse_pgn function which can later be used for automated testing

src/chess/pgn.c file | annotate | diff | comparison | revisions
src/chess/pgn.h file | annotate | diff | comparison | revisions
--- a/src/chess/pgn.c	Sat Jun 13 13:45:19 2026 +0200
+++ b/src/chess/pgn.c	Mon Jul 13 22:13:34 2026 +0200
@@ -60,7 +60,39 @@
     return pgn_error_strings[code];
 }
 
-int read_pgn(FILE* stream, GameState *gamestate) {
+struct pgn_input {
+    union {
+        FILE *f;
+        const char *s;
+    } data;
+    union {
+        int (*f)(FILE*);
+        int (*s)(const char*);
+    } getc;
+    void (*backc)(struct pgn_input *);
+};
+
+static int pgngetc(struct pgn_input *pgndata) {
+    return pgndata->getc.f(pgndata->data.f);
+}
+
+static void pgnbackc(struct pgn_input *pgndata) {
+    pgndata->backc(pgndata);
+}
+
+static void fbackc(struct pgn_input *pgn) {
+    fseek(pgn->data.f, -1, SEEK_CUR);
+}
+
+static void sbackc(struct pgn_input *pgn) {
+    --(pgn->data.s);
+}
+
+static int sgetc(const char *s) {
+    return *s;
+}
+
+static int read_pgn_(struct pgn_input pgndata, GameState *gamestate) {
     int c, i;
     
     char result[8];
@@ -70,32 +102,32 @@
     
     /* read tag pairs */
     while (true) {
-        while (isspace(c = fgetc(stream)));
+        while (isspace(c = pgngetc(&pgndata)));
         if (c == '1') {
             break;
         }
         if (c != '[') {
             return pgn_error_missing_bracket;
         }
-        while (isspace(c = fgetc(stream)));
+        while (isspace(c = pgngetc(&pgndata)));
         i = 0;
         do {
             tagkey[i++] = c;
-        } while (!isspace(c = fgetc(stream)));
+        } while (!isspace(c = pgngetc(&pgndata)));
         tagkey[i] = '\0';
-        while (isspace(c = fgetc(stream)));
+        while (isspace(c = pgngetc(&pgndata)));
         if (c != '"') {
             return pgn_error_missing_quote;
         }
         i = 0;
-        while ((c = fgetc(stream)) != '"') {
+        while ((c = pgngetc(&pgndata)) != '"') {
             if (c == '\n' || c == EOF) {
                 return pgn_error_missing_quote;
             }
             tagvalue[i++] = c;
         }
         tagvalue[i] = '\0';
-        if (fgetc(stream) != ']') {
+        if (pgngetc(&pgndata) != ']') {
             return pgn_error_missing_bracket;
         }
 
@@ -107,7 +139,7 @@
     }
     
     /* read moves */
-    if (fgetc(stream) != '.') {
+    if (pgngetc(&pgndata) != '.') {
         return pgn_error_missing_dot;
     }
     
@@ -117,14 +149,14 @@
     
     while (true) {
         /* move */
-        while (isspace(c = fgetc(stream)));
+        while (isspace(c = pgngetc(&pgndata)));
         i = 0;
         do {
             movestr[i++] = c;
             if (i >= 10) {
                 return pgn_error_move_syntax;
             }
-        } while (!isspace(c = fgetc(stream)));
+        } while (!isspace(c = pgngetc(&pgndata)));
         movestr[i] = '\0';
         if (eval_move(gamestate, movestr, &move, curcol)
                 != VALID_MOVE_SYNTAX) {
@@ -136,19 +168,19 @@
         apply_move(gamestate, &move);
         
         /* skip spaces */
-        while (isspace(c = fgetc(stream)));
+        while (isspace(c = pgngetc(&pgndata)));
         
         /* parse possible comment */
         if (c == '{') {
             // TODO: interpret comment (may contain clock info)
             do {
-                c = fgetc(stream);
+                c = pgngetc(&pgndata);
             } while (c != '}' && c != EOF);
             if (c == EOF) {
                 return pgn_error_missing_brace;
             }
             /* skip spaces */
-            while (isspace(c = fgetc(stream)));
+            while (isspace(c = pgngetc(&pgndata)));
         }
         
         /* end of game data encountered */
@@ -156,13 +188,13 @@
             break;
         }
         if (c == '1' || c == '0') {
-            c = fgetc(stream);
+            c = pgngetc(&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 = fgetc(stream);
+                    c = pgngetc(&pgndata);
                     if (c == '1') {
                         gamestate->wresign = true;
                     } else if (c == '0') {
@@ -177,16 +209,16 @@
                 break;
             } else {
                 /* oops, it was a move number, go back! */
-                fseek(stream, -1, SEEK_CUR);
+                pgnbackc(&pgndata);
             }
         }
         
         /* we have eaten the next valuable byte, so go back */
-        fseek(stream, -1, SEEK_CUR);
+        pgnbackc(&pgndata);
         
         /* skip move number after black move */
         if (curcol == BLACK) {
-            while (isdigit(c = fgetc(stream)));
+            while (isdigit(c = pgngetc(&pgndata)));
             if (c != '.') {
                 return pgn_error_missing_dot;
             }
@@ -197,6 +229,24 @@
     return 0;
 }
 
+int read_pgn(FILE *stream, GameState *gamestate) {
+    return read_pgn_(
+            (struct pgn_input){
+                {.f = stream},
+                {.f = fgetc},
+                fbackc
+            }, gamestate);
+}
+
+int parse_pgn(const char *data, GameState *gamestate) {
+    return read_pgn_(
+            (struct pgn_input){
+                {.s = data},
+                {.s = sgetc},
+                sbackc
+            }, gamestate);
+}
+
 static void pgn_insert_newlines(char *block) {
     size_t pos = 0;
     size_t last_space_pos = 0;
--- a/src/chess/pgn.h	Sat Jun 13 13:45:19 2026 +0200
+++ b/src/chess/pgn.h	Mon Jul 13 22:13:34 2026 +0200
@@ -39,6 +39,7 @@
 #endif
     
 int read_pgn(FILE *stream, GameState *gamestate);
+int parse_pgn(const char *data, GameState *gamestate);
 void write_pgn(FILE* stream, GameState *gamestate, bool export_comments);
 void compute_fen(char *str, GameState *gamestate);
 

mercurial