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