16 static const bool DispMove = FALSE;
\r
17 static const bool DispToken = FALSE;
\r
18 static const bool DispChar = FALSE;
\r
20 static const int TAB_SIZE = 8;
\r
22 static const int CHAR_EOF = 256;
\r
31 TOKEN_INTEGER = 259,
\r
38 static void pgn_token_read (pgn_t * pgn);
\r
39 static void pgn_token_unread (pgn_t * pgn);
\r
41 static void pgn_read_token (pgn_t * pgn);
\r
43 static bool is_symbol_start (int c);
\r
44 static bool is_symbol_next (int c);
\r
46 static void pgn_skip_blanks (pgn_t * pgn);
\r
48 static void pgn_char_read (pgn_t * pgn);
\r
49 static void pgn_char_unread (pgn_t * pgn);
\r
55 void pgn_open(pgn_t * pgn, const char file_name[]) {
\r
58 ASSERT(file_name!=NULL);
\r
60 pgn->file = fopen(file_name,"r");
\r
61 if (pgn->file == NULL) my_fatal("pgn_open(): can't open file \"%s\": %s\n",file_name,strerror(errno));
\r
63 pgn->char_hack = CHAR_EOF; // DEBUG
\r
65 pgn->char_column = 0;
\r
66 pgn->char_unread = FALSE;
\r
67 pgn->char_first = TRUE;
\r
69 pgn->token_type = TOKEN_ERROR; // DEBUG
\r
70 strcpy(pgn->token_string,"?"); // DEBUG
\r
71 pgn->token_length = -1; // DEBUG
\r
72 pgn->token_line = -1; // DEBUG
\r
73 pgn->token_column = -1; // DEBUG
\r
74 pgn->token_unread = FALSE;
\r
75 pgn->token_first = TRUE;
\r
77 strcpy(pgn->result,"?"); // DEBUG
\r
78 strcpy(pgn->fen,"?"); // DEBUG
\r
80 pgn->move_line = -1; // DEBUG
\r
81 pgn->move_column = -1; // DEBUG
\r
86 void pgn_close(pgn_t * pgn) {
\r
95 bool pgn_next_game(pgn_t * pgn) {
\r
97 char name[PGN_STRING_SIZE];
\r
98 char value[PGN_STRING_SIZE];
\r
104 strcpy(pgn->result,"*");
\r
105 strcpy(pgn->fen,"");
\r
111 pgn_token_read(pgn);
\r
113 if (pgn->token_type != '[') break;
\r
117 pgn_token_read(pgn);
\r
118 if (pgn->token_type != TOKEN_SYMBOL) {
\r
119 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
121 strcpy(name,pgn->token_string);
\r
123 pgn_token_read(pgn);
\r
124 if (pgn->token_type != TOKEN_STRING) {
\r
125 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
127 strcpy(value,pgn->token_string);
\r
129 pgn_token_read(pgn);
\r
130 if (pgn->token_type != ']') {
\r
131 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
137 } else if (my_string_equal(name,"Result")) {
\r
138 strcpy(pgn->result,value);
\r
139 } else if (my_string_equal(name,"FEN")) {
\r
140 strcpy(pgn->fen,value);
\r
144 if (pgn->token_type == TOKEN_EOF) return FALSE;
\r
146 pgn_token_unread(pgn);
\r
153 bool pgn_next_move(pgn_t * pgn, char string[], int size) {
\r
158 ASSERT(string!=NULL);
\r
159 ASSERT(size>=PGN_STRING_SIZE);
\r
163 pgn->move_line = -1; // DEBUG
\r
164 pgn->move_column = -1; // DEBUG
\r
172 pgn_token_read(pgn);
\r
176 } else if (pgn->token_type == '(') {
\r
182 } else if (pgn->token_type == ')') {
\r
187 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
193 } else if (pgn->token_type == TOKEN_RESULT) {
\r
198 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
205 // skip optional move number
\r
207 if (pgn->token_type == TOKEN_INTEGER) {
\r
208 do pgn_token_read(pgn); while (pgn->token_type == '.');
\r
211 // move must be a symbol
\r
213 if (pgn->token_type != TOKEN_SYMBOL) {
\r
214 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
217 // store move for later use
\r
221 if (pgn->token_length >= size) {
\r
222 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
225 strcpy(string,pgn->token_string);
\r
226 pgn->move_line = pgn->token_line;
\r
227 pgn->move_column = pgn->token_column;
\r
230 // skip optional NAGs
\r
232 do pgn_token_read(pgn); while (pgn->token_type == TOKEN_NAG);
\r
233 pgn_token_unread(pgn);
\r
238 if (DispMove) printf("move=\"%s\"\n",string);
\r
249 // pgn_token_read()
\r
251 static void pgn_token_read(pgn_t * pgn) {
\r
257 if (pgn->token_unread) {
\r
258 pgn->token_unread = FALSE;
\r
262 // consume the current token
\r
264 if (pgn->token_first) {
\r
265 pgn->token_first = FALSE;
\r
267 ASSERT(pgn->token_type!=TOKEN_ERROR);
\r
268 ASSERT(pgn->token_type!=TOKEN_EOF);
\r
271 // read a new token
\r
273 pgn_read_token(pgn);
\r
274 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
276 if (DispToken) printf("< L%d C%d \"%s\" (%03X)\n",pgn->token_line,pgn->token_column,pgn->token_string,pgn->token_type);
\r
279 // pgn_token_unread()
\r
281 static void pgn_token_unread(pgn_t * pgn) {
\r
285 ASSERT(!pgn->token_unread);
\r
286 ASSERT(!pgn->token_first);
\r
288 pgn->token_unread = TRUE;
\r
291 // pgn_read_token()
\r
293 static void pgn_read_token(pgn_t * pgn) {
\r
297 // skip white-space characters
\r
299 pgn_skip_blanks(pgn);
\r
303 pgn->token_type = TOKEN_ERROR;
\r
304 strcpy(pgn->token_string,"");
\r
305 pgn->token_length = 0;
\r
306 pgn->token_line = pgn->char_line;
\r
307 pgn->token_column = pgn->char_column;
\r
309 // determine token type
\r
313 } else if (pgn->char_hack == CHAR_EOF) {
\r
315 pgn->token_type = TOKEN_EOF;
\r
317 } else if (strchr(".[]()<>",pgn->char_hack) != NULL) {
\r
319 // single-character token
\r
321 pgn->token_type = pgn->char_hack;
\r
322 sprintf(pgn->token_string,"%c",pgn->char_hack);
\r
323 pgn->token_length = 1;
\r
325 } else if (pgn->char_hack == '*') {
\r
327 pgn->token_type = TOKEN_RESULT;
\r
328 sprintf(pgn->token_string,"%c",pgn->char_hack);
\r
329 pgn->token_length = 1;
\r
331 } else if (pgn->char_hack == '!') {
\r
333 pgn_char_read(pgn);
\r
337 } else if (pgn->char_hack == '!') { // "!!"
\r
339 pgn->token_type = TOKEN_NAG;
\r
340 strcpy(pgn->token_string,"3");
\r
341 pgn->token_length = 1;
\r
343 } else if (pgn->char_hack == '?') { // "!?"
\r
345 pgn->token_type = TOKEN_NAG;
\r
346 strcpy(pgn->token_string,"5");
\r
347 pgn->token_length = 1;
\r
351 pgn_char_unread(pgn);
\r
353 pgn->token_type = TOKEN_NAG;
\r
354 strcpy(pgn->token_string,"1");
\r
355 pgn->token_length = 1;
\r
358 } else if (pgn->char_hack == '?') {
\r
360 pgn_char_read(pgn);
\r
364 } else if (pgn->char_hack == '?') { // "??"
\r
366 pgn->token_type = TOKEN_NAG;
\r
367 strcpy(pgn->token_string,"4");
\r
368 pgn->token_length = 1;
\r
370 } else if (pgn->char_hack == '!') { // "?!"
\r
372 pgn->token_type = TOKEN_NAG;
\r
373 strcpy(pgn->token_string,"6");
\r
374 pgn->token_length = 1;
\r
378 pgn_char_unread(pgn);
\r
380 pgn->token_type = TOKEN_NAG;
\r
381 strcpy(pgn->token_string,"2");
\r
382 pgn->token_length = 1;
\r
385 } else if (is_symbol_start(pgn->char_hack)) {
\r
387 // symbol, integer, or result
\r
389 pgn->token_type = TOKEN_INTEGER;
\r
390 pgn->token_length = 0;
\r
394 if (pgn->token_length >= PGN_STRING_SIZE-1) {
\r
395 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
398 if (!isdigit(pgn->char_hack)) pgn->token_type = TOKEN_SYMBOL;
\r
400 pgn->token_string[pgn->token_length++] = pgn->char_hack;
\r
402 pgn_char_read(pgn);
\r
404 } while (is_symbol_next(pgn->char_hack));
\r
406 pgn_char_unread(pgn);
\r
408 ASSERT(pgn->token_length>0&&pgn->token_length<PGN_STRING_SIZE);
\r
409 pgn->token_string[pgn->token_length] = '\0';
\r
411 if (my_string_equal(pgn->token_string,"1-0")
\r
412 || my_string_equal(pgn->token_string,"0-1")
\r
413 || my_string_equal(pgn->token_string,"1/2-1/2")) {
\r
414 pgn->token_type = TOKEN_RESULT;
\r
417 } else if (pgn->char_hack == '"') {
\r
421 pgn->token_type = TOKEN_STRING;
\r
422 pgn->token_length = 0;
\r
426 pgn_char_read(pgn);
\r
428 if (pgn->char_hack == CHAR_EOF) {
\r
429 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
432 if (pgn->char_hack == '"') break;
\r
434 if (pgn->char_hack == '\\') {
\r
436 pgn_char_read(pgn);
\r
438 if (pgn->char_hack == CHAR_EOF) {
\r
439 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
442 if (pgn->char_hack != '"' && pgn->char_hack != '\\') {
\r
444 // bad escape, ignore
\r
446 if (pgn->token_length >= PGN_STRING_SIZE-1) {
\r
447 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
450 pgn->token_string[pgn->token_length++] = '\\';
\r
454 if (pgn->token_length >= PGN_STRING_SIZE-1) {
\r
455 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
458 pgn->token_string[pgn->token_length++] = pgn->char_hack;
\r
461 ASSERT(pgn->token_length>=0&&pgn->token_length<PGN_STRING_SIZE);
\r
462 pgn->token_string[pgn->token_length] = '\0';
\r
464 } else if (pgn->char_hack == '$') {
\r
468 pgn->token_type = TOKEN_NAG;
\r
469 pgn->token_length = 0;
\r
473 pgn_char_read(pgn);
\r
475 if (!isdigit(pgn->char_hack)) break;
\r
477 if (pgn->token_length >= 3) {
\r
478 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
481 pgn->token_string[pgn->token_length++] = pgn->char_hack;
\r
484 pgn_char_unread(pgn);
\r
486 if (pgn->token_length == 0) {
\r
487 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
490 ASSERT(pgn->token_length>0&&pgn->token_length<=3);
\r
491 pgn->token_string[pgn->token_length] = '\0';
\r
497 my_fatal("lexical error at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);
\r
501 // pgn_skip_blanks()
\r
503 static void pgn_skip_blanks(pgn_t * pgn) {
\r
509 pgn_char_read(pgn);
\r
512 }else if(pgn->char_hack==CHAR_EOF){ break;
\r
513 } else if (isspace(pgn->char_hack)) {
\r
515 // skip white space
\r
517 } else if (pgn->char_hack == ';') {
\r
519 // skip comment to EOL
\r
523 pgn_char_read(pgn);
\r
525 if (pgn->char_hack == CHAR_EOF) {
\r
526 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
529 } while (pgn->char_hack != '\n');
\r
531 } else if (pgn->char_hack == '%' && pgn->char_column == 0) {
\r
533 // skip comment to EOL
\r
537 pgn_char_read(pgn);
\r
539 if (pgn->char_hack == CHAR_EOF) {
\r
540 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
543 } while (pgn->char_hack != '\n');
\r
545 } else if (pgn->char_hack == '{') {
\r
547 // skip comment to next '}'
\r
551 pgn_char_read(pgn);
\r
553 if (pgn->char_hack == CHAR_EOF) {
\r
554 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
557 } while (pgn->char_hack != '}');
\r
559 } else { // not a white space
\r
566 // is_symbol_start()
\r
568 static bool is_symbol_start(int c) {
\r
570 return strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",c) != NULL;
\r
573 // is_symbol_next()
\r
575 static bool is_symbol_next(int c) {
\r
577 return strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+#=:-/",c) != NULL;
\r
582 static void pgn_char_read(pgn_t * pgn) {
\r
588 if (pgn->char_unread) {
\r
589 pgn->char_unread = FALSE;
\r
593 // consume the current character
\r
595 if (pgn->char_first) {
\r
597 pgn->char_first = FALSE;
\r
603 ASSERT(pgn->char_hack!=CHAR_EOF);
\r
606 } else if (pgn->char_hack == '\n') {
\r
608 pgn->char_column = 0;
\r
609 } else if (pgn->char_hack == '\t') {
\r
610 pgn->char_column += TAB_SIZE - (pgn->char_column % TAB_SIZE);
\r
612 pgn->char_column++;
\r
616 // read a new character
\r
618 pgn->char_hack = fgetc(pgn->file);
\r
620 if (pgn->char_hack == EOF) {
\r
621 if (ferror(pgn->file)) my_fatal("pgn_char_read(): fgetc(): %s\n",strerror(errno));
\r
622 pgn->char_hack = CHAR_EOF;
\r
625 if (DispChar) printf("< L%d C%d '%c' (%02X)\n",pgn->char_line,pgn->char_column,pgn->char_hack,pgn->char_hack);
\r
628 // pgn_char_unread()
\r
630 static void pgn_char_unread(pgn_t * pgn) {
\r
634 ASSERT(!pgn->char_unread);
\r
635 ASSERT(!pgn->char_first);
\r
637 pgn->char_unread = TRUE;
\r