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)
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.
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.
18 * David Barnes may be contacted as D.J.Barnes@kent.ac.uk
19 * http://www.cs.kent.ac.uk/people/staff/djb/
43 static TokenType current_symbol = NO_TOKEN;
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.
49 static unsigned RAV_level = 0;
51 /* At what file position the current game started. */
52 static long game_start_position = -1;
54 /* Retain details of the header of a game.
55 * This comprises the Tags and any comment prefixing the
61 unsigned header_tags_length;
62 CommentList *prefix_comment;
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);
80 static void setup_for_new_game(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);
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.
94 init_game_header(void)
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;
106 increase_game_header_tags_length(unsigned new_length)
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");
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;
121 GameHeader.header_tags_length = new_length;
124 /* Try to open the given file. Error and exit on failure. */
126 must_open_file(const char *filename,const char *mode)
129 fp = fopen(filename,mode);
131 fprintf(GlobalState.logfile,"Unable to open the file: \"%s\"\n",
138 /* Print out on outfp the current details and
139 * terminate with a newline.
142 report_details(FILE *outfp)
144 if(GameHeader.Tags[WHITE_TAG] != NULL){
145 fprintf(outfp,"%s - ",GameHeader.Tags[WHITE_TAG]);
147 if(GameHeader.Tags[BLACK_TAG] != NULL){
148 fprintf(outfp,"%s ",GameHeader.Tags[BLACK_TAG]);
151 if(GameHeader.Tags[EVENT_TAG] != NULL){
152 fprintf(outfp,"%s ",GameHeader.Tags[EVENT_TAG]);
154 if(GameHeader.Tags[SITE_TAG] != NULL){
155 fprintf(outfp,"%s ",GameHeader.Tags[SITE_TAG]);
158 if(GameHeader.Tags[DATE_TAG] != NULL){
159 fprintf(outfp,"%s ",GameHeader.Tags[DATE_TAG]);
165 /* Check that terminating_result is consistent with
166 * Tags[RESULT_TAG]. If the latter is missing, fill it
167 * in from terminating_result.
170 check_result(char **Tags,const char *terminating_result)
171 { char *result_tag = Tags[RESULT_TAG];
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;
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);
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.
201 select_output_file(StateInfo *GameState,const char *eco)
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. */
208 if(GameState->outputfile != NULL){
209 (void) fclose(GameState->outputfile);
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++;
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.
224 (void) fclose(GameState->outputfile);
225 GameState->outputfile = open_eco_output_file(
226 GameState->ECO_level,
232 return GameState->outputfile;
236 * Conditions for finishing processing, other than all the input
237 * having been processed.
239 static Boolean finished_processing(SourceFileType file_type)
241 return ((file_type != ECOFILE && at_end_of_input()) ||
242 (GlobalState.matching_game_number > 0 &&
243 GlobalState.num_games_matched == GlobalState.matching_game_number));
247 ParseOptGameList(SourceFileType file_type)
248 { Move *move_list = NULL;
250 while(ParseGame(&move_list) && !finished_processing(file_type)){
251 if(file_type == NORMALFILE){
252 DealWithGame(move_list);
254 else if(file_type == CHECKFILE){
255 DealWithGame(move_list);
257 else if(file_type == ECOFILE){
258 if(move_list != NULL){
259 DealWithEcoLine(move_list);
262 fprintf(GlobalState.logfile,"ECO line with zero moves.\n");
263 report_details(GlobalState.logfile);
269 free_move_list(move_list);
272 setup_for_new_game();
274 if(file_type == ECOFILE && GlobalState.dump_eco) {
280 /* Parse a game and return a pointer to any valid list of moves
281 * in returned_move_list.
284 ParseGame(Move **returned_move_list)
285 { /* Boolean something_found = FALSE; */
286 CommentList *prefix_comment;
287 Move *move_list = NULL;
289 /* There shouldn't be a hanging comment before the result,
290 * but there sometimes is.
292 CommentList *hanging_comment;
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.
304 /* something_found = TRUE; */
305 free_comment_list(prefix_comment);
306 prefix_comment = NULL;
308 if(ParseOptTagList()){
309 /* something_found = TRUE; */
311 /* @@@ Beware of comments and/or tags without moves. */
312 move_list = ParseMoveList();
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. */
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;
324 while(last_move->next != NULL){
325 last_move = last_move->next;
327 if(hanging_comment != NULL) {
328 append_comments_to_move(last_move,hanging_comment);
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;
337 fprintf(GlobalState.logfile,"Missing result.\n");
338 report_details(GlobalState.logfile);
340 /* something_found = TRUE; */
343 /* @@@ Nothing to attach the comment to. */
344 (void) free((void *) hanging_comment);
345 hanging_comment = NULL;
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.
353 check_result(GameHeader.Tags,result);
355 (void) free((void *)result);
357 *returned_move_list = NULL;
359 return current_symbol != EOF_TOKEN;
363 ParseOptTagList(void)
364 { Boolean something_found = FALSE;
365 CommentList *prefix_comment;
368 something_found = TRUE;
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);
384 prefix_comment = ParseOptCommentList();
385 if(prefix_comment != NULL){
386 GameHeader.prefix_comment = prefix_comment;
387 something_found = TRUE;
389 return something_found;
394 { Boolean TagFound = TRUE;
396 if(current_symbol == TAG){
397 TagName tag_index = yylval.tag_index;
399 current_symbol = next_token();
400 if(current_symbol == STRING){
401 char *tag_string = yylval.token_string;
403 if(tag_index < GameHeader.header_tags_length){
404 GameHeader.Tags[tag_index] = tag_string;
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);
413 current_symbol = next_token();
416 print_error_context(GlobalState.logfile);
417 fprintf(GlobalState.logfile,"Missing tag string.\n");
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();
435 { Move *head = NULL, *tail = NULL;
437 head = ParseMoveAndVariants();
442 while((next_move = ParseMoveAndVariants()) != NULL){
443 tail->next = next_move;
451 ParseMoveAndVariants(void)
452 { Move *move_details;
454 move_details = ParseMove();
455 if(move_details != NULL){
456 CommentList *comment;
458 move_details->Variants = ParseOptVariantList();
459 comment = ParseOptCommentList();
461 append_comments_to_move(move_details,comment);
470 { Move *move_details = NULL;
472 if(ParseOptMoveNumber()){
474 /* @@@ Watch out for finding just the number. */
475 move_details = ParseMoveUnit();
476 if(move_details != NULL){
477 CommentList *comment;
479 move_details->Nags = ParseOptNAGList();
480 comment = ParseOptCommentList();
482 append_comments_to_move(move_details,comment);
490 { Move *move_details = NULL;
492 if(current_symbol == MOVE){
493 move_details = yylval.move_details;
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");
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();
509 move_details->Comment = ParseOptCommentList();
515 ParseOptCommentList(void)
516 { CommentList *head = NULL, *tail = NULL;
518 while(current_symbol == COMMENT){
520 head = tail = yylval.comment;
523 tail->next = yylval.comment;
526 current_symbol = next_token();
532 ParseOptMoveNumber(void)
533 { Boolean something_found = FALSE;
535 if(current_symbol == MOVE_NUMBER){
536 current_symbol = next_token();
537 something_found = TRUE;
539 return something_found;
543 ParseOptNAGList(void)
544 { StringList *nags = NULL;
546 while(current_symbol == NAG){
547 if(GlobalState.keep_NAGs){
548 nags = save_string_list_item(nags,yylval.token_string);
551 (void) free((void *)yylval.token_string);
553 current_symbol = next_token();
559 ParseOptVariantList(void)
560 { Variation *head = NULL, *tail = NULL,
563 while((variation = ParseVariant()) != NULL){
565 head = tail = variation;
568 tail->next = variation;
577 { Variation *variation = NULL;
579 if(current_symbol == RAV_START){
580 CommentList *prefix_comment;
581 CommentList *suffix_comment;
586 variation = MallocOrDie(sizeof(Variation));
588 current_symbol = next_token();
589 prefix_comment = ParseOptCommentList();
590 if(prefix_comment != NULL){
592 moves = ParseMoveList();
594 print_error_context(GlobalState.logfile);
595 fprintf(GlobalState.logfile,"Missing move list in variation.\n");
597 result = ParseResult();
598 if((result != NULL) && (moves != NULL)){
599 /* Find the last move, to which to append the terminating
602 Move *last_move = moves;
603 CommentList *trailing_comment;
605 while(last_move->next != NULL){
606 last_move = last_move->next;
608 last_move->terminating_result = result;
609 /* Accept a comment after the result, but it will
610 * be printed out preceding the result.
612 trailing_comment = ParseOptCommentList();
613 if(trailing_comment != NULL){
614 append_comments_to_move(last_move,trailing_comment);
620 if(current_symbol == RAV_END){
622 current_symbol = next_token();
625 fprintf(GlobalState.logfile,"Missing ')' to close variation.\n");
627 suffix_comment = ParseOptCommentList();
628 if(suffix_comment != NULL){
630 variation->prefix_comment = prefix_comment;
631 variation->suffix_comment = suffix_comment;
632 variation->moves = moves;
633 variation->next = NULL;
640 { char *result = NULL;
642 if(current_symbol == TERMINATING_RESULT){
643 result = yylval.token_string;
645 /* In the interests of skipping any intervening material
646 * between games, set the lookahead to a dummy token.
648 current_symbol = NO_TOKEN;
651 current_symbol = next_token();
658 setup_for_new_game(void)
660 restart_lex_for_new_game();
662 game_start_position = get_position();
665 /* Discard any data held in the GameHeader.Tags structure. */
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;
678 /* Discard data from a gathered game. */
680 free_string_list(StringList *list)
686 if(next->str != NULL){
687 (void) free((void *)next->str);
689 (void) free((void *)next);
694 free_comment_list(CommentList *comment_list)
696 while(comment_list != NULL){
697 CommentList *this_comment = comment_list;
699 if(comment_list->Comment != NULL){
700 free_string_list(comment_list->Comment);
702 comment_list = comment_list->next;
703 (void) free((void *)this_comment);
708 free_variation(Variation *variation)
711 while(variation != NULL){
713 variation = variation->next;
714 if(next->prefix_comment != NULL){
715 free_comment_list(next->prefix_comment);
717 if(next->suffix_comment != NULL){
718 free_comment_list(next->suffix_comment);
720 if(next->moves != NULL){
721 (void) free_move_list((void *)next->moves);
723 (void) free((void *)next);
728 free_move_list(Move *move_list)
731 while(move_list != NULL){
733 move_list = move_list->next;
734 if(next->Nags != NULL){
735 free_string_list(next->Nags);
737 if(next->Comment != NULL){
738 free_comment_list(next->Comment);
740 if(next->Variants != NULL){
741 free_variation(next->Variants);
743 if(next->epd != NULL){
744 (void) free((void *)next->epd);
746 if(next->bpfen != NULL){
747 (void) free((void *)next->bpfen);
749 if(next->terminating_result != NULL){
750 (void) free((void *)next->terminating_result);
752 (void) free((void *)next);
756 /* Add str onto the tail of list and
757 * return the head of the resulting list.
760 save_string_list_item(StringList *list,const char *str)
763 StringList *new_item;
765 new_item = (StringList *)MallocOrDie(sizeof(*new_item));
767 new_item->next = NULL;
772 StringList *tail = list;
774 while(tail->next != NULL){
777 tail->next = new_item;
783 /* Append any comments in Comment onto the end of
784 * any associated with move.
787 append_comments_to_move(Move *move,CommentList *Comment)
790 /* Add in to the end of any already existing. */
791 if(move->Comment == NULL){
792 move->Comment = Comment;
795 /* Add in the final comment to
796 * the end of any existing for this move.
798 CommentList *tail = move->Comment;
800 while(tail->next != NULL){
803 tail->next = Comment;
809 DealWithGame(Move *move_list)
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.
815 Boolean game_output = FALSE;
816 /* We need a dummy argument for apply_move_list. */
819 /* Update the count of how many games handled. */
820 GlobalState.num_games_processed++;
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;
833 /* Determine whether or not this game is wanted, on the
834 * basis of the various selection criteria available.
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.
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.
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
855 if(CheckTagDetailsNotECO(current_game.tags,current_game.tags_length) &&
856 apply_move_list(¤t_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
865 const char *original_filename = previous_occurance(current_game, plycount);
867 if((original_filename == NULL) && GlobalState.suppress_originals){
868 /* Don't output first occurrences. */
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);
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--;
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. */
888 /* This game is to be kept and output. */
889 FILE *outputfile = select_output_file(&GlobalState,
890 current_game.tags[ECO_TAG]);
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;
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.
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;
910 print_str(outputfile,"{ First found in: ");
911 print_str(outputfile,original_filename);
912 print_str(outputfile," }");
913 terminate_line(outputfile);
915 /* Now output what we have. */
916 output_game(current_game,outputfile);
918 if(GlobalState.verbose){
919 /* Report progress on logfile. */
920 report_details(GlobalState.logfile);
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(¤t_game,&plycount);
932 if(current_game.moves_ok || GlobalState.keep_broken_games){
933 output_game(current_game,GlobalState.non_matching_file);
937 /* Game is finished with, so free everything. */
938 if(GameHeader.prefix_comment != NULL){
939 free_comment_list(GameHeader.prefix_comment);
941 /* Ensure that the GameHeader's prefix comment is NULL for
944 GameHeader.prefix_comment = NULL;
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);
954 DealWithEcoLine(Move *move_list)
956 /* We need to know the length of a game to store with the
957 * hash information as a sanity check.
959 unsigned number_of_half_moves;
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;
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.
976 if(apply_eco_move_list(¤t_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);
983 /* Game is finished with, so free everything. */
984 if(GameHeader.prefix_comment != NULL){
985 free_comment_list(GameHeader.prefix_comment);
987 /* Ensure that the GameHeader's prefix comment is NULL for
990 GameHeader.prefix_comment = NULL;
993 free_move_list(current_game.moves);
996 /* If file_type == ECOFILE we are dealing with a file of ECO
997 * input rather than a normal game file.
1000 yyparse(SourceFileType file_type)
1002 if(file_type != ECOFILE) {
1003 if(!seek_to_begin()) {
1007 setup_for_new_game();
1008 current_symbol = skip_to_next_game(NO_TOKEN);
1009 ParseOptGameList(file_type);
1010 if(current_symbol == EOF_TOKEN){
1014 else if(finished_processing(file_type)) {
1015 /* Ok -- done all we need to. */
1019 fprintf(GlobalState.logfile,"End of input reached before end of file.\n");