src/chess/pgn.c

changeset 209
dc4f34426c66
parent 194
619f07c95894
equal deleted inserted replaced
208:e9c3d3e89465 209:dc4f34426c66
39 "No Error.", 39 "No Error.",
40 "Tag values must be enclosed in double-quotes.", 40 "Tag values must be enclosed in double-quotes.",
41 "Missing closing brace '}' for comment.", 41 "Missing closing brace '}' for comment.",
42 "Tags must be enclosed in square brackets: '[Key \"Value\"]'.", 42 "Tags must be enclosed in square brackets: '[Key \"Value\"]'.",
43 "Move numbers must be terminated with a dot (e.g. '13.' - not '13').", 43 "Move numbers must be terminated with a dot (e.g. '13.' - not '13').",
44 "Move is syntactically incorrect.",
45 "Move is not valid according to chess rules.",
46 "Result syntax is incorrect. Expected 1-0, 0-1, 1/2-1/2, or *.", 44 "Result syntax is incorrect. Expected 1-0, 0-1, 1/2-1/2, or *.",
47 "Result at end of move text does not match result in tag roster.", 45 "Result at end of move text does not match result in tag roster.",
48 "Unexpected end of data.", 46 "Unexpected end of data.",
47 "Invalid move.",
49 }; 48 };
50 49
51 const char* pgn_error_str(int code) { 50 const char* pgn_error_str(int code) {
52 return pgn_error_strings[code]; 51 return pgn_error_strings[code];
53 } 52 }
54 53
55 size_t pgn_error_pos; 54 static const char *pgn_game_result(const GameState *gamestate) {
56
57 size_t pgn_error_position(void) {
58 return pgn_error_pos;
59 }
60
61 static const char *pgn_result(const GameState *gamestate) {
62 if (is_game_drawn(gamestate)) { 55 if (is_game_drawn(gamestate)) {
63 return "1/2-1/2"; 56 return "1/2-1/2";
64 } else if (gamestate->wresign) { 57 } else if (gamestate->wresign) {
65 return "0-1"; 58 return "0-1";
66 } else if (gamestate->bresign) { 59 } else if (gamestate->bresign) {
74 } else { 67 } else {
75 return "*"; 68 return "*";
76 } 69 }
77 } 70 }
78 71
79 #define return(code) pgn_error_pos = pgndata - pgndata0; return code 72 static pgn_result create_pgn_result(
80 73 const char *datastart, const char *datacur,
81 int parse_pgn(const char *pgndata, GameState *gamestate) { 74 enum pgn_error_code code, unsigned move_half_number) {
75 return (pgn_result) {
76 .position = (size_t)(datacur - datastart),
77 .code = code,
78 .move_half_number = move_half_number
79 };
80 }
81
82 static pgn_result create_pgn_move_error(
83 const char *datastart, const char *datacur,
84 int move_error_code, unsigned move_half_number) {
85 return (pgn_result) {
86 .position = (size_t)(datacur - datastart),
87 .code = pgn_error_move,
88 .move_error = move_error_code,
89 .move_half_number = move_half_number
90 };
91 }
92
93 pgn_result parse_pgn(const char *pgndata, GameState *gamestate) {
82 const char * const pgndata0 = pgndata; 94 const char * const pgndata0 = pgndata;
83 int i; 95 int i;
84 char c; 96 char c;
85 97
86 char result[8] = {0}; 98 char result[8] = {0};
93 while (isspace(c = *(pgndata++))); 105 while (isspace(c = *(pgndata++)));
94 if (c == '1') { 106 if (c == '1') {
95 break; 107 break;
96 } 108 }
97 if (c != '[') { 109 if (c != '[') {
98 return(pgn_error_missing_bracket); 110 return create_pgn_result(pgndata0, pgndata,
111 pgn_error_missing_bracket, 0);
99 } 112 }
100 while (isspace(c = *(pgndata++))); 113 while (isspace(c = *(pgndata++)));
101 i = 0; 114 i = 0;
102 do { 115 do {
103 tagkey[i++] = c; 116 tagkey[i++] = c;
104 } while (!isspace(c = *(pgndata++))); 117 } while (!isspace(c = *(pgndata++)));
105 tagkey[i] = '\0'; 118 tagkey[i] = '\0';
106 while (isspace(c = *(pgndata++))); 119 while (isspace(c = *(pgndata++)));
107 if (c != '"') { 120 if (c != '"') {
108 return(pgn_error_missing_quote); 121 return create_pgn_result(pgndata0, pgndata,
122 pgn_error_missing_quote, 0);
109 } 123 }
110 i = 0; 124 i = 0;
111 while ((c = *(pgndata++)) != '"') { 125 while ((c = *(pgndata++)) != '"') {
112 if (c == '\n' || c == 0) { 126 if (c == '\n' || c == 0) {
113 return(pgn_error_missing_quote); 127 return create_pgn_result(pgndata0, pgndata,
128 pgn_error_missing_quote, 0);
114 } 129 }
115 tagvalue[i++] = c; 130 tagvalue[i++] = c;
116 } 131 }
117 tagvalue[i] = '\0'; 132 tagvalue[i] = '\0';
118 if (*(pgndata++) != ']') { 133 if (*(pgndata++) != ']') {
119 return(pgn_error_missing_bracket); 134 return create_pgn_result(pgndata0, pgndata,
135 pgn_error_missing_bracket, 0);
120 } 136 }
121 137
122 // TODO: read clock info 138 // TODO: read clock info
123 139
124 if (strcmp("Result", tagkey) == 0) { 140 if (strcmp("Result", tagkey) == 0) {
126 } 142 }
127 } 143 }
128 144
129 /* read moves */ 145 /* read moves */
130 if (*(pgndata++) != '.') { 146 if (*(pgndata++) != '.') {
131 return(pgn_error_missing_dot); 147 return create_pgn_result(pgndata0, pgndata,
148 pgn_error_missing_dot, 1);
132 } 149 }
133 150
134 char movestr[10]; 151 char movestr[10];
135 Move move; 152 Move move;
136 Color curcol = WHITE; 153 Color curcol = WHITE;
137 bool movetext_ends_with_result = false; 154 bool movetext_ends_with_result = false;
155 unsigned move_half_number = 1;
138 156
139 while (true) { 157 while (true) {
140 /* move */ 158 /* move */
141 while (isspace(c = *(pgndata++))); 159 while (isspace(c = *(pgndata++)));
142 i = 0; 160 i = 0;
143 do { 161 do {
144 movestr[i++] = c; 162 movestr[i++] = c;
145 if (i >= 10) { 163 if (i >= 10) {
146 return(pgn_error_move_syntax); 164 return create_pgn_move_error(pgndata0, pgndata,
165 INVALID_MOVE_SYNTAX, move_half_number);
147 } 166 }
148 } while (!isspace(c = *(pgndata++))); 167 } while (!isspace(c = *(pgndata++)));
149 movestr[i] = '\0'; 168 movestr[i] = '\0';
150 if (eval_move_strict(gamestate, movestr, curcol, &move) 169 int move_result = eval_move_strict(gamestate, movestr, curcol, &move);
151 != VALID_MOVE_SYNTAX) { 170 if (!move_result) {
152 return(pgn_error_move_syntax); 171 move_result = validate_move(gamestate, &move);
153 } 172 }
154 int move_validate_result = validate_move(gamestate, &move); 173 if (move_result) {
155 if (move_validate_result != VALID_MOVE_SEMANTICS) { 174 return create_pgn_move_error(pgndata0, pgndata,
156 return(pgn_error_move_semantics); 175 move_result, move_half_number);
157 } 176 }
158 format_move(gamestate, &move); 177 format_move(gamestate, &move);
159 apply_move(gamestate, &move); 178 apply_move(gamestate, &move);
160 179
161 /* skip spaces */ 180 /* skip spaces */
166 // TODO: interpret comment (may contain clock info) 185 // TODO: interpret comment (may contain clock info)
167 do { 186 do {
168 c = *(pgndata++); 187 c = *(pgndata++);
169 } while (c != '}' && c != 0); 188 } while (c != '}' && c != 0);
170 if (c == 0) { 189 if (c == 0) {
171 return(pgn_error_missing_brace); 190 return create_pgn_result(pgndata0, pgndata,
191 pgn_error_missing_brace, move_half_number);
172 } 192 }
173 /* skip spaces */ 193 /* skip spaces */
174 while (isspace(c = *(pgndata++))); 194 while (isspace(c = *(pgndata++)));
175 } 195 }
176 196
186 if (c == '1') { 206 if (c == '1') {
187 gamestate->wresign = true; 207 gamestate->wresign = true;
188 } else if (c == '0') { 208 } else if (c == '0') {
189 gamestate->bresign = true; 209 gamestate->bresign = true;
190 } else { 210 } else {
191 return(pgn_error_result_syntax); 211 return create_pgn_result(pgndata0, pgndata,
212 pgn_error_result_syntax, 0);
192 } 213 }
193 } 214 }
194 movetext_ends_with_result = true; 215 movetext_ends_with_result = true;
195 break; 216 break;
196 } else if (c == '/') { 217 } else if (c == '/') {
209 230
210 /* skip move number after black move */ 231 /* skip move number after black move */
211 if (curcol == BLACK) { 232 if (curcol == BLACK) {
212 while (isdigit(c = *(pgndata++))); 233 while (isdigit(c = *(pgndata++)));
213 if (c != '.') { 234 if (c != '.') {
214 return(pgn_error_missing_dot); 235 return create_pgn_result(pgndata0, pgndata,
236 pgn_error_missing_dot, move_half_number + 1);
215 } 237 }
216 } 238 }
217 curcol = opponent_color(curcol); 239 curcol = opponent_color(curcol);
240 move_half_number++;
218 } 241 }
219 242
220 /* sanity check result - if it was specified */ 243 /* sanity check result - if it was specified */
221 if (movetext_ends_with_result && result[0]) { 244 if (movetext_ends_with_result && result[0]) {
222 if (strncmp(result, pgn_result(gamestate), 8) != 0) { 245 if (strncmp(result, pgn_game_result(gamestate), 8) != 0) {
223 return(pgn_error_result_mismatch); 246 return create_pgn_result(pgndata0, pgndata,
224 } 247 pgn_error_result_mismatch, 0);
225 } 248 }
226 249 }
227 return(pgn_no_error); 250
251 return create_pgn_result(pgndata0, pgndata, pgn_no_error, 0);
228 } 252 }
229 253
230 #undef return 254 #undef return
231 255
232 int read_pgn(FILE *stream, GameState *gamestate) { 256 pgn_result read_pgn(FILE *stream, GameState *gamestate) {
233 fseek(stream, 0, SEEK_END); 257 fseek(stream, 0, SEEK_END);
234 size_t size = ftell(stream); 258 size_t size = ftell(stream);
235 fseek(stream, 0, SEEK_SET); 259 fseek(stream, 0, SEEK_SET);
236 char *data = malloc(size + 1); 260 char *data = malloc(size + 1);
237 memset(data, 0, size + 1); 261 memset(data, 0, size + 1);
238 size_t read = 0; 262 size_t read = 0;
239 do { 263 do {
240 size_t r = fread(data + read, 1, size - read, stream); 264 size_t r = fread(data + read, 1, size - read, stream);
241 if (r == 0) { 265 if (r == 0) {
242 pgn_error_pos = read; 266 return (pgn_result) {
243 return pgn_error_unexpected_eof; 267 .position = read,
268 .code = pgn_error_unexpected_eof
269 };
244 } 270 }
245 read += r; 271 read += r;
246 } while (read < size); 272 } while (read < size);
247 int result = parse_pgn(data, gamestate); 273 pgn_result result = parse_pgn(data, gamestate);
248 free(data); 274 free(data);
249 return result; 275 return result;
250 } 276 }
251 277
252 static void pgn_insert_newlines(char *block) { 278 static void pgn_insert_newlines(char *block) {
326 resp += sprintf(resp, "[Site \"%s\"]\n", "Somewhere on Earth"); 352 resp += sprintf(resp, "[Site \"%s\"]\n", "Somewhere on Earth");
327 resp += sprintf(resp, "[Date \"%s\"]\n", pgn_date(gamestate)); 353 resp += sprintf(resp, "[Date \"%s\"]\n", pgn_date(gamestate));
328 resp += sprintf(resp, "[Round \"%s\"]\n", "-"); 354 resp += sprintf(resp, "[Round \"%s\"]\n", "-");
329 resp += sprintf(resp, "[White \"%s\"]\n", pgn_player_name(gamestate, WHITE)); 355 resp += sprintf(resp, "[White \"%s\"]\n", pgn_player_name(gamestate, WHITE));
330 resp += sprintf(resp, "[Black \"%s\"]\n", pgn_player_name(gamestate, BLACK)); 356 resp += sprintf(resp, "[Black \"%s\"]\n", pgn_player_name(gamestate, BLACK));
331 const char *gameresult = pgn_result(gamestate); 357 const char *gameresult = pgn_game_result(gamestate);
332 resp += sprintf(resp, "[Result \"%s\"]\n\n", gameresult); 358 resp += sprintf(resp, "[Result \"%s\"]\n\n", gameresult);
333 359
334 // TODO: add optional clock info 360 // TODO: add optional clock info
335 361
336 /* moves */ 362 /* moves */

mercurial