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));
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 char c, *start = inPtr;
289 if(fromString) return 0; // parsing string, so the end is a hard end
290 if(!inputFile) return 0;
291 while((c = fgetc(inputFile)) != EOF) {
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 = 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(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
431 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
433 if(n == 2 && type[0] == type[1]) { // if two identical types, the opposite type in between must have been missing
434 type[2] = type[1]; coord[2] = coord[1];
435 type[1] = NOTHING; coord[1] = -1; n++;
438 // we always get here, and might have read a +, a piece, and upto 4 potential coordinates
439 if(n <= 2) { // could be from-square or disambiguator, when -:xX follow, or drop with @ directly after piece, but also to-square
440 if(**p == '-' || **p == ':' || **p == 'x' || **p == 'X' || // these cannot be move suffix, so to-square must follow
441 (**p == '@' || **p == '*') && n == 0 && !promoted && piece) { // P@ must also be followed by to-square
443 if(n == 1) coord[1] = coord[0]; // must be disambiguator, but we do not know which yet
445 while(n < 4) { // attempt to read to-square
446 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
447 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
450 } else if((**p == '+' || **p == '=') && n == 1 && piece && type[0] == NUMERIC) { // can be traditional Xiangqi notation
453 if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
454 } else if(n == 2) { // only one square mentioned, must be to-square
455 while(n < 4) { coord[n] = coord[n-2], type[n] = type[n-2], coord[n-2] = -1, type[n-2] = NOTHING; n++; }
457 } else if(n == 3 && type[1] != NOTHING) { // must be hyphenless disambiguator + to-square
458 for(i=3; i>0; i--) coord[i] = coord[i-1], type[i] = type[i-1]; // move to-square to where it belongs
459 type[1] = NOTHING; // disambiguator goes in first two positions
462 // we always get here; move must be completely read now, with to-square coord(s) at end
463 if(n == 3) { // incomplete to-square. Could be Xiangqi traditional, or stuff like fxg
464 if(piece && type[1] == NOTHING && type[0] == NUMERIC && type[2] == NUMERIC &&
465 (separator == '+' || separator == '=' || separator == '-')) {
466 // Xiangqi traditional
468 return ImpossibleMove; // for now treat as invalid
470 // fxg stuff, but also things like 0-0, 0-1 and 1-0
471 if(!piece && type[1] == NOTHING && type[0] == ALPHABETIC && type[2] == ALPHABETIC
472 && (coord[0] != 14 || coord[2] != 14) /* reserve oo for castling! */ ) {
473 piece = 'P'; n = 4; // kludge alert: fake full to-square
475 } else if(n == 1 && type[0] == NUMERIC && coord[0] > 1) { while(**p == '.') (*p)++; return Nothing; } // fast exit for move numbers
476 if(n == 4 && type[2] != type[3] && // we have a valid to-square (kludge: type[3] can be NOTHING on fxg type move)
477 (piece || !promoted) && // promoted indicator only valid on named piece type
478 (type[2] == ALPHABETIC || IS_SHOGI(gameInfo.variant))) { // in Shogi also allow alphabetic rank
479 DisambiguateClosure cl;
480 int fromX, fromY, toX, toY;
482 if(slash && (!piece || type[1] == NOTHING)) goto badMove; // slash after piece only in ICS long format
483 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
485 if(type[2] == NUMERIC) { // alpha-rank
486 coord[2] = BOARD_RGHT - BOARD_LEFT - coord[2];
487 coord[3] = BOARD_HEIGHT - coord[3];
488 if(coord[0] >= 0) coord[0] = BOARD_RGHT - BOARD_LEFT - coord[0];
489 if(coord[1] >= 0) coord[1] = BOARD_HEIGHT - coord[1];
491 toX = cl.ftIn = (currentMoveString[2] = coord[2] + 'a') - AAA;
492 toY = cl.rtIn = (currentMoveString[3] = coord[3] + '0') - ONE;
493 if(type[3] == NOTHING) cl.rtIn = -1; // for fxg type moves ask for toY disambiguation
494 else if(toY >= BOARD_HEIGHT || toY < 0) return ImpossibleMove; // vert off-board to-square
495 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return ImpossibleMove;
497 cl.pieceIn = CharToPiece(wom ? piece : piece + 'a' - 'A');
498 if(cl.pieceIn == EmptySquare) return ImpossibleMove; // non-existent piece
499 if(promoted) cl.pieceIn = (ChessSquare) (CHUPROMOTED(cl.pieceIn));
500 } else cl.pieceIn = EmptySquare;
501 if(separator == '@' || separator == '*') { // drop move. We only get here without from-square or promoted piece
502 fromY = DROP_RANK; fromX = cl.pieceIn;
503 currentMoveString[0] = piece;
504 currentMoveString[1] = '@';
505 currentMoveString[4] = NULLCHAR;
506 return LegalityTest(boards[yyboardindex], PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, fromY, fromX, toY, toX, NULLCHAR);
508 if(type[1] == NOTHING && type[0] != NOTHING) { // there is a disambiguator
509 if(type[0] != type[2]) coord[0] = -1, type[1] = type[0], type[0] = NOTHING; // it was a rank-disambiguator
511 if( type[1] != type[2] && // means fromY is of opposite type as ToX, or NOTHING
512 (type[0] == NOTHING || type[0] == type[2]) ) { // well formed
514 fromX = (currentMoveString[0] = coord[0] + 'a') - AAA;
515 fromY = (currentMoveString[1] = coord[1] + '0') - ONE;
516 currentMoveString[4] = cl.promoCharIn = PromoSuffix(p);
517 currentMoveString[5] = NULLCHAR;
518 if(**p == 'x' && !cl.promoCharIn) { // other leg follows
522 if(**p == '-' || **p == 'x') { // 3-leg move!
523 currentMoveString[7] = (kill2X = toX) + AAA; // what we thought was to-square is in fact 1st kill-square of two
524 currentMoveString[8] = (kill2Y = toY) + ONE; // append it after 2nd kill-square
525 toX = x - AAA; // kludge alert: this will become 2nd kill square
528 } else *p = q; // 2-leg move, rewind to leave reading of 2nd leg to code below
530 if(!cl.promoCharIn && (**p == '-' || **p == 'x')) { // Lion-type multi-leg move
531 currentMoveString[5] = (killX = toX) + AAA; // what we thought was to-square is in fact kill-square
532 currentMoveString[6] = (killY = toY) + ONE; // append it as suffix behind long algebraic move
533 currentMoveString[4] = ';';
534 currentMoveString[suffix+1] = NULLCHAR;
535 // read new to-square (VERY non-robust! Assumes correct (non-alpha-rank) syntax, and messes up on errors)
536 toX = cl.ftIn = (currentMoveString[2] = *++*p) - AAA; ++*p;
537 toY = cl.rtIn = (currentMoveString[3] = Number(p) + '0') - ONE;
538 currentMoveString[suffix] = cl.promoCharIn = PromoSuffix(p);
540 if(type[0] != NOTHING && type[1] != NOTHING && type[3] != NOTHING) { // fully specified.
541 ChessSquare realPiece = boards[yyboardindex][fromY][fromX];
542 // Note that Disambiguate does not work for illegal moves, but flags them as impossible
543 if(piece) { // check if correct piece indicated
544 if(PieceToChar(realPiece) == '~') realPiece = (ChessSquare) (DEMOTED(realPiece));
545 if(!(appData.icsActive && PieceToChar(realPiece) == '+') && // trust ICS if it moves promoted pieces
546 piece && realPiece != cl.pieceIn) return ImpossibleMove;
547 } else if(!separator && **p == '+') { // could be a protocol move, where bare '+' suffix means shogi-style promotion
548 if(realPiece < (wom ? WhiteCannon : BlackCannon) && PieceToChar(PROMOTED(realPiece)) == '+') // seems to be that
549 currentMoveString[4] = cl.promoCharIn = *(*p)++; // append promochar after all
551 result = LegalityTest(boards[yyboardindex], PosFlags(yyboardindex), fromY, fromX, toY, toX, cl.promoCharIn);
552 if (currentMoveString[4] == NULLCHAR) { // suppy missing mandatory promotion character
553 if(result == WhitePromotion || result == BlackPromotion) {
554 switch(gameInfo.variant) {
556 case VariantShatranj: currentMoveString[4] = PieceToChar(BlackFerz); break;
557 case VariantGreat: currentMoveString[4] = PieceToChar(BlackMan); break;
558 case VariantShogi: currentMoveString[4] = '+'; break;
559 default: currentMoveString[4] = PieceToChar(BlackQueen);
561 } else if(result == WhiteNonPromotion || result == BlackNonPromotion) {
562 currentMoveString[4] = '=';
564 } else if(appData.testLegality && gameInfo.variant != VariantSChess && // strip off unnecessary and false promo characters
565 !(result == WhitePromotion || result == BlackPromotion ||
566 result == WhiteNonPromotion || result == BlackNonPromotion)) currentMoveString[4] = NULLCHAR;
568 } else if(cl.pieceIn == EmptySquare) cl.pieceIn = wom ? WhitePawn : BlackPawn;
569 cl.ffIn = type[0] == NOTHING ? -1 : coord[0] + 'a' - AAA;
570 cl.rfIn = type[1] == NOTHING ? -1 : coord[1] + '0' - ONE;
572 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
574 if(cl.kind == ImpossibleMove && !piece && type[1] == NOTHING // fxg5 type
575 && toY == (wom ? 4 : 3)) { // could be improperly written e.p.
576 cl.rtIn += wom ? 1 : -1; // shift target square to e.p. square
577 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
578 if((cl.kind != WhiteCapturesEnPassant && cl.kind != BlackCapturesEnPassant))
579 return ImpossibleMove; // nice try, but no cigar
582 currentMoveString[0] = cl.ff + AAA;
583 currentMoveString[1] = cl.rf + ONE;
584 currentMoveString[3] = cl.rt + ONE;
585 if(killX < 0) // [HGM] lion: do not overwrite kill-square suffix
586 currentMoveString[4] = cl.promoChar;
588 if((cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) && (Match("ep", p) || Match("e.p.", p)));
590 return (int) cl.kind;
593 badMove:// we failed to find algebraic move
597 // Next we do some common symbols where the first character commits us to things that cannot possibly be a move
599 // ********* PGN tags ******************************************
601 oldp = ++(*p); kifu = 0;
602 if(Match("--", p)) { // "[--" could be start of position diagram
603 if(!Scan(']', p) && (*p)[-3] == '-' && (*p)[-2] == '-') return PositionDiagram;
607 if(isdigit(**p) || isalpha(**p)) {
608 do (*p)++; while(isdigit(**p) || isalpha(**p) || **p == '+' ||
609 **p == '-' || **p == '=' || **p == '_' || **p == '#');
613 while(**p != '\n' && (*(*p)++ != '"'|| (*p)[-2] == '\\')); // look for unescaped quote
614 if((*p)[-1] !='"') { *p = oldp; Scan(']', p); return Comment; } // string closing delimiter missing
615 SkipWhite(p); if(*(*p)++ == ']') return PGNTag;
618 Scan(']', p); return Comment;
621 // ********* SAN Castings *************************************
622 if(**p == 'O' || **p == 'o' || **p == '0' && !Match("00:", p)) { // exclude 00 in time stamps
623 int castlingType = 0;
624 if(Match("O-O-O", p) || Match("o-o-o", p) || Match("0-0-0", p) ||
625 Match("OOO", p) || Match("ooo", p) || Match("000", p)) castlingType = 2;
626 else if(Match("O-O", p) || Match("o-o", p) || Match("0-0", p) ||
627 Match("OO", p) || Match("oo", p) || Match("00", p)) castlingType = 1;
628 if(castlingType) { //code from old parser, collapsed for both castling types, and streamlined a bit
629 int rf, ff, rt, ft; ChessSquare king;
632 if(gameInfo.variant == VariantSChess) promo = PromoSuffix(p);
634 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
637 rf = castlingRank[0];
638 rt = castlingRank[0];
641 rf = castlingRank[3];
642 rt = castlingRank[3];
645 ff = (BOARD_WIDTH-1)>>1; // this would be d-file
646 if (boards[yyboardindex][rf][ff] == king) {
647 /* ICS wild castling */
648 ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
651 ff = BOARD_WIDTH>>1; // e-file
652 ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
653 if(pieceDesc[king] && (q = strchr(pieceDesc[king], 'O'))) { // redefined to non-default King stride
654 ft = (castlingType == 1 ? ff + atoi(q+1) : ff - atoi(q+1));
657 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
659 ff = initialRights[2];
660 ft = initialRights[castlingType-1];
662 ff = initialRights[5];
663 ft = initialRights[castlingType+2];
665 if (appData.debugMode) fprintf(debugFP, "Parser FRC (type=%d) %d %d\n", castlingType, ff, ft);
666 if(ff == NoRights || ft == NoRights) return ImpossibleMove;
668 sprintf(currentMoveString, "%c%c%c%c%c",ff+AAA,rf+ONE,ft+AAA,rt+ONE,promo);
669 if (appData.debugMode) fprintf(debugFP, "(%d-type) castling %d %d\n", castlingType, ff, ft);
671 return (int) LegalityTest(boards[yyboardindex],
672 PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
673 rf, ff, rt, ft, promo);
674 } else if(Match("01", p)) return Nothing; // prevent this from being mistaken for move number 1
678 // ********* variations (nesting) ******************************
680 if(RdTime(')', p)) return ElapsedTime;
683 if(**p ==')') { (*p)++; return Close; }
684 if(**p == ';') { while(**p != '\n') (*p)++; return Comment; }
687 // ********* Comments and result messages **********************
688 *p = oldp; commentEnd = NULL; result = 0;
690 if(RdTime('}', p)) return ElapsedTime;
691 if(lastChar == '\n' && Match("--------------\n", p)) {
693 i = Scan ('}', p); q = *p - 16;
694 if(Match("\n--------------}\n", &q)) return PositionDiagram;
695 } else i = Scan('}', p);
696 commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
698 if(commentEnd) SkipWhite(p);
699 if(kifu && **p == '*') { // .kif comment
701 while(**p && **p != '\n') { if(q < yytext + 10*MSG_SIZ-3) *q++ = **p; (*p)++; }
702 parseStart = yytext; *yytext = '{'; strcpy(q, "}\n"); // wrap in braces
705 if(Match("*", p)) result = GameUnfinished;
706 else if(**p == '0') {
707 if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
708 Match("0 - 1", p) || Match("0 / 1", p) || Match("0 : 1", p)) result = BlackWins;
709 } else if(**p == '1') {
710 if( Match("1-0", p) || Match("1/0", p) || Match("1:0", p) ||
711 Match("1 - 0", p) || Match("1 / 0", p) || Match("1 : 0", p)) result = WhiteWins;
712 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) ||
713 Match("1 / 2 : 1 / 2", p) || Match("1/2", p) || Match("1 / 2", p)) result = GameIsDrawn;
716 if(Match(" (", p) && !Scan(')', p) || Match(" {", p) && !Scan('}', p)) { // there is a comment after the PGN result!
717 if(commentEnd) { *p = commentEnd; return Comment; } // so comment before it is normal comment; return that first
719 return result; // this returns a possible preceeding comment as result details
721 if(commentEnd) { *p = commentEnd; return Comment; } // there was no PGN result following, so return as normal comment
724 // ********* Move numbers (after castlings or PGN results!) ***********
725 if((i = Number(p)) != BADNUMBER) { // a single number was read as part of our attempt to read a move
727 if(**p == '.') (*p)++; SkipWhite(p);
728 if(**p == '+' || isalpha(**p) || gameInfo.variant == VariantShogi && *p != numEnd && isdigit(**p)) {
730 return i == 1 ? MoveNumberOne : Nothing;
732 *p = numEnd; return Nothing;
736 // ********* non-compliant game-result indicators *********************
737 if(Match("+-+", p) || Word("stalemate", p)) return GameIsDrawn;
738 if(Match("++", p) || Verb("resign", p) || (Word("check", p) || 1) && Word("mate", p) )
739 return (wom ? BlackWins : WhiteWins);
741 if(Word("w", p) && (Match("hite", p) || 1) || Word("b", p) && (Match("lack", p) || 1) ) {
742 if(**p != ' ') return Nothing;
744 if(Verb("disconnect", p)) return GameUnfinished;
745 if(Verb("resign", p) || Verb("forfeit", p) || Word("mated", p) || Word("lost", p) || Word("loses", p))
746 return (c == 'W' ? BlackWins : WhiteWins);
747 if(Word("mates", p) || Word("wins", p) || Word("won", p))
748 return (c != 'W' ? BlackWins : WhiteWins);
751 if(Word("draw", p)) {
752 if(**p == 'n') (*p)++;
753 if(**p != ' ') return GameIsDrawn;
755 if(Word("agreed", p)) return GameIsDrawn;
756 if(Match("by ", p) && (Word("repetition", p) || Word("agreement", p)) ) return GameIsDrawn;
759 while(**p != '\n') if(*(*p)++ == ')') break;
760 if((*p)[-1] == ')') return GameIsDrawn;
762 *p = oldp - 1; return GameIsDrawn;
766 // ********* Numeric annotation glyph **********************************
767 if(**p == '$') { (*p)++; if(Number(p) != BADNUMBER) return NAG; return Nothing; }
770 // ********** by now we are getting down to the silly stuff ************
771 if(Word("gnu", p) || Match("GNU", p)) {
772 if(**p == ' ') (*p)++;
773 if(Word("chess", p) || Match("CHESS", p)) {
775 if((q = strstr(*p, "game")) || (q = strstr(*p, "GAME")) || (q = strstr(*p, "Game"))) {
776 (*p) = q + 4; return GNUChessGame;
781 if(lastChar == '\n' && (Match("# ", p) || Match("; ", p) || Match("% ", p))) {
782 while(**p != '\n' && **p != ' ') (*p)++;
783 if(**p == ' ' && (Match(" game file", p) || Match(" position file", p))) {
784 while(**p != '\n') (*p)++; // skip to EOLN
787 *p = oldp; // we might need to re-match the skipped stuff
790 if(Match("---", p)) { while(**p == '-') (*p)++; return Nothing; } // prevent separators parsing as null move
791 if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
792 strncpy(currentMoveString, "@@@@", 5);
793 return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
796 // ********* Efficient skipping of (mostly) alphabetic chatter **********
797 while(isdigit(**p) || isalpha(**p) || **p == '-') (*p)++;
800 while(isdigit(**p) || isalpha(**p) || **p == '-' || **p == '\'') (*p)++;
801 return Nothing; // random word
803 if(lastChar == '\n' && Match(": ", p)) { // mail header, skip indented lines
805 while(**p != '\n') (*p)++;
806 if(!ReadLine()) return Nothing; // append next line if not EOF
807 } while(Match("\n ", p) || Match("\n\t", p));
812 // ********* Prevent 00 in unprotected time stamps to be mistaken for castling *******
813 if(Match(":00", p)) return Nothing;
815 // ********* Could not match to anything. Return offending character ****
821 Return offset of next pattern in the current file.
826 return ftell(inputFile) - (inPtr - parsePtr); // subtract what is read but not yet parsed
831 { // prepare parse buffer for reading file
833 inPtr = parsePtr = inputBuf;
836 *inPtr = NULLCHAR; // make sure we will start by reading a line
840 yynewstr P((char *s))
849 { // this replaces the flex-generated parser
850 int result = NextUnit(&parsePtr);
851 char *p = parseStart, *q = yytext;
852 if(p == yytext) return result; // kludge to allow kanji expansion
853 while(p < parsePtr) *q++ = *p++; // copy the matched text to yytext[]
861 { // [HGM] wrapper for yylex, which treats nesting of parentheses
862 int symbol, nestingLevel = 0, i=0;
864 static char buf[256*MSG_SIZ];
866 do { // eat away anything not at level 0
868 if(symbol == Open) nestingLevel++;
869 if(nestingLevel) { // save all parsed text between (and including) the ()
870 for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
873 if(symbol == 0) break; // ran into EOF
874 if(symbol == Close) symbol = Comment, nestingLevel--;
875 } while(nestingLevel || symbol == Nothing);
876 yy_text = buf[0] ? buf : (char*)yytext;
881 yylexstr (int boardIndex, char *s, char *buf, int buflen)
884 char *savPP = parsePtr;
886 yyboardindex = boardIndex;
888 ret = (ChessMove) Myylex();
889 strncpy(buf, yy_text, buflen-1);
890 buf[buflen-1] = NULLCHAR;