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