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/
27 #if defined(__BORLANDC__) || defined(_MSC_VER)
50 /* Prototypes for the functions in this file. */
51 static void save_string(const char *result);
52 /* When a move is saved, what is known of its source and destination coordinates
53 * should also be saved.
55 static void save_move(const unsigned char *move);
56 static void save_q_castle(void);
57 static void save_k_castle(void);
58 static void terminate_input(void);
59 static Boolean extract_yytext(const unsigned char *symbol_start,
60 const unsigned char *linep);
61 static Boolean open_input(const char *infile);
62 static Boolean open_input_file(int file_number);
64 static unsigned long line_number = 0;
65 /* Keep track of the Recursive Annotation Variation level. */
66 static unsigned RAV_level = 0;
67 /* Keep track of the last move found. */
68 static unsigned char last_move[MAX_MOVE_LEN+1];
69 /* How many games we have extracted from this file. */
70 static unsigned games_in_file = 0;
72 /* Provide an input file pointer.
73 * This is intialised in init_lex_tables.
75 static FILE *yyin = NULL;
77 /* Define space for holding matched tokens. */
78 #define MAX_YYTEXT 100
79 static unsigned char yytext[MAX_YYTEXT+1];
83 #define ALPHA_DIST ('a'-'A')
84 /* Table of symbol classifications. */
85 static TokenType ChTab[MAX_CHAR];
86 /* A boolean array as to whether a character is allowed in a move or not. */
87 static short MoveChars[MAX_CHAR];
89 /* Define a table to hold the list of tag strings and the corresponding
90 * TagName index. This is initialised in init_list_of_known_tags().
92 static const char **TagList;
93 static unsigned tag_list_length = 0;
95 /* Initialise the TagList. This should be stored in alphabetical order,
96 * by virtue of the order in which the _TAG values are defined.
99 init_list_of_known_tags(void)
102 tag_list_length = ORIGINAL_NUMBER_OF_TAGS;
103 TagList = (const char **) MallocOrDie(tag_list_length*sizeof(*TagList));
104 /* Be paranoid and put a string in every entry. */
105 for(i = 0; i < tag_list_length; i++){
108 TagList[ANNOTATOR_TAG] = "Annotator";
109 TagList[BLACK_TAG] = "Black";
110 TagList[BLACK_ELO_TAG] = "BlackElo";
111 TagList[BLACK_NA_TAG] = "BlackNA";
112 TagList[BLACK_TITLE_TAG] = "BlackTitle";
113 TagList[BLACK_TYPE_TAG] = "BlackType";
114 TagList[BLACK_USCF_TAG] = "BlackUSCF";
115 TagList[BOARD_TAG] = "Board";
116 TagList[DATE_TAG] = "Date";
117 TagList[ECO_TAG] = "ECO";
118 TagList[PSEUDO_ELO_TAG] = "Elo";
119 TagList[EVENT_TAG] = "Event";
120 TagList[EVENT_DATE_TAG] = "EventDate";
121 TagList[EVENT_SPONSOR_TAG] = "EventSponsor";
122 TagList[FEN_TAG] = "FEN";
123 TagList[PSEUDO_FEN_PATTERN_TAG] = "FENPattern";
124 TagList[HASHCODE_TAG] = "HashCode";
125 TagList[LONG_ECO_TAG] = "LongECO";
126 TagList[MODE_TAG] = "Mode";
127 TagList[NIC_TAG] = "NIC";
128 TagList[OPENING_TAG] = "Opening";
129 TagList[PSEUDO_PLAYER_TAG] = "Player";
130 TagList[PLY_COUNT_TAG] = "PlyCount";
131 TagList[RESULT_TAG] = "Result";
132 TagList[ROUND_TAG] = "Round";
133 TagList[SECTION_TAG] = "Section";
134 TagList[SETUP_TAG] = "SetUp";
135 TagList[SITE_TAG] = "Site";
136 TagList[STAGE_TAG] = "Stage";
137 TagList[SUB_VARIATION_TAG] = "SubVariation";
138 TagList[TERMINATION_TAG] = "Termination";
139 TagList[TIME_TAG] = "Time";
140 TagList[TIME_CONTROL_TAG] = "TimeControl";
141 TagList[TOTAL_PLY_COUNT_TAG] = "TotalPlyCount";
142 TagList[UTC_DATE_TAG] = "UTCDate";
143 TagList[UTC_TIME_TAG] = "UTCTime";
144 TagList[VARIATION_TAG] = "Variation";
145 TagList[WHITE_TAG] = "White";
146 TagList[WHITE_ELO_TAG] = "WhiteElo";
147 TagList[WHITE_NA_TAG] = "WhiteNA";
148 TagList[WHITE_TITLE_TAG] = "WhiteTitle";
149 TagList[WHITE_TYPE_TAG] = "WhiteType";
150 TagList[WHITE_USCF_TAG] = "WhiteUSCF";
153 /* Extend TagList to accomodate a new tag string.
154 * Return the current value of tag_list_length as its
155 * index, having incremented its value.
158 make_new_tag(const char *tag)
159 { unsigned tag_index = tag_list_length;
161 TagList = (const char **) ReallocOrDie((void *)TagList,
162 tag_list_length*sizeof(*TagList));
163 TagList[tag_index] = copy_string(tag);
164 /* Ensure that the game header's tags array can accommodate
167 increase_game_header_tags_length(tag_list_length);
172 tag_header_string(TagName tag)
174 if(tag < tag_list_length){
178 fprintf(GlobalState.logfile,"Internal error in tag_header_string(%d)\n",
184 /* Initialise ChTab[], the classification of the initial characters
186 * Initialise MoveChars, the classification of secondary characters
190 init_lex_tables(void)
193 /* Assume standard input will be used, until we know otherwise. */
195 init_list_of_known_tags();
196 /* Initialise ChTab[]. */
197 for(i = 0; i < MAX_CHAR; i++){
198 ChTab[i] = ERROR_TOKEN;
200 ChTab[' '] = WHITESPACE;
201 ChTab['\t'] = WHITESPACE;
202 /* Take account of DOS line-ends. */
203 ChTab['\r'] = WHITESPACE;
204 ChTab['['] = TAG_START;
205 ChTab[']'] = TAG_END;
206 ChTab['"'] = DOUBLE_QUOTE;
207 ChTab['{'] = COMMENT_START;
208 ChTab['}'] = COMMENT_END;
210 ChTab['!'] = ANNOTATE;
211 ChTab['?'] = ANNOTATE;
212 ChTab['+'] = CHECK_SYMBOL;
213 ChTab['#'] = CHECK_SYMBOL;
215 ChTab['('] = RAV_START;
216 ChTab[')'] = RAV_END;
217 ChTab['%'] = PERCENT;
218 ChTab['\\'] = ESCAPE;
223 /* Operators allowed only in the tag file. */
224 ChTab['<'] = OPERATOR;
225 ChTab['>'] = OPERATOR;
226 ChTab['='] = OPERATOR; /* Overloaded in MoveChars. */
228 for(i = '0'; i <= '9'; i++){
231 for(i = 'A'; i <= 'Z'; i++){
233 ChTab[i+ALPHA_DIST] = ALPHA;
237 /* Classify the Russian piece letters as ALPHA. */
238 ChTab[RUSSIAN_KNIGHT_OR_KING] = ALPHA; /* King and Knight. */
239 ChTab[RUSSIAN_KING_SECOND_LETTER] = ALPHA; /* King (second character). */
240 ChTab[RUSSIAN_QUEEN] = ALPHA; /* Queen. */
241 ChTab[RUSSIAN_ROOK] = ALPHA; /* Rook. */
242 ChTab[RUSSIAN_BISHOP] = ALPHA; /* Bishop. */
244 /* Initialise MoveChars[]. */
245 for(i = 0; i < MAX_CHAR; i++){
249 for(i = 'a'; i <= 'h'; i++){
253 for(i = '1'; i <= '8'; i++){
256 /* Upper-case pieces. */
262 /* Lower-case pieces. */
268 /* Other u-c Dutch/German characters. */
269 MoveChars['D'] = 1; /* Queen. */
270 MoveChars['T'] = 1; /* Rook. */
271 MoveChars['S'] = 1; /* Knight. */
272 MoveChars['P'] = 1; /* Knight. */
273 MoveChars['L'] = 1; /* Bishop. */
274 /* Russian characters. */
275 MoveChars[RUSSIAN_KNIGHT_OR_KING] = 1; /* King and Knight. */
276 MoveChars[RUSSIAN_KING_SECOND_LETTER] = 1; /* King (second character). */
277 MoveChars[RUSSIAN_QUEEN] = 1; /* Queen. */
278 MoveChars[RUSSIAN_ROOK] = 1; /* Rook. */
279 MoveChars[RUSSIAN_BISHOP] = 1; /* Bishop. */
281 /* Capture and square separators. */
286 /* Promotion character. */
292 /* Allow a trailing p for ep. */
296 /* Starting from linep in line, gather up the string until
297 * the closing quote. Skip over the closing quote.
300 gather_string(char *line, unsigned char *linep)
301 { LinePair resulting_line;
309 /* Escape the next character. */
317 } while((ch != '"') && (ch != '\0'));
318 /* The last one doesn't belong in the string. */
320 /* Allocate space for the result. */
321 str = MallocOrDie(len+1);
322 strncpy(str,(const char *) (linep-len-1),len);
324 /* Store it in yylval. */
325 yylval.token_string = str;
327 /* Make sure that the string was properly terminated, by
328 * looking at the last character examined.
332 if(!GlobalState.skipping_current_game) {
333 fprintf(GlobalState.logfile,"Missing closing quote in %s\n",line);
336 /* Move back to the null. */
342 /* We have already skipped over the closing quote. */
344 resulting_line.line = line;
345 resulting_line.linep = linep;
346 resulting_line.token = STRING;
347 return resulting_line;
351 * Is ch of the given character class?
352 * External access to ChTab.
355 is_character_class(unsigned char ch, TokenType character_class)
357 return ChTab[ch] == character_class;
360 /* Starting from linep in line, gather up a comment until
361 * the END_COMMENT. Skip over the END_COMMENT.
364 gather_comment(char *line, unsigned char *linep)
365 { LinePair resulting_line;
368 /* The string list in which the current comment will be gathered. */
369 StringList *current_comment = NULL;
370 /* The pointer to be returned. */
371 CommentList *comment;
374 /* Restart a new segment. */
378 } while((ch != '}') && (ch != '\0'));
379 /* The last one doesn't belong in the comment. */
381 if(GlobalState.keep_comments){
384 /* Allocate space for the result. */
385 comment_str = (char *)MallocOrDie(len+1);
386 strncpy(comment_str,(const char *) (linep-len-1) ,len);
387 comment_str[len] = '\0';
388 current_comment = save_string_list_item(current_comment,comment_str);
391 line = next_input_line(yyin);
392 linep = (unsigned char *) line;
394 } while((ch != '}') && (line != NULL));
396 /* Set up the structure to be returned. */
397 comment = MallocOrDie(sizeof(*comment));
398 comment->Comment = current_comment;
399 comment->next = NULL;
400 yylval.comment = comment;
402 resulting_line.line = line;
403 resulting_line.linep = linep;
404 resulting_line.token = COMMENT;
405 return resulting_line;
408 /* Remember that 0 can start 0-1 and 0-0.
409 * Remember that 1 can start 1-0 and 1/2.
412 gather_possible_numeric(char *line, unsigned char *linep, char initial_digit)
413 { LinePair resulting_line;
414 TokenType token = MOVE_NUMBER;
415 /* Keep a record of where this token started. */
416 const unsigned char *symbol_start = linep-1;
418 if(initial_digit == '0'){
419 /* Could be castling or a result. */
420 if(strncmp((const char *) linep,"-1",2) == 0){
421 token = TERMINATING_RESULT;
425 else if(strncmp((const char *) linep,"-0-0",4) == 0){
430 else if(strncmp((const char *) linep,"-0",2) == 0){
439 else if(initial_digit == '1'){
440 if(strncmp((const char *) linep,"-0",2) == 0){
441 token = TERMINATING_RESULT;
445 else if(strncmp((const char *) linep,"/2",2) == 0){
446 token = TERMINATING_RESULT;
448 /* Check for the full form. */
449 if(strncmp((const char *) linep,"-1/2",4) == 0){
450 token = TERMINATING_RESULT;
453 /* Make sure that the full form of the draw result
456 save_string("1/2-1/2");
465 if(token == MOVE_NUMBER){
466 /* Gather the remaining digits. */
467 while(isdigit((unsigned) *linep)){
471 if(token == MOVE_NUMBER){
472 /* Fill out the fields of yylval. */
473 if(extract_yytext(symbol_start,linep)){
474 yylval.move_number = 0;
475 (void) sscanf((const char *)yytext,"%u",&yylval.move_number);
476 /* Skip any trailing dots. */
477 while(*linep == '.'){
486 /* TERMINATING_RESULT and MOVE have already been dealt with. */
488 resulting_line.line = line;
489 resulting_line.linep = linep;
490 resulting_line.token = token;
491 return resulting_line;
494 /* Look up tag_string in TagList[] and return its _TAG
495 * value or -1 if it isn't there.
496 * Although the strings are sorted initially, further
497 * tags identified in the source files will be appended
498 * without further sorting. So we cannot use a binary
499 * search on the list.
502 identify_tag(const char *tag_string)
503 { unsigned tag_index;
505 for(tag_index = 0; tag_index < tag_list_length; tag_index++){
506 if(strcmp(tag_string,TagList[tag_index]) == 0){
514 /* Starting from linep in line, gather up the tag name.
515 * Skip over any preceding white space.
518 gather_tag(char *line, unsigned char *linep)
519 { LinePair resulting_line;
524 /* Check for end of line while skipping white space. */
526 line = next_input_line(yyin);
527 linep = (unsigned char *) line;
530 while(ChTab[(unsigned)*linep] == WHITESPACE){
535 while((line != NULL) && (ChTab[(unsigned)*linep] == '\0'));
539 while(isalpha((unsigned) ch) || isdigit((unsigned) ch) || (ch == '_')){
543 /* The last one wasn't part of the tag. */
549 /* Allocate space for the result. */
550 tag_string = MallocOrDie(len+1);
551 strncpy((char *)tag_string,(const char *)(linep-len),len);
552 tag_string[len] = '\0';
553 tag_item = identify_tag(tag_string);
555 tag_item = make_new_tag(tag_string);
557 if(tag_item >= 0 && ((unsigned) tag_item) < tag_list_length){
558 yylval.tag_index = tag_item;
559 resulting_line.token = TAG;
560 (void) free((void *)tag_string);
563 fprintf(GlobalState.logfile,
564 "Internal error: invalid tag index %d in gather_tag.\n",
570 resulting_line.token = NO_TOKEN;
574 resulting_line.token = NO_TOKEN;
576 resulting_line.line = line;
577 resulting_line.linep = linep;
578 return resulting_line;
582 extract_yytext(const unsigned char *symbol_start,const unsigned char *linep)
583 { /* Whether the string fitted. */
585 long len = linep-symbol_start;
587 if(len < MAX_YYTEXT){
588 strncpy((char *) yytext,(const char *) symbol_start,len);
592 strncpy((char *) yytext,(const char *) symbol_start,MAX_YYTEXT);
593 yytext[MAX_YYTEXT] = '\0';
594 if(!GlobalState.skipping_current_game)
595 fprintf(GlobalState.logfile,"Symbol %s exceeds length of %u.\n",
602 /* Identify the next symbol.
603 * Don't take any action on EOF -- leave that to next_token.
606 get_next_symbol(void)
607 { static char *line = NULL;
608 static unsigned char *linep = NULL;
609 /* The token to be returned. */
611 LinePair resulting_line;
614 /* Remember where in line the current symbol starts. */
615 const unsigned char *symbol_start;
617 /* Clear any remaining symbol. */
620 line = next_input_line(yyin);
621 linep = (unsigned char *) line;
630 int next_char = *linep & 0x0ff;
632 /* Remember where we start. */
633 symbol_start = linep;
635 token = ChTab[next_char];
639 while(ChTab[(unsigned)*linep] == WHITESPACE)
644 resulting_line = gather_tag(line,linep);
645 /* Pick up where we are now. */
646 line = resulting_line.line;
647 linep = resulting_line.linep;
648 token = resulting_line.token;
654 resulting_line = gather_string(line,linep);
655 /* Pick up where we are now. */
656 line = resulting_line.line;
657 linep = resulting_line.linep;
658 token = resulting_line.token;
661 resulting_line = gather_comment(line,linep);
662 /* Pick up where we are now. */
663 line = resulting_line.line;
664 linep = resulting_line.linep;
665 token = resulting_line.token;
668 if(!GlobalState.skipping_current_game) {
669 fprintf(GlobalState.logfile,"Unmatched comment end.\n");
674 while(isdigit((unsigned) *linep)){
677 if(extract_yytext(symbol_start,linep)){
678 save_string((const char *) yytext);
685 /* Don't return anything in case of error. */
687 while(ChTab[(unsigned)*linep] == ANNOTATE){
690 if(extract_yytext(symbol_start,linep)){
725 while(ChTab[(unsigned)*linep] == CHECK_SYMBOL){
730 while(ChTab[(unsigned)*linep] == DOT)
735 /* Trash the rest of the line. */
736 line = next_input_line(yyin);
737 linep = (unsigned char *) line;
741 /* @@@ What to do about this? */
748 /* Not all ALPHAs are move characters. */
749 if(MoveChars[next_char]){
750 /* Scan through the possible move characters. */
751 while(MoveChars[*linep & 0x0ff]){
754 if(extract_yytext(symbol_start,linep)){
755 /* Only classify it as a move if it
756 * seems to be a complete move.
758 if(move_seems_valid(yytext)){
763 if(!GlobalState.skipping_current_game){
764 print_error_context(GlobalState.logfile);
765 fprintf(GlobalState.logfile,
766 "Unknown move text %s.\n",yytext);
776 if(!GlobalState.skipping_current_game){
777 print_error_context(GlobalState.logfile);
778 fprintf(GlobalState.logfile,
779 "Unknown character %c (Hex: %x).\n",
780 next_char,next_char);
782 /* Skip any sequence of them. */
783 while(ChTab[(unsigned)*linep] == ERROR_TOKEN)
788 /* Remember that 0 can start 0-1 and 0-0.
789 * Remember that 1 can start 1-0 and 1/2.
791 resulting_line = gather_possible_numeric(
792 line,linep,next_char);
793 /* Pick up where we are now. */
794 line = resulting_line.line;
795 linep = resulting_line.linep;
796 token = resulting_line.token;
808 if(!GlobalState.skipping_current_game){
809 print_error_context(GlobalState.logfile);
810 fprintf(GlobalState.logfile,"Too many ')' found.\n");
817 token = TERMINATING_RESULT;
820 if(ChTab[(unsigned) *linep] == DASH) {
822 save_move((const unsigned char *) NULL_MOVE_STRING);
826 fprintf(GlobalState.logfile,"Single '-' not allowed.\n");
827 print_error_context(GlobalState.logfile);
832 /* End of the string. */
833 line = next_input_line(yyin);
834 linep = (unsigned char *) line;
838 if(!GlobalState.skipping_current_game){
839 print_error_context(GlobalState.logfile);
840 fprintf(GlobalState.logfile,
841 "Unknown character %c (Hex: %x).\n",
842 next_char,next_char);
844 /* Skip any sequence of them. */
845 while(ChTab[(unsigned)*linep] == ERROR_TOKEN)
849 print_error_context(GlobalState.logfile);
850 fprintf(GlobalState.logfile,
851 "Operator in illegal context: %c.\n",*symbol_start);
852 /* Skip any sequence of them. */
853 while(ChTab[(unsigned)*linep] == OPERATOR)
858 if(!GlobalState.skipping_current_game){
859 print_error_context(GlobalState.logfile);
860 fprintf(GlobalState.logfile,
861 "Internal error: Missing case for %d on char %x.\n",
868 } while(token == NO_TOKEN);
874 { TokenType token = get_next_symbol();
876 /* Don't call yywrap if parsing the ECO file. */
877 while((token == EOF_TOKEN) && !GlobalState.parsing_ECO_file &&
879 token = get_next_symbol();
884 /* Return TRUE if token is one to skip when looking for
885 * the start or end of a game.
888 skip_token(TokenType token)
891 case TERMINATING_RESULT:
901 /* Skip tokens until the next game looks like it is
902 * about to start. This is signalled by
903 * a tag section a terminating result from the
904 * previous game, or a move.
907 skip_to_next_game(TokenType token)
909 if(skip_token(token)){
910 GlobalState.skipping_current_game = TRUE;
912 if(token == COMMENT){
913 /* Free the space. */
914 if((yylval.comment != NULL) &&
915 (yylval.comment->Comment != NULL)){
916 free_string_list(yylval.comment->Comment);
917 free((void *)yylval.comment);
918 yylval.comment = NULL;
921 token = next_token();
922 } while(skip_token(token));
923 GlobalState.skipping_current_game = FALSE;
928 /* Save castling moves in a standard way. */
932 save_move((const unsigned char *) "O-O-O");
935 /* Save castling moves in a standard way. */
939 save_move((const unsigned char *) "O-O");
942 /* Make a copy of the matched text of the move. */
944 save_move(const unsigned char *move)
946 /* Decode the move into its components. */
947 yylval.move_details = decode_move(move);
948 /* Remember the last move. */
949 strcpy((char *) last_move,(const char *) move);
953 restart_lex_for_new_game(void)
959 /* Make it possible to read multiple input files.
960 * These are held in list_of_files. The list
961 * is built up from the program's arguments.
963 static int current_file_num = 0;
964 /* Keep track of the list of PGN files. These will either be the
965 * remaining arguments once flags have been dealt with, or
966 * those read from -c and -f arguments.
968 static FILE_LIST list_of_files = {
969 (const char **) NULL,
970 (SourceFileType *) NULL,
974 /* Return the index number of the current input file in list_of_files. */
976 current_file_number(void)
978 return current_file_num;
982 /* Read a list of lines from fp. These are the names of files
983 * to be added to the existing list_of_files.
984 * list_of_files.list must have a (char *)NULL on the end.
987 add_filename_list_from_file(FILE *fp,SourceFileType file_type)
989 if((list_of_files.files == NULL) || (list_of_files.max_files == 0)){
990 /* Allocate an initial number of pointers for the lines.
991 * This must always include an extra one for terminating NULL.
993 list_of_files.files = (const char **) MallocOrDie((INIT_LIST_SPACE+1)*
994 sizeof(const char *));
995 list_of_files.file_type = (SourceFileType *) MallocOrDie((INIT_LIST_SPACE+1)*
996 sizeof(SourceFileType));
997 list_of_files.max_files = INIT_LIST_SPACE;
998 list_of_files.num_files = 0;
1000 if(list_of_files.files != NULL){
1001 /* Find the first line. */
1002 char *line = read_line(fp);
1004 while(line != NULL){
1005 if(non_blank_line(line)){
1006 add_filename_to_source_list(line,file_type);
1009 (void) free((void *)line);
1011 line = read_line(fp);
1017 add_filename_to_source_list(const char *filename,SourceFileType file_type)
1018 { /* Where to put it. */
1019 unsigned location = list_of_files.num_files;
1021 if(access(filename,R_OK) != 0){
1022 fprintf(GlobalState.logfile,"Unable to find %s\n",filename);
1028 /* See if there is room. */
1029 if(list_of_files.num_files == list_of_files.max_files){
1030 /* There isn't, so increase the amount of available space,
1031 * ensuring that there is always an extra slot for the terminating
1034 if((list_of_files.files == NULL) || (list_of_files.max_files == 0)){
1035 /* Allocate an initial number of pointers for the lines.
1036 * This must always include an extra one for terminating NULL.
1038 list_of_files.files = (const char **) MallocOrDie((INIT_LIST_SPACE+1)*
1039 sizeof(const char *));
1040 list_of_files.file_type = (SourceFileType *)
1041 MallocOrDie((INIT_LIST_SPACE+1)*
1042 sizeof(SourceFileType));
1043 list_of_files.max_files = INIT_LIST_SPACE;
1044 list_of_files.num_files = 0;
1047 list_of_files.files = (const char **)realloc((void *)list_of_files.files,
1048 (list_of_files.max_files+MORE_LIST_SPACE+1)*
1049 sizeof(const char *));
1050 list_of_files.file_type = (SourceFileType *)
1051 realloc((void *)list_of_files.file_type,
1052 (list_of_files.max_files+MORE_LIST_SPACE+1)*
1053 sizeof(SourceFileType));
1054 list_of_files.max_files += MORE_LIST_SPACE;
1055 if((list_of_files.files == NULL) && (list_of_files.file_type == NULL)){
1061 /* We know that there is space. Ensure that CHECKFILEs are all
1062 * stored before NORMALFILEs.
1064 if(file_type == CHECKFILE){
1066 for(location = 0; (location < list_of_files.num_files) &&
1067 (list_of_files.file_type[location] == CHECKFILE); location++){
1070 if(location < list_of_files.num_files){
1071 /* Put the new one here.
1072 * Move the rest down.
1076 for(j = list_of_files.num_files; j > location; j--){
1077 list_of_files.files[j] = list_of_files.files[j-1];
1078 list_of_files.file_type[j] = list_of_files.file_type[j-1];
1082 list_of_files.files[location] = copy_string(filename);
1083 list_of_files.file_type[location] = file_type;
1084 list_of_files.num_files++;
1085 /* Keep the list properly terminated. */
1086 list_of_files.files[list_of_files.num_files] = (char *) NULL;
1089 /* Use infile as the input source. */
1091 open_input(const char *infile)
1093 yyin = fopen(infile,"r");
1095 GlobalState.current_input_file = infile;
1096 if(GlobalState.verbose){
1097 fprintf(GlobalState.logfile,"Processing %s\n",
1098 GlobalState.current_input_file);
1101 return yyin != NULL;
1104 /* Simple interface to open_input for the ECO file. */
1106 open_eco_file(const char *eco_file)
1108 return open_input(eco_file);
1111 /* Open the input file whose number is the argument. */
1113 open_input_file(int file_number)
1115 /* Depending on the type of file, ensure that the
1116 * current_file_type is set correctly.
1118 if(open_input(list_of_files.files[file_number])){
1119 GlobalState.current_file_type = list_of_files.file_type[file_number];
1127 /* Open the first input file. */
1129 open_first_file(void)
1130 { Boolean ok = TRUE;
1132 if(list_of_files.num_files == 0){
1133 /* Use standard input. */
1135 GlobalState.current_input_file = "stdin";
1136 /* @@@ Should this be set?
1137 GlobalState.current_file_type = NORMALFILE;
1139 if(GlobalState.verbose){
1140 fprintf(GlobalState.logfile,"Processing %s\n",
1141 GlobalState.current_input_file);
1144 else if(open_input_file(0)){
1147 fprintf(GlobalState.logfile,
1148 "Unable to open the PGN file: %s\n",input_file_name(0));
1154 /* Return the name of the file corresponding to the given
1158 input_file_name(unsigned file_number)
1160 if(file_number >= list_of_files.num_files) {
1164 return list_of_files.files[file_number];
1169 /* Give some error information. */
1171 print_error_context(FILE *fp)
1173 if(GlobalState.current_input_file != NULL){
1174 fprintf(fp,"File %s: ",GlobalState.current_input_file);
1176 fprintf(fp,"Line number: %lu\n",line_number);
1180 /* Make the given str accessible. */
1182 save_string(const char *str)
1183 { const size_t len = strlen(str);
1186 token = MallocOrDie(len+1);
1188 yylval.token_string = token;
1191 /* Return the next line of input from fp. */
1193 next_input_line(FILE *fp)
1194 { /* Retain each line in turn, so as to be able to free it. */
1195 static char *line = NULL;
1198 (void) free((void *)line);
1201 line = read_line(fp);
1209 /* Handle the end of a file. */
1214 /* Beware of this being called in inappropriate circumstances. */
1215 if(list_of_files.files == NULL){
1216 /* There are no files. */
1219 else if(input_file_name(current_file_num) == NULL){
1220 /* There was no last file! */
1224 /* Close the input files. */
1226 /* See if there is another. */
1228 if(input_file_name(current_file_num) == NULL){
1229 /* We have processed the last file. */
1232 else if(!open_input_file(current_file_num)){
1233 fprintf(GlobalState.logfile,"Unable to open the PGN file: %s\n",
1234 input_file_name(current_file_num));
1238 /* Ok, we opened it. */
1240 /* Set everything up for a new file. */
1241 /* Depending on the type of file, ensure that the
1242 * current_file_type is set correctly.
1244 GlobalState.current_file_type =
1245 list_of_files.file_type[current_file_num];
1246 restart_lex_for_new_game();
1248 reset_line_number();
1251 return time_to_exit;
1255 /* Reset the file's line number. */
1257 reset_line_number(void)
1263 terminate_input(void)
1265 if((yyin != stdin) && (yyin != NULL)){
1266 (void) fclose(yyin);