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