Add forgotten files 1.4.70b
[polyglot.git] / pgn.cpp
1 \r
2 // pgn.cpp\r
3 \r
4 // includes\r
5 \r
6 #include <cctype>\r
7 #include <cerrno>\r
8 #include <cstdio>\r
9 #include <cstring>\r
10 \r
11 #include "pgn.h"\r
12 #include "util.h"\r
13 \r
14 // constants\r
15 \r
16 static const bool DispMove = false;\r
17 static const bool DispToken = false;\r
18 static const bool DispChar = false;\r
19 \r
20 static const int TAB_SIZE = 8;\r
21 \r
22 static const int CHAR_EOF = 256;\r
23 \r
24 // types\r
25 \r
26 enum token_t {\r
27    TOKEN_ERROR   = -1,\r
28    TOKEN_EOF     = 256,\r
29    TOKEN_SYMBOL  = 257,\r
30    TOKEN_STRING  = 258,\r
31    TOKEN_INTEGER = 259,\r
32    TOKEN_NAG     = 260,\r
33    TOKEN_RESULT  = 261\r
34 };\r
35 \r
36 // prototypes\r
37 \r
38 static void pgn_token_read   (pgn_t * pgn);\r
39 static void pgn_token_unread (pgn_t * pgn);\r
40 \r
41 static void pgn_read_token   (pgn_t * pgn);\r
42 \r
43 static bool is_symbol_start  (int c);\r
44 static bool is_symbol_next   (int c);\r
45 \r
46 static void pgn_skip_blanks  (pgn_t * pgn);\r
47 \r
48 static void pgn_char_read    (pgn_t * pgn);\r
49 static void pgn_char_unread  (pgn_t * pgn);\r
50 \r
51 // functions\r
52 \r
53 // pgn_open()\r
54 \r
55 void pgn_open(pgn_t * pgn, const char file_name[]) {\r
56 \r
57    ASSERT(pgn!=NULL);\r
58    ASSERT(file_name!=NULL);\r
59 \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
62 \r
63    pgn->char_hack = CHAR_EOF; // DEBUG\r
64    pgn->char_line = 1;\r
65    pgn->char_column = 0;\r
66    pgn->char_unread = false;\r
67    pgn->char_first = true;\r
68 \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
76 \r
77    strcpy(pgn->result,"?"); // DEBUG\r
78    strcpy(pgn->fen,"?"); // DEBUG\r
79 \r
80    pgn->move_line = -1; // DEBUG\r
81    pgn->move_column = -1; // DEBUG\r
82 }\r
83 \r
84 // pgn_close()\r
85 \r
86 void pgn_close(pgn_t * pgn) {\r
87 \r
88    ASSERT(pgn!=NULL);\r
89 \r
90    fclose(pgn->file);\r
91 }\r
92 \r
93 // pgn_next_game()\r
94 \r
95 bool pgn_next_game(pgn_t * pgn) {\r
96 \r
97    char name[PGN_STRING_SIZE];\r
98    char value[PGN_STRING_SIZE];\r
99 \r
100    ASSERT(pgn!=NULL);\r
101 \r
102    // init\r
103 \r
104    strcpy(pgn->result,"*");\r
105    strcpy(pgn->fen,"");\r
106 \r
107    // loop\r
108 \r
109    while (true) {\r
110 \r
111       pgn_token_read(pgn);\r
112 \r
113       if (pgn->token_type != '[') break;\r
114 \r
115       // tag\r
116 \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
120       }\r
121       strcpy(name,pgn->token_string);\r
122 \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
126       }\r
127       strcpy(value,pgn->token_string);\r
128 \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
132       }\r
133 \r
134       // special tag?\r
135 \r
136       if (false) {\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
141       }\r
142    }\r
143 \r
144    if (pgn->token_type == TOKEN_EOF) return false;\r
145 \r
146    pgn_token_unread(pgn);\r
147 \r
148    return true;\r
149 }\r
150 \r
151 // pgn_next_move()\r
152 \r
153 bool pgn_next_move(pgn_t * pgn, char string[], int size) {\r
154 \r
155    int depth;\r
156 \r
157    ASSERT(pgn!=NULL);\r
158    ASSERT(string!=NULL);\r
159    ASSERT(size>=PGN_STRING_SIZE);\r
160 \r
161    // init\r
162 \r
163    pgn->move_line = -1; // DEBUG\r
164    pgn->move_column = -1; // DEBUG\r
165 \r
166    // loop\r
167 \r
168    depth = 0;\r
169 \r
170    while (true) {\r
171 \r
172       pgn_token_read(pgn);\r
173 \r
174       if (false) {\r
175 \r
176       } else if (pgn->token_type == '(') {\r
177 \r
178          // open RAV\r
179 \r
180          depth++;\r
181 \r
182       } else if (pgn->token_type == ')') {\r
183 \r
184          // close RAV\r
185 \r
186          if (depth == 0) {\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
188          }\r
189 \r
190          depth--;\r
191          ASSERT(depth>=0);\r
192 \r
193       } else if (pgn->token_type == TOKEN_RESULT) {\r
194 \r
195          // game finished\r
196 \r
197          if (depth > 0) {\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
199          }\r
200 \r
201          return false;\r
202 \r
203       } else {\r
204 \r
205          // skip optional move number\r
206 \r
207          if (pgn->token_type == TOKEN_INTEGER) {\r
208             do pgn_token_read(pgn); while (pgn->token_type == '.');\r
209          }\r
210 \r
211          // move must be a symbol\r
212 \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
215          }\r
216 \r
217          // store move for later use\r
218 \r
219          if (depth == 0) {\r
220 \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
223             }\r
224 \r
225             strcpy(string,pgn->token_string);\r
226             pgn->move_line = pgn->token_line;\r
227             pgn->move_column = pgn->token_column;\r
228          }\r
229 \r
230          // skip optional NAGs\r
231 \r
232          do pgn_token_read(pgn); while (pgn->token_type == TOKEN_NAG);\r
233          pgn_token_unread(pgn);\r
234 \r
235          // return move\r
236 \r
237          if (depth == 0) {\r
238             if (DispMove) printf("move=\"%s\"\n",string);\r
239             return true;\r
240          }\r
241       }\r
242    }\r
243 \r
244    ASSERT(false);\r
245 \r
246    return false;\r
247 }\r
248 \r
249 // pgn_token_read()\r
250 \r
251 static void pgn_token_read(pgn_t * pgn) {\r
252 \r
253    ASSERT(pgn!=NULL);\r
254 \r
255    // token "stack"\r
256 \r
257    if (pgn->token_unread) {\r
258       pgn->token_unread = false;\r
259       return;\r
260    }\r
261 \r
262    // consume the current token\r
263 \r
264    if (pgn->token_first) {\r
265       pgn->token_first = false;\r
266    } else {\r
267       ASSERT(pgn->token_type!=TOKEN_ERROR);\r
268       ASSERT(pgn->token_type!=TOKEN_EOF);\r
269    }\r
270 \r
271    // read a new token\r
272 \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
275 \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
277 }\r
278 \r
279 // pgn_token_unread()\r
280 \r
281 static void pgn_token_unread(pgn_t * pgn) {\r
282 \r
283    ASSERT(pgn!=NULL);\r
284 \r
285    ASSERT(!pgn->token_unread);\r
286    ASSERT(!pgn->token_first);\r
287 \r
288    pgn->token_unread = true;\r
289 }\r
290 \r
291 // pgn_read_token()\r
292 \r
293 static void pgn_read_token(pgn_t * pgn) {\r
294 \r
295    ASSERT(pgn!=NULL);\r
296 \r
297    // skip white-space characters\r
298 \r
299    pgn_skip_blanks(pgn);\r
300 \r
301    // init\r
302 \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
308 \r
309    // determine token type\r
310 \r
311    if (false) {\r
312 \r
313    } else if (pgn->char_hack == CHAR_EOF) {\r
314 \r
315       pgn->token_type = TOKEN_EOF;\r
316 \r
317    } else if (strchr(".[]()<>",pgn->char_hack) != NULL) {\r
318 \r
319       // single-character token\r
320 \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
324 \r
325    } else if (pgn->char_hack == '*') {\r
326 \r
327       pgn->token_type = TOKEN_RESULT;\r
328       sprintf(pgn->token_string,"%c",pgn->char_hack);\r
329       pgn->token_length = 1;\r
330 \r
331    } else if (pgn->char_hack == '!') {\r
332 \r
333       pgn_char_read(pgn);\r
334 \r
335       if (false) {\r
336 \r
337       } else if (pgn->char_hack == '!') { // "!!"\r
338 \r
339          pgn->token_type = TOKEN_NAG;\r
340          strcpy(pgn->token_string,"3");\r
341          pgn->token_length = 1;\r
342 \r
343       } else if (pgn->char_hack == '?') { // "!?"\r
344 \r
345          pgn->token_type = TOKEN_NAG;\r
346          strcpy(pgn->token_string,"5");\r
347          pgn->token_length = 1;\r
348 \r
349       } else { // "!"\r
350 \r
351          pgn_char_unread(pgn);\r
352 \r
353          pgn->token_type = TOKEN_NAG;\r
354          strcpy(pgn->token_string,"1");\r
355          pgn->token_length = 1;\r
356       }\r
357 \r
358    } else if (pgn->char_hack == '?') {\r
359 \r
360       pgn_char_read(pgn);\r
361 \r
362       if (false) {\r
363 \r
364       } else if (pgn->char_hack == '?') { // "??"\r
365 \r
366          pgn->token_type = TOKEN_NAG;\r
367          strcpy(pgn->token_string,"4");\r
368          pgn->token_length = 1;\r
369 \r
370       } else if (pgn->char_hack == '!') { // "?!"\r
371 \r
372          pgn->token_type = TOKEN_NAG;\r
373          strcpy(pgn->token_string,"6");\r
374          pgn->token_length = 1;\r
375 \r
376       } else { // "?"\r
377 \r
378          pgn_char_unread(pgn);\r
379 \r
380          pgn->token_type = TOKEN_NAG;\r
381          strcpy(pgn->token_string,"2");\r
382          pgn->token_length = 1;\r
383       }\r
384 \r
385    } else if (is_symbol_start(pgn->char_hack)) {\r
386 \r
387       // symbol, integer, or result\r
388 \r
389       pgn->token_type = TOKEN_INTEGER;\r
390       pgn->token_length = 0;\r
391 \r
392       do {\r
393 \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
396          }\r
397 \r
398          if (!isdigit(pgn->char_hack)) pgn->token_type = TOKEN_SYMBOL;\r
399 \r
400          pgn->token_string[pgn->token_length++] = pgn->char_hack;\r
401 \r
402          pgn_char_read(pgn);\r
403 \r
404       } while (is_symbol_next(pgn->char_hack));\r
405 \r
406       pgn_char_unread(pgn);\r
407 \r
408       ASSERT(pgn->token_length>0&&pgn->token_length<PGN_STRING_SIZE);\r
409       pgn->token_string[pgn->token_length] = '\0';\r
410 \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
415       }\r
416 \r
417    } else if (pgn->char_hack == '"') {\r
418 \r
419       // string\r
420 \r
421       pgn->token_type = TOKEN_STRING;\r
422       pgn->token_length = 0;\r
423 \r
424       while (true) {\r
425 \r
426          pgn_char_read(pgn);\r
427 \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
430          }\r
431 \r
432          if (pgn->char_hack == '"') break;\r
433 \r
434          if (pgn->char_hack == '\\') {\r
435 \r
436             pgn_char_read(pgn);\r
437 \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
440             }\r
441 \r
442             if (pgn->char_hack != '"' && pgn->char_hack != '\\') {\r
443 \r
444                // bad escape, ignore\r
445 \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
448                }\r
449 \r
450                pgn->token_string[pgn->token_length++] = '\\';\r
451             }\r
452          }\r
453 \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
456          }\r
457 \r
458          pgn->token_string[pgn->token_length++] = pgn->char_hack;\r
459       }\r
460 \r
461       ASSERT(pgn->token_length>=0&&pgn->token_length<PGN_STRING_SIZE);\r
462       pgn->token_string[pgn->token_length] = '\0';\r
463 \r
464    } else if (pgn->char_hack == '$') {\r
465 \r
466       // NAG\r
467 \r
468       pgn->token_type = TOKEN_NAG;\r
469       pgn->token_length = 0;\r
470 \r
471       while (true) {\r
472 \r
473          pgn_char_read(pgn);\r
474 \r
475          if (!isdigit(pgn->char_hack)) break;\r
476 \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
479          }\r
480 \r
481          pgn->token_string[pgn->token_length++] = pgn->char_hack;\r
482       }\r
483 \r
484       pgn_char_unread(pgn);\r
485 \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
488       }\r
489 \r
490       ASSERT(pgn->token_length>0&&pgn->token_length<=3);\r
491       pgn->token_string[pgn->token_length] = '\0';\r
492 \r
493    } else {\r
494 \r
495       // unknown token\r
496 \r
497        my_fatal("lexical error at line %d, column %d, game %d\n",pgn->char_line,pgn->char_column,pgn->game_nb);\r
498    }\r
499 }\r
500 \r
501 // pgn_skip_blanks()\r
502 \r
503 static void pgn_skip_blanks(pgn_t * pgn) {\r
504 \r
505    ASSERT(pgn!=NULL);\r
506 \r
507    while (true) {\r
508 \r
509       pgn_char_read(pgn);\r
510 \r
511           if (false) {\r
512           }else if(pgn->char_hack==CHAR_EOF){ break;\r
513       } else if (isspace(pgn->char_hack)) {\r
514 \r
515          // skip white space\r
516 \r
517       } else if (pgn->char_hack == ';') {\r
518 \r
519          // skip comment to EOL\r
520 \r
521          do {\r
522 \r
523             pgn_char_read(pgn);\r
524 \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
527             }\r
528 \r
529          } while (pgn->char_hack != '\n');\r
530 \r
531       } else if (pgn->char_hack == '%' && pgn->char_column == 0) {\r
532 \r
533          // skip comment to EOL\r
534 \r
535          do {\r
536 \r
537             pgn_char_read(pgn);\r
538 \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
541             }\r
542 \r
543          } while (pgn->char_hack != '\n');\r
544 \r
545       } else if (pgn->char_hack == '{') {\r
546 \r
547          // skip comment to next '}'\r
548 \r
549          do {\r
550 \r
551             pgn_char_read(pgn);\r
552 \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
555             }\r
556 \r
557          } while (pgn->char_hack != '}');\r
558 \r
559       } else { // not a white space\r
560 \r
561          break;\r
562       }\r
563    }\r
564 }\r
565 \r
566 // is_symbol_start()\r
567 \r
568 static bool is_symbol_start(int c) {\r
569 \r
570    return strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",c) != NULL;\r
571 }\r
572 \r
573 // is_symbol_next()\r
574 \r
575 static bool is_symbol_next(int c) {\r
576 \r
577    return strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+#=:-/",c) != NULL;\r
578 }\r
579 \r
580 // pgn_char_read()\r
581 \r
582 static void pgn_char_read(pgn_t * pgn) {\r
583 \r
584    ASSERT(pgn!=NULL);\r
585 \r
586    // char "stack"\r
587 \r
588    if (pgn->char_unread) {\r
589       pgn->char_unread = false;\r
590       return;\r
591    }\r
592 \r
593    // consume the current character\r
594 \r
595    if (pgn->char_first) {\r
596 \r
597       pgn->char_first = false;\r
598 \r
599    } else {\r
600 \r
601       // update counters\r
602 \r
603       ASSERT(pgn->char_hack!=CHAR_EOF);\r
604 \r
605       if (false) {\r
606       } else if (pgn->char_hack == '\n') {\r
607          pgn->char_line++;\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
611       } else {\r
612          pgn->char_column++;\r
613       }\r
614    }\r
615 \r
616    // read a new character\r
617 \r
618    pgn->char_hack = fgetc(pgn->file);\r
619 \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
623    }\r
624 \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
626 }\r
627 \r
628 // pgn_char_unread()\r
629 \r
630 static void pgn_char_unread(pgn_t * pgn) {\r
631 \r
632    ASSERT(pgn!=NULL);\r
633 \r
634    ASSERT(!pgn->char_unread);\r
635    ASSERT(!pgn->char_first);\r
636 \r
637    pgn->char_unread = true;\r
638 }\r
639 \r
640 // end of pgn.cpp\r
641 \r