--- /dev/null
+\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