src/chess/pgn.c

changeset 143
e9159c70ff9e
parent 142
780337e5cf6a
equal deleted inserted replaced
142:780337e5cf6a 143:e9159c70ff9e
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;

mercurial