version 1.4.30b
[polyglot.git] / pgn.c
diff --git a/pgn.c b/pgn.c
new file mode 100644 (file)
index 0000000..3de7483
--- /dev/null
+++ b/pgn.c
@@ -0,0 +1,641 @@
+\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