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;
647 if(gameInfo.variant == VariantSChess) promo = PromoSuffix(p);
649 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
652 rf = castlingRank[0];
653 rt = castlingRank[0];
656 rf = castlingRank[3];
657 rt = castlingRank[3];
660 ff = (BOARD_WIDTH-1)>>1; // this would be d-file
661 if (boards[yyboardindex][rf][ff] == king) {
662 /* ICS wild castling */
663 ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
666 ff = BOARD_WIDTH>>1; // e-file
667 ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
668 if(pieceDesc[king] && (q = strchr(pieceDesc[king], 'O'))) { // redefined to non-default King stride
669 ft = (castlingType == 1 ? ff + atoi(q+1) : ff - atoi(q+1));
672 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
674 ff = initialRights[2];
675 ft = initialRights[castlingType-1];
677 ff = initialRights[5];
678 ft = initialRights[castlingType+2];
680 if (appData.debugMode) fprintf(debugFP, "Parser FRC (type=%d) %d %d\n", castlingType, ff, ft);
681 if(ff == NoRights || ft == NoRights) return ImpossibleMove;
683 sprintf(currentMoveString, "%c%c%c%c%c",ff+AAA,rf+ONE,ft+AAA,rt+ONE,promo);
684 if (appData.debugMode) fprintf(debugFP, "(%d-type) castling %d %d\n", castlingType, ff, ft);
686 return (int) LegalityTest(boards[yyboardindex],
687 PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
688 rf, ff, rt, ft, promo);
689 } else if(Match("01", p)) return Nothing; // prevent this from being mistaken for move number 1
693 // ********* variations (nesting) ******************************
695 if(RdTime(')', p)) return ElapsedTime;
698 if(**p ==')') { (*p)++; return Close; }
699 if(**p == ';') { while(**p != '\n') (*p)++; return Comment; }
702 // ********* Comments and result messages **********************
703 *p = oldp; commentEnd = NULL; result = 0;
705 if(RdTime('}', p)) return ElapsedTime;
706 if(lastChar == '\n' && Match("--------------\n", p)) {
708 i = Scan ('}', p); q = *p - 16;
709 if(Match("\n--------------}\n", &q)) return PositionDiagram;
710 } else i = Scan('}', p);
711 commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
713 if(commentEnd) SkipWhite(p);
714 if(kifu && **p == '*') { // .kif comment
716 while(**p && **p != '\n') { if(q < yytext + 10*MSG_SIZ-3) *q++ = **p; (*p)++; }
717 parseStart = yytext; *yytext = '{'; strcpy(q, "}\n"); // wrap in braces
720 if(Match("*", p)) result = GameUnfinished;
721 else if(**p == '0') {
722 if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
723 Match("0 - 1", p) || Match("0 / 1", p) || Match("0 : 1", p)) result = BlackWins;
724 } else if(**p == '1') {
725 if( Match("1-0", p) || Match("1/0", p) || Match("1:0", p) ||
726 Match("1 - 0", p) || Match("1 / 0", p) || Match("1 : 0", p)) result = WhiteWins;
727 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) ||
728 Match("1 / 2 : 1 / 2", p) || Match("1/2", p) || Match("1 / 2", p)) result = GameIsDrawn;
731 if(Match(" (", p) && !Scan(')', p) || Match(" {", p) && !Scan('}', p)) { // there is a comment after the PGN result!
732 if(commentEnd) { *p = commentEnd; return Comment; } // so comment before it is normal comment; return that first
734 return result; // this returns a possible preceeding comment as result details
736 if(commentEnd) { *p = commentEnd; return Comment; } // there was no PGN result following, so return as normal comment
739 // ********* Move numbers (after castlings or PGN results!) ***********
740 if((i = Number(p)) != BADNUMBER) { // a single number was read as part of our attempt to read a move
742 if(**p == '.') (*p)++; SkipWhite(p);
743 if(**p == '+' || isalpha(**p) || gameInfo.variant == VariantShogi && *p != numEnd && isdigit(**p)) {
745 return i == 1 ? MoveNumberOne : Nothing;
747 *p = numEnd; return Nothing;
751 // ********* non-compliant game-result indicators *********************
752 if(Match("+-+", p) || Word("stalemate", p)) return GameIsDrawn;
753 if(Match("++", p) || Verb("resign", p) || (Word("check", p) || 1) && Word("mate", p) )
754 return (wom ? BlackWins : WhiteWins);
756 if(Word("w", p) && (Match("hite", p) || 1) || Word("b", p) && (Match("lack", p) || 1) ) {
757 if(**p != ' ') return Nothing;
759 if(Verb("disconnect", p)) return GameUnfinished;
760 if(Verb("resign", p) || Verb("forfeit", p) || Word("mated", p) || Word("lost", p) || Word("loses", p))
761 return (c == 'W' ? BlackWins : WhiteWins);
762 if(Word("mates", p) || Word("wins", p) || Word("won", p))
763 return (c != 'W' ? BlackWins : WhiteWins);
766 if(Word("draw", p)) {
767 if(**p == 'n') (*p)++;
768 if(**p != ' ') return GameIsDrawn;
770 if(Word("agreed", p)) return GameIsDrawn;
771 if(Match("by ", p) && (Word("repetition", p) || Word("agreement", p)) ) return GameIsDrawn;
774 while(**p != '\n') if(*(*p)++ == ')') break;
775 if((*p)[-1] == ')') return GameIsDrawn;
777 *p = oldp - 1; return GameIsDrawn;
781 // ********* Numeric annotation glyph **********************************
782 if(**p == '$') { (*p)++; if(Number(p) != BADNUMBER) return NAG; return Nothing; }
785 // ********** by now we are getting down to the silly stuff ************
786 if(Word("gnu", p) || Match("GNU", p)) {
787 if(**p == ' ') (*p)++;
788 if(Word("chess", p) || Match("CHESS", p)) {
790 if((q = strstr(*p, "game")) || (q = strstr(*p, "GAME")) || (q = strstr(*p, "Game"))) {
791 (*p) = q + 4; return GNUChessGame;
796 if(lastChar == '\n' && (Match("# ", p) || Match("; ", p) || Match("% ", p))) {
797 while(**p != '\n' && **p != ' ') (*p)++;
798 if(**p == ' ' && (Match(" game file", p) || Match(" position file", p))) {
799 while(**p != '\n') (*p)++; // skip to EOLN
802 *p = oldp; // we might need to re-match the skipped stuff
805 if(Match("---", p)) { while(**p == '-') (*p)++; return Nothing; } // prevent separators parsing as null move
806 if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
807 strncpy(currentMoveString, "@@@@", 5);
808 return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
811 // ********* Efficient skipping of (mostly) alphabetic chatter **********
812 while(isdigit(**p) || isalpha(**p) || **p == '-') (*p)++;
815 while(isdigit(**p) || isalpha(**p) || **p == '-' || **p == '\'') (*p)++;
816 return Nothing; // random word
818 if(lastChar == '\n' && Match(": ", p)) { // mail header, skip indented lines
820 while(**p != '\n') (*p)++;
821 if(!ReadLine()) return Nothing; // append next line if not EOF
822 } while(Match("\n ", p) || Match("\n\t", p));
827 // ********* Prevent 00 in unprotected time stamps to be mistaken for castling *******
828 if(Match(":00", p)) return Nothing;
830 // ********* Could not match to anything. Return offending character ****
836 Return offset of next pattern in the current file.
841 return ftell(inputFile) - (inPtr - parsePtr); // subtract what is read but not yet parsed
846 { // prepare parse buffer for reading file
848 inPtr = parsePtr = inputBuf;
851 *inPtr = NULLCHAR; // make sure we will start by reading a line
855 yynewstr P((char *s))
864 { // this replaces the flex-generated parser
865 int result = NextUnit(&parsePtr);
866 char *p = parseStart, *q = yytext;
867 if(p == yytext) return result; // kludge to allow kanji expansion
868 while(p < parsePtr) *q++ = *p++; // copy the matched text to yytext[]
876 { // [HGM] wrapper for yylex, which treats nesting of parentheses
877 int symbol, nestingLevel = 0, i=0;
879 static char buf[256*MSG_SIZ];
881 do { // eat away anything not at level 0
883 if(symbol == Open) nestingLevel++;
884 if(nestingLevel) { // save all parsed text between (and including) the ()
885 for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
888 if(symbol == 0) break; // ran into EOF
889 if(symbol == Close) symbol = Comment, nestingLevel--;
890 } while(nestingLevel || symbol == Nothing);
891 yy_text = buf[0] ? buf : (char*)yytext;
896 yylexstr (int boardIndex, char *s, char *buf, int buflen)
899 char *savPP = parsePtr;
901 yyboardindex = boardIndex;
903 ret = (ChessMove) Myylex();
904 strncpy(buf, yy_text, buflen-1);
905 buf[buflen-1] = NULLCHAR;