| 41 pgn_error_missing_brace, |
41 pgn_error_missing_brace, |
| 42 pgn_error_missing_dot, |
42 pgn_error_missing_dot, |
| 43 pgn_error_move_syntax, |
43 pgn_error_move_syntax, |
| 44 pgn_error_move_semantics, |
44 pgn_error_move_semantics, |
| 45 pgn_error_result_syntax, |
45 pgn_error_result_syntax, |
| |
46 pgn_error_unexpected_eof, |
| 46 }; |
47 }; |
| 47 |
48 |
| 48 static const char* pgn_error_strings[] = { |
49 static const char* pgn_error_strings[] = { |
| 49 "No Error.", |
50 "No Error.", |
| 50 "Tag values must be enclosed in double-quotes.", |
51 "Tag values must be enclosed in double-quotes.", |
| 52 "Tags must be enclosed in square brackets: '[Key \"Value\"]'.", |
53 "Tags must be enclosed in square brackets: '[Key \"Value\"]'.", |
| 53 "Move numbers must be terminated with a dot (e.g. '13.' - not '13').", |
54 "Move numbers must be terminated with a dot (e.g. '13.' - not '13').", |
| 54 "Move is syntactically incorrect.", |
55 "Move is syntactically incorrect.", |
| 55 "Move is not valid according to chess rules.", |
56 "Move is not valid according to chess rules.", |
| 56 "Result syntax is incorrect. Expected 1-0, 0-1, 1/2-1/2, or *.", |
57 "Result syntax is incorrect. Expected 1-0, 0-1, 1/2-1/2, or *.", |
| |
58 "Unexpected end of data.", |
| 57 }; |
59 }; |
| 58 |
60 |
| 59 const char* pgn_error_str(int code) { |
61 const char* pgn_error_str(int code) { |
| 60 return pgn_error_strings[code]; |
62 return pgn_error_strings[code]; |
| 61 } |
63 } |
| 62 |
64 |
| 63 struct pgn_input { |
65 int parse_pgn(const char *pgndata, GameState *gamestate) { |
| 64 union { |
66 int i; |
| 65 FILE *f; |
67 char c; |
| 66 const char *s; |
|
| 67 } data; |
|
| 68 int (*getc)(struct pgn_input *); |
|
| 69 void (*backc)(struct pgn_input *); |
|
| 70 }; |
|
| 71 |
|
| 72 static int pgngetc(struct pgn_input *pgn) { |
|
| 73 return pgn->getc(pgn); |
|
| 74 } |
|
| 75 |
|
| 76 static void pgnbackc(struct pgn_input *pgn) { |
|
| 77 pgn->backc(pgn); |
|
| 78 } |
|
| 79 |
|
| 80 static int pgnfgetc(struct pgn_input *pgn) { |
|
| 81 return fgetc(pgn->data.f); |
|
| 82 } |
|
| 83 |
|
| 84 static void pgnfbackc(struct pgn_input *pgn) { |
|
| 85 fseek(pgn->data.f, -1, SEEK_CUR); |
|
| 86 } |
|
| 87 |
|
| 88 static void pgnsbackc(struct pgn_input *pgn) { |
|
| 89 --(pgn->data.s); |
|
| 90 } |
|
| 91 |
|
| 92 static int pgnsgetc(struct pgn_input *pgn) { |
|
| 93 int c = *(unsigned char *) pgn->data.s; |
|
| 94 pgn->data.s++; |
|
| 95 return c; |
|
| 96 } |
|
| 97 |
|
| 98 static int read_pgn_(struct pgn_input pgndata, GameState *gamestate) { |
|
| 99 int c, i; |
|
| 100 |
68 |
| 101 char result[8]; |
69 char result[8]; |
| 102 |
70 |
| 103 char tagkey[32]; |
71 char tagkey[32]; |
| 104 char tagvalue[128]; |
72 char tagvalue[128]; |
| 105 |
73 |
| 106 /* read tag pairs */ |
74 /* read tag pairs */ |
| 107 while (true) { |
75 while (true) { |
| 108 while (isspace(c = pgngetc(&pgndata))); |
76 while (isspace(c = *(pgndata++))); |
| 109 if (c == '1') { |
77 if (c == '1') { |
| 110 break; |
78 break; |
| 111 } |
79 } |
| 112 if (c != '[') { |
80 if (c != '[') { |
| 113 return pgn_error_missing_bracket; |
81 return pgn_error_missing_bracket; |
| 114 } |
82 } |
| 115 while (isspace(c = pgngetc(&pgndata))); |
83 while (isspace(c = *(pgndata++))); |
| 116 i = 0; |
84 i = 0; |
| 117 do { |
85 do { |
| 118 tagkey[i++] = c; |
86 tagkey[i++] = c; |
| 119 } while (!isspace(c = pgngetc(&pgndata))); |
87 } while (!isspace(c = *(pgndata++))); |
| 120 tagkey[i] = '\0'; |
88 tagkey[i] = '\0'; |
| 121 while (isspace(c = pgngetc(&pgndata))); |
89 while (isspace(c = *(pgndata++))); |
| 122 if (c != '"') { |
90 if (c != '"') { |
| 123 return pgn_error_missing_quote; |
91 return pgn_error_missing_quote; |
| 124 } |
92 } |
| 125 i = 0; |
93 i = 0; |
| 126 while ((c = pgngetc(&pgndata)) != '"') { |
94 while ((c = *(pgndata++)) != '"') { |
| 127 if (c == '\n' || c == EOF) { |
95 if (c == '\n' || c == 0) { |
| 128 return pgn_error_missing_quote; |
96 return pgn_error_missing_quote; |
| 129 } |
97 } |
| 130 tagvalue[i++] = c; |
98 tagvalue[i++] = c; |
| 131 } |
99 } |
| 132 tagvalue[i] = '\0'; |
100 tagvalue[i] = '\0'; |
| 133 if (pgngetc(&pgndata) != ']') { |
101 if (*(pgndata++) != ']') { |
| 134 return pgn_error_missing_bracket; |
102 return pgn_error_missing_bracket; |
| 135 } |
103 } |
| 136 |
104 |
| 137 // TODO: read clock info |
105 // TODO: read clock info |
| 138 if (strcmp("Result", tagkey) == 0) { |
106 if (strcmp("Result", tagkey) == 0) { |
| 140 memcpy(result, tagvalue, 8); |
108 memcpy(result, tagvalue, 8); |
| 141 } |
109 } |
| 142 } |
110 } |
| 143 |
111 |
| 144 /* read moves */ |
112 /* read moves */ |
| 145 if (pgngetc(&pgndata) != '.') { |
113 if (*(pgndata++) != '.') { |
| 146 return pgn_error_missing_dot; |
114 return pgn_error_missing_dot; |
| 147 } |
115 } |
| 148 |
116 |
| 149 char movestr[10]; |
117 char movestr[10]; |
| 150 Move move; |
118 Move move; |
| 151 uint8_t curcol = WHITE; |
119 uint8_t curcol = WHITE; |
| 152 |
120 |
| 153 while (true) { |
121 while (true) { |
| 154 /* move */ |
122 /* move */ |
| 155 while (isspace(c = pgngetc(&pgndata))); |
123 while (isspace(c = *(pgndata++))); |
| 156 i = 0; |
124 i = 0; |
| 157 do { |
125 do { |
| 158 movestr[i++] = c; |
126 movestr[i++] = c; |
| 159 if (i >= 10) { |
127 if (i >= 10) { |
| 160 return pgn_error_move_syntax; |
128 return pgn_error_move_syntax; |
| 161 } |
129 } |
| 162 } while (!isspace(c = pgngetc(&pgndata))); |
130 } while (!isspace(c = *(pgndata++))); |
| 163 movestr[i] = '\0'; |
131 movestr[i] = '\0'; |
| 164 if (eval_move(gamestate, movestr, &move, curcol) |
132 if (eval_move(gamestate, movestr, &move, curcol) |
| 165 != VALID_MOVE_SYNTAX) { |
133 != VALID_MOVE_SYNTAX) { |
| 166 return pgn_error_move_syntax; |
134 return pgn_error_move_syntax; |
| 167 } |
135 } |
| 169 return pgn_error_move_semantics; |
137 return pgn_error_move_semantics; |
| 170 } |
138 } |
| 171 apply_move(gamestate, &move); |
139 apply_move(gamestate, &move); |
| 172 |
140 |
| 173 /* skip spaces */ |
141 /* skip spaces */ |
| 174 while (isspace(c = pgngetc(&pgndata))); |
142 while (isspace(c = *(pgndata++))); |
| 175 |
143 |
| 176 /* parse possible comment */ |
144 /* parse possible comment */ |
| 177 if (c == '{') { |
145 if (c == '{') { |
| 178 // TODO: interpret comment (may contain clock info) |
146 // TODO: interpret comment (may contain clock info) |
| 179 do { |
147 do { |
| 180 c = pgngetc(&pgndata); |
148 c = *(pgndata++); |
| 181 } while (c != '}' && c != EOF); |
149 } while (c != '}' && c != 0); |
| 182 if (c == EOF) { |
150 if (c == 0) { |
| 183 return pgn_error_missing_brace; |
151 return pgn_error_missing_brace; |
| 184 } |
152 } |
| 185 /* skip spaces */ |
153 /* skip spaces */ |
| 186 while (isspace(c = pgngetc(&pgndata))); |
154 while (isspace(c = *(pgndata++))); |
| 187 } |
155 } |
| 188 |
156 |
| 189 /* end of game data encountered */ |
157 /* end of game data encountered */ |
| 190 if (c == EOF) { |
158 if (c == 0) { |
| 191 break; |
159 break; |
| 192 } |
160 } |
| 193 if (c == '1' || c == '0') { |
161 if (c == '1' || c == '0') { |
| 194 c = pgngetc(&pgndata); |
162 c = *(pgndata++); |
| 195 // TODO: #842 - allow games to be continued if possible |
163 // TODO: #842 - allow games to be continued if possible |
| 196 if (c == '-') { |
164 if (c == '-') { |
| 197 if (!gamestate->checkmate) { |
165 if (!gamestate->checkmate) { |
| 198 // TODO: maybe we should not parse this here; |
166 // TODO: maybe we should not parse this here; |
| 199 // there is an unused result string from the STR |
167 // there is an unused result string from the STR |
| 200 c = pgngetc(&pgndata); |
168 c = *(pgndata++); |
| 201 if (c == '1') { |
169 if (c == '1') { |
| 202 gamestate->wresign = true; |
170 gamestate->wresign = true; |
| 203 } else if (c == '0') { |
171 } else if (c == '0') { |
| 204 gamestate->bresign = true; |
172 gamestate->bresign = true; |
| 205 } else { |
173 } else { |
| 210 } else if (c == '/') { |
178 } else if (c == '/') { |
| 211 gamestate->remis = !gamestate->stalemate; |
179 gamestate->remis = !gamestate->stalemate; |
| 212 break; |
180 break; |
| 213 } else { |
181 } else { |
| 214 /* oops, it was a move number, go back! */ |
182 /* oops, it was a move number, go back! */ |
| 215 pgnbackc(&pgndata); |
183 pgndata--; |
| 216 } |
184 } |
| 217 } |
185 } |
| 218 |
186 |
| 219 /* we have eaten the next valuable byte, so go back */ |
187 /* we have eaten the next valuable byte, so go back */ |
| 220 pgnbackc(&pgndata); |
188 pgndata--; |
| 221 |
189 |
| 222 /* skip move number after black move */ |
190 /* skip move number after black move */ |
| 223 if (curcol == BLACK) { |
191 if (curcol == BLACK) { |
| 224 while (isdigit(c = pgngetc(&pgndata))); |
192 while (isdigit(c = *(pgndata++))); |
| 225 if (c != '.') { |
193 if (c != '.') { |
| 226 return pgn_error_missing_dot; |
194 return pgn_error_missing_dot; |
| 227 } |
195 } |
| 228 } |
196 } |
| 229 curcol = opponent_color(curcol); |
197 curcol = opponent_color(curcol); |
| 231 |
199 |
| 232 return 0; |
200 return 0; |
| 233 } |
201 } |
| 234 |
202 |
| 235 int read_pgn(FILE *stream, GameState *gamestate) { |
203 int read_pgn(FILE *stream, GameState *gamestate) { |
| 236 return read_pgn_( |
204 fseek(stream, 0, SEEK_END); |
| 237 (struct pgn_input){ |
205 long int size = ftell(stream); |
| 238 {.f = stream}, |
206 fseek(stream, 0, SEEK_SET); |
| 239 pgnfgetc, |
207 char *data = malloc(size + 1); |
| 240 pgnfbackc |
208 memset(data, 0, size + 1); |
| 241 }, gamestate); |
209 size_t read = 0; |
| 242 } |
210 do { |
| 243 |
211 size_t r = fread(data + read, 1, size - read, stream); |
| 244 int parse_pgn(const char *data, GameState *gamestate) { |
212 if (r == 0) return pgn_error_unexpected_eof; |
| 245 return read_pgn_( |
213 read += r; |
| 246 (struct pgn_input){ |
214 } while (read < size); |
| 247 {.s = data}, |
215 int result = parse_pgn(data, gamestate); |
| 248 pgnsgetc, |
216 free(data); |
| 249 pgnsbackc |
217 return result; |
| 250 }, gamestate); |
|
| 251 } |
218 } |
| 252 |
219 |
| 253 static void pgn_insert_newlines(char *block) { |
220 static void pgn_insert_newlines(char *block) { |
| 254 size_t pos = 0; |
221 size_t pos = 0; |
| 255 size_t last_space_pos = 0; |
222 size_t last_space_pos = 0; |