Fix Seirawan gating at Rook square in PGN castling moves
[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, gate = 0;
646
647                 if(gameInfo.variant == VariantSChess) {
648                     promo = PromoSuffix(p);
649                     if(promo && **p >= 'a' && **p < AAA + BOARD_RGHT) gate = *(*p)++;
650                 }
651
652                 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
653
654                 if (wom) {
655                     rf = castlingRank[0];
656                     rt = castlingRank[0];
657                     king = WhiteKing;
658                 } else {
659                     rf = castlingRank[3];
660                     rt = castlingRank[3];
661                     king = BlackKing;
662                 }
663                 ff = (BOARD_WIDTH-1)>>1; // this would be d-file
664                 if (boards[yyboardindex][rf][ff] == king) {
665                     /* ICS wild castling */
666                     ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
667                 } else {
668                     char *q;
669                     ff = BOARD_WIDTH>>1; // e-file
670                     ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
671                     if(pieceDesc[king] && (q = strchr(pieceDesc[king], 'O'))) { // redefined to non-default King stride
672                         ft = (castlingType == 1 ? ff + atoi(q+1) : ff - atoi(q+1));
673                     }
674                 }
675                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
676                     if (wom) {
677                         ff = initialRights[2];
678                         ft = initialRights[castlingType-1];
679                     } else {
680                         ff = initialRights[5];
681                         ft = initialRights[castlingType+2];
682                     }
683                     if (appData.debugMode) fprintf(debugFP, "Parser FRC (type=%d) %d %d\n", castlingType, ff, ft);
684                     if(ff == NoRights || ft == NoRights) return ImpossibleMove;
685                 }
686                 if(gate) { // gating disambiguator present
687                     if(gate != ff + AAA) {
688                         int h = ft; ft = ff; ff = h; // reverse for gating at Rook square
689                         if(gate != AAA + initialRights[castlingType+(wom?-1:2)]) return ImpossibleMove;
690                     }
691                 }
692                 sprintf(currentMoveString, "%c%c%c%c%c",ff+AAA,rf+ONE,ft+AAA,rt+ONE,promo);
693                 if (appData.debugMode) fprintf(debugFP, "(%d-type) castling %d %d\n", castlingType, ff, ft);
694
695                 return (int) LegalityTest(boards[yyboardindex],
696                               PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
697                               rf, ff, rt, ft, promo);
698             } else if(Match("01", p)) return Nothing; // prevent this from being mistaken for move number 1
699         }
700
701
702         // ********* variations (nesting) ******************************
703         if(**p =='(') {
704             if(RdTime(')', p)) return ElapsedTime;
705             return Open;
706         }
707         if(**p ==')') { (*p)++; return Close; }
708         if(**p == ';') { while(**p != '\n') (*p)++; return Comment; }
709
710
711         // ********* Comments and result messages **********************
712         *p = oldp; commentEnd = NULL; result = 0;
713         if(**p == '{') {
714             if(RdTime('}', p)) return ElapsedTime;
715             if(lastChar == '\n' && Match("--------------\n", p)) {
716                 char *q;
717                 i = Scan ('}', p); q = *p - 16;
718                 if(Match("\n--------------}\n", &q)) return PositionDiagram;
719             } else i = Scan('}', p);
720             commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
721         }
722         if(commentEnd) SkipWhite(p);
723         if(kifu && **p == '*') { // .kif comment
724             char *q = yytext;
725             while(**p && **p != '\n') { if(q < yytext + 10*MSG_SIZ-3) *q++ = **p; (*p)++; }
726             parseStart = yytext; *yytext = '{'; strcpy(q, "}\n"); // wrap in braces
727             return Comment;
728         }
729         if(Match("*", p)) result = GameUnfinished;
730         else if(**p == '0') {
731             if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
732                 Match("0 - 1", p) || Match("0 / 1", p) || Match("0 : 1", p)) result = BlackWins;
733         } else if(**p == '1') {
734             if( Match("1-0", p) || Match("1/0", p) || Match("1:0", p) ||
735                 Match("1 - 0", p) || Match("1 / 0", p) || Match("1 : 0", p)) result = WhiteWins;
736             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) ||
737                     Match("1 / 2 : 1 / 2", p) || Match("1/2", p) || Match("1 / 2", p)) result = GameIsDrawn;
738         }
739         if(result) {
740             if(Match(" (", p) && !Scan(')', p) || Match(" {", p) && !Scan('}', p)) { // there is a comment after the PGN result!
741                 if(commentEnd) { *p = commentEnd; return Comment; } // so comment before it is normal comment; return that first
742             }
743             return result; // this returns a possible preceeding comment as result details
744         }
745         if(commentEnd) { *p = commentEnd; return Comment; } // there was no PGN result following, so return as normal comment
746
747
748         // ********* Move numbers (after castlings or PGN results!) ***********
749         if((i = Number(p)) != BADNUMBER) { // a single number was read as part of our attempt to read a move
750             char *numEnd = *p;
751             if(**p == '.') (*p)++; SkipWhite(p);
752             if(**p == '+' || isalpha(**p) || gameInfo.variant == VariantShogi && *p != numEnd && isdigit(**p)) {
753                 *p = numEnd;
754                 return i == 1 ? MoveNumberOne : Nothing;
755             }
756             *p = numEnd; return Nothing;
757         }
758
759
760         // ********* non-compliant game-result indicators *********************
761         if(Match("+-+", p) || Word("stalemate", p)) return GameIsDrawn;
762         if(Match("++", p) || Verb("resign", p) || (Word("check", p) || 1) && Word("mate", p) )
763             return (wom ? BlackWins : WhiteWins);
764         c = ToUpper(**p);
765         if(Word("w", p) && (Match("hite", p) || 1) || Word("b", p) && (Match("lack", p) || 1) ) {
766             if(**p != ' ') return Nothing;
767             ++*p;
768             if(Verb("disconnect", p)) return GameUnfinished;
769             if(Verb("resign", p) || Verb("forfeit", p) || Word("mated", p) || Word("lost", p) || Word("loses", p))
770                 return (c == 'W' ? BlackWins : WhiteWins);
771             if(Word("mates", p) || Word("wins", p) || Word("won", p))
772                 return (c != 'W' ? BlackWins : WhiteWins);
773             return Nothing;
774         }
775         if(Word("draw", p)) {
776             if(**p == 'n') (*p)++;
777             if(**p != ' ') return GameIsDrawn;
778             oldp = ++*p;
779             if(Word("agreed", p)) return GameIsDrawn;
780             if(Match("by ", p) && (Word("repetition", p) || Word("agreement", p)) ) return GameIsDrawn;
781             *p = oldp;
782             if(*(*p)++ == '(') {
783                 while(**p != '\n') if(*(*p)++ == ')') break;
784                 if((*p)[-1] == ')')  return GameIsDrawn;
785             }
786             *p = oldp - 1; return GameIsDrawn;
787         }
788
789
790         // ********* Numeric annotation glyph **********************************
791         if(**p == '$') { (*p)++; if(Number(p) != BADNUMBER) return NAG; return Nothing; }
792
793
794         // ********** by now we are getting down to the silly stuff ************
795         if(Word("gnu", p) || Match("GNU", p)) {
796             if(**p == ' ') (*p)++;
797             if(Word("chess", p) || Match("CHESS", p)) {
798                 char *q;
799                 if((q = strstr(*p, "game")) || (q = strstr(*p, "GAME")) || (q = strstr(*p, "Game"))) {
800                     (*p) = q + 4; return GNUChessGame;
801                 }
802             }
803             return Nothing;
804         }
805         if(lastChar == '\n' && (Match("# ", p) || Match("; ", p) || Match("% ", p))) {
806             while(**p != '\n' && **p != ' ') (*p)++;
807             if(**p == ' ' && (Match(" game file", p) || Match(" position file", p))) {
808                 while(**p != '\n') (*p)++; // skip to EOLN
809                 return XBoardGame;
810             }
811             *p = oldp; // we might need to re-match the skipped stuff
812         }
813
814         if(Match("---", p)) { while(**p == '-') (*p)++; return Nothing; } // prevent separators parsing as null move
815         if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
816             strncpy(currentMoveString, "@@@@", 5);
817             return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
818         }
819
820         // ********* Efficient skipping of (mostly) alphabetic chatter **********
821         while(isdigit(**p) || isalpha(**p) || **p == '-') (*p)++;
822         if(*p != oldp) {
823             if(**p == '\'') {
824                 while(isdigit(**p) || isalpha(**p) || **p == '-' || **p == '\'') (*p)++;
825                 return Nothing; // random word
826             }
827             if(lastChar == '\n' && Match(": ", p)) { // mail header, skip indented lines
828                 do {
829                     while(**p != '\n') (*p)++;
830                     if(!ReadLine()) return Nothing; // append next line if not EOF
831                 } while(Match("\n ", p) || Match("\n\t", p));
832             }
833             return Nothing;
834         }
835
836         // ********* Prevent 00 in unprotected time stamps to be mistaken for castling *******
837         if(Match(":00", p)) return Nothing;
838
839         // ********* Could not match to anything. Return offending character ****
840         (*p)++;
841         return Nothing;
842 }
843
844 /*
845     Return offset of next pattern in the current file.
846 */
847 int
848 yyoffset ()
849 {
850     return ftell(inputFile) - (inPtr - parsePtr); // subtract what is read but not yet parsed
851 }
852
853 void
854 yynewfile (FILE *f)
855 {   // prepare parse buffer for reading file
856     inputFile = f;
857     inPtr = parsePtr = inputBuf;
858     fromString = 0;
859     lastChar = '\n';
860     *inPtr = NULLCHAR; // make sure we will start by reading a line
861 }
862
863 void
864 yynewstr P((char *s))
865 {
866     parsePtr = s;
867     inputFile = NULL;
868     fromString = 1;
869 }
870
871 int
872 yylex ()
873 {   // this replaces the flex-generated parser
874     int result = NextUnit(&parsePtr);
875     char *p = parseStart, *q = yytext;
876     if(p == yytext) return result;   // kludge to allow kanji expansion
877     while(p < parsePtr) *q++ = *p++; // copy the matched text to yytext[]
878     *q = NULLCHAR;
879     lastChar = q[-1];
880     return result;
881 }
882
883 int
884 Myylex ()
885 {   // [HGM] wrapper for yylex, which treats nesting of parentheses
886     int symbol, nestingLevel = 0, i=0;
887     char *p;
888     static char buf[256*MSG_SIZ];
889     buf[0] = NULLCHAR;
890     do { // eat away anything not at level 0
891         symbol = yylex();
892         if(symbol == Open) nestingLevel++;
893         if(nestingLevel) { // save all parsed text between (and including) the ()
894             for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
895             buf[i] = NULLCHAR;
896         }
897         if(symbol == 0) break; // ran into EOF
898         if(symbol == Close) symbol = Comment, nestingLevel--;
899     } while(nestingLevel || symbol == Nothing);
900     yy_text = buf[0] ? buf : (char*)yytext;
901     return symbol;
902 }
903
904 ChessMove
905 yylexstr (int boardIndex, char *s, char *buf, int buflen)
906 {
907     ChessMove ret;
908     char *savPP = parsePtr;
909     fromString = 1;
910     yyboardindex = boardIndex;
911     parsePtr = s;
912     ret = (ChessMove) Myylex();
913     strncpy(buf, yy_text, buflen-1);
914     buf[buflen-1] = NULLCHAR;
915     parsePtr = savPP;
916     fromString = 0;
917     return ret;
918 }