]> git.sesse.net Git - pgn-extract/blob - grammar.c
Support scanning only a range of the file.
[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.start_position = game_start_position;
831   
832     /* Determine whether or not this game is wanted, on the
833      * basis of the various selection criteria available.
834      */
835     
836     /*
837      * apply_move_list checks out the moves.
838      * If it returns TRUE as a match, it will also fill in the
839      *                 current_game.final_hash_value and
840      *                 current_game.cumulative_hash_value
841      * fields of current_game so that these can be used in the
842      * previous_occurance function.
843      *
844      * If there are any tag criteria, it will be easy to quickly
845      * eliminate most games without going through the length
846      * process of game matching.
847      *
848      * If ECO adding is done, the order of checking may cause
849      * a conflict here since it won't be possible to reject a game
850      * based on its ECO code unless it already has one.
851      * Therefore, Check for the ECO tag only after everything else has
852      * been checked.
853      */
854     if(CheckTagDetailsNotECO(current_game.tags,current_game.tags_length) &&
855              apply_move_list(&current_game,&plycount) && 
856              check_move_bounds(plycount) &&
857              check_textual_variations(current_game) &&
858              check_for_ending(current_game.moves) &&
859              check_for_only_checkmate(current_game.moves) &&
860              CheckECOTag(current_game.tags)){
861         /* If there is no original filename then the game is not a
862          * duplicate.
863          */
864         const char *original_filename = previous_occurance(current_game, plycount);
865
866         if((original_filename == NULL) && GlobalState.suppress_originals){
867             /* Don't output first occurrences. */
868         }
869         else if((original_filename == NULL) || !GlobalState.suppress_duplicates){
870             GlobalState.num_games_matched++;
871             if(GlobalState.check_only) {
872                 // We are only checking. 
873                 if(GlobalState.verbose){
874                     /* Report progress on logfile. */
875                     report_details(GlobalState.logfile);
876                 }
877             }
878             else if(GlobalState.current_file_type == CHECKFILE){
879                 /* We are only checking, so don't count this as a matched game. */
880                 GlobalState.num_games_matched--;
881             }
882             else if(GlobalState.matching_game_number > 0 &&
883                   GlobalState.num_games_matched != GlobalState.matching_game_number) {
884                 /* This is not the right matching game to be output. */
885             }
886             else {
887                 /* This game is to be kept and output. */
888                 FILE *outputfile = select_output_file(&GlobalState,
889                         current_game.tags[ECO_TAG]);
890
891                 /* See if we wish to separate out duplicates. */
892                 if((original_filename != NULL) &&
893                         (GlobalState.duplicate_file != NULL)){
894                     static const char *last_input_file = NULL;
895   
896                     outputfile = GlobalState.duplicate_file;
897                     if((last_input_file != GlobalState.current_input_file) &&
898                        (GlobalState.current_input_file != NULL)){
899                            /* Record which file this and succeeding
900                             * duplicates come from.
901                             */
902                           print_str(outputfile,"{ From: ");
903                           print_str(outputfile,
904                                       GlobalState.current_input_file);
905                           print_str(outputfile," }");
906                           terminate_line(outputfile);
907                           last_input_file = GlobalState.current_input_file;
908                     }
909                   print_str(outputfile,"{ First found in: ");
910                   print_str(outputfile,original_filename);
911                   print_str(outputfile," }");
912                   terminate_line(outputfile);
913                 }
914                 /* Now output what we have. */
915                 output_game(current_game,outputfile);
916                 game_output = TRUE;
917                 if(GlobalState.verbose){
918                     /* Report progress on logfile. */
919                     report_details(GlobalState.logfile);
920                 }
921             }
922         }
923     }
924     if(!game_output && (GlobalState.non_matching_file != NULL) &&
925                 GlobalState.current_file_type != CHECKFILE){
926         /* The user wants to keep everything else. */
927         if(!current_game.moves_checked){
928              /* Make sure that the move text is in a reasonable state. */
929              (void) apply_move_list(&current_game,&plycount);
930         }
931         if(current_game.moves_ok || GlobalState.keep_broken_games){
932             output_game(current_game,GlobalState.non_matching_file);
933         }
934     }
935   
936     /* Game is finished with, so free everything. */
937     if(GameHeader.prefix_comment != NULL){
938         free_comment_list(GameHeader.prefix_comment);
939     }
940     /* Ensure that the GameHeader's prefix comment is NULL for
941      * the next game.
942      */
943     GameHeader.prefix_comment = NULL;
944   
945     free_tags();
946     free_move_list(current_game.moves);
947     if((GlobalState.num_games_processed % 10) == 0){
948         fprintf(stderr,"Games: %lu\r",GlobalState.num_games_processed);
949     }
950 }
951
952 static void
953 DealWithEcoLine(Move *move_list)
954 {   Game current_game;
955     /* We need to know the length of a game to store with the
956      * hash information as a sanity check.
957      */
958     unsigned number_of_half_moves;
959
960     /* Fill in the information currently known. */
961     current_game.tags = GameHeader.Tags;
962     current_game.tags_length = GameHeader.header_tags_length;
963     current_game.prefix_comment = GameHeader.prefix_comment;
964     current_game.moves = move_list;
965     current_game.moves_checked = FALSE;
966     current_game.moves_ok = FALSE;
967     current_game.error_ply = 0;
968   
969     /* apply_eco_move_list checks out the moves.
970      * It will also fill in the
971      *                 current_game.final_hash_value and
972      *                 current_game.cumulative_hash_value
973      * fields of current_game.
974      */
975     if(apply_eco_move_list(&current_game,&number_of_half_moves)){
976         if(current_game.moves_ok){
977             /* Store the ECO code in the appropriate hash location. */
978             save_eco_details(current_game,number_of_half_moves);
979         }
980     }
981   
982     /* Game is finished with, so free everything. */
983     if(GameHeader.prefix_comment != NULL){
984         free_comment_list(GameHeader.prefix_comment);
985     }
986     /* Ensure that the GameHeader's prefix comment is NULL for
987      * the next game.
988      */
989     GameHeader.prefix_comment = NULL;
990   
991     free_tags();
992     free_move_list(current_game.moves);
993 }
994
995         /* If file_type == ECOFILE we are dealing with a file of ECO
996          * input rather than a normal game file.
997          */
998 int
999 yyparse(SourceFileType file_type)
1000 {
1001     if(file_type != ECOFILE) {
1002         if(!seek_to_begin()) {
1003             return 1;
1004         }
1005     }
1006     setup_for_new_game();
1007     current_symbol = skip_to_next_game(NO_TOKEN);
1008     ParseOptGameList(file_type);
1009     if(current_symbol == EOF_TOKEN){
1010         /* Ok -- EOF. */
1011         return 0;
1012     }
1013     else if(finished_processing(file_type)) {
1014         /* Ok -- done all we need to. */
1015         return 0;
1016     }
1017     else{
1018         fprintf(GlobalState.logfile,"End of input reached before end of file.\n");
1019         return 1;
1020     }
1021 }
1022