| 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 */ |
| 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 */ |