1 /*
2 * parser.c --
3 *
4 * Copyright 2011, 2012, 2013, 2014, 2015, 2016 Free Software Foundation, Inc.
5 * ------------------------------------------------------------------------
6 *
7 * GNU XBoard is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or (at
10 * your option) any later version.
11 *
12 * GNU XBoard is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see http://www.gnu.org/licenses/. *
19 *
20 *------------------------------------------------------------------------
21 ** See the file ChangeLog for a revision history. */
22
23 #include "config.h"
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <ctype.h>
27 #include <string.h>
28 #include "common.h"
29 #include "backend.h"
30 #include "frontend.h"
31 #include "parser.h"
32 #include "moves.h"
33
34
35 extern Board boards[MAX_MOVES];
36 extern int PosFlags(int nr);
37 int yyboardindex;
38 int yyskipmoves = FALSE;
39 char currentMoveString[4096]; // a bit ridiculous size?
40 char *yy_text;
41
42 #define PARSEBUFSIZE 10000
43
44 static FILE *inputFile;
45 static char *inPtr, *parsePtr, *parseStart;
46 static char inputBuf[PARSEBUFSIZE];
47 static char yytext[PARSEBUFSIZE];
48 static char fromString = 0, lastChar = '\n';
49
50 #define NOTHING 0
51 #define NUMERIC 1
52 #define ALPHABETIC 2
53 #define BADNUMBER (-2000000000)
54
55 #define XCO 0
56 #define YCO 53
57 #define PIECE 94
58 #define MISC 155
59 #define JIS 200
60
61 unsigned char kanjiTab[] = {
62 '1', 0357, 0274, 0221, // kanji notation for arabic digits
63 '2', 0357, 0274, 0222,
64 '3', 0357, 0274, 0223,
65 '4', 0357, 0274, 0224,
66 '5', 0357, 0274, 0225,
67 '6', 0357, 0274, 0226,
68 '7', 0357, 0274, 0227,
69 '8', 0357, 0274, 0230,
70 '9', 0357, 0274, 0231,
71 'x', 0345, 0220, 0214,
72 's', 0345, 0205, 0210, // sente
73 'g', 0345, 0276, 0214, // gote
74 '-', 0346, 0212, 0225, // resign
75 0,
76 'a', 0344, 0270, 0200, // in reality these are numbers in Japanese a=1, b=2 etc.
77 'b', 0344, 0272, 0214,
78 'c', 0344, 0270, 0211,
79 'd', 0345, 0233, 0233,
80 'e', 0344, 0272, 0224,
81 'f', 0345, 0205, 0255,
82 'g', 0344, 0270, 0203,
83 'h', 0345, 0205, 0253,
84 'i', 0344, 0271, 0235,
85 ' ', 0343, 0200, 0200,
86 0,
87 'K', 0347, 0216, 0211, // piece names
88 'K', 0347, 0216, 0213,
89 'G', 0351, 0207, 0221,
90 'S', 0351, 0212, 0200,
91 'R', 0351, 0243, 0233,
92 'B', 0350, 0247, 0222,
93 'N', 0346, 0241, 0202,
94 'L', 0351, 0246, 0231,
95 'P', 0346, 0255, 0251,
96 'r', 0351, 0276, 0215,
97 'b', 0351, 0246, 0254,
98 'p', 0343, 0201, 0250,
99 'r', 0347, 0253, 0234,
100 '+', 0346, 0210, 0220,
101 'G', 0, 0, 0,
102 0,
103 '+', 0346, 0210, 0220, // helper
104 '@', 0346, 0211, 0223,
105 'p', 0346, 0211, 0213, // player
106 ':', 0357, 0274, 0232,
107 '-', 0344, 0272, 0206,
108 'f', 0344, 0270, 0212,
109 's', 0345, 0257, 0204,
110 'b', 0345, 0274, 0225,
111 'r', 0345, 0267, 0246,
112 'l', 0345, 0217, 0263,
113 'v', 0347, 0233, 0264,
114 0,
115 // shift-JIS
116 '1', 0202, 0120, 0,
117 '2', 0202, 0121, 0,
118 '3', 0202, 0122, 0,
119 '4', 0202, 0123, 0,
120 '5', 0202, 0124, 0,
121 '6', 0202, 0125, 0,
122 '7', 0202, 0126, 0,
123 '8', 0202, 0127, 0,
124 '9', 0202, 0130, 0,
125 'x', 0223, 0257, 0,
126 's', 0220, 0346, 0,
127 'g', 0214, 0343, 0,
128 '-', 0223, 0212, 0,
129 0,
130 'a', 0210, 0352, 0,
131 'b', 0223, 0361, 0,
132 'c', 0216, 0117, 0,
133 'd', 0216, 0154, 0,
134 'e', 0214, 0334, 0,
135 'f', 0230, 0132, 0,
136 'g', 0216, 0265, 0,
137 'h', 0224, 0252, 0,
138 'i', 0213, 0343, 0,
139 ' ', 0201, 0100, 0,
140 0,
141 'K', 0213, 0312, 0,
142 'K', 0213, 0312, 0,
143 'G', 0213, 0340, 0,
144 'S', 0213, 0342, 0,
145 'R', 0224, 0362, 0,
146 'B', 0212, 0160, 0,
147 'N', 0214, 0152, 0,
148 'L', 0215, 0201, 0,
149 'P', 0225, 0340, 0,
150 'r', 0227, 0264, 0,
151 'b', 0224, 0156, 0,
152 'p', 0202, 0306, 0,
153 'r', 0227, 0263, 0,
154 '+', 0220, 0254, 0,
155 'G', 0, 0, 0,
156 0,
157 '+', 0220, 0254, 0,
158 '@', 0221, 0305, 0,
159 // 'p', 0214, 0343, 0,
160 'p', 0216, 0350, 0,
161 ':', 0201, 0106, 0,
162 '-', 0227, 0271, 0,
163 'f', 0217, 0343, 0,
164 's', 0212, 0361, 0,
165 'b', 0210, 0370, 0,
166 'r', 0215, 0266, 0,
167 'l', 0211, 0105, 0,
168 'v', 0222, 0274, 0,
169 0,
170
171 };
172
173 int NextUnit P((char **p));
174
175 int kifu = 0;
176
177 char
GetKanji(char ** p,int start)178 GetKanji (char **p, int start)
179 {
180 unsigned char *q = *(unsigned char **) p;
181 int i;
182
183 if((*q & 0x80) == 0) return 0; // plain ASCII, refuse to parse
184 if((**p & 0xC0) == 0x80) { // this is an illegal starting code in utf-8, so assume shift-JIS
185 for(i=start+JIS; kanjiTab[i]; i+=4) {
186 if(q[0] == kanjiTab[i+1] && q[1] == kanjiTab[i+2]) {
187 (*p) += 2; kifu = 0x80;
188 return kanjiTab[i];
189 }
190 }
191 (*p) += (kifu ? 2 : 1); // assume this is an unrecognized kanji when reading kif files
192 return 0;
193 }
194
195 for(i=start; kanjiTab[i]; i+=4) {
196 if(q[0] == kanjiTab[i+1] && q[1] == kanjiTab[i+2] && q[2] == kanjiTab[i+3]) {
197 (*p) += 3; kifu = 0x80;
198 return kanjiTab[i];
199 }
200 }
201
202 if((q[0] & 0xE0) == 0xC0 && (q[1] & 0xC0) == 0x80) (*p) += 2; else // for now skip unrecognized utf-8 characters
203 if((q[0] & 0xF0) == 0xE0 && (q[1] & 0xC0) == 0x80 && (q[2] & 0xC0) == 0x80) (*p) += 3; else
204 if((q[0] & 0xF8) == 0xF0 && (q[1] & 0xC0) == 0x80 && (q[2] & 0xC0) == 0x80 && (q[3] & 0xC0) == 0x80) (*p) += 4;
205 else if(**p & 0x80) return -1; // not valid utf-8
206
207 return 0; // unrecognized but valid kanji (skipped), or plain ASCII
208 }
209
210 int
KifuMove(char ** p)211 KifuMove (char **p)
212 {
213 static char buf[MSG_SIZ];
214 char *ptr = buf+3, *q, k;
215 int wom = quickFlag ? quickFlag&1 : WhiteOnMove(yyboardindex);
216 k = GetKanji(p, XCO);
217 if(k < 0) { (*p)++; return Nothing; } // must try shift-JIS here
218 if(k >= '1' && k <= '9') {
219 buf[0] = k; buf[1] = GetKanji(p, YCO); // to-square coords
220 } else if(k == 'x') {
221 if(GetKanji(p, YCO) != ' ') (*p) -= 3; // skip spacer kanji after recapture
222 } else if((k == 's' || k == 'g') && GetKanji(p, MISC) == 'p' && GetKanji(p, MISC) == ':') { // player name
223 snprintf(yytext, MSG_SIZ, "[%s \"", k == 's' ? "White" : "Black"); // construct PGN tag
224 for(q=yytext+8; **p && **p != '\n' && **p != '\r' && q < yytext + MSG_SIZ; ) *q++ = *(*p)++;
225 strcpy(q, "\"]\n"); parseStart = yytext; lastChar = '\n';
226 return PGNTag;
227 } else if(k == '-' && GetKanji(p, MISC) == '-') { // resign
228 int res;
229 parseStart = yytext;
230 if(wom)
231 res = BlackWins, strcpy(yytext, "{sente resigns} 0-1");
232 else res = WhiteWins, strcpy(yytext, "{gote resigns} 1-0");
233 return res;
234 } else {
235 while(**p && **p != '\n') (*p)++; // unrecognized Japanese kanji: skip to end of line
236 return Nothing;
237 }
238 buf[3] = GetKanji(p, PIECE); // piece ID
239 if(buf[3] == '+') buf[2] = '+', buf[3] = GetKanji(p, PIECE); // +N, +L, +S
240 k = GetKanji(p, MISC);
241 if(k == '@') { // drop move
242 buf[4] = '@', buf[5] = buf[0], buf[6] = buf[1]; buf[7] = NULLCHAR;
243 if(appData.debugMode) fprintf(debugFP, "kifu drop %s\n", ptr);
244 return NextUnit(&ptr);
245 }
246
247 kifu = 0x80;
248 do { // read disambiguation (and promotion) kanji
249 switch(k) {
250 case '+': kifu |= 1; break;
251 case 'f': kifu |= 2; break;
252 case 'b': kifu |= 4; break;
253 case 's': kifu |= 8; break;
254 case 'l': kifu |= 0x10; break;
255 case 'r': kifu |= 0x20; break;
256 case 'v': kifu |= 0x40; break;
257 }
258 } while(k = GetKanji(p, MISC));
259
260 if(**p == '(' && (*p)[3] == ')') { // kif disambiguation
261 buf[4] = (*p)[1]; buf[5] = (*p)[2] + 'a' - '1'; buf[6] = buf[0]; buf[7] = buf[1]; buf[8] = (kifu & 1)*'+'; buf[9] = NULLCHAR;
262 (*p) += 4; ptr++; // strip off piece name if we know full from-square
263 if(appData.debugMode) fprintf(debugFP, "kifu move %s\n", ptr);
264 return NextUnit(&ptr);
265 } else { // kif2
266 char *q = buf+4;
267 if(islower(buf[3])) // kludge: kanji for promoted types translate as lower case
268 buf[3] += 'A' - 'a', buf[2] = '+', ptr--; // so prefix with '+'
269 if(kifu * ~1) { // disambiguation was given, and thus is probably needed
270 if(buf[3] != 'B' && buf[3] != 'R') { // stepper, so distance must be <= 1 (N or L never need vertical disambiguation!)
271 if(kifu & 0x10) *q++ = buf[0] - (wom ? -1 : 1); // translate left/right/straight to PSN file disambiguators
272 if(kifu & 0x20) *q++ = buf[0] + (wom ? -1 : 1);
273 if(kifu & 0x40) *q++ = buf[0], kifu |= 2; // kludge: 'straight' only needs disambiguation if forward!
274 if(kifu & 2) *q++ = buf[1] + (wom ? -1 : 1); // translate forward/backward/sideway to PSN rank disambiguators
275 if(kifu & 4) *q++ = buf[1] - (wom ? -1 : 1);
276 if(kifu & 8) *q++ = buf[1];
277 } // for B, R, +B and +R it gets ugly, as we cannot deduce the distance, and the Disambiguate callback has to directly look at 'kifu'
278 }
279 *q++ = buf[0]; *q++ = buf[1]; *q++ = (kifu & 1)*'+'; *q = NULLCHAR;
280 if(appData.debugMode) fprintf(debugFP, "kif2 move %s\n", ptr);
281 return NextUnit(&ptr);
282 }
283 }
284
285 int
ReadLine()286 ReadLine ()
287 { // Read one line from the input file, and append to the buffer
288 int c; char *start = inPtr;
289 if(fromString) return 0; // parsing string, so the end is a hard end
290 if(!inputFile) return 0;
291 while((c = fgetc(inputFile)) != EOF) {
292 *inPtr++ = c;
293 if(c == '\n') { *inPtr = NULLCHAR; return 1; }
294 if(inPtr - inputBuf > PARSEBUFSIZE-2) inPtr--; //prevent crash on overflow
295 }
296 if(inPtr == start) return 0;
297 *inPtr++ = '\n', *inPtr = NULLCHAR; // repair missing linefeed at EOF
298 return 1;
299 }
300
301 int
Scan(char c,char ** p)302 Scan (char c, char **p)
303 { // line-spanning skip to mentioned character or EOF
304 do {
305 while(**p) if(*(*p)++ == c) return 0;
306 } while(ReadLine());
307 // no closing bracket; force match for entire rest of file.
308 return 1;
309 }
310
311 int
SkipWhite(char ** p)312 SkipWhite (char **p)
313 { // skip spaces tabs and newlines; return 1 if anything was skipped
314 char *start = *p;
315 do{
316 while(**p == ' ' || **p == '\t' || **p == '\n' || **p == '\r') (*p)++;
317 } while(**p == NULLCHAR && ReadLine()); // continue as long as ReadLine reads something
318 return *p != start;
319 }
320
321 static inline int
Match(char * pattern,char ** ptr)322 Match (char *pattern, char **ptr)
323 {
324 char *p = pattern, *s = *ptr;
325 while(*p && (*p == *s++ || s[-1] == '\r' && *p--)) p++;
326 if(*p == 0) {
327 *ptr = s;
328 return 1;
329 }
330 return 0; // no match, no ptr update
331 }
332
333 static inline int
Word(char * pattern,char ** p)334 Word (char *pattern, char **p)
335 {
336 if(Match(pattern, p)) return 1;
337 if(*pattern >= 'a' && *pattern <= 'z' && *pattern - **p == 'a' - 'A') { // capitalized
338 (*p)++;
339 if(Match(pattern + 1, p)) return 1;
340 (*p)--;
341 }
342 return 0;
343 }
344
345 int
Verb(char * pattern,char ** p)346 Verb (char *pattern, char **p)
347 {
348 int res = Word(pattern, p);
349 if(res && !Match("s", p)) Match("ed", p); // eat conjugation suffix, if any
350 return res;
351 }
352
353 int
Number(char ** p)354 Number (char **p)
355 {
356 int val = 0;
357 if(**p < '0' || **p > '9') return BADNUMBER;
358 while(**p >= '0' && **p <= '9') {
359 val = 10*val + *(*p)++ - '0';
360 }
361 return val;
362 }
363
364 int
RdTime(char c,char ** p)365 RdTime (char c, char **p)
366 {
367 char *start = ++(*p), *sec; // increment *p, as it was pointing to the opening ( or {
368 if(Number(p) == BADNUMBER) return 0;
369 sec = *p;
370 if(Match(":", p) && Number(p) != BADNUMBER && *p - sec == 3) { // well formed
371 sec = *p;
372 if(Match(".", p) && Number(p) != BADNUMBER && *(*p)++ == c) return 1; // well-formed fraction
373 *p = sec;
374 if(*(*p)++ == c) return 1; // matching bracket without fraction
375 }
376 *p = start; // failure
377 return 0;
378 }
379
380 char
PromoSuffix(char ** p)381 PromoSuffix (char **p)
382 {
383 char *start = *p;
384 if(**p == ' ') return NULLCHAR; // common case, test explicitly for speed
385 if(**p == 'e' && (Match("ep", p) || Match("e.p.", p))) { *p = start; return NULLCHAR; } // non-compliant e.p. suffix is no promoChar!
386 if(**p == '+' && IS_SHOGI(gameInfo.variant)) { (*p)++; return '+'; }
387 if(**p == '=' || (gameInfo.variant == VariantSChess) && **p == '/') (*p)++; // optional = (or / for Seirawan gating)
388 if(**p == '(' && (*p)[2] == ')' && isalpha( (*p)[1] )) { (*p) += 3; return ToLower((*p)[-2]); }
389 if(isalpha(**p) && **p != 'x') return ToLower(*(*p)++); // reserve 'x' for multi-leg captures?
390 if(*p != start) return **p == '+' ? *(*p)++ : '='; // must be the optional = (or =+)
391 return NULLCHAR; // no suffix detected
392 }
393
394 int
NextUnit(char ** p)395 NextUnit (char **p)
396 { // Main parser routine
397 int coord[4], n, result, piece, i;
398 char type[4], promoted, separator, slash, *oldp, *commentEnd, c;
399 int wom = quickFlag ? quickFlag&1 : WhiteOnMove(yyboardindex);
400
401 // ********* try white first, because it is so common **************************
402 if(**p == ' ' || **p == '\n' || **p == '\t') { parseStart = (*p)++; return Nothing; }
403
404
405 if(**p == NULLCHAR) { // make sure there is something to parse
406 if(fromString) return 0; // we are parsing string, so the end is really the end
407 *p = inPtr = parseStart = inputBuf;
408 if(!ReadLine()) return 0; // EOF
409 } else if(inPtr > inputBuf + PARSEBUFSIZE/2) { // buffer fills up with already parsed stuff
410 char *q = *p, *r = inputBuf;
411 while(*r++ = *q++);
412 *p = inputBuf; inPtr = r - 1;
413 }
414 parseStart = oldp = *p; // remember where we begin
415
416 // ********* attempt to recognize a SAN move in the leading non-blank text *****
417 piece = separator = promoted = slash = n = 0;
418 for(i=0; i<4; i++) coord[i] = -1, type[i] = NOTHING;
419 if(**p & 0x80) return KifuMove(p); // non-ascii. Could be some kanj notation for Shogi or Xiangqi
420 if(**p == '+') (*p)++, promoted++;
421 if(**p >= 'a' && **p <= 'z' && (*p)[1]== '@') piece =*(*p)++ + 'A' - 'a'; else
422 if(**p >= 'A' && **p <= 'Z') {
423 static char s[] = SUFFIXES;
424 char *q;
425 piece = *(*p)++; // Note we could test for 2-byte non-ascii names here
426 if(q = strchr(s, **p)) (*p)++, piece += 64*(q - s + 1);
427 if(**p == '/') slash = *(*p)++;
428 }
429 while(n < 4) {
430 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
431 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
432 else break;
433 if(n == 2 && type[0] == type[1]) { // if two identical types, the opposite type in between must have been missing
434 type[2] = type[1]; coord[2] = coord[1];
435 type[1] = NOTHING; coord[1] = -1; n++;
436 }
437 }
438 // we always get here, and might have read a +, a piece, and upto 4 potential coordinates
439 if(n <= 2) { // could be from-square or disambiguator, when -:xX follow, or drop with @ directly after piece, but also to-square
440 if(**p == '-' || **p == ':' || **p == 'x' || **p == 'X' || // these cannot be move suffix, so to-square must follow
441 (**p == '@' || **p == '*') && n == 0 && !promoted && piece) { // P@ must also be followed by to-square
442 separator = *(*p)++;
443 if(n == 1) coord[1] = coord[0]; // must be disambiguator, but we do not know which yet
444 n = 2;
445 while(n < 4) { // attempt to read to-square
446 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
447 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
448 else break;
449 }
450 } else if((**p == '+' || **p == '=') && n == 1 && piece && type[0] == NUMERIC) { // can be traditional Xiangqi notation
451 separator = *(*p)++;
452 n = 2;
453 if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
454 } else if(n == 2) { // only one square mentioned, must be to-square
455 while(n < 4) { coord[n] = coord[n-2], type[n] = type[n-2], coord[n-2] = -1, type[n-2] = NOTHING; n++; }
456 }
457 } else if(n == 3 && type[1] != NOTHING) { // must be hyphenless disambiguator + to-square
458 for(i=3; i>0; i--) coord[i] = coord[i-1], type[i] = type[i-1]; // move to-square to where it belongs
459 type[1] = NOTHING; // disambiguator goes in first two positions
460 n = 4;
461 }
462 // we always get here; move must be completely read now, with to-square coord(s) at end
463 if(n == 3) { // incomplete to-square. Could be Xiangqi traditional, or stuff like fxg
464 if(piece && type[1] == NOTHING && type[0] == NUMERIC && type[2] == NUMERIC &&
465 (separator == '+' || separator == '=' || separator == '-')) {
466 // Xiangqi traditional
467
468 return ImpossibleMove; // for now treat as invalid
469 }
470 // fxg stuff, but also things like 0-0, 0-1 and 1-0
471 if(!piece && type[1] == NOTHING && type[0] == ALPHABETIC && type[2] == ALPHABETIC
472 && (coord[0] != 14 || coord[2] != 14) /* reserve oo for castling! */ ) {
473 piece = 'P'; n = 4; // kludge alert: fake full to-square
474 }
475 } else if(n == 1 && type[0] == NUMERIC && coord[0] > 1) { while(**p == '.') (*p)++; return Nothing; } // fast exit for move numbers
476 if(n == 4 && type[2] != type[3] && // we have a valid to-square (kludge: type[3] can be NOTHING on fxg type move)
477 (piece || !promoted) && // promoted indicator only valid on named piece type
478 (type[2] == ALPHABETIC || IS_SHOGI(gameInfo.variant))) { // in Shogi also allow alphabetic rank
479 DisambiguateClosure cl;
480 int fromX, fromY, toX, toY;
481
482 if(slash && (!piece || type[1] == NOTHING)) goto badMove; // slash after piece only in ICS long format
483 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
484
485 if(type[2] == NUMERIC) { // alpha-rank
486 coord[2] = BOARD_RGHT - BOARD_LEFT - coord[2];
487 coord[3] = BOARD_HEIGHT - coord[3];
488 if(coord[0] >= 0) coord[0] = BOARD_RGHT - BOARD_LEFT - coord[0];
489 if(coord[1] >= 0) coord[1] = BOARD_HEIGHT - coord[1];
490 }
491 toX = cl.ftIn = (currentMoveString[2] = coord[2] + 'a') - AAA;
492 toY = cl.rtIn = (currentMoveString[3] = coord[3] + '0') - ONE;
493 if(type[3] == NOTHING) cl.rtIn = -1; // for fxg type moves ask for toY disambiguation
494 else if(toY >= BOARD_HEIGHT || toY < 0) return ImpossibleMove; // vert off-board to-square
495 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return ImpossibleMove;
496 if(piece) {
497 cl.pieceIn = CharToPiece(wom ? piece : piece + 'a' - 'A');
498 if(cl.pieceIn == EmptySquare) return ImpossibleMove; // non-existent piece
499 if(promoted) cl.pieceIn = (ChessSquare) (CHUPROMOTED(cl.pieceIn));
500 } else cl.pieceIn = EmptySquare;
501 if(separator == '@' || separator == '*') { // drop move. We only get here without from-square or promoted piece
502 fromY = DROP_RANK; fromX = cl.pieceIn;
503 currentMoveString[0] = piece;
504 currentMoveString[1] = '@';
505 currentMoveString[4] = NULLCHAR;
506 return LegalityTest(boards[yyboardindex], PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, fromY, fromX, toY, toX, NULLCHAR);
507 }
508 if(type[1] == NOTHING && type[0] != NOTHING) { // there is a disambiguator
509 if(type[0] != type[2]) coord[0] = -1, type[1] = type[0], type[0] = NOTHING; // it was a rank-disambiguator
510 }
511 if( type[1] != type[2] && // means fromY is of opposite type as ToX, or NOTHING
512 (type[0] == NOTHING || type[0] == type[2]) ) { // well formed
513 int suffix = 7;
514 fromX = (currentMoveString[0] = coord[0] + 'a') - AAA;
515 fromY = (currentMoveString[1] = coord[1] + '0') - ONE;
516 currentMoveString[4] = cl.promoCharIn = PromoSuffix(p);
517 currentMoveString[5] = NULLCHAR;
518 if(**p == 'x' && !cl.promoCharIn) { // other leg follows
519 char *q = *p;
520 int x = *++*p, y;
521 ++*p; y = Number(p);
522 if(**p == '-' || **p == 'x') { // 3-leg move!
523 currentMoveString[7] = (kill2X = toX) + AAA; // what we thought was to-square is in fact 1st kill-square of two
524 currentMoveString[8] = (kill2Y = toY) + ONE; // append it after 2nd kill-square
525 toX = x - AAA; // kludge alert: this will become 2nd kill square
526 toY = y + '0' - ONE;
527 suffix += 2;
528 } else *p = q; // 2-leg move, rewind to leave reading of 2nd leg to code below
529 }
530 if(!cl.promoCharIn && (**p == '-' || **p == 'x')) { // Lion-type multi-leg move
531 currentMoveString[5] = (killX = toX) + AAA; // what we thought was to-square is in fact kill-square
532 currentMoveString[6] = (killY = toY) + ONE; // append it as suffix behind long algebraic move
533 currentMoveString[4] = ';';
534 currentMoveString[suffix+1] = NULLCHAR;
535 // read new to-square (VERY non-robust! Assumes correct (non-alpha-rank) syntax, and messes up on errors)
536 toX = cl.ftIn = (currentMoveString[2] = *++*p) - AAA; ++*p;
537 toY = cl.rtIn = (currentMoveString[3] = Number(p) + '0') - ONE;
538 currentMoveString[suffix] = cl.promoCharIn = PromoSuffix(p);
539 }
540 if(type[0] != NOTHING && type[1] != NOTHING && type[3] != NOTHING) { // fully specified.
541 ChessSquare realPiece = boards[yyboardindex][fromY][fromX];
542 // Note that Disambiguate does not work for illegal moves, but flags them as impossible
543 if(piece) { // check if correct piece indicated
544 if(PieceToChar(realPiece) == '~') realPiece = (ChessSquare) (DEMOTED(realPiece));
545 if(!(appData.icsActive && PieceToChar(realPiece) == '+') && // trust ICS if it moves promoted pieces
546 piece && realPiece != cl.pieceIn) return ImpossibleMove;
547 } else if(!separator && **p == '+') { // could be a protocol move, where bare '+' suffix means shogi-style promotion
548 if(realPiece < (wom ? WhiteCannon : BlackCannon) && PieceToChar(PROMOTED(realPiece)) == '+') // seems to be that
549 currentMoveString[4] = cl.promoCharIn = *(*p)++; // append promochar after all
550 }
551 result = LegalityTest(boards[yyboardindex], PosFlags(yyboardindex), fromY, fromX, toY, toX, cl.promoCharIn);
552 if (currentMoveString[4] == NULLCHAR) { // suppy missing mandatory promotion character
553 if(result == WhitePromotion || result == BlackPromotion) {
554 switch(gameInfo.variant) {
555 case VariantCourier:
556 case VariantShatranj: currentMoveString[4] = PieceToChar(BlackFerz); break;
557 case VariantGreat: currentMoveString[4] = PieceToChar(BlackMan); break;
558 case VariantShogi: currentMoveString[4] = '+'; break;
559 default: currentMoveString[4] = PieceToChar(BlackQueen);
560 }
561 } else if(result == WhiteNonPromotion || result == BlackNonPromotion) {
562 currentMoveString[4] = '=';
563 }
564 } else if(appData.testLegality && gameInfo.variant != VariantSChess && // strip off unnecessary and false promo characters
565 !(result == WhitePromotion || result == BlackPromotion ||
566 result == WhiteNonPromotion || result == BlackNonPromotion)) currentMoveString[4] = NULLCHAR;
567 return result;
568 } else if(cl.pieceIn == EmptySquare) cl.pieceIn = wom ? WhitePawn : BlackPawn;
569 cl.ffIn = type[0] == NOTHING ? -1 : coord[0] + 'a' - AAA;
570 cl.rfIn = type[1] == NOTHING ? -1 : coord[1] + '0' - ONE;
571
572 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
573
574 if(cl.kind == ImpossibleMove && !piece && type[1] == NOTHING // fxg5 type
575 && toY == (wom ? 4 : 3)) { // could be improperly written e.p.
576 cl.rtIn += wom ? 1 : -1; // shift target square to e.p. square
577 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
578 if((cl.kind != WhiteCapturesEnPassant && cl.kind != BlackCapturesEnPassant))
579 return ImpossibleMove; // nice try, but no cigar
580 }
581
582 currentMoveString[0] = cl.ff + AAA;
583 currentMoveString[1] = cl.rf + ONE;
584 currentMoveString[3] = cl.rt + ONE;
585 if(killX < 0) // [HGM] lion: do not overwrite kill-square suffix
586 currentMoveString[4] = cl.promoChar;
587
588 if((cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) && !Match("ep", p)) Match("e.p.", p);
589
590 return (int) cl.kind;
591 }
592 }
593 badMove:// we failed to find algebraic move
594 *p = oldp;
595
596
597 // Next we do some common symbols where the first character commits us to things that cannot possibly be a move
598
599 // ********* PGN tags ******************************************
600 if(**p == '[') {
601 oldp = ++(*p); kifu = 0;
602 if(Match("--", p)) { // "[--" could be start of position diagram
603 if(!Scan(']', p) && (*p)[-3] == '-' && (*p)[-2] == '-') return PositionDiagram;
604 *p = oldp;
605 }
606 SkipWhite(p);
607 if(isdigit(**p) || isalpha(**p)) {
608 do (*p)++; while(isdigit(**p) || isalpha(**p) || **p == '+' ||
609 **p == '-' || **p == '=' || **p == '_' || **p == '#');
610 SkipWhite(p);
611 if(**p == '"') {
612 (*p)++;
613 while(**p != '\n' && (*(*p)++ != '"'|| (*p)[-2] == '\\')); // look for unescaped quote
614 if((*p)[-1] !='"') { *p = oldp; Scan(']', p); return Comment; } // string closing delimiter missing
615 SkipWhite(p); if(*(*p)++ == ']') return PGNTag;
616 }
617 }
618 Scan(']', p); return Comment;
619 }
620
621 // ********* SAN Castings *************************************
622 if(**p == 'O' || **p == 'o' || **p == '0' && !Match("00:", p)) { // exclude 00 in time stamps
623 int castlingType = 0;
624 if(Match("O-O-O", p) || Match("o-o-o", p) || Match("0-0-0", p) ||
625 Match("OOO", p) || Match("ooo", p) || Match("000", p)) castlingType = 2;
626 else if(Match("O-O", p) || Match("o-o", p) || Match("0-0", p) ||
627 Match("OO", p) || Match("oo", p) || Match("00", p)) castlingType = 1;
628 if(castlingType) { //code from old parser, collapsed for both castling types, and streamlined a bit
629 int rf, ff, rt, ft; ChessSquare king;
630 char promo=NULLCHAR;
631
632 if(gameInfo.variant == VariantSChess) promo = PromoSuffix(p);
633
634 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
635
636 if (wom) {
637 rf = castlingRank[0];
638 rt = castlingRank[0];
639 king = WhiteKing;
640 } else {
641 rf = castlingRank[3];
642 rt = castlingRank[3];
643 king = BlackKing;
644 }
645 ff = (BOARD_WIDTH-1)>>1; // this would be d-file
646 if (boards[yyboardindex][rf][ff] == king) {
647 /* ICS wild castling */
648 ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
649 } else {
650 char *q;
651 ff = BOARD_WIDTH>>1; // e-file
652 ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
653 if(pieceDesc[king] && (q = strchr(pieceDesc[king], 'O'))) { // redefined to non-default King stride
654 ft = (castlingType == 1 ? ff + atoi(q+1) : ff - atoi(q+1));
655 }
656 }
657 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
658 if (wom) {
659 ff = initialRights[2];
660 ft = initialRights[castlingType-1];
661 } else {
662 ff = initialRights[5];
663 ft = initialRights[castlingType+2];
664 }
665 if (appData.debugMode) fprintf(debugFP, "Parser FRC (type=%d) %d %d\n", castlingType, ff, ft);
666 if(ff == NoRights || ft == NoRights) return ImpossibleMove;
667 }
668 sprintf(currentMoveString, "%c%c%c%c%c",ff+AAA,rf+ONE,ft+AAA,rt+ONE,promo);
669 if (appData.debugMode) fprintf(debugFP, "(%d-type) castling %d %d\n", castlingType, ff, ft);
670
671 return (int) LegalityTest(boards[yyboardindex],
672 PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
673 rf, ff, rt, ft, promo);
674 } else if(Match("01", p)) return Nothing; // prevent this from being mistaken for move number 1
675 }
676
677
678 // ********* variations (nesting) ******************************
679 if(**p =='(') {
680 if(RdTime(')', p)) return ElapsedTime;
681 return Open;
682 }
683 if(**p ==')') { (*p)++; return Close; }
684 if(**p == ';') { while(**p != '\n') (*p)++; return Comment; }
685
686
687 // ********* Comments and result messages **********************
688 *p = oldp; commentEnd = NULL; result = 0;
689 if(**p == '{') {
690 if(RdTime('}', p)) return ElapsedTime;
691 if(lastChar == '\n' && Match("--------------\n", p)) {
692 char *q;
693 i = Scan ('}', p); q = *p - 16;
694 if(Match("\n--------------}\n", &q)) return PositionDiagram;
695 } else i = Scan('}', p);
696 commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
697 }
698 if(commentEnd) SkipWhite(p);
699 if(kifu && **p == '*') { // .kif comment
700 char *q = yytext;
701 while(**p && **p != '\n') { if(q < yytext + 10*MSG_SIZ-3) *q++ = **p; (*p)++; }
702 parseStart = yytext; *yytext = '{'; strcpy(q, "}\n"); // wrap in braces
703 return Comment;
704 }
705 if(Match("*", p)) result = GameUnfinished;
706 else if(**p == '0') {
707 if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
708 Match("0 - 1", p) || Match("0 / 1", p) || Match("0 : 1", p)) result = BlackWins;
709 } else if(**p == '1') {
710 if( Match("1-0", p) || Match("1/0", p) || Match("1:0", p) ||
711 Match("1 - 0", p) || Match("1 / 0", p) || Match("1 : 0", p)) result = WhiteWins;
712 else if(Match("1/2 - 1/2", p) || Match("1/2:1/2", p) || Match("1/2 : 1/2", p) || Match("1 / 2 - 1 / 2", p) ||
713 Match("1 / 2 : 1 / 2", p) || Match("1/2", p) || Match("1 / 2", p)) result = GameIsDrawn;
714 }
715 if(result) {
716 if(Match(" (", p) && !Scan(')', p) || Match(" {", p) && !Scan('}', p)) { // there is a comment after the PGN result!
717 if(commentEnd) { *p = commentEnd; return Comment; } // so comment before it is normal comment; return that first
718 }
719 return result; // this returns a possible preceeding comment as result details
720 }
721 if(commentEnd) { *p = commentEnd; return Comment; } // there was no PGN result following, so return as normal comment
722
723
724 // ********* Move numbers (after castlings or PGN results!) ***********
725 if((i = Number(p)) != BADNUMBER) { // a single number was read as part of our attempt to read a move
726 char *numEnd = *p;
727 if(**p == '.') (*p)++; SkipWhite(p);
728 if(**p == '+' || isalpha(**p) || gameInfo.variant == VariantShogi && *p != numEnd && isdigit(**p)) {
729 *p = numEnd;
730 return i == 1 ? MoveNumberOne : Nothing;
731 }
732 *p = numEnd; return Nothing;
733 }
734
735
736 // ********* non-compliant game-result indicators *********************
737 if(Match("+-+", p) || Word("stalemate", p)) return GameIsDrawn;
738 if(Match("++", p) || Verb("resign", p) || (Word("check", p) || 1) && Word("mate", p) )
739 return (wom ? BlackWins : WhiteWins);
740 c = ToUpper(**p);
741 if(Word("w", p) && (Match("hite", p) || 1) || Word("b", p) && (Match("lack", p) || 1) ) {
742 if(**p != ' ') return Nothing;
743 ++*p;
744 if(Verb("disconnect", p)) return GameUnfinished;
745 if(Verb("resign", p) || Verb("forfeit", p) || Word("mated", p) || Word("lost", p) || Word("loses", p))
746 return (c == 'W' ? BlackWins : WhiteWins);
747 if(Word("mates", p) || Word("wins", p) || Word("won", p))
748 return (c != 'W' ? BlackWins : WhiteWins);
749 return Nothing;
750 }
751 if(Word("draw", p)) {
752 if(**p == 'n') (*p)++;
753 if(**p != ' ') return GameIsDrawn;
754 oldp = ++*p;
755 if(Word("agreed", p)) return GameIsDrawn;
756 if(Match("by ", p) && (Word("repetition", p) || Word("agreement", p)) ) return GameIsDrawn;
757 *p = oldp;
758 if(*(*p)++ == '(') {
759 while(**p != '\n') if(*(*p)++ == ')') break;
760 if((*p)[-1] == ')') return GameIsDrawn;
761 }
762 *p = oldp - 1; return GameIsDrawn;
763 }
764
765
766 // ********* Numeric annotation glyph **********************************
767 if(**p == '$') { (*p)++; if(Number(p) != BADNUMBER) return NAG; return Nothing; }
768
769
770 // ********** by now we are getting down to the silly stuff ************
771 if(Word("gnu", p) || Match("GNU", p)) {
772 if(**p == ' ') (*p)++;
773 if(Word("chess", p) || Match("CHESS", p)) {
774 char *q;
775 if((q = strstr(*p, "game")) || (q = strstr(*p, "GAME")) || (q = strstr(*p, "Game"))) {
776 (*p) = q + 4; return GNUChessGame;
777 }
778 }
779 return Nothing;
780 }
781 if(lastChar == '\n' && (Match("# ", p) || Match("; ", p) || Match("% ", p))) {
782 while(**p != '\n' && **p != ' ') (*p)++;
783 if(**p == ' ' && (Match(" game file", p) || Match(" position file", p))) {
784 while(**p != '\n') (*p)++; // skip to EOLN
785 return XBoardGame;
786 }
787 *p = oldp; // we might need to re-match the skipped stuff
788 }
789
790 if(Match("---", p)) { while(**p == '-') (*p)++; return Nothing; } // prevent separators parsing as null move
791 if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
792 strncpy(currentMoveString, "@@@@", 5);
793 return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
794 }
795
796 // ********* Efficient skipping of (mostly) alphabetic chatter **********
797 while(isdigit(**p) || isalpha(**p) || **p == '-') (*p)++;
798 if(*p != oldp) {
799 if(**p == '\'') {
800 while(isdigit(**p) || isalpha(**p) || **p == '-' || **p == '\'') (*p)++;
801 return Nothing; // random word
802 }
803 if(lastChar == '\n' && Match(": ", p)) { // mail header, skip indented lines
804 do {
805 while(**p != '\n') (*p)++;
806 if(!ReadLine()) return Nothing; // append next line if not EOF
807 } while(Match("\n ", p) || Match("\n\t", p));
808 }
809 return Nothing;
810 }
811
812 // ********* Prevent 00 in unprotected time stamps to be mistaken for castling *******
813 if(Match(":00", p)) return Nothing;
814
815 // ********* Could not match to anything. Return offending character ****
816 (*p)++;
817 return Nothing;
818 }
819
820 /*
821 Return offset of next pattern in the current file.
822 */
823 int
yyoffset()824 yyoffset ()
825 {
826 return ftell(inputFile) - (inPtr - parsePtr); // subtract what is read but not yet parsed
827 }
828
829 void
yynewfile(FILE * f)830 yynewfile (FILE *f)
831 { // prepare parse buffer for reading file
832 inputFile = f;
833 inPtr = parsePtr = inputBuf;
834 fromString = 0;
835 lastChar = '\n';
836 *inPtr = NULLCHAR; // make sure we will start by reading a line
837 }
838
839 void
yynewstr(char * s)840 yynewstr P((char *s))
841 {
842 parsePtr = s;
843 inputFile = NULL;
844 fromString = 1;
845 }
846
847 int
yylex()848 yylex ()
849 { // this replaces the flex-generated parser
850 int result = NextUnit(&parsePtr);
851 char *p = parseStart, *q = yytext;
852 if(p == yytext) return result; // kludge to allow kanji expansion
853 while(p < parsePtr) *q++ = *p++; // copy the matched text to yytext[]
854 *q = NULLCHAR;
855 lastChar = q[-1];
856 return result;
857 }
858
859 int
Myylex()860 Myylex ()
861 { // [HGM] wrapper for yylex, which treats nesting of parentheses
862 int symbol, nestingLevel = 0, i=0;
863 char *p;
864 static char buf[256*MSG_SIZ];
865 buf[0] = NULLCHAR;
866 do { // eat away anything not at level 0
867 symbol = yylex();
868 if(symbol == Open) nestingLevel++;
869 if(nestingLevel) { // save all parsed text between (and including) the ()
870 for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
871 buf[i] = NULLCHAR;
872 }
873 if(symbol == 0) break; // ran into EOF
874 if(symbol == Close) symbol = Comment, nestingLevel--;
875 } while(nestingLevel || symbol == Nothing);
876 yy_text = buf[0] ? buf : (char*)yytext;
877 return symbol;
878 }
879
880 ChessMove
yylexstr(int boardIndex,char * s,char * buf,int buflen)881 yylexstr (int boardIndex, char *s, char *buf, int buflen)
882 {
883 ChessMove ret;
884 char *savPP = parsePtr;
885 fromString = 1;
886 yyboardindex = boardIndex;
887 parsePtr = s;
888 ret = (ChessMove) Myylex();
889 strncpy(buf, yy_text, buflen-1);
890 buf[buflen-1] = NULLCHAR;
891 parsePtr = savPP;
892 fromString = 0;
893 return ret;
894 }
895