Add forgotten files 1.4.70b
[polyglot.git] / pgn.c
diff --git a/pgn.c b/pgn.c
index 3de7483..aabd4e1 100644 (file)
--- a/pgn.c
+++ b/pgn.c
-\r
-// pgn.c\r
-\r
-// includes\r
-\r
-#include <ctype.h>\r
-#include <errno.h>\r
-#include <stdio.h>\r
-#include <string.h>\r
-\r
-#include "pgn.h"\r
-#include "util.h"\r
-\r
-// constants\r
-\r
-static const bool DispMove = FALSE;\r
-static const bool DispToken = FALSE;\r
-static const bool DispChar = FALSE;\r
-\r
-static const int TAB_SIZE = 8;\r
-\r
-static const int CHAR_EOF = 256;\r
-\r
-// types\r
-\r
-enum token_t {\r
-   TOKEN_ERROR   = -1,\r
-   TOKEN_EOF     = 256,\r
-   TOKEN_SYMBOL  = 257,\r
-   TOKEN_STRING  = 258,\r
-   TOKEN_INTEGER = 259,\r
-   TOKEN_NAG     = 260,\r
-   TOKEN_RESULT  = 261\r
-};\r
-\r
-// prototypes\r
-\r
-static void pgn_token_read   (pgn_t * pgn);\r
-static void pgn_token_unread (pgn_t * pgn);\r
-\r
-static void pgn_read_token   (pgn_t * pgn);\r
-\r
-static bool is_symbol_start  (int c);\r
-static bool is_symbol_next   (int c);\r
-\r
-static void pgn_skip_blanks  (pgn_t * pgn);\r
-\r
-static void pgn_char_read    (pgn_t * pgn);\r
-static void pgn_char_unread  (pgn_t * pgn);\r
-\r
-// functions\r
-\r
-// pgn_open()\r
-\r
-void pgn_open(pgn_t * pgn, const char file_name[]) {\r
-\r
-   ASSERT(pgn!=NULL);\r
-   ASSERT(file_name!=NULL);\r
-\r
-   pgn->file = fopen(file_name,"r");\r
-   if (pgn->file == NULL) my_fatal("pgn_open(): can't open file \"%s\": %s\n",file_name,strerror(errno));\r
-\r
-   pgn->char_hack = CHAR_EOF; // DEBUG\r
-   pgn->char_line = 1;\r
-   pgn->char_column = 0;\r
-   pgn->char_unread = FALSE;\r
-   pgn->char_first = TRUE;\r
-\r
-   pgn->token_type = TOKEN_ERROR; // DEBUG\r
-   strcpy(pgn->token_string,"?"); // DEBUG\r
-   pgn->token_length = -1; // DEBUG\r
-   pgn->token_line = -1; // DEBUG\r
-   pgn->token_column = -1; // DEBUG\r
-   pgn->token_unread = FALSE;\r
-   pgn->token_first = TRUE;\r
-\r
-   strcpy(pgn->result,"?"); // DEBUG\r
-   strcpy(pgn->fen,"?"); // DEBUG\r
-\r
-   pgn->move_line = -1; // DEBUG\r
-   pgn->move_column = -1; // DEBUG\r
-}\r
-\r
-// pgn_close()\r
-\r
-void pgn_close(pgn_t * pgn) {\r
-\r
-   ASSERT(pgn!=NULL);\r
-\r
-   fclose(pgn->file);\r
-}\r
-\r
-// pgn_next_game()\r
-\r
-bool pgn_next_game(pgn_t * pgn) {\r
-\r
-   char name[PGN_STRING_SIZE];\r
-   char value[PGN_STRING_SIZE];\r
-\r
-   ASSERT(pgn!=NULL);\r
-\r
-   // init\r
-\r
-   strcpy(pgn->result,"*");\r
-   strcpy(pgn->fen,"");\r
-\r
-   // loop\r
-\r
-   while (TRUE) {\r
-\r
-      pgn_token_read(pgn);\r
-\r
-      if (pgn->token_type != '[') break;\r
-\r
-      // tag\r
-\r
-      pgn_token_read(pgn);\r
-      if (pgn->token_type != TOKEN_SYMBOL) {\r
-         my_fatal("pgn_next_game(): malformed tag at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);\r
-      }\r
-      strcpy(name,pgn->token_string);\r
-\r
-      pgn_token_read(pgn);\r
-      if (pgn->token_type != TOKEN_STRING) {\r
-         my_fatal("pgn_next_game(): malformed tag at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);\r
-      }\r
-      strcpy(value,pgn->token_string);\r
-\r
-      pgn_token_read(pgn);\r
-      if (pgn->token_type != ']') {\r
-         my_fatal("pgn_next_game(): malformed tag at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);\r
-      }\r
-\r
-      // special tag?\r
-\r
-      if (FALSE) {\r
-      } else if (my_string_equal(name,"Result")) {\r
-         strcpy(pgn->result,value);\r
-      } else if (my_string_equal(name,"FEN")) {\r
-         strcpy(pgn->fen,value);\r
-      }\r
-   }\r
-\r
-   if (pgn->token_type == TOKEN_EOF) return FALSE;\r
-\r
-   pgn_token_unread(pgn);\r
-\r
-   return TRUE;\r
-}\r
-\r
-// pgn_next_move()\r
-\r
-bool pgn_next_move(pgn_t * pgn, char string[], int size) {\r
-\r
-   int depth;\r
-\r
-   ASSERT(pgn!=NULL);\r
-   ASSERT(string!=NULL);\r
-   ASSERT(size>=PGN_STRING_SIZE);\r
-\r
-   // init\r
-\r
-   pgn->move_line = -1; // DEBUG\r
-   pgn->move_column = -1; // DEBUG\r
-\r
-   // loop\r
-\r
-   depth = 0;\r
-\r
-   while (TRUE) {\r
-\r
-      pgn_token_read(pgn);\r
-\r
-      if (FALSE) {\r
-\r
-      } else if (pgn->token_type == '(') {\r
-\r
-         // open RAV\r
-\r
-         depth++;\r
-\r
-      } else if (pgn->token_type == ')') {\r
-\r
-         // close RAV\r
-\r
-         if (depth == 0) {\r
-            my_fatal("pgn_next_move(): malformed variation at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);\r
-         }\r
-\r
-         depth--;\r
-         ASSERT(depth>=0);\r
-\r
-      } else if (pgn->token_type == TOKEN_RESULT) {\r
-\r
-         // game finished\r
-\r
-         if (depth > 0) {\r
-            my_fatal("pgn_next_move(): malformed variation at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);\r
-         }\r
-\r
-         return FALSE;\r
-\r
-      } else {\r
-\r
-         // skip optional move number\r
-\r
-         if (pgn->token_type == TOKEN_INTEGER) {\r
-            do pgn_token_read(pgn); while (pgn->token_type == '.');\r
-         }\r
-\r
-         // move must be a symbol\r
-\r
-         if (pgn->token_type != TOKEN_SYMBOL) {\r
-            my_fatal("pgn_next_move(): malformed move at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);\r
-         }\r
-\r
-         // store move for later use\r
-\r
-         if (depth == 0) {\r
-\r
-            if (pgn->token_length >= size) {\r
-               my_fatal("pgn_next_move(): move too long at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);\r
-            }\r
-\r
-            strcpy(string,pgn->token_string);\r
-            pgn->move_line = pgn->token_line;\r
-            pgn->move_column = pgn->token_column;\r
-         }\r
-\r
-         // skip optional NAGs\r
-\r
-         do pgn_token_read(pgn); while (pgn->token_type == TOKEN_NAG);\r
-         pgn_token_unread(pgn);\r
-\r
-         // return move\r
-\r
-         if (depth == 0) {\r
-            if (DispMove) printf("move=\"%s\"\n",string);\r
-            return TRUE;\r
-         }\r
-      }\r
-   }\r
-\r
-   ASSERT(FALSE);\r
-\r
-   return FALSE;\r
-}\r
-\r
-// pgn_token_read()\r
-\r
-static void pgn_token_read(pgn_t * pgn) {\r
-\r
-   ASSERT(pgn!=NULL);\r
-\r
-   // token "stack"\r
-\r
-   if (pgn->token_unread) {\r
-      pgn->token_unread = FALSE;\r
-      return;\r
-   }\r
-\r
-   // consume the current token\r
-\r
-   if (pgn->token_first) {\r
-      pgn->token_first = FALSE;\r
-   } else {\r
-      ASSERT(pgn->token_type!=TOKEN_ERROR);\r
-      ASSERT(pgn->token_type!=TOKEN_EOF);\r
-   }\r
-\r
-   // read a new token\r
-\r
-   pgn_read_token(pgn);\r
-   if (pgn->token_type == TOKEN_ERROR) my_fatal("pgn_token_read(): lexical error at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);\r
-\r
-   if (DispToken) printf("< L%d C%d \"%s\" (%03X)\n",pgn->token_line,pgn->token_column,pgn->token_string,pgn->token_type);\r
-}\r
-\r
-// pgn_token_unread()\r
-\r
-static void pgn_token_unread(pgn_t * pgn) {\r
-\r
-   ASSERT(pgn!=NULL);\r
-\r
-   ASSERT(!pgn->token_unread);\r
-   ASSERT(!pgn->token_first);\r
-\r
-   pgn->token_unread = TRUE;\r
-}\r
-\r
-// pgn_read_token()\r
-\r
-static void pgn_read_token(pgn_t * pgn) {\r
-\r
-   ASSERT(pgn!=NULL);\r
-\r
-   // skip white-space characters\r
-\r
-   pgn_skip_blanks(pgn);\r
-\r
-   // init\r
-\r
-   pgn->token_type = TOKEN_ERROR;\r
-   strcpy(pgn->token_string,"");\r
-   pgn->token_length = 0;\r
-   pgn->token_line = pgn->char_line;\r
-   pgn->token_column = pgn->char_column;\r
-\r
-   // determine token type\r
-\r
-   if (FALSE) {\r
-\r
-   } else if (pgn->char_hack == CHAR_EOF) {\r
-\r
-      pgn->token_type = TOKEN_EOF;\r
-\r
-   } else if (strchr(".[]()<>",pgn->char_hack) != NULL) {\r
-\r
-      // single-character token\r
-\r
-      pgn->token_type = pgn->char_hack;\r
-      sprintf(pgn->token_string,"%c",pgn->char_hack);\r
-      pgn->token_length = 1;\r
-\r
-   } else if (pgn->char_hack == '*') {\r
-\r
-      pgn->token_type = TOKEN_RESULT;\r
-      sprintf(pgn->token_string,"%c",pgn->char_hack);\r
-      pgn->token_length = 1;\r
-\r
-   } else if (pgn->char_hack == '!') {\r
-\r
-      pgn_char_read(pgn);\r
-\r
-      if (FALSE) {\r
-\r
-      } else if (pgn->char_hack == '!') { // "!!"\r
-\r
-         pgn->token_type = TOKEN_NAG;\r
-         strcpy(pgn->token_string,"3");\r
-         pgn->token_length = 1;\r
-\r
-      } else if (pgn->char_hack == '?') { // "!?"\r
-\r
-         pgn->token_type = TOKEN_NAG;\r
-         strcpy(pgn->token_string,"5");\r
-         pgn->token_length = 1;\r
-\r
-      } else { // "!"\r
-\r
-         pgn_char_unread(pgn);\r
-\r
-         pgn->token_type = TOKEN_NAG;\r
-         strcpy(pgn->token_string,"1");\r
-         pgn->token_length = 1;\r
-      }\r
-\r
-   } else if (pgn->char_hack == '?') {\r
-\r
-      pgn_char_read(pgn);\r
-\r
-      if (FALSE) {\r
-\r
-      } else if (pgn->char_hack == '?') { // "??"\r
-\r
-         pgn->token_type = TOKEN_NAG;\r
-         strcpy(pgn->token_string,"4");\r
-         pgn->token_length = 1;\r
-\r
-      } else if (pgn->char_hack == '!') { // "?!"\r
-\r
-         pgn->token_type = TOKEN_NAG;\r
-         strcpy(pgn->token_string,"6");\r
-         pgn->token_length = 1;\r
-\r
-      } else { // "?"\r
-\r
-         pgn_char_unread(pgn);\r
-\r
-         pgn->token_type = TOKEN_NAG;\r
-         strcpy(pgn->token_string,"2");\r
-         pgn->token_length = 1;\r
-      }\r
-\r
-   } else if (is_symbol_start(pgn->char_hack)) {\r
-\r
-      // symbol, integer, or result\r
-\r
-      pgn->token_type = TOKEN_INTEGER;\r
-      pgn->token_length = 0;\r
-\r
-      do {\r
-\r
-         if (pgn->token_length >= PGN_STRING_SIZE-1) {\r
-            my_fatal("pgn_read_token(): symbol too long at line %d, column %d,game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);\r
-         }\r
-\r
-         if (!isdigit(pgn->char_hack)) pgn->token_type = TOKEN_SYMBOL;\r
-\r
-         pgn->token_string[pgn->token_length++] = pgn->char_hack;\r
-\r
-         pgn_char_read(pgn);\r
-\r
-      } while (is_symbol_next(pgn->char_hack));\r
-\r
-      pgn_char_unread(pgn);\r
-\r
-      ASSERT(pgn->token_length>0&&pgn->token_length<PGN_STRING_SIZE);\r
-      pgn->token_string[pgn->token_length] = '\0';\r
-\r
-      if (my_string_equal(pgn->token_string,"1-0")\r
-       || my_string_equal(pgn->token_string,"0-1")\r
-       || my_string_equal(pgn->token_string,"1/2-1/2")) {\r
-         pgn->token_type = TOKEN_RESULT;\r
-      }\r
-\r
-   } else if (pgn->char_hack == '"') {\r
-\r
-      // string\r
-\r
-      pgn->token_type = TOKEN_STRING;\r
-      pgn->token_length = 0;\r
-\r
-      while (TRUE) {\r
-\r
-         pgn_char_read(pgn);\r
-\r
-         if (pgn->char_hack == CHAR_EOF) {\r
-            my_fatal("pgn_read_token(): EOF in string at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);\r
-         }\r
-\r
-         if (pgn->char_hack == '"') break;\r
-\r
-         if (pgn->char_hack == '\\') {\r
-\r
-            pgn_char_read(pgn);\r
-\r
-            if (pgn->char_hack == CHAR_EOF) {\r
-               my_fatal("pgn_read_token(): EOF in string at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);\r
-            }\r
-\r
-            if (pgn->char_hack != '"' && pgn->char_hack != '\\') {\r
-\r
-               // bad escape, ignore\r
-\r
-               if (pgn->token_length >= PGN_STRING_SIZE-1) {\r
-                  my_fatal("pgn_read_token(): string too long at line %d, column %d,game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);\r
-               }\r
-\r
-               pgn->token_string[pgn->token_length++] = '\\';\r
-            }\r
-         }\r
-\r
-         if (pgn->token_length >= PGN_STRING_SIZE-1) {\r
-            my_fatal("pgn_read_token(): string too long at line %d, column %d,game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);\r
-         }\r
-\r
-         pgn->token_string[pgn->token_length++] = pgn->char_hack;\r
-      }\r
-\r
-      ASSERT(pgn->token_length>=0&&pgn->token_length<PGN_STRING_SIZE);\r
-      pgn->token_string[pgn->token_length] = '\0';\r
-\r
-   } else if (pgn->char_hack == '$') {\r
-\r
-      // NAG\r
-\r
-      pgn->token_type = TOKEN_NAG;\r
-      pgn->token_length = 0;\r
-\r
-      while (TRUE) {\r
-\r
-         pgn_char_read(pgn);\r
-\r
-         if (!isdigit(pgn->char_hack)) break;\r
-\r
-         if (pgn->token_length >= 3) {\r
-            my_fatal("pgn_read_token(): NAG too long at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);\r
-         }\r
-\r
-         pgn->token_string[pgn->token_length++] = pgn->char_hack;\r
-      }\r
-\r
-      pgn_char_unread(pgn);\r
-\r
-      if (pgn->token_length == 0) {\r
-         my_fatal("pgn_read_token(): malformed NAG at line %d, column %d,game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);\r
-      }\r
-\r
-      ASSERT(pgn->token_length>0&&pgn->token_length<=3);\r
-      pgn->token_string[pgn->token_length] = '\0';\r
-\r
-   } else {\r
-\r
-      // unknown token\r
-\r
-       my_fatal("lexical error at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);\r
-   }\r
-}\r
-\r
-// pgn_skip_blanks()\r
-\r
-static void pgn_skip_blanks(pgn_t * pgn) {\r
-\r
-   ASSERT(pgn!=NULL);\r
-\r
-   while (TRUE) {\r
-\r
-      pgn_char_read(pgn);\r
-\r
-         if (FALSE) {\r
-         }else if(pgn->char_hack==CHAR_EOF){ break;\r
-      } else if (isspace(pgn->char_hack)) {\r
-\r
-         // skip white space\r
-\r
-      } else if (pgn->char_hack == ';') {\r
-\r
-         // skip comment to EOL\r
-\r
-         do {\r
-\r
-            pgn_char_read(pgn);\r
-\r
-            if (pgn->char_hack == CHAR_EOF) {\r
-               my_fatal("pgn_skip_blanks(): EOF in comment at line %d, column %d,game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);\r
-            }\r
-\r
-         } while (pgn->char_hack != '\n');\r
-\r
-      } else if (pgn->char_hack == '%' && pgn->char_column == 0) {\r
-\r
-         // skip comment to EOL\r
-\r
-         do {\r
-\r
-            pgn_char_read(pgn);\r
-\r
-            if (pgn->char_hack == CHAR_EOF) {\r
-               my_fatal("pgn_skip_blanks(): EOF in comment at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);\r
-            }\r
-\r
-         } while (pgn->char_hack != '\n');\r
-\r
-      } else if (pgn->char_hack == '{') {\r
-\r
-         // skip comment to next '}'\r
-\r
-         do {\r
-\r
-            pgn_char_read(pgn);\r
-\r
-            if (pgn->char_hack == CHAR_EOF) {\r
-               my_fatal("pgn_skip_blanks(): EOF in comment at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);\r
-            }\r
-\r
-         } while (pgn->char_hack != '}');\r
-\r
-      } else { // not a white space\r
-\r
-         break;\r
-      }\r
-   }\r
-}\r
-\r
-// is_symbol_start()\r
-\r
-static bool is_symbol_start(int c) {\r
-\r
-   return strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",c) != NULL;\r
-}\r
-\r
-// is_symbol_next()\r
-\r
-static bool is_symbol_next(int c) {\r
-\r
-   return strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+#=:-/",c) != NULL;\r
-}\r
-\r
-// pgn_char_read()\r
-\r
-static void pgn_char_read(pgn_t * pgn) {\r
-\r
-   ASSERT(pgn!=NULL);\r
-\r
-   // char "stack"\r
-\r
-   if (pgn->char_unread) {\r
-      pgn->char_unread = FALSE;\r
-      return;\r
-   }\r
-\r
-   // consume the current character\r
-\r
-   if (pgn->char_first) {\r
-\r
-      pgn->char_first = FALSE;\r
-\r
-   } else {\r
-\r
-      // update counters\r
-\r
-      ASSERT(pgn->char_hack!=CHAR_EOF);\r
-\r
-      if (FALSE) {\r
-      } else if (pgn->char_hack == '\n') {\r
-         pgn->char_line++;\r
-         pgn->char_column = 0;\r
-      } else if (pgn->char_hack == '\t') {\r
-         pgn->char_column += TAB_SIZE - (pgn->char_column % TAB_SIZE);\r
-      } else {\r
-         pgn->char_column++;\r
-      }\r
-   }\r
-\r
-   // read a new character\r
-\r
-   pgn->char_hack = fgetc(pgn->file);\r
-\r
-   if (pgn->char_hack == EOF) {\r
-      if (ferror(pgn->file)) my_fatal("pgn_char_read(): fgetc(): %s\n",strerror(errno));\r
-      pgn->char_hack = CHAR_EOF;\r
-   }\r
-\r
-   if (DispChar) printf("< L%d C%d '%c' (%02X)\n",pgn->char_line,pgn->char_column,pgn->char_hack,pgn->char_hack);\r
-}\r
-\r
-// pgn_char_unread()\r
-\r
-static void pgn_char_unread(pgn_t * pgn) {\r
-\r
-   ASSERT(pgn!=NULL);\r
-\r
-   ASSERT(!pgn->char_unread);\r
-   ASSERT(!pgn->char_first);\r
-\r
-   pgn->char_unread = TRUE;\r
-}\r
-\r
-// end of pgn.cpp\r
-\r
+
+// pgn.c
+
+// includes
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "pgn.h"
+#include "util.h"
+
+// constants
+
+static const bool DispMove = FALSE;
+static const bool DispToken = FALSE;
+static const bool DispChar = FALSE;
+
+static const int TAB_SIZE = 8;
+
+static const int CHAR_EOF = 256;
+
+// types
+
+enum token_t {
+   TOKEN_ERROR   = -1,
+   TOKEN_EOF     = 256,
+   TOKEN_SYMBOL  = 257,
+   TOKEN_STRING  = 258,
+   TOKEN_INTEGER = 259,
+   TOKEN_NAG     = 260,
+   TOKEN_RESULT  = 261
+};
+
+// prototypes
+
+static void pgn_token_read   (pgn_t * pgn);
+static void pgn_token_unread (pgn_t * pgn);
+
+static void pgn_read_token   (pgn_t * pgn);
+
+static bool is_symbol_start  (int c);
+static bool is_symbol_next   (int c);
+
+static void pgn_skip_blanks  (pgn_t * pgn);
+
+static void pgn_char_read    (pgn_t * pgn);
+static void pgn_char_unread  (pgn_t * pgn);
+
+// functions
+
+// pgn_open()
+
+void pgn_open(pgn_t * pgn, const char file_name[]) {
+
+   ASSERT(pgn!=NULL);
+   ASSERT(file_name!=NULL);
+
+   pgn->file = fopen(file_name,"r");
+   if (pgn->file == NULL) my_fatal("pgn_open(): can't open file \"%s\": %s\n",file_name,strerror(errno));
+
+   pgn->char_hack = CHAR_EOF; // DEBUG
+   pgn->char_line = 1;
+   pgn->char_column = 0;
+   pgn->char_unread = FALSE;
+   pgn->char_first = TRUE;
+
+   pgn->token_type = TOKEN_ERROR; // DEBUG
+   strcpy(pgn->token_string,"?"); // DEBUG
+   pgn->token_length = -1; // DEBUG
+   pgn->token_line = -1; // DEBUG
+   pgn->token_column = -1; // DEBUG
+   pgn->token_unread = FALSE;
+   pgn->token_first = TRUE;
+
+   strcpy(pgn->result,"?"); // DEBUG
+   strcpy(pgn->fen,"?"); // DEBUG
+
+   pgn->move_line = -1; // DEBUG
+   pgn->move_column = -1; // DEBUG
+}
+
+// pgn_close()
+
+void pgn_close(pgn_t * pgn) {
+
+   ASSERT(pgn!=NULL);
+
+   fclose(pgn->file);
+}
+
+// pgn_next_game()
+
+bool pgn_next_game(pgn_t * pgn) {
+
+   char name[PGN_STRING_SIZE];
+   char value[PGN_STRING_SIZE];
+
+   ASSERT(pgn!=NULL);
+
+   // init
+
+   strcpy(pgn->result,"*");
+   strcpy(pgn->fen,"");
+
+   // loop
+
+   while (TRUE) {
+
+      pgn_token_read(pgn);
+
+      if (pgn->token_type != '[') break;
+
+      // tag
+
+      pgn_token_read(pgn);
+      if (pgn->token_type != TOKEN_SYMBOL) {
+         my_fatal("pgn_next_game(): malformed tag at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);
+      }
+      strcpy(name,pgn->token_string);
+
+      pgn_token_read(pgn);
+      if (pgn->token_type != TOKEN_STRING) {
+         my_fatal("pgn_next_game(): malformed tag at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);
+      }
+      strcpy(value,pgn->token_string);
+
+      pgn_token_read(pgn);
+      if (pgn->token_type != ']') {
+         my_fatal("pgn_next_game(): malformed tag at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);
+      }
+
+      // special tag?
+
+      if (FALSE) {
+      } else if (my_string_equal(name,"Result")) {
+         strcpy(pgn->result,value);
+      } else if (my_string_equal(name,"FEN")) {
+         strcpy(pgn->fen,value);
+      }
+   }
+
+   if (pgn->token_type == TOKEN_EOF) return FALSE;
+
+   pgn_token_unread(pgn);
+
+   return TRUE;
+}
+
+// pgn_next_move()
+
+bool pgn_next_move(pgn_t * pgn, char string[], int size) {
+
+   int depth;
+
+   ASSERT(pgn!=NULL);
+   ASSERT(string!=NULL);
+   ASSERT(size>=PGN_STRING_SIZE);
+
+   // init
+
+   pgn->move_line = -1; // DEBUG
+   pgn->move_column = -1; // DEBUG
+
+   // loop
+
+   depth = 0;
+
+   while (TRUE) {
+
+      pgn_token_read(pgn);
+
+      if (FALSE) {
+
+      } else if (pgn->token_type == '(') {
+
+         // open RAV
+
+         depth++;
+
+      } else if (pgn->token_type == ')') {
+
+         // close RAV
+
+         if (depth == 0) {
+            my_fatal("pgn_next_move(): malformed variation at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);
+         }
+
+         depth--;
+         ASSERT(depth>=0);
+
+      } else if (pgn->token_type == TOKEN_RESULT) {
+
+         // game finished
+
+         if (depth > 0) {
+            my_fatal("pgn_next_move(): malformed variation at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);
+         }
+
+         return FALSE;
+
+      } else {
+
+         // skip optional move number
+
+         if (pgn->token_type == TOKEN_INTEGER) {
+            do pgn_token_read(pgn); while (pgn->token_type == '.');
+         }
+
+         // move must be a symbol
+
+         if (pgn->token_type != TOKEN_SYMBOL) {
+            my_fatal("pgn_next_move(): malformed move at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);
+         }
+
+         // store move for later use
+
+         if (depth == 0) {
+
+            if (pgn->token_length >= size) {
+               my_fatal("pgn_next_move(): move too long at line %d, column %d, game %d\n",pgn->token_line,pgn->token_column,pgn->game_nb);
+            }
+
+            strcpy(string,pgn->token_string);
+            pgn->move_line = pgn->token_line;
+            pgn->move_column = pgn->token_column;
+         }
+
+         // skip optional NAGs
+
+         do pgn_token_read(pgn); while (pgn->token_type == TOKEN_NAG);
+         pgn_token_unread(pgn);
+
+         // return move
+
+         if (depth == 0) {
+            if (DispMove) printf("move=\"%s\"\n",string);
+            return TRUE;
+         }
+      }
+   }
+
+   ASSERT(FALSE);
+
+   return FALSE;
+}
+
+// pgn_token_read()
+
+static void pgn_token_read(pgn_t * pgn) {
+
+   ASSERT(pgn!=NULL);
+
+   // token "stack"
+
+   if (pgn->token_unread) {
+      pgn->token_unread = FALSE;
+      return;
+   }
+
+   // consume the current token
+
+   if (pgn->token_first) {
+      pgn->token_first = FALSE;
+   } else {
+      ASSERT(pgn->token_type!=TOKEN_ERROR);
+      ASSERT(pgn->token_type!=TOKEN_EOF);
+   }
+
+   // read a new token
+
+   pgn_read_token(pgn);
+   if (pgn->token_type == TOKEN_ERROR) my_fatal("pgn_token_read(): lexical error at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);
+
+   if (DispToken) printf("< L%d C%d \"%s\" (%03X)\n",pgn->token_line,pgn->token_column,pgn->token_string,pgn->token_type);
+}
+
+// pgn_token_unread()
+
+static void pgn_token_unread(pgn_t * pgn) {
+
+   ASSERT(pgn!=NULL);
+
+   ASSERT(!pgn->token_unread);
+   ASSERT(!pgn->token_first);
+
+   pgn->token_unread = TRUE;
+}
+
+// pgn_read_token()
+
+static void pgn_read_token(pgn_t * pgn) {
+
+   ASSERT(pgn!=NULL);
+
+   // skip white-space characters
+
+   pgn_skip_blanks(pgn);
+
+   // init
+
+   pgn->token_type = TOKEN_ERROR;
+   strcpy(pgn->token_string,"");
+   pgn->token_length = 0;
+   pgn->token_line = pgn->char_line;
+   pgn->token_column = pgn->char_column;
+
+   // determine token type
+
+   if (FALSE) {
+
+   } else if (pgn->char_hack == CHAR_EOF) {
+
+      pgn->token_type = TOKEN_EOF;
+
+   } else if (strchr(".[]()<>",pgn->char_hack) != NULL) {
+
+      // single-character token
+
+      pgn->token_type = pgn->char_hack;
+      sprintf(pgn->token_string,"%c",pgn->char_hack);
+      pgn->token_length = 1;
+
+   } else if (pgn->char_hack == '*') {
+
+      pgn->token_type = TOKEN_RESULT;
+      sprintf(pgn->token_string,"%c",pgn->char_hack);
+      pgn->token_length = 1;
+
+   } else if (pgn->char_hack == '!') {
+
+      pgn_char_read(pgn);
+
+      if (FALSE) {
+
+      } else if (pgn->char_hack == '!') { // "!!"
+
+         pgn->token_type = TOKEN_NAG;
+         strcpy(pgn->token_string,"3");
+         pgn->token_length = 1;
+
+      } else if (pgn->char_hack == '?') { // "!?"
+
+         pgn->token_type = TOKEN_NAG;
+         strcpy(pgn->token_string,"5");
+         pgn->token_length = 1;
+
+      } else { // "!"
+
+         pgn_char_unread(pgn);
+
+         pgn->token_type = TOKEN_NAG;
+         strcpy(pgn->token_string,"1");
+         pgn->token_length = 1;
+      }
+
+   } else if (pgn->char_hack == '?') {
+
+      pgn_char_read(pgn);
+
+      if (FALSE) {
+
+      } else if (pgn->char_hack == '?') { // "??"
+
+         pgn->token_type = TOKEN_NAG;
+         strcpy(pgn->token_string,"4");
+         pgn->token_length = 1;
+
+      } else if (pgn->char_hack == '!') { // "?!"
+
+         pgn->token_type = TOKEN_NAG;
+         strcpy(pgn->token_string,"6");
+         pgn->token_length = 1;
+
+      } else { // "?"
+
+         pgn_char_unread(pgn);
+
+         pgn->token_type = TOKEN_NAG;
+         strcpy(pgn->token_string,"2");
+         pgn->token_length = 1;
+      }
+
+   } else if (is_symbol_start(pgn->char_hack)) {
+
+      // symbol, integer, or result
+
+      pgn->token_type = TOKEN_INTEGER;
+      pgn->token_length = 0;
+
+      do {
+
+         if (pgn->token_length >= PGN_STRING_SIZE-1) {
+            my_fatal("pgn_read_token(): symbol too long at line %d, column %d,game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);
+         }
+
+         if (!isdigit(pgn->char_hack)) pgn->token_type = TOKEN_SYMBOL;
+
+         pgn->token_string[pgn->token_length++] = pgn->char_hack;
+
+         pgn_char_read(pgn);
+
+      } while (is_symbol_next(pgn->char_hack));
+
+      pgn_char_unread(pgn);
+
+      ASSERT(pgn->token_length>0&&pgn->token_length<PGN_STRING_SIZE);
+      pgn->token_string[pgn->token_length] = '\0';
+
+      if (my_string_equal(pgn->token_string,"1-0")
+       || my_string_equal(pgn->token_string,"0-1")
+       || my_string_equal(pgn->token_string,"1/2-1/2")) {
+         pgn->token_type = TOKEN_RESULT;
+      }
+
+   } else if (pgn->char_hack == '"') {
+
+      // string
+
+      pgn->token_type = TOKEN_STRING;
+      pgn->token_length = 0;
+
+      while (TRUE) {
+
+         pgn_char_read(pgn);
+
+         if (pgn->char_hack == CHAR_EOF) {
+            my_fatal("pgn_read_token(): EOF in string at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);
+         }
+
+         if (pgn->char_hack == '"') break;
+
+         if (pgn->char_hack == '\\') {
+
+            pgn_char_read(pgn);
+
+            if (pgn->char_hack == CHAR_EOF) {
+               my_fatal("pgn_read_token(): EOF in string at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);
+            }
+
+            if (pgn->char_hack != '"' && pgn->char_hack != '\\') {
+
+               // bad escape, ignore
+
+               if (pgn->token_length >= PGN_STRING_SIZE-1) {
+                  my_fatal("pgn_read_token(): string too long at line %d, column %d,game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);
+               }
+
+               pgn->token_string[pgn->token_length++] = '\\';
+            }
+         }
+
+         if (pgn->token_length >= PGN_STRING_SIZE-1) {
+            my_fatal("pgn_read_token(): string too long at line %d, column %d,game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);
+         }
+
+         pgn->token_string[pgn->token_length++] = pgn->char_hack;
+      }
+
+      ASSERT(pgn->token_length>=0&&pgn->token_length<PGN_STRING_SIZE);
+      pgn->token_string[pgn->token_length] = '\0';
+
+   } else if (pgn->char_hack == '$') {
+
+      // NAG
+
+      pgn->token_type = TOKEN_NAG;
+      pgn->token_length = 0;
+
+      while (TRUE) {
+
+         pgn_char_read(pgn);
+
+         if (!isdigit(pgn->char_hack)) break;
+
+         if (pgn->token_length >= 3) {
+            my_fatal("pgn_read_token(): NAG too long at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);
+         }
+
+         pgn->token_string[pgn->token_length++] = pgn->char_hack;
+      }
+
+      pgn_char_unread(pgn);
+
+      if (pgn->token_length == 0) {
+         my_fatal("pgn_read_token(): malformed NAG at line %d, column %d,game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);
+      }
+
+      ASSERT(pgn->token_length>0&&pgn->token_length<=3);
+      pgn->token_string[pgn->token_length] = '\0';
+
+   } else {
+
+      // unknown token
+
+       my_fatal("lexical error at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);
+   }
+}
+
+// pgn_skip_blanks()
+
+static void pgn_skip_blanks(pgn_t * pgn) {
+
+   ASSERT(pgn!=NULL);
+
+   while (TRUE) {
+
+      pgn_char_read(pgn);
+
+         if (FALSE) {
+         }else if(pgn->char_hack==CHAR_EOF){ break;
+      } else if (isspace(pgn->char_hack)) {
+
+         // skip white space
+
+      } else if (pgn->char_hack == ';') {
+
+         // skip comment to EOL
+
+         do {
+
+            pgn_char_read(pgn);
+
+            if (pgn->char_hack == CHAR_EOF) {
+               my_fatal("pgn_skip_blanks(): EOF in comment at line %d, column %d,game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);
+            }
+
+         } while (pgn->char_hack != '\n');
+
+      } else if (pgn->char_hack == '%' && pgn->char_column == 0) {
+
+         // skip comment to EOL
+
+         do {
+
+            pgn_char_read(pgn);
+
+            if (pgn->char_hack == CHAR_EOF) {
+               my_fatal("pgn_skip_blanks(): EOF in comment at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);
+            }
+
+         } while (pgn->char_hack != '\n');
+
+      } else if (pgn->char_hack == '{') {
+
+         // skip comment to next '}'
+
+         do {
+
+            pgn_char_read(pgn);
+
+            if (pgn->char_hack == CHAR_EOF) {
+               my_fatal("pgn_skip_blanks(): EOF in comment at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);
+            }
+
+         } while (pgn->char_hack != '}');
+
+      } else { // not a white space
+
+         break;
+      }
+   }
+}
+
+// is_symbol_start()
+
+static bool is_symbol_start(int c) {
+
+   return strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",c) != NULL;
+}
+
+// is_symbol_next()
+
+static bool is_symbol_next(int c) {
+
+   return strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+#=:-/",c) != NULL;
+}
+
+// pgn_char_read()
+
+static void pgn_char_read(pgn_t * pgn) {
+
+   ASSERT(pgn!=NULL);
+
+   // char "stack"
+
+   if (pgn->char_unread) {
+      pgn->char_unread = FALSE;
+      return;
+   }
+
+   // consume the current character
+
+   if (pgn->char_first) {
+
+      pgn->char_first = FALSE;
+
+   } else {
+
+      // update counters
+
+      ASSERT(pgn->char_hack!=CHAR_EOF);
+
+      if (FALSE) {
+      } else if (pgn->char_hack == '\n') {
+         pgn->char_line++;
+         pgn->char_column = 0;
+      } else if (pgn->char_hack == '\t') {
+         pgn->char_column += TAB_SIZE - (pgn->char_column % TAB_SIZE);
+      } else {
+         pgn->char_column++;
+      }
+   }
+
+   // read a new character
+
+   pgn->char_hack = fgetc(pgn->file);
+
+   if (pgn->char_hack == EOF) {
+      if (ferror(pgn->file)) my_fatal("pgn_char_read(): fgetc(): %s\n",strerror(errno));
+      pgn->char_hack = CHAR_EOF;
+   }
+
+   if (DispChar) printf("< L%d C%d '%c' (%02X)\n",pgn->char_line,pgn->char_column,pgn->char_hack,pgn->char_hack);
+}
+
+// pgn_char_unread()
+
+static void pgn_char_unread(pgn_t * pgn) {
+
+   ASSERT(pgn!=NULL);
+
+   ASSERT(!pgn->char_unread);
+   ASSERT(!pgn->char_first);
+
+   pgn->char_unread = TRUE;
+}
+
+// end of pgn.cpp
+