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