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