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