-\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
+