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