Merge branch 'v4.8.x'
[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;
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     char c, *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 = 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
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(!cl.promoCharIn && (**p == '-' || **p == 'x')) { // Lion-type multi-leg move
519                     currentMoveString[5] = (killX = toX) + AAA; // what we thought was to-square is in fact kill-square
520                     currentMoveString[6] = (killY = toY) + ONE; // append it as suffix behind long algebraic move
521                     currentMoveString[4] = ';';
522                     currentMoveString[7] = NULLCHAR;
523                     // read new to-square (VERY non-robust! Assumes correct (non-alpha-rank) syntax, and messes up on errors)
524                     toX = cl.ftIn = (currentMoveString[2] = *++*p) - AAA; ++*p;
525                     toY = cl.rtIn = (currentMoveString[3] = Number(p) + '0') - ONE;
526                 }
527                 if(type[0] != NOTHING && type[1] != NOTHING && type[3] != NOTHING) { // fully specified.
528                     ChessSquare realPiece = boards[yyboardindex][fromY][fromX];
529                     // Note that Disambiguate does not work for illegal moves, but flags them as impossible
530                     if(piece) { // check if correct piece indicated
531                         if(PieceToChar(realPiece) == '~') realPiece = (ChessSquare) (DEMOTED(realPiece));
532                         if(!(appData.icsActive && PieceToChar(realPiece) == '+') && // trust ICS if it moves promoted pieces
533                            piece && realPiece != cl.pieceIn) return ImpossibleMove;
534                     } else if(!separator && **p == '+') { // could be a protocol move, where bare '+' suffix means shogi-style promotion
535                         if(realPiece < (wom ?  WhiteCannon : BlackCannon) && PieceToChar(PROMOTED(realPiece)) == '+') // seems to be that
536                            currentMoveString[4] = cl.promoCharIn = *(*p)++; // append promochar after all
537                     }
538                     result = LegalityTest(boards[yyboardindex], PosFlags(yyboardindex), fromY, fromX, toY, toX, cl.promoCharIn);
539                     if (currentMoveString[4] == NULLCHAR) { // suppy missing mandatory promotion character
540                       if(result == WhitePromotion  || result == BlackPromotion) {
541                         switch(gameInfo.variant) {
542                           case VariantCourier:
543                           case VariantShatranj: currentMoveString[4] = PieceToChar(BlackFerz); break;
544                           case VariantGreat:    currentMoveString[4] = PieceToChar(BlackMan); break;
545                           case VariantShogi:    currentMoveString[4] = '+'; break;
546                           default:              currentMoveString[4] = PieceToChar(BlackQueen);
547                         }
548                       } else if(result == WhiteNonPromotion  || result == BlackNonPromotion) {
549                                                 currentMoveString[4] = '=';
550                       }
551                     } else if(appData.testLegality && gameInfo.variant != VariantSChess && // strip off unnecessary and false promo characters
552                        !(result == WhitePromotion  || result == BlackPromotion ||
553                          result == WhiteNonPromotion || result == BlackNonPromotion)) currentMoveString[4] = NULLCHAR;
554                     return result;
555                 } else if(cl.pieceIn == EmptySquare) cl.pieceIn = wom ? WhitePawn : BlackPawn;
556                 cl.ffIn = type[0] == NOTHING ? -1 : coord[0] + 'a' - AAA;
557                 cl.rfIn = type[1] == NOTHING ? -1 : coord[1] + '0' - ONE;
558
559                 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
560
561                 if(cl.kind == ImpossibleMove && !piece && type[1] == NOTHING // fxg5 type
562                         && toY == (wom ? 4 : 3)) { // could be improperly written e.p.
563                     cl.rtIn += wom ? 1 : -1; // shift target square to e.p. square
564                     Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
565                     if((cl.kind != WhiteCapturesEnPassant && cl.kind != BlackCapturesEnPassant))
566                         return ImpossibleMove; // nice try, but no cigar
567                 }
568
569                 currentMoveString[0] = cl.ff + AAA;
570                 currentMoveString[1] = cl.rf + ONE;
571                 currentMoveString[3] = cl.rt + ONE;
572                 if(killX < 0) // [HGM] lion: do not overwrite kill-square suffix
573                 currentMoveString[4] = cl.promoChar;
574
575                 if((cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) && (Match("ep", p) || Match("e.p.", p)));
576
577                 return (int) cl.kind;
578             }
579         }
580 badMove:// we failed to find algebraic move
581         *p = oldp;
582
583
584         // Next we do some common symbols where the first character commits us to things that cannot possibly be a move
585
586         // ********* PGN tags ******************************************
587         if(**p == '[') {
588             oldp = ++(*p); kifu = 0;
589             if(Match("--", p)) { // "[--" could be start of position diagram
590                 if(!Scan(']', p) && (*p)[-3] == '-' && (*p)[-2] == '-') return PositionDiagram;
591                 *p = oldp;
592             }
593             SkipWhite(p);
594             if(isdigit(**p) || isalpha(**p)) {
595                 do (*p)++; while(isdigit(**p) || isalpha(**p) || **p == '+' ||
596                                 **p == '-' || **p == '=' || **p == '_' || **p == '#');
597                 SkipWhite(p);
598                 if(**p == '"') {
599                     (*p)++;
600                     while(**p != '\n' && (*(*p)++ != '"'|| (*p)[-2] == '\\')); // look for unescaped quote
601                     if((*p)[-1] !='"') { *p = oldp; Scan(']', p); return Comment; } // string closing delimiter missing
602                     SkipWhite(p); if(*(*p)++ == ']') return PGNTag;
603                 }
604             }
605             Scan(']', p); return Comment;
606         }
607
608         // ********* SAN Castings *************************************
609         if(**p == 'O' || **p == 'o' || **p == '0' && !Match("00:", p)) { // exclude 00 in time stamps
610             int castlingType = 0;
611             if(Match("O-O-O", p) || Match("o-o-o", p) || Match("0-0-0", p) ||
612                Match("OOO", p) || Match("ooo", p) || Match("000", p)) castlingType = 2;
613             else if(Match("O-O", p) || Match("o-o", p) || Match("0-0", p) ||
614                     Match("OO", p) || Match("oo", p) || Match("00", p)) castlingType = 1;
615             if(castlingType) { //code from old parser, collapsed for both castling types, and streamlined a bit
616                 int rf, ff, rt, ft; ChessSquare king;
617                 char promo=NULLCHAR;
618
619                 if(gameInfo.variant == VariantSChess) promo = PromoSuffix(p);
620
621                 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
622
623                 if (wom) {
624                     rf = castlingRank[0];
625                     rt = castlingRank[0];
626                     king = WhiteKing;
627                 } else {
628                     rf = castlingRank[3];
629                     rt = castlingRank[3];
630                     king = BlackKing;
631                 }
632                 ff = (BOARD_WIDTH-1)>>1; // this would be d-file
633                 if (boards[yyboardindex][rf][ff] == king) {
634                     /* ICS wild castling */
635                     ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
636                 } else {
637                     char *q;
638                     ff = BOARD_WIDTH>>1; // e-file
639                     ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
640                     if(pieceDesc[king] && (q = strchr(pieceDesc[king], 'O'))) { // redefined to non-default King stride
641                         ft = (castlingType == 1 ? ff + atoi(q+1) : ff - atoi(q+1));
642                     }
643                 }
644                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
645                     if (wom) {
646                         ff = initialRights[2];
647                         ft = initialRights[castlingType-1];
648                     } else {
649                         ff = initialRights[5];
650                         ft = initialRights[castlingType+2];
651                     }
652                     if (appData.debugMode) fprintf(debugFP, "Parser FRC (type=%d) %d %d\n", castlingType, ff, ft);
653                     if(ff == NoRights || ft == NoRights) return ImpossibleMove;
654                 }
655                 sprintf(currentMoveString, "%c%c%c%c%c",ff+AAA,rf+ONE,ft+AAA,rt+ONE,promo);
656                 if (appData.debugMode) fprintf(debugFP, "(%d-type) castling %d %d\n", castlingType, ff, ft);
657
658                 return (int) LegalityTest(boards[yyboardindex],
659                               PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
660                               rf, ff, rt, ft, promo);
661             } else if(Match("01", p)) return Nothing; // prevent this from being mistaken for move number 1
662         }
663
664
665         // ********* variations (nesting) ******************************
666         if(**p =='(') {
667             if(RdTime(')', p)) return ElapsedTime;
668             return Open;
669         }
670         if(**p ==')') { (*p)++; return Close; }
671         if(**p == ';') { while(**p != '\n') (*p)++; return Comment; }
672
673
674         // ********* Comments and result messages **********************
675         *p = oldp; commentEnd = NULL; result = 0;
676         if(**p == '{') {
677             if(RdTime('}', p)) return ElapsedTime;
678             if(lastChar == '\n' && Match("--------------\n", p)) {
679                 char *q;
680                 i = Scan ('}', p); q = *p - 16;
681                 if(Match("\n--------------}\n", &q)) return PositionDiagram;
682             } else i = Scan('}', p);
683             commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
684         }
685         if(commentEnd) SkipWhite(p);
686         if(kifu && **p == '*') { // .kif comment
687             char *q = yytext;
688             while(**p && **p != '\n') { if(q < yytext + 10*MSG_SIZ-3) *q++ = **p; (*p)++; }
689             parseStart = yytext; *yytext = '{'; strcpy(q, "}\n"); // wrap in braces
690             return Comment;
691         }
692         if(Match("*", p)) result = GameUnfinished;
693         else if(**p == '0') {
694             if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
695                 Match("0 - 1", p) || Match("0 / 1", p) || Match("0 : 1", p)) result = BlackWins;
696         } else if(**p == '1') {
697             if( Match("1-0", p) || Match("1/0", p) || Match("1:0", p) ||
698                 Match("1 - 0", p) || Match("1 / 0", p) || Match("1 : 0", p)) result = WhiteWins;
699             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) ||
700                     Match("1 / 2 : 1 / 2", p) || Match("1/2", p) || Match("1 / 2", p)) result = GameIsDrawn;
701         }
702         if(result) {
703             if(Match(" (", p) && !Scan(')', p) || Match(" {", p) && !Scan('}', p)) { // there is a comment after the PGN result!
704                 if(commentEnd) { *p = commentEnd; return Comment; } // so comment before it is normal comment; return that first
705             }
706             return result; // this returns a possible preceeding comment as result details
707         }
708         if(commentEnd) { *p = commentEnd; return Comment; } // there was no PGN result following, so return as normal comment
709
710
711         // ********* Move numbers (after castlings or PGN results!) ***********
712         if((i = Number(p)) != BADNUMBER) { // a single number was read as part of our attempt to read a move
713             char *numEnd = *p;
714             if(**p == '.') (*p)++; SkipWhite(p);
715             if(**p == '+' || isalpha(**p) || gameInfo.variant == VariantShogi && *p != numEnd && isdigit(**p)) {
716                 *p = numEnd;
717                 return i == 1 ? MoveNumberOne : Nothing;
718             }
719             *p = numEnd; return Nothing;
720         }
721
722
723         // ********* non-compliant game-result indicators *********************
724         if(Match("+-+", p) || Word("stalemate", p)) return GameIsDrawn;
725         if(Match("++", p) || Verb("resign", p) || (Word("check", p) || 1) && Word("mate", p) )
726             return (wom ? BlackWins : WhiteWins);
727         c = ToUpper(**p);
728         if(Word("w", p) && (Match("hite", p) || 1) || Word("b", p) && (Match("lack", p) || 1) ) {
729             if(**p != ' ') return Nothing;
730             ++*p;
731             if(Verb("disconnect", p)) return GameUnfinished;
732             if(Verb("resign", p) || Verb("forfeit", p) || Word("mated", p) || Word("lost", p) || Word("loses", p))
733                 return (c == 'W' ? BlackWins : WhiteWins);
734             if(Word("mates", p) || Word("wins", p) || Word("won", p))
735                 return (c != 'W' ? BlackWins : WhiteWins);
736             return Nothing;
737         }
738         if(Word("draw", p)) {
739             if(**p == 'n') (*p)++;
740             if(**p != ' ') return GameIsDrawn;
741             oldp = ++*p;
742             if(Word("agreed", p)) return GameIsDrawn;
743             if(Match("by ", p) && (Word("repetition", p) || Word("agreement", p)) ) return GameIsDrawn;
744             *p = oldp;
745             if(*(*p)++ == '(') {
746                 while(**p != '\n') if(*(*p)++ == ')') break;
747                 if((*p)[-1] == ')')  return GameIsDrawn;
748             }
749             *p = oldp - 1; return GameIsDrawn;
750         }
751
752
753         // ********* Numeric annotation glyph **********************************
754         if(**p == '$') { (*p)++; if(Number(p) != BADNUMBER) return NAG; return Nothing; }
755
756
757         // ********** by now we are getting down to the silly stuff ************
758         if(Word("gnu", p) || Match("GNU", p)) {
759             if(**p == ' ') (*p)++;
760             if(Word("chess", p) || Match("CHESS", p)) {
761                 char *q;
762                 if((q = strstr(*p, "game")) || (q = strstr(*p, "GAME")) || (q = strstr(*p, "Game"))) {
763                     (*p) = q + 4; return GNUChessGame;
764                 }
765             }
766             return Nothing;
767         }
768         if(lastChar == '\n' && (Match("# ", p) || Match("; ", p) || Match("% ", p))) {
769             while(**p != '\n' && **p != ' ') (*p)++;
770             if(**p == ' ' && (Match(" game file", p) || Match(" position file", p))) {
771                 while(**p != '\n') (*p)++; // skip to EOLN
772                 return XBoardGame;
773             }
774             *p = oldp; // we might need to re-match the skipped stuff
775         }
776
777         if(Match("---", p)) { while(**p == '-') (*p)++; return Nothing; } // prevent separators parsing as null move
778         if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
779             strncpy(currentMoveString, "@@@@", 5);
780             return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
781         }
782
783         // ********* Efficient skipping of (mostly) alphabetic chatter **********
784         while(isdigit(**p) || isalpha(**p) || **p == '-') (*p)++;
785         if(*p != oldp) {
786             if(**p == '\'') {
787                 while(isdigit(**p) || isalpha(**p) || **p == '-' || **p == '\'') (*p)++;
788                 return Nothing; // random word
789             }
790             if(lastChar == '\n' && Match(": ", p)) { // mail header, skip indented lines
791                 do {
792                     while(**p != '\n') (*p)++;
793                     if(!ReadLine()) return Nothing; // append next line if not EOF
794                 } while(Match("\n ", p) || Match("\n\t", p));
795             }
796             return Nothing;
797         }
798
799         // ********* Prevent 00 in unprotected time stamps to be mistaken for castling *******
800         if(Match(":00", p)) return Nothing;
801
802         // ********* Could not match to anything. Return offending character ****
803         (*p)++;
804         return Nothing;
805 }
806
807 /*
808     Return offset of next pattern in the current file.
809 */
810 int
811 yyoffset ()
812 {
813     return ftell(inputFile) - (inPtr - parsePtr); // subtract what is read but not yet parsed
814 }
815
816 void
817 yynewfile (FILE *f)
818 {   // prepare parse buffer for reading file
819     inputFile = f;
820     inPtr = parsePtr = inputBuf;
821     fromString = 0;
822     lastChar = '\n';
823     *inPtr = NULLCHAR; // make sure we will start by reading a line
824 }
825
826 void
827 yynewstr P((char *s))
828 {
829     parsePtr = s;
830     inputFile = NULL;
831     fromString = 1;
832 }
833
834 int
835 yylex ()
836 {   // this replaces the flex-generated parser
837     int result = NextUnit(&parsePtr);
838     char *p = parseStart, *q = yytext;
839     if(p == yytext) return result;   // kludge to allow kanji expansion
840     while(p < parsePtr) *q++ = *p++; // copy the matched text to yytext[]
841     *q = NULLCHAR;
842     lastChar = q[-1];
843     return result;
844 }
845
846 int
847 Myylex ()
848 {   // [HGM] wrapper for yylex, which treats nesting of parentheses
849     int symbol, nestingLevel = 0, i=0;
850     char *p;
851     static char buf[256*MSG_SIZ];
852     buf[0] = NULLCHAR;
853     do { // eat away anything not at level 0
854         symbol = yylex();
855         if(symbol == Open) nestingLevel++;
856         if(nestingLevel) { // save all parsed text between (and including) the ()
857             for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
858             buf[i] = NULLCHAR;
859         }
860         if(symbol == 0) break; // ran into EOF
861         if(symbol == Close) symbol = Comment, nestingLevel--;
862     } while(nestingLevel || symbol == Nothing);
863     yy_text = buf[0] ? buf : (char*)yytext;
864     return symbol;
865 }
866
867 ChessMove
868 yylexstr (int boardIndex, char *s, char *buf, int buflen)
869 {
870     ChessMove ret;
871     char *savPP = parsePtr;
872     fromString = 1;
873     yyboardindex = boardIndex;
874     parsePtr = s;
875     ret = (ChessMove) Myylex();
876     strncpy(buf, yy_text, buflen-1);
877     buf[buflen-1] = NULLCHAR;
878     parsePtr = savPP;
879     fromString = 0;
880     return ret;
881 }