]> git.sesse.net Git - pgn-extract/blob - grammar.c
Store the current file number (with offset) in the binary output.
[pgn-extract] / grammar.c
1 /*
2  *  Program: pgn-extract: a Portable Game Notation (PGN) extractor.
3  *  Copyright (C) 1994-2014 David Barnes
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 1, or (at your option)
7  *  any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  *  David Barnes may be contacted as D.J.Barnes@kent.ac.uk
19  *  http://www.cs.kent.ac.uk/people/staff/djb/
20  *
21  */
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <ctype.h>
26 #include "bool.h"
27 #include "mymalloc.h"
28 #include "defs.h"
29 #include "typedef.h"
30 #include "tokens.h"
31 #include "taglist.h"
32 #include "lex.h"
33 #include "moves.h"
34 #include "map.h"
35 #include "lists.h"
36 #include "apply.h"
37 #include "output.h"
38 #include "eco.h"
39 #include "end.h"
40 #include "grammar.h"
41 #include "hashing.h"
42
43 static TokenType current_symbol = NO_TOKEN;
44
45 /* Keep track of which RAV level we are at.
46  * This is used to check whether a TERMINATING_RESULT is the final one
47  * and whether NULL_MOVEs are allowed.
48  */
49 static unsigned RAV_level = 0;
50
51 /* At what file position the current game started. */
52 static long game_start_position = -1;
53
54 /* Retain details of the header of a game.
55  * This comprises the Tags and any comment prefixing the
56  * moves of the game.
57  */
58 static struct{
59     /* The tag values. */
60     char **Tags;
61     unsigned header_tags_length;
62     CommentList *prefix_comment;
63 } GameHeader;
64
65 static void ParseOptGameList(SourceFileType file_type);
66 static Boolean ParseGame(Move **returned_move_list);
67 Boolean ParseOptTagList(void);
68 Boolean ParseTag(void);
69 static Move *ParseMoveList(void);
70 static Move *ParseMoveAndVariants(void);
71 static Move *ParseMove(void);
72 static Move *ParseMoveUnit(void);
73 static CommentList *ParseOptCommentList(void);
74 Boolean ParseOptMoveNumber(void);
75 static StringList *ParseOptNAGList(void);
76 static Variation *ParseOptVariantList(void);
77 static Variation *ParseVariant(void);
78 static char *ParseResult(void);
79
80 static void setup_for_new_game(void);
81 void free_tags(void);
82 static void check_result(char **Tags,const char *terminating_result);
83 static void free_comment_list(CommentList *comment_list);
84 static void DealWithEcoLine(Move *move_list);
85 static void DealWithGame(Move *move_list);
86 static Boolean finished_processing(SourceFileType file_type);
87
88     /* Initialise the game header structure to contain
89      * space for the default number of tags.
90      * The space will have to be increased if new tags are
91      * identified in the program source.
92      */
93 void
94 init_game_header(void)
95 {
96     unsigned i;
97     GameHeader.header_tags_length = ORIGINAL_NUMBER_OF_TAGS;
98     GameHeader.Tags = (char **) MallocOrDie(GameHeader.header_tags_length*
99                     sizeof(*GameHeader.Tags));
100     for(i = 0; i < GameHeader.header_tags_length; i++){
101         GameHeader.Tags[i] = (char *) NULL;
102     }
103 }
104
105 void
106 increase_game_header_tags_length(unsigned new_length)
107 {   unsigned i;
108
109     if(new_length <= GameHeader.header_tags_length){
110         fprintf(GlobalState.logfile,
111                 "Internal error: inappropriate length %d ",new_length);
112         fprintf(GlobalState.logfile,
113                 " passed to increase_game_header_tags().\n");
114         exit(1);
115     }
116     GameHeader.Tags = (char **) ReallocOrDie((void *) GameHeader.Tags,
117                                       new_length*sizeof(*GameHeader.Tags));
118     for(i = GameHeader.header_tags_length; i < new_length; i++){
119         GameHeader.Tags[i] = NULL;
120     }
121     GameHeader.header_tags_length = new_length;
122 }
123
124         /* Try to open the given file. Error and exit on failure. */
125 FILE *
126 must_open_file(const char *filename,const char *mode)
127 {   FILE *fp;
128
129     fp = fopen(filename,mode);
130     if(fp == NULL){
131         fprintf(GlobalState.logfile,"Unable to open the file: \"%s\"\n",
132                 filename);
133         exit(1);
134     }
135     return fp;
136 }
137
138         /* Print out on outfp the current details and
139          * terminate with a newline.
140          */
141 void
142 report_details(FILE *outfp)
143 {
144       if(GameHeader.Tags[WHITE_TAG] != NULL){
145           fprintf(outfp,"%s - ",GameHeader.Tags[WHITE_TAG]);
146       }
147       if(GameHeader.Tags[BLACK_TAG] != NULL){
148           fprintf(outfp,"%s ",GameHeader.Tags[BLACK_TAG]);
149       }
150
151       if(GameHeader.Tags[EVENT_TAG] != NULL){
152           fprintf(outfp,"%s ",GameHeader.Tags[EVENT_TAG]);
153       }
154       if(GameHeader.Tags[SITE_TAG] != NULL){
155           fprintf(outfp,"%s ",GameHeader.Tags[SITE_TAG]);
156       }
157
158       if(GameHeader.Tags[DATE_TAG] != NULL){
159           fprintf(outfp,"%s ",GameHeader.Tags[DATE_TAG]);
160       }
161       putc('\n',outfp);
162       fflush(outfp);
163 }
164
165         /* Check that terminating_result is consistent with
166          * Tags[RESULT_TAG].  If the latter is missing, fill it
167          * in from terminating_result.
168          */
169 static void
170 check_result(char **Tags,const char *terminating_result)
171 {   char *result_tag = Tags[RESULT_TAG];
172
173     if(terminating_result != NULL){
174         if((result_tag == NULL) || (*result_tag == '\0') ||
175                     (strcmp(result_tag,"?") == 0)){
176             /* Use a copy of terminating result. */
177             result_tag = copy_string(terminating_result);
178             Tags[RESULT_TAG] = result_tag;
179         }
180         else if((result_tag != NULL) &&
181                     (strcmp(terminating_result,"*") != 0) &&
182                     (strcmp(result_tag,terminating_result) != 0)){
183             print_error_context(GlobalState.logfile);
184             fprintf(GlobalState.logfile,
185                         "Inconsistent result strings in the following game.\n");
186             report_details(GlobalState.logfile);
187         }
188         else{
189             /* Ok. */
190         }
191     }
192 }
193
194         /* Select which file to write to based upon the game state.
195          * This will depend upon:
196          *        Whether the number of games per file is limited.
197          *        Whether ECO_level > DONT_DIVIDE.
198          */
199
200 static FILE *
201 select_output_file(StateInfo *GameState,const char *eco)
202 {   
203     if(GameState->games_per_file > 0){
204         if((GameState->num_games_matched % GameState->games_per_file) == 0){
205               /* Time to open the next one. */
206               char filename[50];
207
208               if(GameState->outputfile != NULL){
209                   (void) fclose(GameState->outputfile);
210               }
211               sprintf(filename,"%u%s",
212                         GameState->next_file_number,
213                         output_file_suffix(GameState->output_format));
214               GameState->outputfile = must_open_file(filename,"w");
215               GameState->next_file_number++;
216         }
217     }
218     else if(GameState->ECO_level > DONT_DIVIDE){
219         /* Open a file of the appropriate name. */
220         if(GameState->outputfile != NULL){
221             /* @@@ In practice, this might need refinement.
222              * Repeated opening and closing may prove inefficient.
223              */
224             (void) fclose(GameState->outputfile);
225             GameState->outputfile = open_eco_output_file(
226                                 GameState->ECO_level,
227                                 eco);
228         }
229     }
230     else{
231     }
232     return GameState->outputfile;
233 }
234
235   /*
236    * Conditions for finishing processing, other than all the input
237    * having been processed.
238    */
239 static Boolean finished_processing(SourceFileType file_type)
240 {
241     return ((file_type != ECOFILE && at_end_of_input()) ||
242             (GlobalState.matching_game_number > 0 &&
243              GlobalState.num_games_matched == GlobalState.matching_game_number));
244 }
245
246 static void
247 ParseOptGameList(SourceFileType file_type)
248 {   Move *move_list = NULL;
249
250     while(ParseGame(&move_list) && !finished_processing(file_type)){
251         if(file_type == NORMALFILE){
252             DealWithGame(move_list);
253         }
254         else if(file_type == CHECKFILE){
255             DealWithGame(move_list);
256         }
257         else if(file_type == ECOFILE){
258             if(move_list != NULL){
259                 DealWithEcoLine(move_list);
260             }
261             else {
262                 fprintf(GlobalState.logfile,"ECO line with zero moves.\n");
263                 report_details(GlobalState.logfile);
264             }
265         }
266         else{
267             /* Unknown type. */
268             free_tags();
269             free_move_list(move_list);
270         }
271         move_list = NULL;
272         setup_for_new_game();
273     }
274     if(file_type == ECOFILE && GlobalState.dump_eco) {
275         dumpEcoTable();
276         exit(0);
277     }
278 }
279
280         /* Parse a game and return a pointer to any valid list of moves
281          * in returned_move_list.
282          */
283 static Boolean
284 ParseGame(Move **returned_move_list)
285 {   /* Boolean something_found = FALSE; */
286     CommentList *prefix_comment;
287     Move *move_list = NULL;
288     char *result;
289     /* There shouldn't be a hanging comment before the result,
290      * but there sometimes is.
291      */
292     CommentList *hanging_comment;
293
294     /* Assume that we won't return anything. */
295     *returned_move_list = NULL;
296     /* Skip over any junk between games. */
297     current_symbol = skip_to_next_game(current_symbol);
298     prefix_comment = ParseOptCommentList();
299     if(prefix_comment != NULL){
300         /* Free this here, as it is hard to
301          * know whether it belongs to the game or the file.
302          * It is better to put game comments after the tags.
303          */
304         /* something_found = TRUE; */
305         free_comment_list(prefix_comment);
306         prefix_comment = NULL;
307     }
308     if(ParseOptTagList()){
309         /* something_found = TRUE; */
310     }
311     /* @@@ Beware of comments and/or tags without moves. */
312     move_list = ParseMoveList();
313
314     /* @@@ Look for a comment with no move text before the result. */
315     hanging_comment = ParseOptCommentList();
316     /* Append this to the final move, if there is one. */
317
318     /* Look for a result, even if there were no moves. */
319     result = ParseResult();
320     if(move_list != NULL){
321         /* Find the last move. */
322         Move *last_move = move_list;
323
324         while(last_move->next != NULL){
325             last_move = last_move->next;
326         }
327         if(hanging_comment != NULL) {
328             append_comments_to_move(last_move,hanging_comment);
329         }
330         if(result != NULL){
331             /* Append it to the last move. */
332             last_move->terminating_result = result;
333             check_result(GameHeader.Tags,result);
334             *returned_move_list = move_list;
335         }
336         else{
337             fprintf(GlobalState.logfile,"Missing result.\n");
338             report_details(GlobalState.logfile);
339         }
340         /* something_found = TRUE; */
341     }
342     else{
343         /* @@@ Nothing to attach the comment to. */
344         (void) free((void *) hanging_comment);
345         hanging_comment = NULL;
346         /*
347          * Workaround for games with zero moves.
348          * Check the result for consistency with the tags, but then
349          * there is no move to attach it to.
350          * When outputting a game, the missing result in this case
351          * will have to be supplied from the tags.
352          */
353         check_result(GameHeader.Tags,result);
354         if(result != NULL){
355             (void) free((void *)result);
356         }
357         *returned_move_list = NULL;
358     }
359     return current_symbol != EOF_TOKEN;
360 }
361
362 Boolean
363 ParseOptTagList(void)
364 {   Boolean something_found = FALSE;
365     CommentList *prefix_comment;
366  
367     while(ParseTag()){
368         something_found = TRUE;
369     }
370     if(something_found){
371         /* Perform any consistency checks. */
372         if((GameHeader.Tags[SETUP_TAG] != NULL) &&
373                 (strcmp(GameHeader.Tags[SETUP_TAG],"1") == 0)){
374             /* There must be a FEN_TAG to go with it. */
375             if(GameHeader.Tags[FEN_TAG] == NULL){
376                  fprintf(GlobalState.logfile,
377                         "Missing %s Tag to accompany %s Tag.\n",
378                         tag_header_string(FEN_TAG),
379                         tag_header_string(SETUP_TAG));
380                 print_error_context(GlobalState.logfile);
381             }
382         }
383     }
384     prefix_comment = ParseOptCommentList();
385     if(prefix_comment != NULL){
386         GameHeader.prefix_comment = prefix_comment;
387         something_found = TRUE;
388     }
389     return something_found;
390 }
391
392 Boolean
393 ParseTag(void)
394 {   Boolean TagFound = TRUE;
395
396     if(current_symbol == TAG){
397         TagName tag_index = yylval.tag_index;
398
399         current_symbol = next_token();
400         if(current_symbol == STRING){
401             char *tag_string = yylval.token_string;
402
403             if(tag_index < GameHeader.header_tags_length){
404                 GameHeader.Tags[tag_index] = tag_string;
405             }
406             else{
407                 print_error_context(GlobalState.logfile);
408                 fprintf(GlobalState.logfile,
409                         "Internal error: Illegal tag index %d for %s\n",
410                         tag_index,tag_string);
411                 exit(1);
412             }
413             current_symbol = next_token();
414         }
415         else{
416             print_error_context(GlobalState.logfile);
417             fprintf(GlobalState.logfile,"Missing tag string.\n");
418         }
419     }
420     else if(current_symbol == STRING){
421         print_error_context(GlobalState.logfile);
422         fprintf(GlobalState.logfile,"Missing tag for %s.\n",yylval.token_string);
423         (void) free((void *)yylval.token_string);
424         current_symbol = next_token();
425     }
426     else{
427         TagFound = FALSE;
428     }
429     return TagFound;
430 }
431
432
433 static Move *
434 ParseMoveList(void)
435 {   Move *head = NULL, *tail = NULL;
436
437     head = ParseMoveAndVariants();
438     if(head != NULL){
439         Move *next_move;
440
441         tail = head;
442         while((next_move = ParseMoveAndVariants()) != NULL){
443             tail->next = next_move;
444             tail = next_move;
445         }
446     }
447     return head;
448 }
449
450 static Move *
451 ParseMoveAndVariants(void)
452 {   Move *move_details;
453
454     move_details = ParseMove();
455     if(move_details != NULL){
456         CommentList *comment;
457
458         move_details->Variants = ParseOptVariantList();
459         comment = ParseOptCommentList();
460         if(comment != NULL){
461             append_comments_to_move(move_details,comment);
462         }
463     }
464     return move_details;
465 }
466
467
468 static Move *
469 ParseMove(void)
470 {   Move *move_details = NULL;
471
472     if(ParseOptMoveNumber()){
473     }
474     /* @@@ Watch out for finding just the number. */
475     move_details = ParseMoveUnit();
476     if(move_details != NULL){
477         CommentList *comment;
478
479         move_details->Nags = ParseOptNAGList();
480         comment = ParseOptCommentList();
481         if(comment != NULL){
482             append_comments_to_move(move_details,comment);
483         }
484     }
485     return move_details;
486 }
487
488 static Move *
489 ParseMoveUnit(void)
490 {   Move *move_details = NULL;
491
492     if(current_symbol == MOVE){
493         move_details = yylval.move_details;
494
495         if(move_details->class == NULL_MOVE && RAV_level == 0) {
496             print_error_context(GlobalState.logfile);
497             fprintf(GlobalState.logfile, "Null moves (--) only allowed in variations.\n");
498         }
499
500         current_symbol = next_token();
501         if(current_symbol == CHECK_SYMBOL){
502             strcat((char *) move_details->move,"+");
503             current_symbol = next_token();
504             /* Sometimes + is followed by #, so cover this case. */
505             if(current_symbol == CHECK_SYMBOL){
506                 current_symbol = next_token();
507             }
508         }
509         move_details->Comment = ParseOptCommentList();
510     }
511     return move_details;
512 }
513
514 static CommentList *
515 ParseOptCommentList(void)
516 {   CommentList *head = NULL, *tail = NULL;
517
518     while(current_symbol == COMMENT){
519         if(head == NULL){
520             head = tail = yylval.comment;
521         }
522         else{
523             tail->next = yylval.comment;
524             tail = tail->next;
525         }
526         current_symbol = next_token();
527     }
528     return head;
529 }
530
531 Boolean
532 ParseOptMoveNumber(void)
533 {   Boolean something_found = FALSE;
534
535     if(current_symbol == MOVE_NUMBER){
536         current_symbol = next_token();
537         something_found = TRUE;
538     }
539     return something_found;
540 }
541
542 static StringList *
543 ParseOptNAGList(void)
544 {   StringList *nags = NULL;
545  
546     while(current_symbol == NAG){
547         if(GlobalState.keep_NAGs){
548             nags = save_string_list_item(nags,yylval.token_string);
549         }
550         else{
551             (void) free((void *)yylval.token_string);
552         }
553         current_symbol = next_token();
554     }
555     return nags;
556 }
557
558 static Variation *
559 ParseOptVariantList(void)
560 {   Variation *head = NULL, *tail = NULL,
561         *variation;
562  
563     while((variation = ParseVariant()) != NULL){
564         if(head == NULL){
565             head = tail = variation;
566         }
567         else{
568             tail->next = variation;
569             tail = variation;
570         }
571     }
572     return head;
573 }
574
575 static Variation *
576 ParseVariant(void)
577 {   Variation *variation = NULL;
578
579     if(current_symbol == RAV_START){
580         CommentList *prefix_comment;
581         CommentList *suffix_comment;
582         char *result = NULL;
583         Move *moves;
584
585         RAV_level++;
586         variation = MallocOrDie(sizeof(Variation));
587
588         current_symbol = next_token();
589         prefix_comment = ParseOptCommentList();
590         if(prefix_comment != NULL){
591         }
592         moves = ParseMoveList();
593         if(moves == NULL){
594             print_error_context(GlobalState.logfile);
595             fprintf(GlobalState.logfile,"Missing move list in variation.\n");
596         }
597         result = ParseResult();
598         if((result != NULL) && (moves != NULL)){
599             /* Find the last move, to which to append the terminating
600              * result.
601              */
602             Move *last_move = moves;
603             CommentList *trailing_comment;
604
605             while(last_move->next != NULL){
606                 last_move = last_move->next;
607             }
608             last_move->terminating_result = result;
609             /* Accept a comment after the result, but it will
610              * be printed out preceding the result.
611              */
612             trailing_comment = ParseOptCommentList();
613             if(trailing_comment != NULL){
614                 append_comments_to_move(last_move,trailing_comment);
615             }
616         }
617         else{
618             /* Ok. */
619         }
620         if(current_symbol == RAV_END){
621             RAV_level--;
622             current_symbol = next_token();
623         }
624         else{
625             fprintf(GlobalState.logfile,"Missing ')' to close variation.\n");
626         }
627         suffix_comment = ParseOptCommentList();
628         if(suffix_comment != NULL){
629         }
630         variation->prefix_comment = prefix_comment;
631         variation->suffix_comment = suffix_comment;
632         variation->moves = moves;
633         variation->next = NULL;
634     }
635     return variation;
636 }
637
638 static char *
639 ParseResult(void)
640 {   char *result = NULL;
641
642     if(current_symbol == TERMINATING_RESULT){
643         result = yylval.token_string;
644         if(RAV_level == 0){
645             /* In the interests of skipping any intervening material
646              * between games, set the lookahead to a dummy token.
647              */
648             current_symbol = NO_TOKEN;
649         }
650         else{
651             current_symbol = next_token();
652         }
653     }
654     return result;
655 }
656
657 static void
658 setup_for_new_game(void)
659 {
660     restart_lex_for_new_game();
661     RAV_level = 0;
662     game_start_position = get_position();
663 }
664
665         /* Discard any data held in the GameHeader.Tags structure. */
666 void
667 free_tags(void)
668 {   unsigned tag;
669
670     for(tag = 0; tag < GameHeader.header_tags_length; tag++){
671         if(GameHeader.Tags[tag] != NULL){
672             free(GameHeader.Tags[tag]);
673             GameHeader.Tags[tag] = NULL;
674         }
675     }
676 }
677
678         /* Discard data from a gathered game. */
679 void
680 free_string_list(StringList *list)
681 {   StringList *next;
682
683     while(list != NULL){
684         next = list;
685         list = list->next;
686         if(next->str != NULL){
687             (void) free((void *)next->str);
688         }
689         (void) free((void *)next);
690     }
691 }
692
693 static void
694 free_comment_list(CommentList *comment_list)
695 {
696     while(comment_list != NULL){
697         CommentList *this_comment = comment_list;
698
699         if(comment_list->Comment != NULL){
700             free_string_list(comment_list->Comment);
701         }
702         comment_list = comment_list->next;
703         (void) free((void *)this_comment);
704     }
705 }
706
707 static void
708 free_variation(Variation *variation)
709 {   Variation *next;
710
711     while(variation != NULL){
712         next = variation;
713         variation = variation->next;
714         if(next->prefix_comment != NULL){
715             free_comment_list(next->prefix_comment);
716         }
717         if(next->suffix_comment != NULL){
718             free_comment_list(next->suffix_comment);
719         }
720         if(next->moves != NULL){
721             (void) free_move_list((void *)next->moves);
722         }
723         (void) free((void *)next);
724     }
725 }
726
727 void
728 free_move_list(Move *move_list)
729 {   Move *next;
730
731     while(move_list != NULL){
732         next = move_list;
733         move_list = move_list->next;
734         if(next->Nags != NULL){
735             free_string_list(next->Nags);
736         }
737         if(next->Comment != NULL){
738             free_comment_list(next->Comment);
739         }
740         if(next->Variants != NULL){
741             free_variation(next->Variants);
742         }
743         if(next->epd != NULL){
744             (void) free((void *)next->epd);
745         }
746         if(next->bpfen != NULL){
747             (void) free((void *)next->bpfen);
748         }
749         if(next->terminating_result != NULL){
750             (void) free((void *)next->terminating_result);
751         }
752         (void) free((void *)next);
753     }
754 }
755
756         /* Add str onto the tail of list and
757          * return the head of the resulting list.
758          */
759 StringList *
760 save_string_list_item(StringList *list,const char *str)
761 {
762     if(str != NULL){
763       StringList *new_item;
764
765       new_item = (StringList *)MallocOrDie(sizeof(*new_item));
766       new_item->str = str;
767       new_item->next = NULL;
768       if(list == NULL){
769           list = new_item;
770       }
771       else{
772           StringList *tail = list;
773
774           while(tail->next != NULL){
775               tail = tail->next;
776           }
777           tail->next = new_item;
778       }
779     }
780     return list;
781 }
782
783         /* Append any comments in Comment onto the end of
784          * any associated with move.
785          */
786 void
787 append_comments_to_move(Move *move,CommentList *Comment)
788 {
789     if(Comment != NULL){
790         /* Add in to the end of any already existing. */
791         if(move->Comment == NULL){
792             move->Comment = Comment;
793         }
794         else{
795             /* Add in the final comment to 
796              * the end of any existing for this move.
797              */
798             CommentList *tail = move->Comment;
799
800             while(tail->next != NULL){
801                 tail = tail->next;
802             }
803             tail->next = Comment;
804         }
805     }
806 }
807
808 static void
809 DealWithGame(Move *move_list)
810 {   Game current_game;
811     /* Record whether the game has been printed or not.
812      * This is used for the case of the -n flag which catches
813      * all non-printed games.
814      */
815     Boolean game_output = FALSE;
816     /* We need a dummy argument for apply_move_list. */
817     unsigned plycount;
818
819     /* Update the count of how many games handled. */
820     GlobalState.num_games_processed++;
821
822     /* Fill in the information currently known. */
823     current_game.tags = GameHeader.Tags;
824     current_game.tags_length = GameHeader.header_tags_length;
825     current_game.prefix_comment = GameHeader.prefix_comment;
826     current_game.moves = move_list;
827     current_game.moves_checked = FALSE;
828     current_game.moves_ok = FALSE;
829     current_game.error_ply = 0;
830     current_game.file_number = GlobalState.current_file_number;
831     current_game.start_position = game_start_position;
832   
833     /* Determine whether or not this game is wanted, on the
834      * basis of the various selection criteria available.
835      */
836     
837     /*
838      * apply_move_list checks out the moves.
839      * If it returns TRUE as a match, it will also fill in the
840      *                 current_game.final_hash_value and
841      *                 current_game.cumulative_hash_value
842      * fields of current_game so that these can be used in the
843      * previous_occurance function.
844      *
845      * If there are any tag criteria, it will be easy to quickly
846      * eliminate most games without going through the length
847      * process of game matching.
848      *
849      * If ECO adding is done, the order of checking may cause
850      * a conflict here since it won't be possible to reject a game
851      * based on its ECO code unless it already has one.
852      * Therefore, Check for the ECO tag only after everything else has
853      * been checked.
854      */
855     if(CheckTagDetailsNotECO(current_game.tags,current_game.tags_length) &&
856              apply_move_list(&current_game,&plycount) && 
857              check_move_bounds(plycount) &&
858              check_textual_variations(current_game) &&
859              check_for_ending(current_game.moves) &&
860              check_for_only_checkmate(current_game.moves) &&
861              CheckECOTag(current_game.tags)){
862         /* If there is no original filename then the game is not a
863          * duplicate.
864          */
865         const char *original_filename = previous_occurance(current_game, plycount);
866
867         if((original_filename == NULL) && GlobalState.suppress_originals){
868             /* Don't output first occurrences. */
869         }
870         else if((original_filename == NULL) || !GlobalState.suppress_duplicates){
871             GlobalState.num_games_matched++;
872             if(GlobalState.check_only) {
873                 // We are only checking. 
874                 if(GlobalState.verbose){
875                     /* Report progress on logfile. */
876                     report_details(GlobalState.logfile);
877                 }
878             }
879             else if(GlobalState.current_file_type == CHECKFILE){
880                 /* We are only checking, so don't count this as a matched game. */
881                 GlobalState.num_games_matched--;
882             }
883             else if(GlobalState.matching_game_number > 0 &&
884                   GlobalState.num_games_matched != GlobalState.matching_game_number) {
885                 /* This is not the right matching game to be output. */
886             }
887             else {
888                 /* This game is to be kept and output. */
889                 FILE *outputfile = select_output_file(&GlobalState,
890                         current_game.tags[ECO_TAG]);
891
892                 /* See if we wish to separate out duplicates. */
893                 if((original_filename != NULL) &&
894                         (GlobalState.duplicate_file != NULL)){
895                     static const char *last_input_file = NULL;
896   
897                     outputfile = GlobalState.duplicate_file;
898                     if((last_input_file != GlobalState.current_input_file) &&
899                        (GlobalState.current_input_file != NULL)){
900                            /* Record which file this and succeeding
901                             * duplicates come from.
902                             */
903                           print_str(outputfile,"{ From: ");
904                           print_str(outputfile,
905                                       GlobalState.current_input_file);
906                           print_str(outputfile," }");
907                           terminate_line(outputfile);
908                           last_input_file = GlobalState.current_input_file;
909                     }
910                   print_str(outputfile,"{ First found in: ");
911                   print_str(outputfile,original_filename);
912                   print_str(outputfile," }");
913                   terminate_line(outputfile);
914                 }
915                 /* Now output what we have. */
916                 output_game(current_game,outputfile);
917                 game_output = TRUE;
918                 if(GlobalState.verbose){
919                     /* Report progress on logfile. */
920                     report_details(GlobalState.logfile);
921                 }
922             }
923         }
924     }
925     if(!game_output && (GlobalState.non_matching_file != NULL) &&
926                 GlobalState.current_file_type != CHECKFILE){
927         /* The user wants to keep everything else. */
928         if(!current_game.moves_checked){
929              /* Make sure that the move text is in a reasonable state. */
930              (void) apply_move_list(&current_game,&plycount);
931         }
932         if(current_game.moves_ok || GlobalState.keep_broken_games){
933             output_game(current_game,GlobalState.non_matching_file);
934         }
935     }
936   
937     /* Game is finished with, so free everything. */
938     if(GameHeader.prefix_comment != NULL){
939         free_comment_list(GameHeader.prefix_comment);
940     }
941     /* Ensure that the GameHeader's prefix comment is NULL for
942      * the next game.
943      */
944     GameHeader.prefix_comment = NULL;
945   
946     free_tags();
947     free_move_list(current_game.moves);
948     if((GlobalState.num_games_processed % 10) == 0){
949         fprintf(stderr,"Games: %lu\r",GlobalState.num_games_processed);
950     }
951 }
952
953 static void
954 DealWithEcoLine(Move *move_list)
955 {   Game current_game;
956     /* We need to know the length of a game to store with the
957      * hash information as a sanity check.
958      */
959     unsigned number_of_half_moves;
960
961     /* Fill in the information currently known. */
962     current_game.tags = GameHeader.Tags;
963     current_game.tags_length = GameHeader.header_tags_length;
964     current_game.prefix_comment = GameHeader.prefix_comment;
965     current_game.moves = move_list;
966     current_game.moves_checked = FALSE;
967     current_game.moves_ok = FALSE;
968     current_game.error_ply = 0;
969   
970     /* apply_eco_move_list checks out the moves.
971      * It will also fill in the
972      *                 current_game.final_hash_value and
973      *                 current_game.cumulative_hash_value
974      * fields of current_game.
975      */
976     if(apply_eco_move_list(&current_game,&number_of_half_moves)){
977         if(current_game.moves_ok){
978             /* Store the ECO code in the appropriate hash location. */
979             save_eco_details(current_game,number_of_half_moves);
980         }
981     }
982   
983     /* Game is finished with, so free everything. */
984     if(GameHeader.prefix_comment != NULL){
985         free_comment_list(GameHeader.prefix_comment);
986     }
987     /* Ensure that the GameHeader's prefix comment is NULL for
988      * the next game.
989      */
990     GameHeader.prefix_comment = NULL;
991   
992     free_tags();
993     free_move_list(current_game.moves);
994 }
995
996         /* If file_type == ECOFILE we are dealing with a file of ECO
997          * input rather than a normal game file.
998          */
999 int
1000 yyparse(SourceFileType file_type)
1001 {
1002     if(file_type != ECOFILE) {
1003         if(!seek_to_begin()) {
1004             return 1;
1005         }
1006     }
1007     setup_for_new_game();
1008     current_symbol = skip_to_next_game(NO_TOKEN);
1009     ParseOptGameList(file_type);
1010     if(current_symbol == EOF_TOKEN){
1011         /* Ok -- EOF. */
1012         return 0;
1013     }
1014     else if(finished_processing(file_type)) {
1015         /* Ok -- done all we need to. */
1016         return 0;
1017     }
1018     else{
1019         fprintf(GlobalState.logfile,"End of input reached before end of file.\n");
1020         return 1;
1021     }
1022 }
1023