4 * Copyright 2011, 2012, 2013, 2014 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. */
34 extern Board boards[MAX_MOVES];
35 extern int PosFlags(int nr);
37 int yyskipmoves = FALSE;
38 char currentMoveString[4096]; // a bit ridiculous size?
41 #define PARSEBUFSIZE 10000
43 static FILE *inputFile;
44 static char *inPtr, *parsePtr, *parseStart;
45 static char inputBuf[PARSEBUFSIZE];
46 static char yytext[PARSEBUFSIZE];
47 static char fromString = 0, lastChar = '\n';
52 #define BADNUMBER (-2000000000)
60 unsigned char kanjiTab[] = {
61 '1', 0357, 0274, 0221, // kanji notation for arabic digits
62 '2', 0357, 0274, 0222,
63 '3', 0357, 0274, 0223,
64 '4', 0357, 0274, 0224,
65 '5', 0357, 0274, 0225,
66 '6', 0357, 0274, 0226,
67 '7', 0357, 0274, 0227,
68 '8', 0357, 0274, 0230,
69 '9', 0357, 0274, 0231,
70 'x', 0345, 0220, 0214,
71 's', 0345, 0205, 0210, // sente
72 'g', 0345, 0276, 0214, // gote
73 '-', 0346, 0212, 0225, // resign
75 'a', 0344, 0270, 0200, // in reality these are numbers in Japanese a=1, b=2 etc.
76 'b', 0344, 0272, 0214,
77 'c', 0344, 0270, 0211,
78 'd', 0345, 0233, 0233,
79 'e', 0344, 0272, 0224,
80 'f', 0345, 0205, 0255,
81 'g', 0344, 0270, 0203,
82 'h', 0345, 0205, 0253,
83 'i', 0344, 0271, 0235,
84 ' ', 0343, 0200, 0200,
86 'K', 0347, 0216, 0211, // piece names
87 'K', 0347, 0216, 0213,
88 'G', 0351, 0207, 0221,
89 'S', 0351, 0212, 0200,
90 'R', 0351, 0243, 0233,
91 'B', 0350, 0247, 0222,
92 'N', 0346, 0241, 0202,
93 'L', 0351, 0246, 0231,
94 'P', 0346, 0255, 0251,
95 'r', 0351, 0276, 0215,
96 'b', 0351, 0246, 0254,
97 'p', 0343, 0201, 0250,
98 'r', 0347, 0253, 0234,
99 '+', 0346, 0210, 0220,
102 '+', 0346, 0210, 0220, // helper
103 '@', 0346, 0211, 0223,
104 'p', 0346, 0211, 0213, // player
105 ':', 0357, 0274, 0232,
106 '-', 0344, 0272, 0206,
107 'f', 0344, 0270, 0212,
108 's', 0345, 0257, 0204,
109 'b', 0345, 0274, 0225,
110 'r', 0345, 0267, 0246,
111 'l', 0345, 0217, 0263,
112 'v', 0347, 0233, 0264,
158 // 'p', 0214, 0343, 0,
172 int NextUnit P((char **p));
177 GetKanji (char **p, int start)
179 unsigned char *q = *(unsigned char **) p;
182 if((*q & 0x80) == 0) return 0; // plain ASCII, refuse to parse
183 fprintf(debugFP, "kanji %03o %03o\n", *q, q[1]);
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 piece = *(*p)++; // Note we could test for 2-byte non-ascii names here
423 if(**p == '/') slash = *(*p)++;
426 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
427 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
429 if(n == 2 && type[0] == type[1]) { // if two identical types, the opposite type in between must have been missing
430 type[2] = type[1]; coord[2] = coord[1];
431 type[1] = NOTHING; coord[1] = -1; n++;
434 // we always get here, and might have read a +, a piece, and upto 4 potential coordinates
435 if(n <= 2) { // could be from-square or disambiguator, when -:xX follow, or drop with @ directly after piece, but also to-square
436 if(**p == '-' || **p == ':' || **p == 'x' || **p == 'X' || // these cannot be move suffix, so to-square must follow
437 (**p == '@' || **p == '*') && n == 0 && !promoted && piece) { // P@ must also be followed by to-square
439 if(n == 1) coord[1] = coord[0]; // must be disambiguator, but we do not know which yet
441 while(n < 4) { // attempt to read to-square
442 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
443 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
446 } else if((**p == '+' || **p == '=') && n == 1 && piece && type[0] == NUMERIC) { // can be traditional Xiangqi notation
449 if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
450 } else if(n == 2) { // only one square mentioned, must be to-square
451 while(n < 4) { coord[n] = coord[n-2], type[n] = type[n-2], coord[n-2] = -1, type[n-2] = NOTHING; n++; }
453 } else if(n == 3 && type[1] != NOTHING) { // must be hyphenless disambiguator + to-square
454 for(i=3; i>0; i--) coord[i] = coord[i-1], type[i] = type[i-1]; // move to-square to where it belongs
455 type[1] = NOTHING; // disambiguator goes in first two positions
458 // we always get here; move must be completely read now, with to-square coord(s) at end
459 if(n == 3) { // incomplete to-square. Could be Xiangqi traditional, or stuff like fxg
460 if(piece && type[1] == NOTHING && type[0] == NUMERIC && type[2] == NUMERIC &&
461 (separator == '+' || separator == '=' || separator == '-')) {
462 // Xiangqi traditional
464 return ImpossibleMove; // for now treat as invalid
466 // fxg stuff, but also things like 0-0, 0-1 and 1-0
467 if(!piece && type[1] == NOTHING && type[0] == ALPHABETIC && type[2] == ALPHABETIC
468 && (coord[0] != 14 || coord[2] != 14) /* reserve oo for castling! */ ) {
469 piece = 'P'; n = 4; // kludge alert: fake full to-square
471 } else if(n == 1 && type[0] == NUMERIC && coord[0] > 1) { while(**p == '.') (*p)++; return Nothing; } // fast exit for move numbers
472 if(n == 4 && type[2] != type[3] && // we have a valid to-square (kludge: type[3] can be NOTHING on fxg type move)
473 (piece || !promoted) && // promoted indicator only valid on named piece type
474 (type[2] == ALPHABETIC || IS_SHOGI(gameInfo.variant))) { // in Shogi also allow alphabetic rank
475 DisambiguateClosure cl;
476 int fromX, fromY, toX, toY;
478 if(slash && (!piece || type[1] == NOTHING)) goto badMove; // slash after piece only in ICS long format
479 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
481 if(type[2] == NUMERIC) { // alpha-rank
482 coord[2] = BOARD_RGHT - BOARD_LEFT - coord[2];
483 coord[3] = BOARD_HEIGHT - coord[3];
484 if(coord[0] >= 0) coord[0] = BOARD_RGHT - BOARD_LEFT - coord[0];
485 if(coord[1] >= 0) coord[1] = BOARD_HEIGHT - coord[1];
487 toX = cl.ftIn = (currentMoveString[2] = coord[2] + 'a') - AAA;
488 toY = cl.rtIn = (currentMoveString[3] = coord[3] + '0') - ONE;
489 if(type[3] == NOTHING) cl.rtIn = -1; // for fxg type moves ask for toY disambiguation
490 else if(toY >= BOARD_HEIGHT || toY < 0) return ImpossibleMove; // vert off-board to-square
491 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return ImpossibleMove;
493 cl.pieceIn = CharToPiece(wom ? piece : ToLower(piece));
494 if(cl.pieceIn == EmptySquare) return ImpossibleMove; // non-existent piece
495 if(promoted) cl.pieceIn = (ChessSquare) (CHUPROMOTED cl.pieceIn);
496 } else cl.pieceIn = EmptySquare;
497 if(separator == '@' || separator == '*') { // drop move. We only get here without from-square or promoted piece
498 fromY = DROP_RANK; fromX = cl.pieceIn;
499 currentMoveString[0] = piece;
500 currentMoveString[1] = '@';
501 currentMoveString[4] = NULLCHAR;
502 return LegalityTest(boards[yyboardindex], PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, fromY, fromX, toY, toX, NULLCHAR);
504 if(type[1] == NOTHING && type[0] != NOTHING) { // there is a disambiguator
505 if(type[0] != type[2]) coord[0] = -1, type[1] = type[0], type[0] = NOTHING; // it was a rank-disambiguator
507 if( type[1] != type[2] && // means fromY is of opposite type as ToX, or NOTHING
508 (type[0] == NOTHING || type[0] == type[2]) ) { // well formed
510 fromX = (currentMoveString[0] = coord[0] + 'a') - AAA;
511 fromY = (currentMoveString[1] = coord[1] + '0') - ONE;
512 currentMoveString[4] = cl.promoCharIn = PromoSuffix(p);
513 currentMoveString[5] = NULLCHAR;
514 if(!cl.promoCharIn && (**p == '-' || **p == 'x')) { // Lion-type multi-leg move
515 currentMoveString[5] = (killX = toX) + AAA; // what we thought was to-square is in fact kill-square
516 currentMoveString[6] = (killY = toY) + ONE; // append it as suffix behind long algebraic move
517 currentMoveString[4] = ';';
518 currentMoveString[7] = NULLCHAR;
519 // read new to-square (VERY non-robust! Assumes correct (non-alpha-rank) syntax, and messes up on errors)
520 toX = cl.ftIn = (currentMoveString[2] = *++*p) - AAA; ++*p;
521 toY = cl.rtIn = (currentMoveString[3] = Number(p) + '0') - ONE;
523 if(type[0] != NOTHING && type[1] != NOTHING && type[3] != NOTHING) { // fully specified.
524 ChessSquare realPiece = boards[yyboardindex][fromY][fromX];
525 // Note that Disambiguate does not work for illegal moves, but flags them as impossible
526 if(piece) { // check if correct piece indicated
527 if(PieceToChar(realPiece) == '~') realPiece = (ChessSquare) (DEMOTED realPiece);
528 if(!(appData.icsActive && PieceToChar(realPiece) == '+') && // trust ICS if it moves promoted pieces
529 piece && realPiece != cl.pieceIn) return ImpossibleMove;
530 } else if(!separator && **p == '+') { // could be a protocol move, where bare '+' suffix means shogi-style promotion
531 if(realPiece < (wom ? WhiteCannon : BlackCannon) && PieceToChar(PROMOTED realPiece) == '+') // seems to be that
532 currentMoveString[4] = cl.promoCharIn = *(*p)++; // append promochar after all
534 result = LegalityTest(boards[yyboardindex], PosFlags(yyboardindex), fromY, fromX, toY, toX, cl.promoCharIn);
535 if (currentMoveString[4] == NULLCHAR) { // suppy missing mandatory promotion character
536 if(result == WhitePromotion || result == BlackPromotion) {
537 switch(gameInfo.variant) {
539 case VariantShatranj: currentMoveString[4] = PieceToChar(BlackFerz); break;
540 case VariantGreat: currentMoveString[4] = PieceToChar(BlackMan); break;
541 case VariantShogi: currentMoveString[4] = '+'; break;
542 default: currentMoveString[4] = PieceToChar(BlackQueen);
544 } else if(result == WhiteNonPromotion || result == BlackNonPromotion) {
545 currentMoveString[4] = '=';
547 } else if(appData.testLegality && gameInfo.variant != VariantSChess && // strip off unnecessary and false promo characters
548 !(result == WhitePromotion || result == BlackPromotion ||
549 result == WhiteNonPromotion || result == BlackNonPromotion)) currentMoveString[4] = NULLCHAR;
551 } else if(cl.pieceIn == EmptySquare) cl.pieceIn = wom ? WhitePawn : BlackPawn;
552 cl.ffIn = type[0] == NOTHING ? -1 : coord[0] + 'a' - AAA;
553 cl.rfIn = type[1] == NOTHING ? -1 : coord[1] + '0' - ONE;
555 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
557 if(cl.kind == ImpossibleMove && !piece && type[1] == NOTHING // fxg5 type
558 && toY == (wom ? 4 : 3)) { // could be improperly written e.p.
559 cl.rtIn += wom ? 1 : -1; // shift target square to e.p. square
560 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
561 if((cl.kind != WhiteCapturesEnPassant && cl.kind != BlackCapturesEnPassant))
562 return ImpossibleMove; // nice try, but no cigar
565 currentMoveString[0] = cl.ff + AAA;
566 currentMoveString[1] = cl.rf + ONE;
567 currentMoveString[3] = cl.rt + ONE;
568 if(killX < 0) // [HGM] lion: do not overwrite kill-square suffix
569 currentMoveString[4] = cl.promoChar;
571 if((cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) && (Match("ep", p) || Match("e.p.", p)));
573 return (int) cl.kind;
576 badMove:// we failed to find algebraic move
580 // Next we do some common symbols where the first character commits us to things that cannot possibly be a move
582 // ********* PGN tags ******************************************
584 oldp = ++(*p); kifu = 0;
585 if(Match("--", p)) { // "[--" could be start of position diagram
586 if(!Scan(']', p) && (*p)[-3] == '-' && (*p)[-2] == '-') return PositionDiagram;
590 if(isdigit(**p) || isalpha(**p)) {
591 do (*p)++; while(isdigit(**p) || isalpha(**p) || **p == '+' ||
592 **p == '-' || **p == '=' || **p == '_' || **p == '#');
596 while(**p != '\n' && (*(*p)++ != '"'|| (*p)[-2] == '\\')); // look for unescaped quote
597 if((*p)[-1] !='"') { *p = oldp; Scan(']', p); return Comment; } // string closing delimiter missing
598 SkipWhite(p); if(*(*p)++ == ']') return PGNTag;
601 Scan(']', p); return Comment;
604 // ********* SAN Castings *************************************
605 if(**p == 'O' || **p == 'o' || **p == '0' && !Match("00:", p)) { // exclude 00 in time stamps
606 int castlingType = 0;
607 if(Match("O-O-O", p) || Match("o-o-o", p) || Match("0-0-0", p) ||
608 Match("OOO", p) || Match("ooo", p) || Match("000", p)) castlingType = 2;
609 else if(Match("O-O", p) || Match("o-o", p) || Match("0-0", p) ||
610 Match("OO", p) || Match("oo", p) || Match("00", p)) castlingType = 1;
611 if(castlingType) { //code from old parser, collapsed for both castling types, and streamlined a bit
612 int rf, ff, rt, ft; ChessSquare king;
615 if(gameInfo.variant == VariantSChess) promo = PromoSuffix(p);
617 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
628 ff = (BOARD_WIDTH-1)>>1; // this would be d-file
629 if (boards[yyboardindex][rf][ff] == king) {
630 /* ICS wild castling */
631 ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
633 ff = BOARD_WIDTH>>1; // e-file
634 ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
636 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
638 ff = initialRights[2];
639 ft = initialRights[castlingType-1];
641 ff = initialRights[5];
642 ft = initialRights[castlingType+2];
644 if (appData.debugMode) fprintf(debugFP, "Parser FRC (type=%d) %d %d\n", castlingType, ff, ft);
645 if(ff == NoRights || ft == NoRights) return ImpossibleMove;
647 sprintf(currentMoveString, "%c%c%c%c%c",ff+AAA,rf+ONE,ft+AAA,rt+ONE,promo);
648 if (appData.debugMode) fprintf(debugFP, "(%d-type) castling %d %d\n", castlingType, ff, ft);
650 return (int) LegalityTest(boards[yyboardindex],
651 PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
652 rf, ff, rt, ft, promo);
653 } else if(Match("01", p)) return Nothing; // prevent this from being mistaken for move number 1
657 // ********* variations (nesting) ******************************
659 if(RdTime(')', p)) return ElapsedTime;
662 if(**p ==')') { (*p)++; return Close; }
663 if(**p == ';') { while(**p != '\n') (*p)++; return Comment; }
666 // ********* Comments and result messages **********************
667 *p = oldp; commentEnd = NULL; result = 0;
669 if(RdTime('}', p)) return ElapsedTime;
670 if(lastChar == '\n' && Match("--------------\n", p)) {
672 i = Scan ('}', p); q = *p - 16;
673 if(Match("\n--------------}\n", &q)) return PositionDiagram;
674 } else i = Scan('}', p);
675 commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
677 if(commentEnd) SkipWhite(p);
678 if(lastChar == '\n' && kifu && **p == '*') { while(**p && **p != '\n') (*p)++; return Comment; } // .kif comment
679 if(Match("*", p)) result = GameUnfinished;
680 else if(**p == '0') {
681 if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
682 Match("0 - 1", p) || Match("0 / 1", p) || Match("0 : 1", p)) result = BlackWins;
683 } else if(**p == '1') {
684 if( Match("1-0", p) || Match("1/0", p) || Match("1:0", p) ||
685 Match("1 - 0", p) || Match("1 / 0", p) || Match("1 : 0", p)) result = WhiteWins;
686 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) ||
687 Match("1 / 2 : 1 / 2", p) || Match("1/2", p) || Match("1 / 2", p)) result = GameIsDrawn;
690 if(Match(" (", p) && !Scan(')', p) || Match(" {", p) && !Scan('}', p)) { // there is a comment after the PGN result!
691 if(commentEnd) { *p = commentEnd; return Comment; } // so comment before it is normal comment; return that first
693 return result; // this returns a possible preceeding comment as result details
695 if(commentEnd) { *p = commentEnd; return Comment; } // there was no PGN result following, so return as normal comment
698 // ********* Move numbers (after castlings or PGN results!) ***********
699 if((i = Number(p)) != BADNUMBER) { // a single number was read as part of our attempt to read a move
701 if(**p == '.') (*p)++; SkipWhite(p);
702 if(**p == '+' || isalpha(**p) || gameInfo.variant == VariantShogi && *p != numEnd && isdigit(**p)) {
704 return i == 1 ? MoveNumberOne : Nothing;
706 *p = numEnd; return Nothing;
710 // ********* non-compliant game-result indicators *********************
711 if(Match("+-+", p) || Word("stalemate", p)) return GameIsDrawn;
712 if(Match("++", p) || Verb("resign", p) || (Word("check", p) || 1) && Word("mate", p) )
713 return (wom ? BlackWins : WhiteWins);
715 if(Word("w", p) && (Match("hite", p) || 1) || Word("b", p) && (Match("lack", p) || 1) ) {
716 if(**p != ' ') return Nothing;
718 if(Verb("disconnect", p)) return GameUnfinished;
719 if(Verb("resign", p) || Verb("forfeit", p) || Word("mated", p) || Word("lost", p) || Word("loses", p))
720 return (c == 'W' ? BlackWins : WhiteWins);
721 if(Word("mates", p) || Word("wins", p) || Word("won", p))
722 return (c != 'W' ? BlackWins : WhiteWins);
725 if(Word("draw", p)) {
726 if(**p == 'n') (*p)++;
727 if(**p != ' ') return GameIsDrawn;
729 if(Word("agreed", p)) return GameIsDrawn;
730 if(Match("by ", p) && (Word("repetition", p) || Word("agreement", p)) ) return GameIsDrawn;
733 while(**p != '\n') if(*(*p)++ == ')') break;
734 if((*p)[-1] == ')') return GameIsDrawn;
736 *p = oldp - 1; return GameIsDrawn;
740 // ********* Numeric annotation glyph **********************************
741 if(**p == '$') { (*p)++; if(Number(p) != BADNUMBER) return NAG; return Nothing; }
744 // ********** by now we are getting down to the silly stuff ************
745 if(Word("gnu", p) || Match("GNU", p)) {
746 if(**p == ' ') (*p)++;
747 if(Word("chess", p) || Match("CHESS", p)) {
749 if((q = strstr(*p, "game")) || (q = strstr(*p, "GAME")) || (q = strstr(*p, "Game"))) {
750 (*p) = q + 4; return GNUChessGame;
755 if(lastChar == '\n' && (Match("# ", p) || Match("; ", p) || Match("% ", p))) {
756 while(**p != '\n' && **p != ' ') (*p)++;
757 if(**p == ' ' && (Match(" game file", p) || Match(" position file", p))) {
758 while(**p != '\n') (*p)++; // skip to EOLN
761 *p = oldp; // we might need to re-match the skipped stuff
764 if(Match("---", p)) { while(**p == '-') (*p)++; return Nothing; } // prevent separators parsing as null move
765 if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
766 strncpy(currentMoveString, "@@@@", 5);
767 return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
770 // ********* Efficient skipping of (mostly) alphabetic chatter **********
771 while(isdigit(**p) || isalpha(**p) || **p == '-') (*p)++;
774 while(isdigit(**p) || isalpha(**p) || **p == '-' || **p == '\'') (*p)++;
775 return Nothing; // random word
777 if(lastChar == '\n' && Match(": ", p)) { // mail header, skip indented lines
779 while(**p != '\n') (*p)++;
780 if(!ReadLine()) return Nothing; // append next line if not EOF
781 } while(Match("\n ", p) || Match("\n\t", p));
786 // ********* Prevent 00 in unprotected time stamps to be mistaken for castling *******
787 if(Match(":00", p)) return Nothing;
789 // ********* Could not match to anything. Return offending character ****
795 Return offset of next pattern in the current file.
800 return ftell(inputFile) - (inPtr - parsePtr); // subtract what is read but not yet parsed
805 { // prepare parse buffer for reading file
807 inPtr = parsePtr = inputBuf;
810 *inPtr = NULLCHAR; // make sure we will start by reading a line
814 yynewstr P((char *s))
823 { // this replaces the flex-generated parser
824 int result = NextUnit(&parsePtr);
825 char *p = parseStart, *q = yytext;
826 if(p == yytext) return result; // kludge to allow kanji expansion
827 while(p < parsePtr) *q++ = *p++; // copy the matched text to yytext[]
835 { // [HGM] wrapper for yylex, which treats nesting of parentheses
836 int symbol, nestingLevel = 0, i=0;
838 static char buf[256*MSG_SIZ];
840 do { // eat away anything not at level 0
842 if(symbol == Open) nestingLevel++;
843 if(nestingLevel) { // save all parsed text between (and including) the ()
844 for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
847 if(symbol == 0) break; // ran into EOF
848 if(symbol == Close) symbol = Comment, nestingLevel--;
849 } while(nestingLevel || symbol == Nothing);
850 yy_text = buf[0] ? buf : (char*)yytext;
855 yylexstr (int boardIndex, char *s, char *buf, int buflen)
858 char *savPP = parsePtr;
860 yyboardindex = boardIndex;
862 ret = (ChessMove) Myylex();
863 strncpy(buf, yy_text, buflen-1);
864 buf[buflen-1] = NULLCHAR;