4 * Copyright 2011, 2012, 2013, 2014, 2015 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 == 'e' && (Match("ep", p) || Match("e.p.", p))) { *p = start; return NULLCHAR; } // non-compliant e.p. suffix is no promoChar!
385 if(**p == '+' && IS_SHOGI(gameInfo.variant)) { (*p)++; return '+'; }
386 if(**p == '=' || (gameInfo.variant == VariantSChess) && **p == '/') (*p)++; // optional = (or / for Seirawan gating)
387 if(**p == '(' && (*p)[2] == ')' && isalpha( (*p)[1] )) { (*p) += 3; return ToLower((*p)[-2]); }
388 if(isalpha(**p) && **p != 'x') return ToLower(*(*p)++); // reserve 'x' for multi-leg captures?
389 if(*p != start) return **p == '+' ? *(*p)++ : '='; // must be the optional = (or =+)
390 return NULLCHAR; // no suffix detected
395 { // Main parser routine
396 int coord[4], n, result, piece, i;
397 char type[4], promoted, separator, slash, *oldp, *commentEnd, c;
398 int wom = quickFlag ? quickFlag&1 : WhiteOnMove(yyboardindex);
400 // ********* try white first, because it is so common **************************
401 if(**p == ' ' || **p == '\n' || **p == '\t') { parseStart = (*p)++; return Nothing; }
404 if(**p == NULLCHAR) { // make sure there is something to parse
405 if(fromString) return 0; // we are parsing string, so the end is really the end
406 *p = inPtr = inputBuf;
407 if(!ReadLine()) return 0; // EOF
408 } else if(inPtr > inputBuf + PARSEBUFSIZE/2) { // buffer fills up with already parsed stuff
409 char *q = *p, *r = inputBuf;
411 *p = inputBuf; inPtr = r - 1;
413 parseStart = oldp = *p; // remember where we begin
415 // ********* attempt to recognize a SAN move in the leading non-blank text *****
416 piece = separator = promoted = slash = n = 0;
417 for(i=0; i<4; i++) coord[i] = -1, type[i] = NOTHING;
418 if(**p & 0x80) return KifuMove(p); // non-ascii. Could be some kanj notation for Shogi or Xiangqi
419 if(**p == '+') (*p)++, promoted++;
420 if(**p >= 'a' && **p <= 'z' && (*p)[1]== '@') piece =*(*p)++ + 'A' - 'a'; else
421 if(**p >= 'A' && **p <= 'Z') {
422 static char s[] = SUFFIXES;
424 piece = *(*p)++; // Note we could test for 2-byte non-ascii names here
425 if(q = strchr(s, **p)) (*p)++, piece += 64*(q - s + 1);
426 if(**p == '/') slash = *(*p)++;
429 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
430 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
432 if(n == 2 && type[0] == type[1]) { // if two identical types, the opposite type in between must have been missing
433 type[2] = type[1]; coord[2] = coord[1];
434 type[1] = NOTHING; coord[1] = -1; n++;
437 // we always get here, and might have read a +, a piece, and upto 4 potential coordinates
438 if(n <= 2) { // could be from-square or disambiguator, when -:xX follow, or drop with @ directly after piece, but also to-square
439 if(**p == '-' || **p == ':' || **p == 'x' || **p == 'X' || // these cannot be move suffix, so to-square must follow
440 (**p == '@' || **p == '*') && n == 0 && !promoted && piece) { // P@ must also be followed by to-square
442 if(n == 1) coord[1] = coord[0]; // must be disambiguator, but we do not know which yet
444 while(n < 4) { // attempt to read to-square
445 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
446 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
449 } else if((**p == '+' || **p == '=') && n == 1 && piece && type[0] == NUMERIC) { // can be traditional Xiangqi notation
452 if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
453 } else if(n == 2) { // only one square mentioned, must be to-square
454 while(n < 4) { coord[n] = coord[n-2], type[n] = type[n-2], coord[n-2] = -1, type[n-2] = NOTHING; n++; }
456 } else if(n == 3 && type[1] != NOTHING) { // must be hyphenless disambiguator + to-square
457 for(i=3; i>0; i--) coord[i] = coord[i-1], type[i] = type[i-1]; // move to-square to where it belongs
458 type[1] = NOTHING; // disambiguator goes in first two positions
461 // we always get here; move must be completely read now, with to-square coord(s) at end
462 if(n == 3) { // incomplete to-square. Could be Xiangqi traditional, or stuff like fxg
463 if(piece && type[1] == NOTHING && type[0] == NUMERIC && type[2] == NUMERIC &&
464 (separator == '+' || separator == '=' || separator == '-')) {
465 // Xiangqi traditional
467 return ImpossibleMove; // for now treat as invalid
469 // fxg stuff, but also things like 0-0, 0-1 and 1-0
470 if(!piece && type[1] == NOTHING && type[0] == ALPHABETIC && type[2] == ALPHABETIC
471 && (coord[0] != 14 || coord[2] != 14) /* reserve oo for castling! */ ) {
472 piece = 'P'; n = 4; // kludge alert: fake full to-square
474 } else if(n == 1 && type[0] == NUMERIC && coord[0] > 1) { while(**p == '.') (*p)++; return Nothing; } // fast exit for move numbers
475 if(n == 4 && type[2] != type[3] && // we have a valid to-square (kludge: type[3] can be NOTHING on fxg type move)
476 (piece || !promoted) && // promoted indicator only valid on named piece type
477 (type[2] == ALPHABETIC || IS_SHOGI(gameInfo.variant))) { // in Shogi also allow alphabetic rank
478 DisambiguateClosure cl;
479 int fromX, fromY, toX, toY;
481 if(slash && (!piece || type[1] == NOTHING)) goto badMove; // slash after piece only in ICS long format
482 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
484 if(type[2] == NUMERIC) { // alpha-rank
485 coord[2] = BOARD_RGHT - BOARD_LEFT - coord[2];
486 coord[3] = BOARD_HEIGHT - coord[3];
487 if(coord[0] >= 0) coord[0] = BOARD_RGHT - BOARD_LEFT - coord[0];
488 if(coord[1] >= 0) coord[1] = BOARD_HEIGHT - coord[1];
490 toX = cl.ftIn = (currentMoveString[2] = coord[2] + 'a') - AAA;
491 toY = cl.rtIn = (currentMoveString[3] = coord[3] + '0') - ONE;
492 if(type[3] == NOTHING) cl.rtIn = -1; // for fxg type moves ask for toY disambiguation
493 else if(toY >= BOARD_HEIGHT || toY < 0) return ImpossibleMove; // vert off-board to-square
494 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return ImpossibleMove;
496 cl.pieceIn = CharToPiece(wom ? piece : piece + 'a' - 'A');
497 if(cl.pieceIn == EmptySquare) return ImpossibleMove; // non-existent piece
498 if(promoted) cl.pieceIn = (ChessSquare) (CHUPROMOTED cl.pieceIn);
499 } else cl.pieceIn = EmptySquare;
500 if(separator == '@' || separator == '*') { // drop move. We only get here without from-square or promoted piece
501 fromY = DROP_RANK; fromX = cl.pieceIn;
502 currentMoveString[0] = piece;
503 currentMoveString[1] = '@';
504 currentMoveString[4] = NULLCHAR;
505 return LegalityTest(boards[yyboardindex], PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, fromY, fromX, toY, toX, NULLCHAR);
507 if(type[1] == NOTHING && type[0] != NOTHING) { // there is a disambiguator
508 if(type[0] != type[2]) coord[0] = -1, type[1] = type[0], type[0] = NOTHING; // it was a rank-disambiguator
510 if( type[1] != type[2] && // means fromY is of opposite type as ToX, or NOTHING
511 (type[0] == NOTHING || type[0] == type[2]) ) { // well formed
513 fromX = (currentMoveString[0] = coord[0] + 'a') - AAA;
514 fromY = (currentMoveString[1] = coord[1] + '0') - ONE;
515 currentMoveString[4] = cl.promoCharIn = PromoSuffix(p);
516 currentMoveString[5] = NULLCHAR;
517 if(!cl.promoCharIn && (**p == '-' || **p == 'x')) { // Lion-type multi-leg move
518 currentMoveString[5] = (killX = toX) + AAA; // what we thought was to-square is in fact kill-square
519 currentMoveString[6] = (killY = toY) + ONE; // append it as suffix behind long algebraic move
520 currentMoveString[4] = ';';
521 currentMoveString[7] = NULLCHAR;
522 // read new to-square (VERY non-robust! Assumes correct (non-alpha-rank) syntax, and messes up on errors)
523 toX = cl.ftIn = (currentMoveString[2] = *++*p) - AAA; ++*p;
524 toY = cl.rtIn = (currentMoveString[3] = Number(p) + '0') - ONE;
526 if(type[0] != NOTHING && type[1] != NOTHING && type[3] != NOTHING) { // fully specified.
527 ChessSquare realPiece = boards[yyboardindex][fromY][fromX];
528 // Note that Disambiguate does not work for illegal moves, but flags them as impossible
529 if(piece) { // check if correct piece indicated
530 if(PieceToChar(realPiece) == '~') realPiece = (ChessSquare) (DEMOTED realPiece);
531 if(!(appData.icsActive && PieceToChar(realPiece) == '+') && // trust ICS if it moves promoted pieces
532 piece && realPiece != cl.pieceIn) return ImpossibleMove;
533 } else if(!separator && **p == '+') { // could be a protocol move, where bare '+' suffix means shogi-style promotion
534 if(realPiece < (wom ? WhiteCannon : BlackCannon) && PieceToChar(PROMOTED realPiece) == '+') // seems to be that
535 currentMoveString[4] = cl.promoCharIn = *(*p)++; // append promochar after all
537 result = LegalityTest(boards[yyboardindex], PosFlags(yyboardindex), fromY, fromX, toY, toX, cl.promoCharIn);
538 if (currentMoveString[4] == NULLCHAR) { // suppy missing mandatory promotion character
539 if(result == WhitePromotion || result == BlackPromotion) {
540 switch(gameInfo.variant) {
542 case VariantShatranj: currentMoveString[4] = PieceToChar(BlackFerz); break;
543 case VariantGreat: currentMoveString[4] = PieceToChar(BlackMan); break;
544 case VariantShogi: currentMoveString[4] = '+'; break;
545 default: currentMoveString[4] = PieceToChar(BlackQueen);
547 } else if(result == WhiteNonPromotion || result == BlackNonPromotion) {
548 currentMoveString[4] = '=';
550 } else if(appData.testLegality && gameInfo.variant != VariantSChess && // strip off unnecessary and false promo characters
551 !(result == WhitePromotion || result == BlackPromotion ||
552 result == WhiteNonPromotion || result == BlackNonPromotion)) currentMoveString[4] = NULLCHAR;
554 } else if(cl.pieceIn == EmptySquare) cl.pieceIn = wom ? WhitePawn : BlackPawn;
555 cl.ffIn = type[0] == NOTHING ? -1 : coord[0] + 'a' - AAA;
556 cl.rfIn = type[1] == NOTHING ? -1 : coord[1] + '0' - ONE;
558 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
560 if(cl.kind == ImpossibleMove && !piece && type[1] == NOTHING // fxg5 type
561 && toY == (wom ? 4 : 3)) { // could be improperly written e.p.
562 cl.rtIn += wom ? 1 : -1; // shift target square to e.p. square
563 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
564 if((cl.kind != WhiteCapturesEnPassant && cl.kind != BlackCapturesEnPassant))
565 return ImpossibleMove; // nice try, but no cigar
568 currentMoveString[0] = cl.ff + AAA;
569 currentMoveString[1] = cl.rf + ONE;
570 currentMoveString[3] = cl.rt + ONE;
571 if(killX < 0) // [HGM] lion: do not overwrite kill-square suffix
572 currentMoveString[4] = cl.promoChar;
574 if((cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) && (Match("ep", p) || Match("e.p.", p)));
576 return (int) cl.kind;
579 badMove:// we failed to find algebraic move
583 // Next we do some common symbols where the first character commits us to things that cannot possibly be a move
585 // ********* PGN tags ******************************************
587 oldp = ++(*p); kifu = 0;
588 if(Match("--", p)) { // "[--" could be start of position diagram
589 if(!Scan(']', p) && (*p)[-3] == '-' && (*p)[-2] == '-') return PositionDiagram;
593 if(isdigit(**p) || isalpha(**p)) {
594 do (*p)++; while(isdigit(**p) || isalpha(**p) || **p == '+' ||
595 **p == '-' || **p == '=' || **p == '_' || **p == '#');
599 while(**p != '\n' && (*(*p)++ != '"'|| (*p)[-2] == '\\')); // look for unescaped quote
600 if((*p)[-1] !='"') { *p = oldp; Scan(']', p); return Comment; } // string closing delimiter missing
601 SkipWhite(p); if(*(*p)++ == ']') return PGNTag;
604 Scan(']', p); return Comment;
607 // ********* SAN Castings *************************************
608 if(**p == 'O' || **p == 'o' || **p == '0' && !Match("00:", p)) { // exclude 00 in time stamps
609 int castlingType = 0;
610 if(Match("O-O-O", p) || Match("o-o-o", p) || Match("0-0-0", p) ||
611 Match("OOO", p) || Match("ooo", p) || Match("000", p)) castlingType = 2;
612 else if(Match("O-O", p) || Match("o-o", p) || Match("0-0", p) ||
613 Match("OO", p) || Match("oo", p) || Match("00", p)) castlingType = 1;
614 if(castlingType) { //code from old parser, collapsed for both castling types, and streamlined a bit
615 int rf, ff, rt, ft; ChessSquare king;
618 if(gameInfo.variant == VariantSChess) promo = PromoSuffix(p);
620 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
623 rf = castlingRank[0];
624 rt = castlingRank[0];
627 rf = castlingRank[3];
628 rt = castlingRank[3];
631 ff = (BOARD_WIDTH-1)>>1; // this would be d-file
632 if (boards[yyboardindex][rf][ff] == king) {
633 /* ICS wild castling */
634 ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
637 ff = BOARD_WIDTH>>1; // e-file
638 ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
639 if(pieceDesc[king] && (q = strchr(pieceDesc[king], 'O'))) { // redefined to non-default King stride
640 ft = (castlingType == 1 ? ff + atoi(q+1) : ff - atoi(q+1));
643 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
645 ff = initialRights[2];
646 ft = initialRights[castlingType-1];
648 ff = initialRights[5];
649 ft = initialRights[castlingType+2];
651 if (appData.debugMode) fprintf(debugFP, "Parser FRC (type=%d) %d %d\n", castlingType, ff, ft);
652 if(ff == NoRights || ft == NoRights) return ImpossibleMove;
654 sprintf(currentMoveString, "%c%c%c%c%c",ff+AAA,rf+ONE,ft+AAA,rt+ONE,promo);
655 if (appData.debugMode) fprintf(debugFP, "(%d-type) castling %d %d\n", castlingType, ff, ft);
657 return (int) LegalityTest(boards[yyboardindex],
658 PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
659 rf, ff, rt, ft, promo);
660 } else if(Match("01", p)) return Nothing; // prevent this from being mistaken for move number 1
664 // ********* variations (nesting) ******************************
666 if(RdTime(')', p)) return ElapsedTime;
669 if(**p ==')') { (*p)++; return Close; }
670 if(**p == ';') { while(**p != '\n') (*p)++; return Comment; }
673 // ********* Comments and result messages **********************
674 *p = oldp; commentEnd = NULL; result = 0;
676 if(RdTime('}', p)) return ElapsedTime;
677 if(lastChar == '\n' && Match("--------------\n", p)) {
679 i = Scan ('}', p); q = *p - 16;
680 if(Match("\n--------------}\n", &q)) return PositionDiagram;
681 } else i = Scan('}', p);
682 commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
684 if(commentEnd) SkipWhite(p);
685 if(kifu && **p == '*') { // .kif comment
687 while(**p && **p != '\n') { if(q < yytext + 10*MSG_SIZ-3) *q++ = **p; (*p)++; }
688 parseStart = yytext; *yytext = '{'; strcpy(q, "}\n"); // wrap in braces
691 if(Match("*", p)) result = GameUnfinished;
692 else if(**p == '0') {
693 if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
694 Match("0 - 1", p) || Match("0 / 1", p) || Match("0 : 1", p)) result = BlackWins;
695 } else if(**p == '1') {
696 if( Match("1-0", p) || Match("1/0", p) || Match("1:0", p) ||
697 Match("1 - 0", p) || Match("1 / 0", p) || Match("1 : 0", p)) result = WhiteWins;
698 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) ||
699 Match("1 / 2 : 1 / 2", p) || Match("1/2", p) || Match("1 / 2", p)) result = GameIsDrawn;
702 if(Match(" (", p) && !Scan(')', p) || Match(" {", p) && !Scan('}', p)) { // there is a comment after the PGN result!
703 if(commentEnd) { *p = commentEnd; return Comment; } // so comment before it is normal comment; return that first
705 return result; // this returns a possible preceeding comment as result details
707 if(commentEnd) { *p = commentEnd; return Comment; } // there was no PGN result following, so return as normal comment
710 // ********* Move numbers (after castlings or PGN results!) ***********
711 if((i = Number(p)) != BADNUMBER) { // a single number was read as part of our attempt to read a move
713 if(**p == '.') (*p)++; SkipWhite(p);
714 if(**p == '+' || isalpha(**p) || gameInfo.variant == VariantShogi && *p != numEnd && isdigit(**p)) {
716 return i == 1 ? MoveNumberOne : Nothing;
718 *p = numEnd; return Nothing;
722 // ********* non-compliant game-result indicators *********************
723 if(Match("+-+", p) || Word("stalemate", p)) return GameIsDrawn;
724 if(Match("++", p) || Verb("resign", p) || (Word("check", p) || 1) && Word("mate", p) )
725 return (wom ? BlackWins : WhiteWins);
727 if(Word("w", p) && (Match("hite", p) || 1) || Word("b", p) && (Match("lack", p) || 1) ) {
728 if(**p != ' ') return Nothing;
730 if(Verb("disconnect", p)) return GameUnfinished;
731 if(Verb("resign", p) || Verb("forfeit", p) || Word("mated", p) || Word("lost", p) || Word("loses", p))
732 return (c == 'W' ? BlackWins : WhiteWins);
733 if(Word("mates", p) || Word("wins", p) || Word("won", p))
734 return (c != 'W' ? BlackWins : WhiteWins);
737 if(Word("draw", p)) {
738 if(**p == 'n') (*p)++;
739 if(**p != ' ') return GameIsDrawn;
741 if(Word("agreed", p)) return GameIsDrawn;
742 if(Match("by ", p) && (Word("repetition", p) || Word("agreement", p)) ) return GameIsDrawn;
745 while(**p != '\n') if(*(*p)++ == ')') break;
746 if((*p)[-1] == ')') return GameIsDrawn;
748 *p = oldp - 1; return GameIsDrawn;
752 // ********* Numeric annotation glyph **********************************
753 if(**p == '$') { (*p)++; if(Number(p) != BADNUMBER) return NAG; return Nothing; }
756 // ********** by now we are getting down to the silly stuff ************
757 if(Word("gnu", p) || Match("GNU", p)) {
758 if(**p == ' ') (*p)++;
759 if(Word("chess", p) || Match("CHESS", p)) {
761 if((q = strstr(*p, "game")) || (q = strstr(*p, "GAME")) || (q = strstr(*p, "Game"))) {
762 (*p) = q + 4; return GNUChessGame;
767 if(lastChar == '\n' && (Match("# ", p) || Match("; ", p) || Match("% ", p))) {
768 while(**p != '\n' && **p != ' ') (*p)++;
769 if(**p == ' ' && (Match(" game file", p) || Match(" position file", p))) {
770 while(**p != '\n') (*p)++; // skip to EOLN
773 *p = oldp; // we might need to re-match the skipped stuff
776 if(Match("---", p)) { while(**p == '-') (*p)++; return Nothing; } // prevent separators parsing as null move
777 if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
778 strncpy(currentMoveString, "@@@@", 5);
779 return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
782 // ********* Efficient skipping of (mostly) alphabetic chatter **********
783 while(isdigit(**p) || isalpha(**p) || **p == '-') (*p)++;
786 while(isdigit(**p) || isalpha(**p) || **p == '-' || **p == '\'') (*p)++;
787 return Nothing; // random word
789 if(lastChar == '\n' && Match(": ", p)) { // mail header, skip indented lines
791 while(**p != '\n') (*p)++;
792 if(!ReadLine()) return Nothing; // append next line if not EOF
793 } while(Match("\n ", p) || Match("\n\t", p));
798 // ********* Prevent 00 in unprotected time stamps to be mistaken for castling *******
799 if(Match(":00", p)) return Nothing;
801 // ********* Could not match to anything. Return offending character ****
807 Return offset of next pattern in the current file.
812 return ftell(inputFile) - (inPtr - parsePtr); // subtract what is read but not yet parsed
817 { // prepare parse buffer for reading file
819 inPtr = parsePtr = inputBuf;
822 *inPtr = NULLCHAR; // make sure we will start by reading a line
826 yynewstr P((char *s))
835 { // this replaces the flex-generated parser
836 int result = NextUnit(&parsePtr);
837 char *p = parseStart, *q = yytext;
838 if(p == yytext) return result; // kludge to allow kanji expansion
839 while(p < parsePtr) *q++ = *p++; // copy the matched text to yytext[]
847 { // [HGM] wrapper for yylex, which treats nesting of parentheses
848 int symbol, nestingLevel = 0, i=0;
850 static char buf[256*MSG_SIZ];
852 do { // eat away anything not at level 0
854 if(symbol == Open) nestingLevel++;
855 if(nestingLevel) { // save all parsed text between (and including) the ()
856 for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
859 if(symbol == 0) break; // ran into EOF
860 if(symbol == Close) symbol = Comment, nestingLevel--;
861 } while(nestingLevel || symbol == Nothing);
862 yy_text = buf[0] ? buf : (char*)yytext;
867 yylexstr (int boardIndex, char *s, char *buf, int buflen)
870 char *savPP = parsePtr;
872 yyboardindex = boardIndex;
874 ret = (ChessMove) Myylex();
875 strncpy(buf, yy_text, buflen-1);
876 buf[buflen-1] = NULLCHAR;