Remove castling rights from Suicide start position
[xboard.git] / parser.c
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, xqUBB = 0;
176
177 char
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
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
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
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
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
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
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
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
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
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
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
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
430         if(xqUBB) { // Xiangqi UBB movelist
431             while(isdigit(**p)) {
432                 type[n] = NUMERIC; coord[n++] = *(*p)++ - '0';
433                 if(n >= 4) break;
434             }
435             if(n < 4) *p -= n, xqUBB = n = 0; else type[0] = type[2] = ALPHABETIC, coord[1] = 9 - coord[1], coord[3] = 9 - coord[3];
436         }
437         while(n < 4) {
438             if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
439             else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
440             else break;
441             if(n == 2 && type[0] == type[1]) { // if two identical types, the opposite type in between must have been missing
442                 type[2] = type[1]; coord[2] = coord[1];
443                 type[1] = NOTHING; coord[1] = -1; n++;
444             }
445         }
446         // we always get here, and might have read a +, a piece, and upto 4 potential coordinates
447         if(n <= 2) { // could be from-square or disambiguator, when -:xX follow, or drop with @ directly after piece, but also to-square
448              if(**p == '-' || **p == ':' || **p == 'x' || **p == 'X' || // these cannot be move suffix, so to-square must follow
449                  (**p == '@' || **p == '*') && n == 0 && !promoted && piece) { // P@ must also be followed by to-square
450                 separator = *(*p)++;
451                 if(n == 1) coord[1] = coord[0]; // must be disambiguator, but we do not know which yet
452                 n = 2;
453                 while(n < 4) { // attempt to read to-square
454                     if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
455                     else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
456                     else break;
457                 }
458             } else if((**p == '+' || **p == '=') && n == 1 && piece && type[0] == NUMERIC) { // can be traditional Xiangqi notation
459                 separator = *(*p)++;
460                 n = 2;
461                 if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
462             } else if(n == 2) { // only one square mentioned, must be to-square
463                 while(n < 4) { coord[n] = coord[n-2], type[n] = type[n-2], coord[n-2] = -1, type[n-2] = NOTHING; n++; }
464             }
465         } else if(n == 3 && type[1] != NOTHING) { // must be hyphenless disambiguator + to-square
466             for(i=3; i>0; i--) coord[i] = coord[i-1], type[i] = type[i-1]; // move to-square to where it belongs
467             type[1] = NOTHING; // disambiguator goes in first two positions
468             n = 4;
469         }
470         // we always get here; move must be completely read now, with to-square coord(s) at end
471         if(n == 3) { // incomplete to-square. Could be Xiangqi traditional, or stuff like fxg
472             if(piece && type[1] == NOTHING && type[0] == NUMERIC && type[2] == NUMERIC &&
473                 (separator == '+' || separator == '=' || separator == '-')) {
474                      // Xiangqi traditional
475
476                 return ImpossibleMove; // for now treat as invalid
477             }
478             // fxg stuff, but also things like 0-0, 0-1 and 1-0
479             if(!piece && type[1] == NOTHING && type[0] == ALPHABETIC && type[2] == ALPHABETIC
480                  && (coord[0] != 14 || coord[2] != 14) /* reserve oo for castling! */ ) {
481                 piece = 'P'; n = 4; // kludge alert: fake full to-square
482             }
483         } else if(n == 1 && type[0] == NUMERIC && coord[0] > 1) { while(**p == '.') (*p)++; return Nothing; } // fast exit for move numbers
484         if(n == 4 && type[2] != type[3] && // we have a valid to-square (kludge: type[3] can be NOTHING on fxg type move)
485                      (piece || !promoted) && // promoted indicator only valid on named piece type
486                      (type[2] == ALPHABETIC || IS_SHOGI(gameInfo.variant))) { // in Shogi also allow alphabetic rank
487             DisambiguateClosure cl;
488             int fromX, fromY, toX, toY;
489
490             if(slash && (!piece || type[1] == NOTHING)) goto badMove; // slash after piece only in ICS long format
491             if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
492
493             if(type[2] == NUMERIC) { // alpha-rank
494                 coord[2] = BOARD_RGHT - BOARD_LEFT - coord[2];
495                 coord[3] = BOARD_HEIGHT - coord[3];
496                 if(coord[0] >= 0) coord[0] = BOARD_RGHT - BOARD_LEFT - coord[0];
497                 if(coord[1] >= 0) coord[1] = BOARD_HEIGHT - coord[1];
498             }
499             toX = cl.ftIn = (currentMoveString[2] = coord[2] + 'a') - AAA;
500             toY = cl.rtIn = (currentMoveString[3] = coord[3] + '0') - ONE;
501             if(type[3] == NOTHING) cl.rtIn = -1; // for fxg type moves ask for toY disambiguation
502             else if(toY >= BOARD_HEIGHT || toY < 0)   return ImpossibleMove; // vert off-board to-square
503             if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return ImpossibleMove;
504             if(piece) {
505                 cl.pieceIn = CharToPiece(wom ? piece : piece + 'a' - 'A');
506                 if(cl.pieceIn == EmptySquare) return ImpossibleMove; // non-existent piece
507                 if(promoted) cl.pieceIn = (ChessSquare) (CHUPROMOTED(cl.pieceIn));
508             } else cl.pieceIn = EmptySquare;
509             if(separator == '@' || separator == '*') { // drop move. We only get here without from-square or promoted piece
510                 fromY = DROP_RANK; fromX = cl.pieceIn;
511                 currentMoveString[0] = piece;
512                 currentMoveString[1] = '@';
513                 currentMoveString[4] = NULLCHAR;
514                 return LegalityTest(boards[yyboardindex], PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, fromY, fromX, toY, toX, NULLCHAR);
515             }
516             if(type[1] == NOTHING && type[0] != NOTHING) { // there is a disambiguator
517                 if(type[0] != type[2]) coord[0] = -1, type[1] = type[0], type[0] = NOTHING; // it was a rank-disambiguator
518             }
519             if(  type[1] != type[2] && // means fromY is of opposite type as ToX, or NOTHING
520                 (type[0] == NOTHING || type[0] == type[2]) ) { // well formed
521                 int suffix = 7;
522                 fromX = (currentMoveString[0] = coord[0] + 'a') - AAA;
523                 fromY = (currentMoveString[1] = coord[1] + '0') - ONE;
524                 currentMoveString[4] = cl.promoCharIn = PromoSuffix(p);
525                 currentMoveString[5] = NULLCHAR;
526                 if(**p == 'x' && !cl.promoCharIn) { // other leg follows
527                     char *q = *p;
528                     int x = *++*p, y;
529                     ++*p; y = Number(p);
530                     if(**p == '-' || **p == 'x') {  // 3-leg move!
531                         currentMoveString[7] = (kill2X = toX) + AAA; // what we thought was to-square is in fact 1st kill-square of two
532                         currentMoveString[8] = (kill2Y = toY) + ONE; // append it after 2nd kill-square
533                         toX = x - AAA;       // kludge alert: this will become 2nd kill square
534                         toY = y + '0' - ONE;
535                         suffix += 2;
536                     } else *p = q; // 2-leg move, rewind to leave reading of 2nd leg to code below
537                 }
538                 if(!cl.promoCharIn && (**p == '-' || **p == 'x')) { // Lion-type multi-leg move
539                     currentMoveString[5] = (killX = toX) + AAA; // what we thought was to-square is in fact kill-square
540                     currentMoveString[6] = (killY = toY) + ONE; // append it as suffix behind long algebraic move
541                     currentMoveString[4] = ';';
542                     currentMoveString[suffix+1] = NULLCHAR;
543                     // read new to-square (VERY non-robust! Assumes correct (non-alpha-rank) syntax, and messes up on errors)
544                     toX = cl.ftIn = (currentMoveString[2] = *++*p) - AAA; ++*p;
545                     toY = cl.rtIn = (currentMoveString[3] = Number(p) + '0') - ONE;
546                     currentMoveString[suffix] = cl.promoCharIn = PromoSuffix(p);
547                 }
548                 if(type[0] != NOTHING && type[1] != NOTHING && type[3] != NOTHING) { // fully specified.
549                     ChessSquare realPiece = boards[yyboardindex][fromY][fromX];
550                     // Note that Disambiguate does not work for illegal moves, but flags them as impossible
551                     if(piece) { // check if correct piece indicated
552                         if(PieceToChar(realPiece) == '~') realPiece = (ChessSquare) (DEMOTED(realPiece));
553                         if(!(appData.icsActive && PieceToChar(realPiece) == '+') && // trust ICS if it moves promoted pieces
554                            piece && realPiece != cl.pieceIn) return ImpossibleMove;
555                     } else if(!separator && **p == '+') { // could be a protocol move, where bare '+' suffix means shogi-style promotion
556                         if(realPiece < (wom ?  WhiteCannon : BlackCannon) && PieceToChar(PROMOTED(realPiece)) == '+') // seems to be that
557                            currentMoveString[4] = cl.promoCharIn = *(*p)++; // append promochar after all
558                     }
559                     result = LegalityTest(boards[yyboardindex], PosFlags(yyboardindex), fromY, fromX, toY, toX, cl.promoCharIn);
560                     if (currentMoveString[4] == NULLCHAR) { // suppy missing mandatory promotion character
561                       if(result == WhitePromotion  || result == BlackPromotion) {
562                         switch(gameInfo.variant) {
563                           case VariantCourier:
564                           case VariantShatranj: currentMoveString[4] = PieceToChar(BlackFerz); break;
565                           case VariantGreat:    currentMoveString[4] = PieceToChar(BlackMan); break;
566                           case VariantShogi:    currentMoveString[4] = '+'; break;
567                           default:              currentMoveString[4] = PieceToChar(BlackQueen);
568                         }
569                       } else if(result == WhiteNonPromotion  || result == BlackNonPromotion) {
570                                                 currentMoveString[4] = '=';
571                       }
572                     } else if(appData.testLegality && gameInfo.variant != VariantSChess && // strip off unnecessary and false promo characters
573                        !(result == WhitePromotion  || result == BlackPromotion ||
574                          result == WhiteNonPromotion || result == BlackNonPromotion)) currentMoveString[4] = NULLCHAR;
575                     return result;
576                 } else if(cl.pieceIn == EmptySquare) cl.pieceIn = wom ? WhitePawn : BlackPawn;
577                 cl.ffIn = type[0] == NOTHING ? -1 : coord[0] + 'a' - AAA;
578                 cl.rfIn = type[1] == NOTHING ? -1 : coord[1] + '0' - ONE;
579
580                 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
581
582                 if(cl.kind == ImpossibleMove && !piece && type[1] == NOTHING // fxg5 type
583                         && toY == (wom ? 4 : 3)) { // could be improperly written e.p.
584                     cl.rtIn += wom ? 1 : -1; // shift target square to e.p. square
585                     Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
586                     if((cl.kind != WhiteCapturesEnPassant && cl.kind != BlackCapturesEnPassant))
587                         return ImpossibleMove; // nice try, but no cigar
588                 }
589
590                 currentMoveString[0] = cl.ff + AAA;
591                 currentMoveString[1] = cl.rf + ONE;
592                 currentMoveString[3] = cl.rt + ONE;
593                 if(killX < 0) // [HGM] lion: do not overwrite kill-square suffix
594                 currentMoveString[4] = cl.promoChar;
595
596                 if((cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) && !Match("ep", p)) Match("e.p.", p);
597
598                 return (int) cl.kind;
599             }
600         }
601 badMove:// we failed to find algebraic move
602         *p = oldp;
603
604
605         // Next we do some common symbols where the first character commits us to things that cannot possibly be a move
606
607         // ********* PGN tags ******************************************
608         if(**p == '[') {
609             oldp = ++(*p); kifu = 0;
610             if(Match("--", p)) { // "[--" could be start of position diagram
611                 if(!Scan(']', p) && (*p)[-3] == '-' && (*p)[-2] == '-') return PositionDiagram;
612                 *p = oldp;
613             }
614             SkipWhite(p);
615             if(Match("DhtmlXQ", p)) { // Xiangqi UBB tags
616                 int res = Nothing;
617                 if(**p == ']') strcpy(parseStart = yytext, "[Variant \"xiangqi\"]"), res = PGNTag; else
618                 if(Match("_movelist", p)) xqUBB = 1; else Scan(']', p);
619                 Scan(']', p); // for non-movelist tags this skips to the closing tag (disarming any enclosed kanji)!
620                 return res;
621             } else if(Match("/DhtmlXQ", p)) { Scan(']', p); return Nothing; }
622             if(isdigit(**p) || isalpha(**p)) {
623                 do (*p)++; while(isdigit(**p) || isalpha(**p) || **p == '+' ||
624                                 **p == '-' || **p == '=' || **p == '_' || **p == '#');
625                 SkipWhite(p);
626                 if(**p == '"') {
627                     (*p)++;
628                     while(**p != '\n' && (*(*p)++ != '"'|| (*p)[-2] == '\\')); // look for unescaped quote
629                     if((*p)[-1] !='"') { *p = oldp; Scan(']', p); return Comment; } // string closing delimiter missing
630                     SkipWhite(p); if(*(*p)++ == ']') return PGNTag;
631                 }
632             }
633             Scan(']', p); return Comment;
634         }
635
636         // ********* SAN Castings *************************************
637         if(**p == 'O' || **p == 'o' || **p == '0' && !Match("00:", p)) { // exclude 00 in time stamps
638             int castlingType = 0;
639             if(Match("O-O-O", p) || Match("o-o-o", p) || Match("0-0-0", p) ||
640                Match("OOO", p) || Match("ooo", p) || Match("000", p)) castlingType = 2;
641             else if(Match("O-O", p) || Match("o-o", p) || Match("0-0", p) ||
642                     Match("OO", p) || Match("oo", p) || Match("00", p)) castlingType = 1;
643             if(castlingType) { //code from old parser, collapsed for both castling types, and streamlined a bit
644                 int rf, ff, rt, ft; ChessSquare king;
645                 char promo=NULLCHAR;
646
647                 if(gameInfo.variant == VariantSChess) promo = PromoSuffix(p);
648
649                 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
650
651                 if (wom) {
652                     rf = castlingRank[0];
653                     rt = castlingRank[0];
654                     king = WhiteKing;
655                 } else {
656                     rf = castlingRank[3];
657                     rt = castlingRank[3];
658                     king = BlackKing;
659                 }
660                 ff = (BOARD_WIDTH-1)>>1; // this would be d-file
661                 if (boards[yyboardindex][rf][ff] == king) {
662                     /* ICS wild castling */
663                     ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
664                 } else {
665                     char *q;
666                     ff = BOARD_WIDTH>>1; // e-file
667                     ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
668                     if(pieceDesc[king] && (q = strchr(pieceDesc[king], 'O'))) { // redefined to non-default King stride
669                         ft = (castlingType == 1 ? ff + atoi(q+1) : ff - atoi(q+1));
670                     }
671                 }
672                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
673                     if (wom) {
674                         ff = initialRights[2];
675                         ft = initialRights[castlingType-1];
676                     } else {
677                         ff = initialRights[5];
678                         ft = initialRights[castlingType+2];
679                     }
680                     if (appData.debugMode) fprintf(debugFP, "Parser FRC (type=%d) %d %d\n", castlingType, ff, ft);
681                     if(ff == NoRights || ft == NoRights) return ImpossibleMove;
682                 }
683                 sprintf(currentMoveString, "%c%c%c%c%c",ff+AAA,rf+ONE,ft+AAA,rt+ONE,promo);
684                 if (appData.debugMode) fprintf(debugFP, "(%d-type) castling %d %d\n", castlingType, ff, ft);
685
686                 return (int) LegalityTest(boards[yyboardindex],
687                               PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
688                               rf, ff, rt, ft, promo);
689             } else if(Match("01", p)) return Nothing; // prevent this from being mistaken for move number 1
690         }
691
692
693         // ********* variations (nesting) ******************************
694         if(**p =='(') {
695             if(RdTime(')', p)) return ElapsedTime;
696             return Open;
697         }
698         if(**p ==')') { (*p)++; return Close; }
699         if(**p == ';') { while(**p != '\n') (*p)++; return Comment; }
700
701
702         // ********* Comments and result messages **********************
703         *p = oldp; commentEnd = NULL; result = 0;
704         if(**p == '{') {
705             if(RdTime('}', p)) return ElapsedTime;
706             if(lastChar == '\n' && Match("--------------\n", p)) {
707                 char *q;
708                 i = Scan ('}', p); q = *p - 16;
709                 if(Match("\n--------------}\n", &q)) return PositionDiagram;
710             } else i = Scan('}', p);
711             commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
712         }
713         if(commentEnd) SkipWhite(p);
714         if(kifu && **p == '*') { // .kif comment
715             char *q = yytext;
716             while(**p && **p != '\n') { if(q < yytext + 10*MSG_SIZ-3) *q++ = **p; (*p)++; }
717             parseStart = yytext; *yytext = '{'; strcpy(q, "}\n"); // wrap in braces
718             return Comment;
719         }
720         if(Match("*", p)) result = GameUnfinished;
721         else if(**p == '0') {
722             if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
723                 Match("0 - 1", p) || Match("0 / 1", p) || Match("0 : 1", p)) result = BlackWins;
724         } else if(**p == '1') {
725             if( Match("1-0", p) || Match("1/0", p) || Match("1:0", p) ||
726                 Match("1 - 0", p) || Match("1 / 0", p) || Match("1 : 0", p)) result = WhiteWins;
727             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) ||
728                     Match("1 / 2 : 1 / 2", p) || Match("1/2", p) || Match("1 / 2", p)) result = GameIsDrawn;
729         }
730         if(result) {
731             if(Match(" (", p) && !Scan(')', p) || Match(" {", p) && !Scan('}', p)) { // there is a comment after the PGN result!
732                 if(commentEnd) { *p = commentEnd; return Comment; } // so comment before it is normal comment; return that first
733             }
734             return result; // this returns a possible preceeding comment as result details
735         }
736         if(commentEnd) { *p = commentEnd; return Comment; } // there was no PGN result following, so return as normal comment
737
738
739         // ********* Move numbers (after castlings or PGN results!) ***********
740         if((i = Number(p)) != BADNUMBER) { // a single number was read as part of our attempt to read a move
741             char *numEnd = *p;
742             if(**p == '.') (*p)++; SkipWhite(p);
743             if(**p == '+' || isalpha(**p) || gameInfo.variant == VariantShogi && *p != numEnd && isdigit(**p)) {
744                 *p = numEnd;
745                 return i == 1 ? MoveNumberOne : Nothing;
746             }
747             *p = numEnd; return Nothing;
748         }
749
750
751         // ********* non-compliant game-result indicators *********************
752         if(Match("+-+", p) || Word("stalemate", p)) return GameIsDrawn;
753         if(Match("++", p) || Verb("resign", p) || (Word("check", p) || 1) && Word("mate", p) )
754             return (wom ? BlackWins : WhiteWins);
755         c = ToUpper(**p);
756         if(Word("w", p) && (Match("hite", p) || 1) || Word("b", p) && (Match("lack", p) || 1) ) {
757             if(**p != ' ') return Nothing;
758             ++*p;
759             if(Verb("disconnect", p)) return GameUnfinished;
760             if(Verb("resign", p) || Verb("forfeit", p) || Word("mated", p) || Word("lost", p) || Word("loses", p))
761                 return (c == 'W' ? BlackWins : WhiteWins);
762             if(Word("mates", p) || Word("wins", p) || Word("won", p))
763                 return (c != 'W' ? BlackWins : WhiteWins);
764             return Nothing;
765         }
766         if(Word("draw", p)) {
767             if(**p == 'n') (*p)++;
768             if(**p != ' ') return GameIsDrawn;
769             oldp = ++*p;
770             if(Word("agreed", p)) return GameIsDrawn;
771             if(Match("by ", p) && (Word("repetition", p) || Word("agreement", p)) ) return GameIsDrawn;
772             *p = oldp;
773             if(*(*p)++ == '(') {
774                 while(**p != '\n') if(*(*p)++ == ')') break;
775                 if((*p)[-1] == ')')  return GameIsDrawn;
776             }
777             *p = oldp - 1; return GameIsDrawn;
778         }
779
780
781         // ********* Numeric annotation glyph **********************************
782         if(**p == '$') { (*p)++; if(Number(p) != BADNUMBER) return NAG; return Nothing; }
783
784
785         // ********** by now we are getting down to the silly stuff ************
786         if(Word("gnu", p) || Match("GNU", p)) {
787             if(**p == ' ') (*p)++;
788             if(Word("chess", p) || Match("CHESS", p)) {
789                 char *q;
790                 if((q = strstr(*p, "game")) || (q = strstr(*p, "GAME")) || (q = strstr(*p, "Game"))) {
791                     (*p) = q + 4; return GNUChessGame;
792                 }
793             }
794             return Nothing;
795         }
796         if(lastChar == '\n' && (Match("# ", p) || Match("; ", p) || Match("% ", p))) {
797             while(**p != '\n' && **p != ' ') (*p)++;
798             if(**p == ' ' && (Match(" game file", p) || Match(" position file", p))) {
799                 while(**p != '\n') (*p)++; // skip to EOLN
800                 return XBoardGame;
801             }
802             *p = oldp; // we might need to re-match the skipped stuff
803         }
804
805         if(Match("---", p)) { while(**p == '-') (*p)++; return Nothing; } // prevent separators parsing as null move
806         if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
807             strncpy(currentMoveString, "@@@@", 5);
808             return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
809         }
810
811         // ********* Efficient skipping of (mostly) alphabetic chatter **********
812         while(isdigit(**p) || isalpha(**p) || **p == '-') (*p)++;
813         if(*p != oldp) {
814             if(**p == '\'') {
815                 while(isdigit(**p) || isalpha(**p) || **p == '-' || **p == '\'') (*p)++;
816                 return Nothing; // random word
817             }
818             if(lastChar == '\n' && Match(": ", p)) { // mail header, skip indented lines
819                 do {
820                     while(**p != '\n') (*p)++;
821                     if(!ReadLine()) return Nothing; // append next line if not EOF
822                 } while(Match("\n ", p) || Match("\n\t", p));
823             }
824             return Nothing;
825         }
826
827         // ********* Prevent 00 in unprotected time stamps to be mistaken for castling *******
828         if(Match(":00", p)) return Nothing;
829
830         // ********* Could not match to anything. Return offending character ****
831         (*p)++;
832         return Nothing;
833 }
834
835 /*
836     Return offset of next pattern in the current file.
837 */
838 int
839 yyoffset ()
840 {
841     return ftell(inputFile) - (inPtr - parsePtr); // subtract what is read but not yet parsed
842 }
843
844 void
845 yynewfile (FILE *f)
846 {   // prepare parse buffer for reading file
847     inputFile = f;
848     inPtr = parsePtr = inputBuf;
849     fromString = 0;
850     lastChar = '\n';
851     *inPtr = NULLCHAR; // make sure we will start by reading a line
852 }
853
854 void
855 yynewstr P((char *s))
856 {
857     parsePtr = s;
858     inputFile = NULL;
859     fromString = 1;
860 }
861
862 int
863 yylex ()
864 {   // this replaces the flex-generated parser
865     int result = NextUnit(&parsePtr);
866     char *p = parseStart, *q = yytext;
867     if(p == yytext) return result;   // kludge to allow kanji expansion
868     while(p < parsePtr) *q++ = *p++; // copy the matched text to yytext[]
869     *q = NULLCHAR;
870     lastChar = q[-1];
871     return result;
872 }
873
874 int
875 Myylex ()
876 {   // [HGM] wrapper for yylex, which treats nesting of parentheses
877     int symbol, nestingLevel = 0, i=0;
878     char *p;
879     static char buf[256*MSG_SIZ];
880     buf[0] = NULLCHAR;
881     do { // eat away anything not at level 0
882         symbol = yylex();
883         if(symbol == Open) nestingLevel++;
884         if(nestingLevel) { // save all parsed text between (and including) the ()
885             for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
886             buf[i] = NULLCHAR;
887         }
888         if(symbol == 0) break; // ran into EOF
889         if(symbol == Close) symbol = Comment, nestingLevel--;
890     } while(nestingLevel || symbol == Nothing);
891     yy_text = buf[0] ? buf : (char*)yytext;
892     return symbol;
893 }
894
895 ChessMove
896 yylexstr (int boardIndex, char *s, char *buf, int buflen)
897 {
898     ChessMove ret;
899     char *savPP = parsePtr;
900     fromString = 1;
901     yyboardindex = boardIndex;
902     parsePtr = s;
903     ret = (ChessMove) Myylex();
904     strncpy(buf, yy_text, buflen-1);
905     buf[buflen-1] = NULLCHAR;
906     parsePtr = savPP;
907     fromString = 0;
908     return ret;
909 }