Let parsing of O-O castlings pay attention to castling rank
[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 <ctype.h>
26 #include <string.h>
27 #include "common.h"
28 #include "backend.h"
29 #include "frontend.h"
30 #include "parser.h"
31 #include "moves.h"
32
33
34 extern Board    boards[MAX_MOVES];
35 extern int      PosFlags(int nr);
36 int             yyboardindex;
37 int             yyskipmoves = FALSE;
38 char            currentMoveString[4096]; // a bit ridiculous size?
39 char *yy_text;
40
41 #define PARSEBUFSIZE 10000
42
43 static FILE *inputFile;
44 static char *inPtr, *parsePtr, *parseStart;
45 static char inputBuf[PARSEBUFSIZE];
46 static char yytext[PARSEBUFSIZE];
47 static char fromString = 0, lastChar = '\n';
48
49 #define NOTHING 0
50 #define NUMERIC 1
51 #define ALPHABETIC 2
52 #define BADNUMBER (-2000000000)
53
54 #define XCO    0
55 #define YCO   53
56 #define PIECE 94
57 #define MISC 155
58 #define JIS  200
59
60 unsigned char kanjiTab[] = {
61   '1', 0357, 0274, 0221, // kanji notation for arabic digits
62   '2', 0357, 0274, 0222,
63   '3', 0357, 0274, 0223,
64   '4', 0357, 0274, 0224,
65   '5', 0357, 0274, 0225,
66   '6', 0357, 0274, 0226,
67   '7', 0357, 0274, 0227,
68   '8', 0357, 0274, 0230,
69   '9', 0357, 0274, 0231,
70   'x', 0345, 0220, 0214,
71   's', 0345, 0205, 0210, // sente
72   'g', 0345, 0276, 0214, // gote
73   '-', 0346, 0212, 0225, // resign
74    0,
75   'a', 0344, 0270, 0200, // in reality these are numbers in Japanese a=1, b=2 etc.
76   'b', 0344, 0272, 0214,
77   'c', 0344, 0270, 0211,
78   'd', 0345, 0233, 0233,
79   'e', 0344, 0272, 0224,
80   'f', 0345, 0205, 0255,
81   'g', 0344, 0270, 0203,
82   'h', 0345, 0205, 0253,
83   'i', 0344, 0271, 0235,
84   ' ', 0343, 0200, 0200,
85    0,
86   'K', 0347, 0216, 0211, // piece names
87   'K', 0347, 0216, 0213,
88   'G', 0351, 0207, 0221,
89   'S', 0351, 0212, 0200,
90   'R', 0351, 0243, 0233,
91   'B', 0350, 0247, 0222,
92   'N', 0346, 0241, 0202,
93   'L', 0351, 0246, 0231,
94   'P', 0346, 0255, 0251,
95   'r', 0351, 0276, 0215,
96   'b', 0351, 0246, 0254,
97   'p', 0343, 0201, 0250,
98   'r', 0347, 0253, 0234,
99   '+', 0346, 0210, 0220,
100   'G', 0, 0, 0,
101    0,
102   '+', 0346, 0210, 0220, // helper
103   '@', 0346, 0211, 0223,
104   'p', 0346, 0211, 0213, // player
105   ':', 0357, 0274, 0232,
106   '-', 0344, 0272, 0206,
107   'f', 0344, 0270, 0212,
108   's', 0345, 0257, 0204,
109   'b', 0345, 0274, 0225,
110   'r', 0345, 0267, 0246,
111   'l', 0345, 0217, 0263,
112   'v', 0347, 0233, 0264,
113    0,
114    // shift-JIS
115   '1', 0202, 0120, 0,
116   '2', 0202, 0121, 0,
117   '3', 0202, 0122, 0,
118   '4', 0202, 0123, 0,
119   '5', 0202, 0124, 0,
120   '6', 0202, 0125, 0,
121   '7', 0202, 0126, 0,
122   '8', 0202, 0127, 0,
123   '9', 0202, 0130, 0,
124   'x', 0223, 0257, 0,
125   's', 0220, 0346, 0,
126   'g', 0214, 0343, 0,
127   '-', 0223, 0212, 0,
128    0,
129   'a', 0210, 0352, 0, 
130   'b', 0223, 0361, 0, 
131   'c', 0216, 0117, 0, 
132   'd', 0216, 0154, 0, 
133   'e', 0214, 0334, 0, 
134   'f', 0230, 0132, 0, 
135   'g', 0216, 0265, 0, 
136   'h', 0224, 0252, 0, 
137   'i', 0213, 0343, 0, 
138   ' ', 0201, 0100, 0, 
139    0,
140   'K', 0213, 0312, 0, 
141   'K', 0213, 0312, 0, 
142   'G', 0213, 0340, 0, 
143   'S', 0213, 0342, 0, 
144   'R', 0224, 0362, 0, 
145   'B', 0212, 0160, 0,
146   'N', 0214, 0152, 0, 
147   'L', 0215, 0201, 0, 
148   'P', 0225, 0340, 0, 
149   'r', 0227, 0264, 0, 
150   'b', 0224, 0156, 0, 
151   'p', 0202, 0306, 0, 
152   'r', 0227, 0263, 0, 
153   '+', 0220, 0254, 0, 
154   'G', 0, 0, 0, 
155    0,
156   '+', 0220, 0254, 0, 
157   '@', 0221, 0305, 0, 
158 //  'p', 0214, 0343, 0,
159   'p', 0216, 0350, 0,
160   ':', 0201, 0106, 0,
161   '-', 0227, 0271, 0,
162   'f', 0217, 0343, 0,
163   's', 0212, 0361, 0,
164   'b', 0210, 0370, 0,
165   'r', 0215, 0266, 0,
166   'l', 0211, 0105, 0,
167   'v', 0222, 0274, 0,
168    0,
169      
170 };
171
172 int NextUnit P((char **p));
173
174 int kifu = 0;
175
176 char
177 GetKanji (char **p, int start)
178 {
179     unsigned char *q = *(unsigned char **) p;
180     int i;
181
182     if((*q & 0x80) == 0) return 0; // plain ASCII, refuse to parse
183     if((**p & 0xC0) == 0x80) { // this is an illegal starting code in utf-8, so assume shift-JIS
184         for(i=start+JIS; kanjiTab[i]; i+=4) {
185             if(q[0] == kanjiTab[i+1] && q[1] == kanjiTab[i+2]) {
186                 (*p) += 2; kifu = 0x80;
187                 return kanjiTab[i];
188             }
189         }
190         (*p) += (kifu ? 2 : 1); // assume this is an unrecognized kanji when reading kif files
191         return 0;
192     }
193
194     for(i=start; kanjiTab[i]; i+=4) {
195         if(q[0] == kanjiTab[i+1] && q[1] == kanjiTab[i+2] && q[2] == kanjiTab[i+3]) {
196             (*p) += 3; kifu = 0x80;
197             return kanjiTab[i];
198         }
199     }
200
201     if((q[0] & 0xE0) == 0xC0 && (q[1] & 0xC0) == 0x80) (*p) += 2; else // for now skip unrecognized utf-8 characters
202     if((q[0] & 0xF0) == 0xE0 && (q[1] & 0xC0) == 0x80 && (q[2] & 0xC0) == 0x80) (*p) += 3; else
203     if((q[0] & 0xF8) == 0xF0 && (q[1] & 0xC0) == 0x80 && (q[2] & 0xC0) == 0x80 && (q[3] & 0xC0) == 0x80) (*p) += 4;
204     else if(**p & 0x80) return -1; // not valid utf-8
205
206     return 0; // unrecognized but valid kanji (skipped), or plain ASCII
207 }
208
209 int
210 KifuMove (char **p)
211 {
212     static char buf[MSG_SIZ];
213     char *ptr = buf+3, *q, k;
214     int wom = quickFlag ? quickFlag&1 : WhiteOnMove(yyboardindex);
215     k = GetKanji(p, XCO);
216     if(k < 0) { (*p)++; return Nothing; } // must try shift-JIS here
217     if(k >= '1' && k <= '9') {
218         buf[0] = k; buf[1] = GetKanji(p, YCO); // to-square coords
219     } else if(k == 'x') {
220         if(GetKanji(p, YCO) != ' ') (*p) -= 3; // skip spacer kanji after recapture 
221     } else if((k == 's' || k == 'g') && GetKanji(p, MISC) == 'p' && GetKanji(p, MISC) == ':') { // player name
222         snprintf(yytext, MSG_SIZ, "[%s \"", k == 's' ? "White" : "Black"); // construct PGN tag
223         for(q=yytext+8; **p && **p != '\n' && **p != '\r' && q < yytext + MSG_SIZ; ) *q++ = *(*p)++;
224         strcpy(q, "\"]\n"); parseStart = yytext; lastChar = '\n';
225         return PGNTag;
226     } else if(k == '-' && GetKanji(p, MISC) == '-') { // resign
227         int res;
228         parseStart = yytext;
229         if(wom)
230              res = BlackWins, strcpy(yytext, "{sente resigns} 0-1"); 
231         else res = WhiteWins, strcpy(yytext, "{gote resigns} 1-0");
232         return res;
233     } else {
234         while(**p && **p != '\n') (*p)++; // unrecognized Japanese kanji: skip to end of line
235         return Nothing;
236     }
237     buf[3] = GetKanji(p, PIECE); // piece ID
238     if(buf[3] == '+') buf[2] = '+', buf[3] = GetKanji(p, PIECE); // +N, +L, +S
239     k = GetKanji(p, MISC);
240     if(k == '@') { // drop move
241         buf[4] = '@', buf[5] = buf[0], buf[6] = buf[1]; buf[7] = NULLCHAR;
242         if(appData.debugMode) fprintf(debugFP, "kifu drop %s\n", ptr);
243         return NextUnit(&ptr);
244     }
245
246     kifu = 0x80;
247     do { // read disambiguation (and promotion) kanji
248         switch(k) {
249           case '+': kifu |= 1; break;
250           case 'f': kifu |= 2; break;
251           case 'b': kifu |= 4; break;
252           case 's': kifu |= 8; break;
253           case 'l': kifu |= 0x10; break;
254           case 'r': kifu |= 0x20; break;
255           case 'v': kifu |= 0x40; break;
256         }
257     } while(k = GetKanji(p, MISC));
258
259     if(**p == '(' && (*p)[3] == ')') { // kif disambiguation
260         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;
261         (*p) += 4; ptr++; // strip off piece name if we know full from-square
262         if(appData.debugMode) fprintf(debugFP, "kifu move %s\n", ptr);
263         return NextUnit(&ptr);
264     } else { // kif2
265         char *q = buf+4;
266         if(islower(buf[3])) // kludge: kanji for promoted types translate as lower case
267             buf[3] += 'A' - 'a', buf[2] = '+', ptr--;        // so prefix with '+'
268         if(kifu * ~1) { // disambiguation was given, and thus is probably needed
269             if(buf[3] != 'B' && buf[3] != 'R') {                // stepper, so distance must be <= 1 (N or L never need vertical disambiguation!)
270                 if(kifu & 0x10) *q++ = buf[0] - (wom ? -1 : 1); // translate left/right/straight to PSN file disambiguators
271                 if(kifu & 0x20) *q++ = buf[0] + (wom ? -1 : 1);
272                 if(kifu & 0x40) *q++ = buf[0], kifu |= 2;       // kludge: 'straight' only needs disambiguation if forward!
273                 if(kifu & 2) *q++ = buf[1] + (wom ? -1 : 1);    // translate forward/backward/sideway to PSN rank disambiguators
274                 if(kifu & 4) *q++ = buf[1] - (wom ? -1 : 1);
275                 if(kifu & 8) *q++ = buf[1];
276             } // 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'
277         }
278         *q++ = buf[0]; *q++ = buf[1]; *q++ = (kifu & 1)*'+'; *q = NULLCHAR;
279         if(appData.debugMode) fprintf(debugFP, "kif2 move %s\n", ptr);
280         return NextUnit(&ptr);
281     }
282 }
283
284 int
285 ReadLine ()
286 {   // Read one line from the input file, and append to the buffer
287     char c, *start = inPtr;
288     if(fromString) return 0; // parsing string, so the end is a hard end
289     if(!inputFile) return 0;
290     while((c = fgetc(inputFile)) != EOF) {
291         *inPtr++ = c;
292         if(c == '\n') { *inPtr = NULLCHAR; return 1; }
293         if(inPtr - inputBuf > PARSEBUFSIZE-2) inPtr--; //prevent crash on overflow
294     }
295     if(inPtr == start) return 0;
296     *inPtr++ = '\n', *inPtr = NULLCHAR; // repair missing linefeed at EOF
297     return 1;
298 }
299
300 int
301 Scan (char c, char **p)
302 {   // line-spanning skip to mentioned character or EOF
303     do {
304         while(**p) if(*(*p)++ == c) return 0;
305     } while(ReadLine());
306     // no closing bracket; force match for entire rest of file.
307     return 1;
308 }
309
310 int
311 SkipWhite (char **p)
312 {   // skip spaces tabs and newlines; return 1 if anything was skipped
313     char *start = *p;
314     do{
315         while(**p == ' ' || **p == '\t' || **p == '\n' || **p == '\r') (*p)++;
316     } while(**p == NULLCHAR && ReadLine()); // continue as long as ReadLine reads something
317     return *p != start;
318 }
319
320 static inline int
321 Match (char *pattern, char **ptr)
322 {
323     char *p = pattern, *s = *ptr;
324     while(*p && (*p == *s++ || s[-1] == '\r' && *p--)) p++;
325     if(*p == 0) {
326         *ptr = s;
327         return 1;
328     }
329     return 0; // no match, no ptr update
330 }
331
332 static inline int
333 Word (char *pattern, char **p)
334 {
335     if(Match(pattern, p)) return 1;
336     if(*pattern >= 'a' && *pattern <= 'z' && *pattern - **p == 'a' - 'A') { // capitalized
337         (*p)++;
338         if(Match(pattern + 1, p)) return 1;
339         (*p)--;
340     }
341     return 0;
342 }
343
344 int
345 Verb (char *pattern, char **p)
346 {
347     int res = Word(pattern, p);
348     if(res && !Match("s", p)) Match("ed", p); // eat conjugation suffix, if any
349     return res;
350 }
351
352 int
353 Number (char **p)
354 {
355     int val = 0;
356     if(**p < '0' || **p > '9') return BADNUMBER;
357     while(**p >= '0' && **p <= '9') {
358         val = 10*val + *(*p)++ - '0';
359     }
360     return val;
361 }
362
363 int
364 RdTime (char c, char **p)
365 {
366     char *start = ++(*p), *sec; // increment *p, as it was pointing to the opening ( or {
367     if(Number(p) == BADNUMBER) return 0;
368     sec = *p;
369     if(Match(":", p) && Number(p) != BADNUMBER && *p - sec == 3) { // well formed
370         sec = *p;
371         if(Match(".", p) && Number(p) != BADNUMBER && *(*p)++ == c) return 1; // well-formed fraction
372         *p = sec;
373         if(*(*p)++ == c) return 1; // matching bracket without fraction
374     }
375     *p = start; // failure
376     return 0;
377 }
378
379 char
380 PromoSuffix (char **p)
381 {
382     char *start = *p;
383     if(**p == 'e' && (Match("ep", p) || Match("e.p.", p))) { *p = start; return NULLCHAR; } // non-compliant e.p. suffix is no promoChar!
384     if(**p == '+' && IS_SHOGI(gameInfo.variant)) { (*p)++; return '+'; }
385     if(**p == '=' || (gameInfo.variant == VariantSChess) && **p == '/') (*p)++; // optional = (or / for Seirawan gating)
386     if(**p == '(' && (*p)[2] == ')' && isalpha( (*p)[1] )) { (*p) += 3; return ToLower((*p)[-2]); }
387     if(isalpha(**p) && **p != 'x') return ToLower(*(*p)++); // reserve 'x' for multi-leg captures? 
388     if(*p != start) return **p == '+' ? *(*p)++ : '='; // must be the optional = (or =+)
389     return NULLCHAR; // no suffix detected
390 }
391
392 int
393 NextUnit (char **p)
394 {       // Main parser routine
395         int coord[4], n, result, piece, i;
396         char type[4], promoted, separator, slash, *oldp, *commentEnd, c;
397         int wom = quickFlag ? quickFlag&1 : WhiteOnMove(yyboardindex);
398
399         // ********* try white first, because it is so common **************************
400         if(**p == ' ' || **p == '\n' || **p == '\t') { parseStart = (*p)++; return Nothing; }
401
402
403         if(**p == NULLCHAR) { // make sure there is something to parse
404             if(fromString) return 0; // we are parsing string, so the end is really the end
405             *p = inPtr = inputBuf;
406             if(!ReadLine()) return 0; // EOF
407         } else if(inPtr > inputBuf + PARSEBUFSIZE/2) { // buffer fills up with already parsed stuff
408             char *q = *p, *r = inputBuf;
409             while(*r++ = *q++);
410             *p = inputBuf; inPtr = r - 1;
411         }
412         parseStart = oldp = *p; // remember where we begin
413
414         // ********* attempt to recognize a SAN move in the leading non-blank text *****
415         piece = separator = promoted = slash = n = 0;
416         for(i=0; i<4; i++) coord[i] = -1, type[i] = NOTHING;
417         if(**p & 0x80) return KifuMove(p); // non-ascii. Could be some kanj notation for Shogi or Xiangqi
418         if(**p == '+') (*p)++, promoted++;
419         if(**p >= 'a' && **p <= 'z' && (*p)[1]== '@') piece =*(*p)++ + 'A' - 'a'; else
420         if(**p >= 'A' && **p <= 'Z') {
421              static char s[] = SUFFIXES;
422              char *q;
423              piece = *(*p)++; // Note we could test for 2-byte non-ascii names here
424              if(q = strchr(s, **p)) (*p)++, piece += 64*(q - s + 1);
425              if(**p == '/') slash = *(*p)++;
426         }
427         while(n < 4) {
428             if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
429             else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
430             else break;
431             if(n == 2 && type[0] == type[1]) { // if two identical types, the opposite type in between must have been missing
432                 type[2] = type[1]; coord[2] = coord[1];
433                 type[1] = NOTHING; coord[1] = -1; n++;
434             }
435         }
436         // we always get here, and might have read a +, a piece, and upto 4 potential coordinates
437         if(n <= 2) { // could be from-square or disambiguator, when -:xX follow, or drop with @ directly after piece, but also to-square
438              if(**p == '-' || **p == ':' || **p == 'x' || **p == 'X' || // these cannot be move suffix, so to-square must follow
439                  (**p == '@' || **p == '*') && n == 0 && !promoted && piece) { // P@ must also be followed by to-square
440                 separator = *(*p)++;
441                 if(n == 1) coord[1] = coord[0]; // must be disambiguator, but we do not know which yet
442                 n = 2;
443                 while(n < 4) { // attempt to read to-square
444                     if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
445                     else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
446                     else break;
447                 }
448             } else if((**p == '+' || **p == '=') && n == 1 && piece && type[0] == NUMERIC) { // can be traditional Xiangqi notation
449                 separator = *(*p)++;
450                 n = 2;
451                 if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
452             } else if(n == 2) { // only one square mentioned, must be to-square
453                 while(n < 4) { coord[n] = coord[n-2], type[n] = type[n-2], coord[n-2] = -1, type[n-2] = NOTHING; n++; }
454             }
455         } else if(n == 3 && type[1] != NOTHING) { // must be hyphenless disambiguator + to-square
456             for(i=3; i>0; i--) coord[i] = coord[i-1], type[i] = type[i-1]; // move to-square to where it belongs
457             type[1] = NOTHING; // disambiguator goes in first two positions
458             n = 4;
459         }
460         // we always get here; move must be completely read now, with to-square coord(s) at end
461         if(n == 3) { // incomplete to-square. Could be Xiangqi traditional, or stuff like fxg
462             if(piece && type[1] == NOTHING && type[0] == NUMERIC && type[2] == NUMERIC &&
463                 (separator == '+' || separator == '=' || separator == '-')) {
464                      // Xiangqi traditional
465
466                 return ImpossibleMove; // for now treat as invalid
467             }
468             // fxg stuff, but also things like 0-0, 0-1 and 1-0
469             if(!piece && type[1] == NOTHING && type[0] == ALPHABETIC && type[2] == ALPHABETIC
470                  && (coord[0] != 14 || coord[2] != 14) /* reserve oo for castling! */ ) {
471                 piece = 'P'; n = 4; // kludge alert: fake full to-square
472             }
473         } else if(n == 1 && type[0] == NUMERIC && coord[0] > 1) { while(**p == '.') (*p)++; return Nothing; } // fast exit for move numbers
474         if(n == 4 && type[2] != type[3] && // we have a valid to-square (kludge: type[3] can be NOTHING on fxg type move)
475                      (piece || !promoted) && // promoted indicator only valid on named piece type
476                      (type[2] == ALPHABETIC || IS_SHOGI(gameInfo.variant))) { // in Shogi also allow alphabetic rank
477             DisambiguateClosure cl;
478             int fromX, fromY, toX, toY;
479
480             if(slash && (!piece || type[1] == NOTHING)) goto badMove; // slash after piece only in ICS long format
481             if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
482
483             if(type[2] == NUMERIC) { // alpha-rank
484                 coord[2] = BOARD_RGHT - BOARD_LEFT - coord[2];
485                 coord[3] = BOARD_HEIGHT - coord[3];
486                 if(coord[0] >= 0) coord[0] = BOARD_RGHT - BOARD_LEFT - coord[0];
487                 if(coord[1] >= 0) coord[1] = BOARD_HEIGHT - coord[1];
488             }
489             toX = cl.ftIn = (currentMoveString[2] = coord[2] + 'a') - AAA;
490             toY = cl.rtIn = (currentMoveString[3] = coord[3] + '0') - ONE;
491             if(type[3] == NOTHING) cl.rtIn = -1; // for fxg type moves ask for toY disambiguation
492             else if(toY >= BOARD_HEIGHT || toY < 0)   return ImpossibleMove; // vert off-board to-square
493             if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return ImpossibleMove;
494             if(piece) {
495                 cl.pieceIn = CharToPiece(wom ? piece : piece + 'a' - 'A');
496                 if(cl.pieceIn == EmptySquare) return ImpossibleMove; // non-existent piece
497                 if(promoted) cl.pieceIn = (ChessSquare) (CHUPROMOTED cl.pieceIn);
498             } else cl.pieceIn = EmptySquare;
499             if(separator == '@' || separator == '*') { // drop move. We only get here without from-square or promoted piece
500                 fromY = DROP_RANK; fromX = cl.pieceIn;
501                 currentMoveString[0] = piece;
502                 currentMoveString[1] = '@';
503                 currentMoveString[4] = NULLCHAR;
504                 return LegalityTest(boards[yyboardindex], PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, fromY, fromX, toY, toX, NULLCHAR);
505             }
506             if(type[1] == NOTHING && type[0] != NOTHING) { // there is a disambiguator
507                 if(type[0] != type[2]) coord[0] = -1, type[1] = type[0], type[0] = NOTHING; // it was a rank-disambiguator
508             }
509             if(  type[1] != type[2] && // means fromY is of opposite type as ToX, or NOTHING
510                 (type[0] == NOTHING || type[0] == type[2]) ) { // well formed
511
512                 fromX = (currentMoveString[0] = coord[0] + 'a') - AAA;
513                 fromY = (currentMoveString[1] = coord[1] + '0') - ONE;
514                 currentMoveString[4] = cl.promoCharIn = PromoSuffix(p);
515                 currentMoveString[5] = NULLCHAR;
516                 if(!cl.promoCharIn && (**p == '-' || **p == 'x')) { // Lion-type multi-leg move
517                     currentMoveString[5] = (killX = toX) + AAA; // what we thought was to-square is in fact kill-square
518                     currentMoveString[6] = (killY = toY) + ONE; // append it as suffix behind long algebraic move
519                     currentMoveString[4] = ';';
520                     currentMoveString[7] = NULLCHAR;
521                     // read new to-square (VERY non-robust! Assumes correct (non-alpha-rank) syntax, and messes up on errors)
522                     toX = cl.ftIn = (currentMoveString[2] = *++*p) - AAA; ++*p;
523                     toY = cl.rtIn = (currentMoveString[3] = Number(p) + '0') - ONE;
524                 }
525                 if(type[0] != NOTHING && type[1] != NOTHING && type[3] != NOTHING) { // fully specified.
526                     ChessSquare realPiece = boards[yyboardindex][fromY][fromX];
527                     // Note that Disambiguate does not work for illegal moves, but flags them as impossible
528                     if(piece) { // check if correct piece indicated
529                         if(PieceToChar(realPiece) == '~') realPiece = (ChessSquare) (DEMOTED realPiece);
530                         if(!(appData.icsActive && PieceToChar(realPiece) == '+') && // trust ICS if it moves promoted pieces
531                            piece && realPiece != cl.pieceIn) return ImpossibleMove;
532                     } else if(!separator && **p == '+') { // could be a protocol move, where bare '+' suffix means shogi-style promotion
533                         if(realPiece < (wom ?  WhiteCannon : BlackCannon) && PieceToChar(PROMOTED realPiece) == '+') // seems to be that
534                            currentMoveString[4] = cl.promoCharIn = *(*p)++; // append promochar after all
535                     }
536                     result = LegalityTest(boards[yyboardindex], PosFlags(yyboardindex), fromY, fromX, toY, toX, cl.promoCharIn);
537                     if (currentMoveString[4] == NULLCHAR) { // suppy missing mandatory promotion character
538                       if(result == WhitePromotion  || result == BlackPromotion) {
539                         switch(gameInfo.variant) {
540                           case VariantCourier:
541                           case VariantShatranj: currentMoveString[4] = PieceToChar(BlackFerz); break;
542                           case VariantGreat:    currentMoveString[4] = PieceToChar(BlackMan); break;
543                           case VariantShogi:    currentMoveString[4] = '+'; break;
544                           default:              currentMoveString[4] = PieceToChar(BlackQueen);
545                         }
546                       } else if(result == WhiteNonPromotion  || result == BlackNonPromotion) {
547                                                 currentMoveString[4] = '=';
548                       }
549                     } else if(appData.testLegality && gameInfo.variant != VariantSChess && // strip off unnecessary and false promo characters
550                        !(result == WhitePromotion  || result == BlackPromotion ||
551                          result == WhiteNonPromotion || result == BlackNonPromotion)) currentMoveString[4] = NULLCHAR;
552                     return result;
553                 } else if(cl.pieceIn == EmptySquare) cl.pieceIn = wom ? WhitePawn : BlackPawn;
554                 cl.ffIn = type[0] == NOTHING ? -1 : coord[0] + 'a' - AAA;
555                 cl.rfIn = type[1] == NOTHING ? -1 : coord[1] + '0' - ONE;
556
557                 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
558
559                 if(cl.kind == ImpossibleMove && !piece && type[1] == NOTHING // fxg5 type
560                         && toY == (wom ? 4 : 3)) { // could be improperly written e.p.
561                     cl.rtIn += wom ? 1 : -1; // shift target square to e.p. square
562                     Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
563                     if((cl.kind != WhiteCapturesEnPassant && cl.kind != BlackCapturesEnPassant))
564                         return ImpossibleMove; // nice try, but no cigar
565                 }
566
567                 currentMoveString[0] = cl.ff + AAA;
568                 currentMoveString[1] = cl.rf + ONE;
569                 currentMoveString[3] = cl.rt + ONE;
570                 if(killX < 0) // [HGM] lion: do not overwrite kill-square suffix
571                 currentMoveString[4] = cl.promoChar;
572
573                 if((cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) && (Match("ep", p) || Match("e.p.", p)));
574
575                 return (int) cl.kind;
576             }
577         }
578 badMove:// we failed to find algebraic move
579         *p = oldp;
580
581
582         // Next we do some common symbols where the first character commits us to things that cannot possibly be a move
583
584         // ********* PGN tags ******************************************
585         if(**p == '[') {
586             oldp = ++(*p); kifu = 0;
587             if(Match("--", p)) { // "[--" could be start of position diagram
588                 if(!Scan(']', p) && (*p)[-3] == '-' && (*p)[-2] == '-') return PositionDiagram;
589                 *p = oldp;
590             }
591             SkipWhite(p);
592             if(isdigit(**p) || isalpha(**p)) {
593                 do (*p)++; while(isdigit(**p) || isalpha(**p) || **p == '+' ||
594                                 **p == '-' || **p == '=' || **p == '_' || **p == '#');
595                 SkipWhite(p);
596                 if(**p == '"') {
597                     (*p)++;
598                     while(**p != '\n' && (*(*p)++ != '"'|| (*p)[-2] == '\\')); // look for unescaped quote
599                     if((*p)[-1] !='"') { *p = oldp; Scan(']', p); return Comment; } // string closing delimiter missing
600                     SkipWhite(p); if(*(*p)++ == ']') return PGNTag;
601                 }
602             }
603             Scan(']', p); return Comment;
604         }
605
606         // ********* SAN Castings *************************************
607         if(**p == 'O' || **p == 'o' || **p == '0' && !Match("00:", p)) { // exclude 00 in time stamps
608             int castlingType = 0;
609             if(Match("O-O-O", p) || Match("o-o-o", p) || Match("0-0-0", p) ||
610                Match("OOO", p) || Match("ooo", p) || Match("000", p)) castlingType = 2;
611             else if(Match("O-O", p) || Match("o-o", p) || Match("0-0", p) ||
612                     Match("OO", p) || Match("oo", p) || Match("00", p)) castlingType = 1;
613             if(castlingType) { //code from old parser, collapsed for both castling types, and streamlined a bit
614                 int rf, ff, rt, ft; ChessSquare king;
615                 char promo=NULLCHAR;
616
617                 if(gameInfo.variant == VariantSChess) promo = PromoSuffix(p);
618
619                 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
620
621                 if (wom) {
622                     rf = castlingRank[0];
623                     rt = castlingRank[0];
624                     king = WhiteKing;
625                 } else {
626                     rf = castlingRank[3];
627                     rt = castlingRank[3];
628                     king = BlackKing;
629                 }
630                 ff = (BOARD_WIDTH-1)>>1; // this would be d-file
631                 if (boards[yyboardindex][rf][ff] == king) {
632                     /* ICS wild castling */
633                     ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
634                 } else {
635                     ff = BOARD_WIDTH>>1; // e-file
636                     ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
637                 }
638                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
639                     if (wom) {
640                         ff = initialRights[2];
641                         ft = initialRights[castlingType-1];
642                     } else {
643                         ff = initialRights[5];
644                         ft = initialRights[castlingType+2];
645                     }
646                     if (appData.debugMode) fprintf(debugFP, "Parser FRC (type=%d) %d %d\n", castlingType, ff, ft);
647                     if(ff == NoRights || ft == NoRights) return ImpossibleMove;
648                 }
649                 sprintf(currentMoveString, "%c%c%c%c%c",ff+AAA,rf+ONE,ft+AAA,rt+ONE,promo);
650                 if (appData.debugMode) fprintf(debugFP, "(%d-type) castling %d %d\n", castlingType, ff, ft);
651
652                 return (int) LegalityTest(boards[yyboardindex],
653                               PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
654                               rf, ff, rt, ft, promo);
655             } else if(Match("01", p)) return Nothing; // prevent this from being mistaken for move number 1
656         }
657
658
659         // ********* variations (nesting) ******************************
660         if(**p =='(') {
661             if(RdTime(')', p)) return ElapsedTime;
662             return Open;
663         }
664         if(**p ==')') { (*p)++; return Close; }
665         if(**p == ';') { while(**p != '\n') (*p)++; return Comment; }
666
667
668         // ********* Comments and result messages **********************
669         *p = oldp; commentEnd = NULL; result = 0;
670         if(**p == '{') {
671             if(RdTime('}', p)) return ElapsedTime;
672             if(lastChar == '\n' && Match("--------------\n", p)) {
673                 char *q;
674                 i = Scan ('}', p); q = *p - 16;
675                 if(Match("\n--------------}\n", &q)) return PositionDiagram;
676             } else i = Scan('}', p);
677             commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
678         }
679         if(commentEnd) SkipWhite(p);
680         if(kifu && **p == '*') { // .kif comment
681             char *q = yytext;
682             while(**p && **p != '\n') { if(q < yytext + 10*MSG_SIZ-3) *q++ = **p; (*p)++; }
683             parseStart = yytext; *yytext = '{'; strcpy(q, "}\n"); // wrap in braces
684             return Comment;
685         }
686         if(Match("*", p)) result = GameUnfinished;
687         else if(**p == '0') {
688             if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
689                 Match("0 - 1", p) || Match("0 / 1", p) || Match("0 : 1", p)) result = BlackWins;
690         } else if(**p == '1') {
691             if( Match("1-0", p) || Match("1/0", p) || Match("1:0", p) ||
692                 Match("1 - 0", p) || Match("1 / 0", p) || Match("1 : 0", p)) result = WhiteWins;
693             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) ||
694                     Match("1 / 2 : 1 / 2", p) || Match("1/2", p) || Match("1 / 2", p)) result = GameIsDrawn;
695         }
696         if(result) {
697             if(Match(" (", p) && !Scan(')', p) || Match(" {", p) && !Scan('}', p)) { // there is a comment after the PGN result!
698                 if(commentEnd) { *p = commentEnd; return Comment; } // so comment before it is normal comment; return that first
699             }
700             return result; // this returns a possible preceeding comment as result details
701         }
702         if(commentEnd) { *p = commentEnd; return Comment; } // there was no PGN result following, so return as normal comment
703
704
705         // ********* Move numbers (after castlings or PGN results!) ***********
706         if((i = Number(p)) != BADNUMBER) { // a single number was read as part of our attempt to read a move
707             char *numEnd = *p;
708             if(**p == '.') (*p)++; SkipWhite(p);
709             if(**p == '+' || isalpha(**p) || gameInfo.variant == VariantShogi && *p != numEnd && isdigit(**p)) {
710                 *p = numEnd;
711                 return i == 1 ? MoveNumberOne : Nothing;
712             }
713             *p = numEnd; return Nothing;
714         }
715
716
717         // ********* non-compliant game-result indicators *********************
718         if(Match("+-+", p) || Word("stalemate", p)) return GameIsDrawn;
719         if(Match("++", p) || Verb("resign", p) || (Word("check", p) || 1) && Word("mate", p) )
720             return (wom ? BlackWins : WhiteWins);
721         c = ToUpper(**p);
722         if(Word("w", p) && (Match("hite", p) || 1) || Word("b", p) && (Match("lack", p) || 1) ) {
723             if(**p != ' ') return Nothing;
724             ++*p;
725             if(Verb("disconnect", p)) return GameUnfinished;
726             if(Verb("resign", p) || Verb("forfeit", p) || Word("mated", p) || Word("lost", p) || Word("loses", p))
727                 return (c == 'W' ? BlackWins : WhiteWins);
728             if(Word("mates", p) || Word("wins", p) || Word("won", p))
729                 return (c != 'W' ? BlackWins : WhiteWins);
730             return Nothing;
731         }
732         if(Word("draw", p)) {
733             if(**p == 'n') (*p)++;
734             if(**p != ' ') return GameIsDrawn;
735             oldp = ++*p;
736             if(Word("agreed", p)) return GameIsDrawn;
737             if(Match("by ", p) && (Word("repetition", p) || Word("agreement", p)) ) return GameIsDrawn;
738             *p = oldp;
739             if(*(*p)++ == '(') {
740                 while(**p != '\n') if(*(*p)++ == ')') break;
741                 if((*p)[-1] == ')')  return GameIsDrawn;
742             }
743             *p = oldp - 1; return GameIsDrawn;
744         }
745
746
747         // ********* Numeric annotation glyph **********************************
748         if(**p == '$') { (*p)++; if(Number(p) != BADNUMBER) return NAG; return Nothing; }
749
750
751         // ********** by now we are getting down to the silly stuff ************
752         if(Word("gnu", p) || Match("GNU", p)) {
753             if(**p == ' ') (*p)++;
754             if(Word("chess", p) || Match("CHESS", p)) {
755                 char *q;
756                 if((q = strstr(*p, "game")) || (q = strstr(*p, "GAME")) || (q = strstr(*p, "Game"))) {
757                     (*p) = q + 4; return GNUChessGame;
758                 }
759             }
760             return Nothing;
761         }
762         if(lastChar == '\n' && (Match("# ", p) || Match("; ", p) || Match("% ", p))) {
763             while(**p != '\n' && **p != ' ') (*p)++;
764             if(**p == ' ' && (Match(" game file", p) || Match(" position file", p))) {
765                 while(**p != '\n') (*p)++; // skip to EOLN
766                 return XBoardGame;
767             }
768             *p = oldp; // we might need to re-match the skipped stuff
769         }
770
771         if(Match("---", p)) { while(**p == '-') (*p)++; return Nothing; } // prevent separators parsing as null move
772         if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
773             strncpy(currentMoveString, "@@@@", 5);
774             return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
775         }
776
777         // ********* Efficient skipping of (mostly) alphabetic chatter **********
778         while(isdigit(**p) || isalpha(**p) || **p == '-') (*p)++;
779         if(*p != oldp) {
780             if(**p == '\'') {
781                 while(isdigit(**p) || isalpha(**p) || **p == '-' || **p == '\'') (*p)++;
782                 return Nothing; // random word
783             }
784             if(lastChar == '\n' && Match(": ", p)) { // mail header, skip indented lines
785                 do {
786                     while(**p != '\n') (*p)++;
787                     if(!ReadLine()) return Nothing; // append next line if not EOF
788                 } while(Match("\n ", p) || Match("\n\t", p));
789             }
790             return Nothing;
791         }
792
793         // ********* Prevent 00 in unprotected time stamps to be mistaken for castling *******
794         if(Match(":00", p)) return Nothing;
795
796         // ********* Could not match to anything. Return offending character ****
797         (*p)++;
798         return Nothing;
799 }
800
801 /*
802     Return offset of next pattern in the current file.
803 */
804 int
805 yyoffset ()
806 {
807     return ftell(inputFile) - (inPtr - parsePtr); // subtract what is read but not yet parsed
808 }
809
810 void
811 yynewfile (FILE *f)
812 {   // prepare parse buffer for reading file
813     inputFile = f;
814     inPtr = parsePtr = inputBuf;
815     fromString = 0;
816     lastChar = '\n';
817     *inPtr = NULLCHAR; // make sure we will start by reading a line
818 }
819
820 void
821 yynewstr P((char *s))
822 {
823     parsePtr = s;
824     inputFile = NULL;
825     fromString = 1;
826 }
827
828 int
829 yylex ()
830 {   // this replaces the flex-generated parser
831     int result = NextUnit(&parsePtr);
832     char *p = parseStart, *q = yytext;
833     if(p == yytext) return result;   // kludge to allow kanji expansion
834     while(p < parsePtr) *q++ = *p++; // copy the matched text to yytext[]
835     *q = NULLCHAR;
836     lastChar = q[-1];
837     return result;
838 }
839
840 int
841 Myylex ()
842 {   // [HGM] wrapper for yylex, which treats nesting of parentheses
843     int symbol, nestingLevel = 0, i=0;
844     char *p;
845     static char buf[256*MSG_SIZ];
846     buf[0] = NULLCHAR;
847     do { // eat away anything not at level 0
848         symbol = yylex();
849         if(symbol == Open) nestingLevel++;
850         if(nestingLevel) { // save all parsed text between (and including) the ()
851             for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
852             buf[i] = NULLCHAR;
853         }
854         if(symbol == 0) break; // ran into EOF
855         if(symbol == Close) symbol = Comment, nestingLevel--;
856     } while(nestingLevel || symbol == Nothing);
857     yy_text = buf[0] ? buf : (char*)yytext;
858     return symbol;
859 }
860
861 ChessMove
862 yylexstr (int boardIndex, char *s, char *buf, int buflen)
863 {
864     ChessMove ret;
865     char *savPP = parsePtr;
866     fromString = 1;
867     yyboardindex = boardIndex;
868     parsePtr = s;
869     ret = (ChessMove) Myylex();
870     strncpy(buf, yy_text, buflen-1);
871     buf[buflen-1] = NULLCHAR;
872     parsePtr = savPP;
873     fromString = 0;
874     return ret;
875 }