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