4 * Copyright 2011, 2012, 2013, 2014, 2015, 2016 Free Software Foundation, Inc.
5 * ------------------------------------------------------------------------
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.
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.
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/. *
20 *------------------------------------------------------------------------
21 ** See the file ChangeLog for a revision history. */
35 extern Board boards[MAX_MOVES];
36 extern int PosFlags(int nr);
38 int yyskipmoves = FALSE;
39 char currentMoveString[4096]; // a bit ridiculous size?
42 #define PARSEBUFSIZE 10000
44 static FILE *inputFile;
45 static char *inPtr, *parsePtr, *parseStart;
46 static char inputBuf[PARSEBUFSIZE];
47 static char yytext[PARSEBUFSIZE];
48 static char fromString = 0, lastChar = '\n';
53 #define BADNUMBER (-2000000000)
61 unsigned char kanjiTab[] = {
62 '1', 0357, 0274, 0221, // kanji notation for arabic digits
63 '2', 0357, 0274, 0222,
64 '3', 0357, 0274, 0223,
65 '4', 0357, 0274, 0224,
66 '5', 0357, 0274, 0225,
67 '6', 0357, 0274, 0226,
68 '7', 0357, 0274, 0227,
69 '8', 0357, 0274, 0230,
70 '9', 0357, 0274, 0231,
71 'x', 0345, 0220, 0214,
72 's', 0345, 0205, 0210, // sente
73 'g', 0345, 0276, 0214, // gote
74 '-', 0346, 0212, 0225, // resign
76 'a', 0344, 0270, 0200, // in reality these are numbers in Japanese a=1, b=2 etc.
77 'b', 0344, 0272, 0214,
78 'c', 0344, 0270, 0211,
79 'd', 0345, 0233, 0233,
80 'e', 0344, 0272, 0224,
81 'f', 0345, 0205, 0255,
82 'g', 0344, 0270, 0203,
83 'h', 0345, 0205, 0253,
84 'i', 0344, 0271, 0235,
85 ' ', 0343, 0200, 0200,
87 'K', 0347, 0216, 0211, // piece names
88 'K', 0347, 0216, 0213,
89 'G', 0351, 0207, 0221,
90 'S', 0351, 0212, 0200,
91 'R', 0351, 0243, 0233,
92 'B', 0350, 0247, 0222,
93 'N', 0346, 0241, 0202,
94 'L', 0351, 0246, 0231,
95 'P', 0346, 0255, 0251,
96 'r', 0351, 0276, 0215,
97 'b', 0351, 0246, 0254,
98 'p', 0343, 0201, 0250,
99 'r', 0347, 0253, 0234,
100 '+', 0346, 0210, 0220,
103 '+', 0346, 0210, 0220, // helper
104 '@', 0346, 0211, 0223,
105 'p', 0346, 0211, 0213, // player
106 ':', 0357, 0274, 0232,
107 '-', 0344, 0272, 0206,
108 'f', 0344, 0270, 0212,
109 's', 0345, 0257, 0204,
110 'b', 0345, 0274, 0225,
111 'r', 0345, 0267, 0246,
112 'l', 0345, 0217, 0263,
113 'v', 0347, 0233, 0264,
159 // 'p', 0214, 0343, 0,
173 int NextUnit P((char **p));
175 int kifu = 0, xqUBB = 0;
178 GetKanji (char **p, int start)
180 unsigned char *q = *(unsigned char **) p;
183 if((*q & 0x80) == 0) return 0; // plain ASCII, refuse to parse
184 if((**p & 0xC0) == 0x80) { // this is an illegal starting code in utf-8, so assume shift-JIS
185 for(i=start+JIS; kanjiTab[i]; i+=4) {
186 if(q[0] == kanjiTab[i+1] && q[1] == kanjiTab[i+2]) {
187 (*p) += 2; kifu = 0x80;
191 (*p) += (kifu ? 2 : 1); // assume this is an unrecognized kanji when reading kif files
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;
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
207 return 0; // unrecognized but valid kanji (skipped), or plain ASCII
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';
227 } else if(k == '-' && GetKanji(p, MISC) == '-') { // resign
231 res = BlackWins, strcpy(yytext, "{sente resigns} 0-1");
232 else res = WhiteWins, strcpy(yytext, "{gote resigns} 1-0");
235 while(**p && **p != '\n') (*p)++; // unrecognized Japanese kanji: skip to end of line
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);
248 do { // read disambiguation (and promotion) kanji
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;
258 } while(k = GetKanji(p, MISC));
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);
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'
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);
287 { // Read one line from the input file, and append to the buffer
288 int c; char *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) {
293 if(c == '\n') { *inPtr = NULLCHAR; return 1; }
294 if(inPtr - inputBuf > PARSEBUFSIZE-2) inPtr--; //prevent crash on overflow
296 if(inPtr == start) return 0;
297 *inPtr++ = '\n', *inPtr = NULLCHAR; // repair missing linefeed at EOF
302 Scan (char c, char **p)
303 { // line-spanning skip to mentioned character or EOF
305 while(**p) if(*(*p)++ == c) return 0;
307 // no closing bracket; force match for entire rest of file.
313 { // skip spaces tabs and newlines; return 1 if anything was skipped
316 while(**p == ' ' || **p == '\t' || **p == '\n' || **p == '\r') (*p)++;
317 } while(**p == NULLCHAR && ReadLine()); // continue as long as ReadLine reads something
322 Match (char *pattern, char **ptr)
324 char *p = pattern, *s = *ptr;
325 while(*p && (*p == *s++ || s[-1] == '\r' && *p--)) p++;
330 return 0; // no match, no ptr update
334 Word (char *pattern, char **p)
336 if(Match(pattern, p)) return 1;
337 if(*pattern >= 'a' && *pattern <= 'z' && *pattern - **p == 'a' - 'A') { // capitalized
339 if(Match(pattern + 1, p)) return 1;
346 Verb (char *pattern, char **p)
348 int res = Word(pattern, p);
349 if(res && !Match("s", p)) Match("ed", p); // eat conjugation suffix, if any
357 if(**p < '0' || **p > '9') return BADNUMBER;
358 while(**p >= '0' && **p <= '9') {
359 val = 10*val + *(*p)++ - '0';
365 RdTime (char c, char **p)
367 char *start = ++(*p), *sec; // increment *p, as it was pointing to the opening ( or {
368 if(Number(p) == BADNUMBER) return 0;
370 if(Match(":", p) && Number(p) != BADNUMBER && *p - sec == 3) { // well formed
372 if(Match(".", p) && Number(p) != BADNUMBER && *(*p)++ == c) return 1; // well-formed fraction
374 if(*(*p)++ == c) return 1; // matching bracket without fraction
376 *p = start; // failure
381 PromoSuffix (char **p)
384 if(**p == ' ') return NULLCHAR; // common case, test explicitly for speed
385 if(**p == 'e' && (Match("ep", p) || Match("e.p.", p))) { *p = start; return NULLCHAR; } // non-compliant e.p. suffix is no promoChar!
386 if(**p == '+' && IS_SHOGI(gameInfo.variant)) { (*p)++; return '+'; }
387 if(**p == '=' || (gameInfo.variant == VariantSChess) && **p == '/') (*p)++; // optional = (or / for Seirawan gating)
388 if(**p == '(' && (*p)[2] == ')' && isalpha( (*p)[1] )) { (*p) += 3; return ToLower((*p)[-2]); }
389 if(isalpha(**p) && **p != 'x') return ToLower(*(*p)++); // reserve 'x' for multi-leg captures?
390 if(*p != start) return **p == '+' ? *(*p)++ : '='; // must be the optional = (or =+)
391 return NULLCHAR; // no suffix detected
396 { // Main parser routine
397 int coord[4], n, result, piece, i;
398 char type[4], promoted, separator, slash, *oldp, *commentEnd, c;
399 int wom = quickFlag ? quickFlag&1 : WhiteOnMove(yyboardindex);
401 // ********* try white first, because it is so common **************************
402 if(**p == ' ' || **p == '\n' || **p == '\t') { parseStart = (*p)++; return Nothing; }
405 if(**p == NULLCHAR) { // make sure there is something to parse
406 if(fromString) return 0; // we are parsing string, so the end is really the end
407 *p = inPtr = parseStart = inputBuf;
408 if(!ReadLine()) return 0; // EOF
409 } else if(inPtr > inputBuf + PARSEBUFSIZE/2) { // buffer fills up with already parsed stuff
410 char *q = *p, *r = inputBuf;
412 *p = inputBuf; inPtr = r - 1;
414 parseStart = oldp = *p; // remember where we begin
416 // ********* attempt to recognize a SAN move in the leading non-blank text *****
417 piece = separator = promoted = slash = n = 0;
418 for(i=0; i<4; i++) coord[i] = -1, type[i] = NOTHING;
419 if(**p & 0x80) return KifuMove(p); // non-ascii. Could be some kanj notation for Shogi or Xiangqi
420 if(**p == '+') (*p)++, promoted++;
421 if(**p >= 'a' && **p <= 'z' && (*p)[1]== '@') piece =*(*p)++ + 'A' - 'a'; else
422 if(**p >= 'A' && **p <= 'Z') {
423 static char s[] = SUFFIXES;
425 piece = *(*p)++; // Note we could test for 2-byte non-ascii names here
426 if(q = strchr(s, **p)) (*p)++, piece += 64*(q - s + 1);
427 if(**p == '/') slash = *(*p)++;
430 if(xqUBB) { // Xiangqi UBB movelist
431 while(isdigit(**p)) {
432 type[n] = NUMERIC; coord[n++] = *(*p)++ - '0';
435 if(n < 4) *p -= n, xqUBB = n = 0; else type[0] = type[2] = ALPHABETIC, coord[1] = 9 - coord[1], coord[3] = 9 - coord[3];
438 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
439 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
441 if(n == 2 && type[0] == type[1]) { // if two identical types, the opposite type in between must have been missing
442 type[2] = type[1]; coord[2] = coord[1];
443 type[1] = NOTHING; coord[1] = -1; n++;
446 // we always get here, and might have read a +, a piece, and upto 4 potential coordinates
447 if(n <= 2) { // could be from-square or disambiguator, when -:xX follow, or drop with @ directly after piece, but also to-square
448 if(**p == '-' || **p == ':' || **p == 'x' || **p == 'X' || // these cannot be move suffix, so to-square must follow
449 (**p == '@' || **p == '*') && n == 0 && !promoted && piece) { // P@ must also be followed by to-square
451 if(n == 1) coord[1] = coord[0]; // must be disambiguator, but we do not know which yet
453 while(n < 4) { // attempt to read to-square
454 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
455 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
458 } else if((**p == '+' || **p == '=') && n == 1 && piece && type[0] == NUMERIC) { // can be traditional Xiangqi notation
461 if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
462 } else if(n == 2) { // only one square mentioned, must be to-square
463 while(n < 4) { coord[n] = coord[n-2], type[n] = type[n-2], coord[n-2] = -1, type[n-2] = NOTHING; n++; }
465 } else if(n == 3 && type[1] != NOTHING) { // must be hyphenless disambiguator + to-square
466 for(i=3; i>0; i--) coord[i] = coord[i-1], type[i] = type[i-1]; // move to-square to where it belongs
467 type[1] = NOTHING; // disambiguator goes in first two positions
470 // we always get here; move must be completely read now, with to-square coord(s) at end
471 if(n == 3) { // incomplete to-square. Could be Xiangqi traditional, or stuff like fxg
472 if(piece && type[1] == NOTHING && type[0] == NUMERIC && type[2] == NUMERIC &&
473 (separator == '+' || separator == '=' || separator == '-')) {
474 // Xiangqi traditional
476 return ImpossibleMove; // for now treat as invalid
478 // fxg stuff, but also things like 0-0, 0-1 and 1-0
479 if(!piece && type[1] == NOTHING && type[0] == ALPHABETIC && type[2] == ALPHABETIC
480 && (coord[0] != 14 || coord[2] != 14) /* reserve oo for castling! */ ) {
481 piece = 'P'; n = 4; // kludge alert: fake full to-square
483 } else if(n == 1 && type[0] == NUMERIC && coord[0] > 1) { while(**p == '.') (*p)++; return Nothing; } // fast exit for move numbers
484 if(n == 4 && type[2] != type[3] && // we have a valid to-square (kludge: type[3] can be NOTHING on fxg type move)
485 (piece || !promoted) && // promoted indicator only valid on named piece type
486 (type[2] == ALPHABETIC || IS_SHOGI(gameInfo.variant))) { // in Shogi also allow alphabetic rank
487 DisambiguateClosure cl;
488 int fromX, fromY, toX, toY;
490 if(slash && (!piece || type[1] == NOTHING)) goto badMove; // slash after piece only in ICS long format
491 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
493 if(type[2] == NUMERIC) { // alpha-rank
494 coord[2] = BOARD_RGHT - BOARD_LEFT - coord[2];
495 coord[3] = BOARD_HEIGHT - coord[3];
496 if(coord[0] >= 0) coord[0] = BOARD_RGHT - BOARD_LEFT - coord[0];
497 if(coord[1] >= 0) coord[1] = BOARD_HEIGHT - coord[1];
499 toX = cl.ftIn = (currentMoveString[2] = coord[2] + 'a') - AAA;
500 toY = cl.rtIn = (currentMoveString[3] = coord[3] + '0') - ONE;
501 if(type[3] == NOTHING) cl.rtIn = -1; // for fxg type moves ask for toY disambiguation
502 else if(toY >= BOARD_HEIGHT || toY < 0) return ImpossibleMove; // vert off-board to-square
503 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return ImpossibleMove;
505 cl.pieceIn = CharToPiece(wom ? piece : piece + 'a' - 'A');
506 if(cl.pieceIn == EmptySquare) return ImpossibleMove; // non-existent piece
507 if(promoted) cl.pieceIn = (ChessSquare) (CHUPROMOTED(cl.pieceIn));
508 } else cl.pieceIn = EmptySquare;
509 if(separator == '@' || separator == '*') { // drop move. We only get here without from-square or promoted piece
510 fromY = DROP_RANK; fromX = cl.pieceIn;
511 currentMoveString[0] = piece;
512 currentMoveString[1] = '@';
513 currentMoveString[4] = NULLCHAR;
514 return LegalityTest(boards[yyboardindex], PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, fromY, fromX, toY, toX, NULLCHAR);
516 if(type[1] == NOTHING && type[0] != NOTHING) { // there is a disambiguator
517 if(type[0] != type[2]) coord[0] = -1, type[1] = type[0], type[0] = NOTHING; // it was a rank-disambiguator
519 if( type[1] != type[2] && // means fromY is of opposite type as ToX, or NOTHING
520 (type[0] == NOTHING || type[0] == type[2]) ) { // well formed
522 fromX = (currentMoveString[0] = coord[0] + 'a') - AAA;
523 fromY = (currentMoveString[1] = coord[1] + '0') - ONE;
524 currentMoveString[4] = cl.promoCharIn = PromoSuffix(p);
525 currentMoveString[5] = NULLCHAR;
526 if(**p == 'x' && !cl.promoCharIn) { // other leg follows
530 if(**p == '-' || **p == 'x') { // 3-leg move!
531 currentMoveString[7] = (kill2X = toX) + AAA; // what we thought was to-square is in fact 1st kill-square of two
532 currentMoveString[8] = (kill2Y = toY) + ONE; // append it after 2nd kill-square
533 toX = x - AAA; // kludge alert: this will become 2nd kill square
536 } else *p = q; // 2-leg move, rewind to leave reading of 2nd leg to code below
538 if(!cl.promoCharIn && (**p == '-' || **p == 'x')) { // Lion-type multi-leg move
539 currentMoveString[5] = (killX = toX) + AAA; // what we thought was to-square is in fact kill-square
540 currentMoveString[6] = (killY = toY) + ONE; // append it as suffix behind long algebraic move
541 currentMoveString[4] = ';';
542 currentMoveString[suffix+1] = NULLCHAR;
543 // read new to-square (VERY non-robust! Assumes correct (non-alpha-rank) syntax, and messes up on errors)
544 toX = cl.ftIn = (currentMoveString[2] = *++*p) - AAA; ++*p;
545 toY = cl.rtIn = (currentMoveString[3] = Number(p) + '0') - ONE;
546 currentMoveString[suffix] = cl.promoCharIn = PromoSuffix(p);
548 if(type[0] != NOTHING && type[1] != NOTHING && type[3] != NOTHING) { // fully specified.
549 ChessSquare realPiece = boards[yyboardindex][fromY][fromX];
550 // Note that Disambiguate does not work for illegal moves, but flags them as impossible
551 if(piece) { // check if correct piece indicated
552 if(PieceToChar(realPiece) == '~') realPiece = (ChessSquare) (DEMOTED(realPiece));
553 if(!(appData.icsActive && PieceToChar(realPiece) == '+') && // trust ICS if it moves promoted pieces
554 piece && realPiece != cl.pieceIn) return ImpossibleMove;
555 } else if(!separator && **p == '+') { // could be a protocol move, where bare '+' suffix means shogi-style promotion
556 if(realPiece < (wom ? WhiteCannon : BlackCannon) && PieceToChar(PROMOTED(realPiece)) == '+') // seems to be that
557 currentMoveString[4] = cl.promoCharIn = *(*p)++; // append promochar after all
559 result = LegalityTest(boards[yyboardindex], PosFlags(yyboardindex), fromY, fromX, toY, toX, cl.promoCharIn);
560 if (currentMoveString[4] == NULLCHAR) { // suppy missing mandatory promotion character
561 if(result == WhitePromotion || result == BlackPromotion) {
562 switch(gameInfo.variant) {
564 case VariantShatranj: currentMoveString[4] = PieceToChar(BlackFerz); break;
565 case VariantGreat: currentMoveString[4] = PieceToChar(BlackMan); break;
566 case VariantShogi: currentMoveString[4] = '+'; break;
567 default: currentMoveString[4] = PieceToChar(BlackQueen);
569 } else if(result == WhiteNonPromotion || result == BlackNonPromotion) {
570 currentMoveString[4] = '=';
572 } else if(appData.testLegality && gameInfo.variant != VariantSChess && // strip off unnecessary and false promo characters
573 !(result == WhitePromotion || result == BlackPromotion ||
574 result == WhiteNonPromotion || result == BlackNonPromotion)) currentMoveString[4] = NULLCHAR;
576 } else if(cl.pieceIn == EmptySquare) cl.pieceIn = wom ? WhitePawn : BlackPawn;
577 cl.ffIn = type[0] == NOTHING ? -1 : coord[0] + 'a' - AAA;
578 cl.rfIn = type[1] == NOTHING ? -1 : coord[1] + '0' - ONE;
580 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
582 if(cl.kind == ImpossibleMove && !piece && type[1] == NOTHING // fxg5 type
583 && toY == (wom ? 4 : 3)) { // could be improperly written e.p.
584 cl.rtIn += wom ? 1 : -1; // shift target square to e.p. square
585 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
586 if((cl.kind != WhiteCapturesEnPassant && cl.kind != BlackCapturesEnPassant))
587 return ImpossibleMove; // nice try, but no cigar
590 currentMoveString[0] = cl.ff + AAA;
591 currentMoveString[1] = cl.rf + ONE;
592 currentMoveString[3] = cl.rt + ONE;
593 if(killX < 0) // [HGM] lion: do not overwrite kill-square suffix
594 currentMoveString[4] = cl.promoChar;
596 if((cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) && !Match("ep", p)) Match("e.p.", p);
598 return (int) cl.kind;
601 badMove:// we failed to find algebraic move
605 // Next we do some common symbols where the first character commits us to things that cannot possibly be a move
607 // ********* PGN tags ******************************************
609 oldp = ++(*p); kifu = 0;
610 if(Match("--", p)) { // "[--" could be start of position diagram
611 if(!Scan(']', p) && (*p)[-3] == '-' && (*p)[-2] == '-') return PositionDiagram;
615 if(Match("DhtmlXQ", p)) { // Xiangqi UBB tags
617 if(**p == ']') strcpy(parseStart = yytext, "[Variant \"xiangqi\"]"), res = PGNTag; else
618 if(Match("_movelist", p)) xqUBB = 1; else Scan(']', p);
619 Scan(']', p); // for non-movelist tags this skips to the closing tag (disarming any enclosed kanji)!
621 } else if(Match("/DhtmlXQ", p)) { Scan(']', p); return Nothing; }
622 if(isdigit(**p) || isalpha(**p)) {
623 do (*p)++; while(isdigit(**p) || isalpha(**p) || **p == '+' ||
624 **p == '-' || **p == '=' || **p == '_' || **p == '#');
628 while(**p != '\n' && (*(*p)++ != '"'|| (*p)[-2] == '\\')); // look for unescaped quote
629 if((*p)[-1] !='"') { *p = oldp; Scan(']', p); return Comment; } // string closing delimiter missing
630 SkipWhite(p); if(*(*p)++ == ']') return PGNTag;
633 Scan(']', p); return Comment;
636 // ********* SAN Castings *************************************
637 if(**p == 'O' || **p == 'o' || **p == '0' && !Match("00:", p)) { // exclude 00 in time stamps
638 int castlingType = 0;
639 if(Match("O-O-O", p) || Match("o-o-o", p) || Match("0-0-0", p) ||
640 Match("OOO", p) || Match("ooo", p) || Match("000", p)) castlingType = 2;
641 else if(Match("O-O", p) || Match("o-o", p) || Match("0-0", p) ||
642 Match("OO", p) || Match("oo", p) || Match("00", p)) castlingType = 1;
643 if(castlingType) { //code from old parser, collapsed for both castling types, and streamlined a bit
644 int rf, ff, rt, ft; ChessSquare king;
645 char promo=NULLCHAR, gate = 0;
647 if(gameInfo.variant == VariantSChess) {
648 promo = PromoSuffix(p);
649 if(promo && **p >= 'a' && **p < AAA + BOARD_RGHT) gate = *(*p)++;
652 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
655 rf = castlingRank[0];
656 rt = castlingRank[0];
659 rf = castlingRank[3];
660 rt = castlingRank[3];
663 ff = (BOARD_WIDTH-1)>>1; // this would be d-file
664 if (boards[yyboardindex][rf][ff] == king) {
665 /* ICS wild castling */
666 ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
669 ff = BOARD_WIDTH>>1; // e-file
670 ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
671 if(pieceDesc[king] && (q = strchr(pieceDesc[king], 'O'))) { // redefined to non-default King stride
672 ft = (castlingType == 1 ? ff + atoi(q+1) : ff - atoi(q+1));
675 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
677 ff = initialRights[2];
678 ft = initialRights[castlingType-1];
680 ff = initialRights[5];
681 ft = initialRights[castlingType+2];
683 if (appData.debugMode) fprintf(debugFP, "Parser FRC (type=%d) %d %d\n", castlingType, ff, ft);
684 if(ff == NoRights || ft == NoRights) return ImpossibleMove;
686 if(gate) { // gating disambiguator present
687 if(gate != ff + AAA) {
688 int h = ft; ft = ff; ff = h; // reverse for gating at Rook square
689 if(gate != AAA + initialRights[castlingType+(wom?-1:2)]) return ImpossibleMove;
692 sprintf(currentMoveString, "%c%c%c%c%c",ff+AAA,rf+ONE,ft+AAA,rt+ONE,promo);
693 if (appData.debugMode) fprintf(debugFP, "(%d-type) castling %d %d\n", castlingType, ff, ft);
695 return (int) LegalityTest(boards[yyboardindex],
696 PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
697 rf, ff, rt, ft, promo);
698 } else if(Match("01", p)) return Nothing; // prevent this from being mistaken for move number 1
702 // ********* variations (nesting) ******************************
704 if(RdTime(')', p)) return ElapsedTime;
707 if(**p ==')') { (*p)++; return Close; }
708 if(**p == ';') { while(**p != '\n') (*p)++; return Comment; }
711 // ********* Comments and result messages **********************
712 *p = oldp; commentEnd = NULL; result = 0;
714 if(RdTime('}', p)) return ElapsedTime;
715 if(lastChar == '\n' && Match("--------------\n", p)) {
717 i = Scan ('}', p); q = *p - 16;
718 if(Match("\n--------------}\n", &q)) return PositionDiagram;
719 } else i = Scan('}', p);
720 commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
722 if(commentEnd) SkipWhite(p);
723 if(kifu && **p == '*') { // .kif comment
725 while(**p && **p != '\n') { if(q < yytext + 10*MSG_SIZ-3) *q++ = **p; (*p)++; }
726 parseStart = yytext; *yytext = '{'; strcpy(q, "}\n"); // wrap in braces
729 if(Match("*", p)) result = GameUnfinished;
730 else if(**p == '0') {
731 if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
732 Match("0 - 1", p) || Match("0 / 1", p) || Match("0 : 1", p)) result = BlackWins;
733 } else if(**p == '1') {
734 if( Match("1-0", p) || Match("1/0", p) || Match("1:0", p) ||
735 Match("1 - 0", p) || Match("1 / 0", p) || Match("1 : 0", p)) result = WhiteWins;
736 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) ||
737 Match("1 / 2 : 1 / 2", p) || Match("1/2", p) || Match("1 / 2", p)) result = GameIsDrawn;
740 if(Match(" (", p) && !Scan(')', p) || Match(" {", p) && !Scan('}', p)) { // there is a comment after the PGN result!
741 if(commentEnd) { *p = commentEnd; return Comment; } // so comment before it is normal comment; return that first
743 return result; // this returns a possible preceeding comment as result details
745 if(commentEnd) { *p = commentEnd; return Comment; } // there was no PGN result following, so return as normal comment
748 // ********* Move numbers (after castlings or PGN results!) ***********
749 if((i = Number(p)) != BADNUMBER) { // a single number was read as part of our attempt to read a move
751 if(**p == '.') (*p)++; SkipWhite(p);
752 if(**p == '+' || isalpha(**p) || gameInfo.variant == VariantShogi && *p != numEnd && isdigit(**p)) {
754 return i == 1 ? MoveNumberOne : Nothing;
756 *p = numEnd; return Nothing;
760 // ********* non-compliant game-result indicators *********************
761 if(Match("+-+", p) || Word("stalemate", p)) return GameIsDrawn;
762 if(Match("++", p) || Verb("resign", p) || (Word("check", p) || 1) && Word("mate", p) )
763 return (wom ? BlackWins : WhiteWins);
765 if(Word("w", p) && (Match("hite", p) || 1) || Word("b", p) && (Match("lack", p) || 1) ) {
766 if(**p != ' ') return Nothing;
768 if(Verb("disconnect", p)) return GameUnfinished;
769 if(Verb("resign", p) || Verb("forfeit", p) || Word("mated", p) || Word("lost", p) || Word("loses", p))
770 return (c == 'W' ? BlackWins : WhiteWins);
771 if(Word("mates", p) || Word("wins", p) || Word("won", p))
772 return (c != 'W' ? BlackWins : WhiteWins);
775 if(Word("draw", p)) {
776 if(**p == 'n') (*p)++;
777 if(**p != ' ') return GameIsDrawn;
779 if(Word("agreed", p)) return GameIsDrawn;
780 if(Match("by ", p) && (Word("repetition", p) || Word("agreement", p)) ) return GameIsDrawn;
783 while(**p != '\n') if(*(*p)++ == ')') break;
784 if((*p)[-1] == ')') return GameIsDrawn;
786 *p = oldp - 1; return GameIsDrawn;
790 // ********* Numeric annotation glyph **********************************
791 if(**p == '$') { (*p)++; if(Number(p) != BADNUMBER) return NAG; return Nothing; }
794 // ********** by now we are getting down to the silly stuff ************
795 if(Word("gnu", p) || Match("GNU", p)) {
796 if(**p == ' ') (*p)++;
797 if(Word("chess", p) || Match("CHESS", p)) {
799 if((q = strstr(*p, "game")) || (q = strstr(*p, "GAME")) || (q = strstr(*p, "Game"))) {
800 (*p) = q + 4; return GNUChessGame;
805 if(lastChar == '\n' && (Match("# ", p) || Match("; ", p) || Match("% ", p))) {
806 while(**p != '\n' && **p != ' ') (*p)++;
807 if(**p == ' ' && (Match(" game file", p) || Match(" position file", p))) {
808 while(**p != '\n') (*p)++; // skip to EOLN
811 *p = oldp; // we might need to re-match the skipped stuff
814 if(Match("---", p)) { while(**p == '-') (*p)++; return Nothing; } // prevent separators parsing as null move
815 if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
816 strncpy(currentMoveString, "@@@@", 5);
817 return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
820 // ********* Efficient skipping of (mostly) alphabetic chatter **********
821 while(isdigit(**p) || isalpha(**p) || **p == '-') (*p)++;
824 while(isdigit(**p) || isalpha(**p) || **p == '-' || **p == '\'') (*p)++;
825 return Nothing; // random word
827 if(lastChar == '\n' && Match(": ", p)) { // mail header, skip indented lines
829 while(**p != '\n') (*p)++;
830 if(!ReadLine()) return Nothing; // append next line if not EOF
831 } while(Match("\n ", p) || Match("\n\t", p));
836 // ********* Prevent 00 in unprotected time stamps to be mistaken for castling *******
837 if(Match(":00", p)) return Nothing;
839 // ********* Could not match to anything. Return offending character ****
845 Return offset of next pattern in the current file.
850 return ftell(inputFile) - (inPtr - parsePtr); // subtract what is read but not yet parsed
855 { // prepare parse buffer for reading file
857 inPtr = parsePtr = inputBuf;
860 *inPtr = NULLCHAR; // make sure we will start by reading a line
864 yynewstr P((char *s))
873 { // this replaces the flex-generated parser
874 int result = NextUnit(&parsePtr);
875 char *p = parseStart, *q = yytext;
876 if(p == yytext) return result; // kludge to allow kanji expansion
877 while(p < parsePtr) *q++ = *p++; // copy the matched text to yytext[]
885 { // [HGM] wrapper for yylex, which treats nesting of parentheses
886 int symbol, nestingLevel = 0, i=0;
888 static char buf[256*MSG_SIZ];
890 do { // eat away anything not at level 0
892 if(symbol == Open) nestingLevel++;
893 if(nestingLevel) { // save all parsed text between (and including) the ()
894 for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
897 if(symbol == 0) break; // ran into EOF
898 if(symbol == Close) symbol = Comment, nestingLevel--;
899 } while(nestingLevel || symbol == Nothing);
900 yy_text = buf[0] ? buf : (char*)yytext;
905 yylexstr (int boardIndex, char *s, char *buf, int buflen)
908 char *savPP = parsePtr;
910 yyboardindex = boardIndex;
912 ret = (ChessMove) Myylex();
913 strncpy(buf, yy_text, buflen-1);
914 buf[buflen-1] = NULLCHAR;