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