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. */
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 if((**p & 0xC0) == 0x80) { // this is an illegal starting code in utf-8, so assume shift-JIS
184 for(i=start+JIS; kanjiTab[i]; i+=4) {
185 if(q[0] == kanjiTab[i+1] && q[1] == kanjiTab[i+2]) {
186 (*p) += 2; kifu = 0x80;
190 (*p) += (kifu ? 2 : 1); // assume this is an unrecognized kanji when reading kif files
194 for(i=start; kanjiTab[i]; i+=4) {
195 if(q[0] == kanjiTab[i+1] && q[1] == kanjiTab[i+2] && q[2] == kanjiTab[i+3]) {
196 (*p) += 3; kifu = 0x80;
201 if((q[0] & 0xE0) == 0xC0 && (q[1] & 0xC0) == 0x80) (*p) += 2; else // for now skip unrecognized utf-8 characters
202 if((q[0] & 0xF0) == 0xE0 && (q[1] & 0xC0) == 0x80 && (q[2] & 0xC0) == 0x80) (*p) += 3; else
203 if((q[0] & 0xF8) == 0xF0 && (q[1] & 0xC0) == 0x80 && (q[2] & 0xC0) == 0x80 && (q[3] & 0xC0) == 0x80) (*p) += 4;
204 else if(**p & 0x80) return -1; // not valid utf-8
206 return 0; // unrecognized but valid kanji (skipped), or plain ASCII
212 static char buf[MSG_SIZ];
213 char *ptr = buf+3, *q, k;
214 int wom = quickFlag ? quickFlag&1 : WhiteOnMove(yyboardindex);
215 k = GetKanji(p, XCO);
216 if(k < 0) { (*p)++; return Nothing; } // must try shift-JIS here
217 if(k >= '1' && k <= '9') {
218 buf[0] = k; buf[1] = GetKanji(p, YCO); // to-square coords
219 } else if(k == 'x') {
220 if(GetKanji(p, YCO) != ' ') (*p) -= 3; // skip spacer kanji after recapture
221 } else if((k == 's' || k == 'g') && GetKanji(p, MISC) == 'p' && GetKanji(p, MISC) == ':') { // player name
222 snprintf(yytext, MSG_SIZ, "[%s \"", k == 's' ? "White" : "Black"); // construct PGN tag
223 for(q=yytext+8; **p && **p != '\n' && **p != '\r' && q < yytext + MSG_SIZ; ) *q++ = *(*p)++;
224 strcpy(q, "\"]\n"); parseStart = yytext; lastChar = '\n';
226 } else if(k == '-' && GetKanji(p, MISC) == '-') { // resign
230 res = BlackWins, strcpy(yytext, "{sente resigns} 0-1");
231 else res = WhiteWins, strcpy(yytext, "{gote resigns} 1-0");
234 while(**p && **p != '\n') (*p)++; // unrecognized Japanese kanji: skip to end of line
237 buf[3] = GetKanji(p, PIECE); // piece ID
238 if(buf[3] == '+') buf[2] = '+', buf[3] = GetKanji(p, PIECE); // +N, +L, +S
239 k = GetKanji(p, MISC);
240 if(k == '@') { // drop move
241 buf[4] = '@', buf[5] = buf[0], buf[6] = buf[1]; buf[7] = NULLCHAR;
242 if(appData.debugMode) fprintf(debugFP, "kifu drop %s\n", ptr);
243 return NextUnit(&ptr);
247 do { // read disambiguation (and promotion) kanji
249 case '+': kifu |= 1; break;
250 case 'f': kifu |= 2; break;
251 case 'b': kifu |= 4; break;
252 case 's': kifu |= 8; break;
253 case 'l': kifu |= 0x10; break;
254 case 'r': kifu |= 0x20; break;
255 case 'v': kifu |= 0x40; break;
257 } while(k = GetKanji(p, MISC));
259 if(**p == '(' && (*p)[3] == ')') { // kif disambiguation
260 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;
261 (*p) += 4; ptr++; // strip off piece name if we know full from-square
262 if(appData.debugMode) fprintf(debugFP, "kifu move %s\n", ptr);
263 return NextUnit(&ptr);
266 if(islower(buf[3])) // kludge: kanji for promoted types translate as lower case
267 buf[3] += 'A' - 'a', buf[2] = '+', ptr--; // so prefix with '+'
268 if(kifu * ~1) { // disambiguation was given, and thus is probably needed
269 if(buf[3] != 'B' && buf[3] != 'R') { // stepper, so distance must be <= 1 (N or L never need vertical disambiguation!)
270 if(kifu & 0x10) *q++ = buf[0] - (wom ? -1 : 1); // translate left/right/straight to PSN file disambiguators
271 if(kifu & 0x20) *q++ = buf[0] + (wom ? -1 : 1);
272 if(kifu & 0x40) *q++ = buf[0], kifu |= 2; // kludge: 'straight' only needs disambiguation if forward!
273 if(kifu & 2) *q++ = buf[1] + (wom ? -1 : 1); // translate forward/backward/sideway to PSN rank disambiguators
274 if(kifu & 4) *q++ = buf[1] - (wom ? -1 : 1);
275 if(kifu & 8) *q++ = buf[1];
276 } // 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'
278 *q++ = buf[0]; *q++ = buf[1]; *q++ = (kifu & 1)*'+'; *q = NULLCHAR;
279 if(appData.debugMode) fprintf(debugFP, "kif2 move %s\n", ptr);
280 return NextUnit(&ptr);
286 { // Read one line from the input file, and append to the buffer
287 char c, *start = inPtr;
288 if(fromString) return 0; // parsing string, so the end is a hard end
289 if(!inputFile) return 0;
290 while((c = fgetc(inputFile)) != EOF) {
292 if(c == '\n') { *inPtr = NULLCHAR; return 1; }
293 if(inPtr - inputBuf > PARSEBUFSIZE-2) inPtr--; //prevent crash on overflow
295 if(inPtr == start) return 0;
296 *inPtr++ = '\n', *inPtr = NULLCHAR; // repair missing linefeed at EOF
301 Scan (char c, char **p)
302 { // line-spanning skip to mentioned character or EOF
304 while(**p) if(*(*p)++ == c) return 0;
306 // no closing bracket; force match for entire rest of file.
312 { // skip spaces tabs and newlines; return 1 if anything was skipped
315 while(**p == ' ' || **p == '\t' || **p == '\n' || **p == '\r') (*p)++;
316 } while(**p == NULLCHAR && ReadLine()); // continue as long as ReadLine reads something
321 Match (char *pattern, char **ptr)
323 char *p = pattern, *s = *ptr;
324 while(*p && (*p == *s++ || s[-1] == '\r' && *p--)) p++;
329 return 0; // no match, no ptr update
333 Word (char *pattern, char **p)
335 if(Match(pattern, p)) return 1;
336 if(*pattern >= 'a' && *pattern <= 'z' && *pattern - **p == 'a' - 'A') { // capitalized
338 if(Match(pattern + 1, p)) return 1;
345 Verb (char *pattern, char **p)
347 int res = Word(pattern, p);
348 if(res && !Match("s", p)) Match("ed", p); // eat conjugation suffix, if any
356 if(**p < '0' || **p > '9') return BADNUMBER;
357 while(**p >= '0' && **p <= '9') {
358 val = 10*val + *(*p)++ - '0';
364 RdTime (char c, char **p)
366 char *start = ++(*p), *sec; // increment *p, as it was pointing to the opening ( or {
367 if(Number(p) == BADNUMBER) return 0;
369 if(Match(":", p) && Number(p) != BADNUMBER && *p - sec == 3) { // well formed
371 if(Match(".", p) && Number(p) != BADNUMBER && *(*p)++ == c) return 1; // well-formed fraction
373 if(*(*p)++ == c) return 1; // matching bracket without fraction
375 *p = start; // failure
380 PromoSuffix (char **p)
383 if(**p == 'e' && (Match("ep", p) || Match("e.p.", p))) { *p = start; return NULLCHAR; } // non-compliant e.p. suffix is no promoChar!
384 if(**p == '+' && IS_SHOGI(gameInfo.variant)) { (*p)++; return '+'; }
385 if(**p == '=' || (gameInfo.variant == VariantSChess) && **p == '/') (*p)++; // optional = (or / for Seirawan gating)
386 if(**p == '(' && (*p)[2] == ')' && isalpha( (*p)[1] )) { (*p) += 3; return ToLower((*p)[-2]); }
387 if(isalpha(**p) && **p != 'x') return ToLower(*(*p)++); // reserve 'x' for multi-leg captures?
388 if(*p != start) return **p == '+' ? *(*p)++ : '='; // must be the optional = (or =+)
389 return NULLCHAR; // no suffix detected
394 { // Main parser routine
395 int coord[4], n, result, piece, i;
396 char type[4], promoted, separator, slash, *oldp, *commentEnd, c;
397 int wom = quickFlag ? quickFlag&1 : WhiteOnMove(yyboardindex);
399 // ********* try white first, because it is so common **************************
400 if(**p == ' ' || **p == '\n' || **p == '\t') { parseStart = (*p)++; return Nothing; }
403 if(**p == NULLCHAR) { // make sure there is something to parse
404 if(fromString) return 0; // we are parsing string, so the end is really the end
405 *p = inPtr = inputBuf;
406 if(!ReadLine()) return 0; // EOF
407 } else if(inPtr > inputBuf + PARSEBUFSIZE/2) { // buffer fills up with already parsed stuff
408 char *q = *p, *r = inputBuf;
410 *p = inputBuf; inPtr = r - 1;
412 parseStart = oldp = *p; // remember where we begin
414 // ********* attempt to recognize a SAN move in the leading non-blank text *****
415 piece = separator = promoted = slash = n = 0;
416 for(i=0; i<4; i++) coord[i] = -1, type[i] = NOTHING;
417 if(**p & 0x80) return KifuMove(p); // non-ascii. Could be some kanj notation for Shogi or Xiangqi
418 if(**p == '+') (*p)++, promoted++;
419 if(**p >= 'a' && **p <= 'z' && (*p)[1]== '@') piece =*(*p)++ + 'A' - 'a'; else
420 if(**p >= 'A' && **p <= 'Z') {
421 static char s[] = SUFFIXES;
423 piece = *(*p)++; // Note we could test for 2-byte non-ascii names here
424 if(q = strchr(s, **p)) (*p)++, piece += 64*(q - s + 1);
425 if(**p == '/') slash = *(*p)++;
428 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
429 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
431 if(n == 2 && type[0] == type[1]) { // if two identical types, the opposite type in between must have been missing
432 type[2] = type[1]; coord[2] = coord[1];
433 type[1] = NOTHING; coord[1] = -1; n++;
436 // we always get here, and might have read a +, a piece, and upto 4 potential coordinates
437 if(n <= 2) { // could be from-square or disambiguator, when -:xX follow, or drop with @ directly after piece, but also to-square
438 if(**p == '-' || **p == ':' || **p == 'x' || **p == 'X' || // these cannot be move suffix, so to-square must follow
439 (**p == '@' || **p == '*') && n == 0 && !promoted && piece) { // P@ must also be followed by to-square
441 if(n == 1) coord[1] = coord[0]; // must be disambiguator, but we do not know which yet
443 while(n < 4) { // attempt to read to-square
444 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
445 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
448 } else if((**p == '+' || **p == '=') && n == 1 && piece && type[0] == NUMERIC) { // can be traditional Xiangqi notation
451 if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
452 } else if(n == 2) { // only one square mentioned, must be to-square
453 while(n < 4) { coord[n] = coord[n-2], type[n] = type[n-2], coord[n-2] = -1, type[n-2] = NOTHING; n++; }
455 } else if(n == 3 && type[1] != NOTHING) { // must be hyphenless disambiguator + to-square
456 for(i=3; i>0; i--) coord[i] = coord[i-1], type[i] = type[i-1]; // move to-square to where it belongs
457 type[1] = NOTHING; // disambiguator goes in first two positions
460 // we always get here; move must be completely read now, with to-square coord(s) at end
461 if(n == 3) { // incomplete to-square. Could be Xiangqi traditional, or stuff like fxg
462 if(piece && type[1] == NOTHING && type[0] == NUMERIC && type[2] == NUMERIC &&
463 (separator == '+' || separator == '=' || separator == '-')) {
464 // Xiangqi traditional
466 return ImpossibleMove; // for now treat as invalid
468 // fxg stuff, but also things like 0-0, 0-1 and 1-0
469 if(!piece && type[1] == NOTHING && type[0] == ALPHABETIC && type[2] == ALPHABETIC
470 && (coord[0] != 14 || coord[2] != 14) /* reserve oo for castling! */ ) {
471 piece = 'P'; n = 4; // kludge alert: fake full to-square
473 } else if(n == 1 && type[0] == NUMERIC && coord[0] > 1) { while(**p == '.') (*p)++; return Nothing; } // fast exit for move numbers
474 if(n == 4 && type[2] != type[3] && // we have a valid to-square (kludge: type[3] can be NOTHING on fxg type move)
475 (piece || !promoted) && // promoted indicator only valid on named piece type
476 (type[2] == ALPHABETIC || IS_SHOGI(gameInfo.variant))) { // in Shogi also allow alphabetic rank
477 DisambiguateClosure cl;
478 int fromX, fromY, toX, toY;
480 if(slash && (!piece || type[1] == NOTHING)) goto badMove; // slash after piece only in ICS long format
481 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
483 if(type[2] == NUMERIC) { // alpha-rank
484 coord[2] = BOARD_RGHT - BOARD_LEFT - coord[2];
485 coord[3] = BOARD_HEIGHT - coord[3];
486 if(coord[0] >= 0) coord[0] = BOARD_RGHT - BOARD_LEFT - coord[0];
487 if(coord[1] >= 0) coord[1] = BOARD_HEIGHT - coord[1];
489 toX = cl.ftIn = (currentMoveString[2] = coord[2] + 'a') - AAA;
490 toY = cl.rtIn = (currentMoveString[3] = coord[3] + '0') - ONE;
491 if(type[3] == NOTHING) cl.rtIn = -1; // for fxg type moves ask for toY disambiguation
492 else if(toY >= BOARD_HEIGHT || toY < 0) return ImpossibleMove; // vert off-board to-square
493 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return ImpossibleMove;
495 cl.pieceIn = CharToPiece(wom ? piece : piece + 'a' - 'A');
496 if(cl.pieceIn == EmptySquare) return ImpossibleMove; // non-existent piece
497 if(promoted) cl.pieceIn = (ChessSquare) (CHUPROMOTED cl.pieceIn);
498 } else cl.pieceIn = EmptySquare;
499 if(separator == '@' || separator == '*') { // drop move. We only get here without from-square or promoted piece
500 fromY = DROP_RANK; fromX = cl.pieceIn;
501 currentMoveString[0] = piece;
502 currentMoveString[1] = '@';
503 currentMoveString[4] = NULLCHAR;
504 return LegalityTest(boards[yyboardindex], PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, fromY, fromX, toY, toX, NULLCHAR);
506 if(type[1] == NOTHING && type[0] != NOTHING) { // there is a disambiguator
507 if(type[0] != type[2]) coord[0] = -1, type[1] = type[0], type[0] = NOTHING; // it was a rank-disambiguator
509 if( type[1] != type[2] && // means fromY is of opposite type as ToX, or NOTHING
510 (type[0] == NOTHING || type[0] == type[2]) ) { // well formed
512 fromX = (currentMoveString[0] = coord[0] + 'a') - AAA;
513 fromY = (currentMoveString[1] = coord[1] + '0') - ONE;
514 currentMoveString[4] = cl.promoCharIn = PromoSuffix(p);
515 currentMoveString[5] = NULLCHAR;
516 if(!cl.promoCharIn && (**p == '-' || **p == 'x')) { // Lion-type multi-leg move
517 currentMoveString[5] = (killX = toX) + AAA; // what we thought was to-square is in fact kill-square
518 currentMoveString[6] = (killY = toY) + ONE; // append it as suffix behind long algebraic move
519 currentMoveString[4] = ';';
520 currentMoveString[7] = NULLCHAR;
521 // read new to-square (VERY non-robust! Assumes correct (non-alpha-rank) syntax, and messes up on errors)
522 toX = cl.ftIn = (currentMoveString[2] = *++*p) - AAA; ++*p;
523 toY = cl.rtIn = (currentMoveString[3] = Number(p) + '0') - ONE;
525 if(type[0] != NOTHING && type[1] != NOTHING && type[3] != NOTHING) { // fully specified.
526 ChessSquare realPiece = boards[yyboardindex][fromY][fromX];
527 // Note that Disambiguate does not work for illegal moves, but flags them as impossible
528 if(piece) { // check if correct piece indicated
529 if(PieceToChar(realPiece) == '~') realPiece = (ChessSquare) (DEMOTED realPiece);
530 if(!(appData.icsActive && PieceToChar(realPiece) == '+') && // trust ICS if it moves promoted pieces
531 piece && realPiece != cl.pieceIn) return ImpossibleMove;
532 } else if(!separator && **p == '+') { // could be a protocol move, where bare '+' suffix means shogi-style promotion
533 if(realPiece < (wom ? WhiteCannon : BlackCannon) && PieceToChar(PROMOTED realPiece) == '+') // seems to be that
534 currentMoveString[4] = cl.promoCharIn = *(*p)++; // append promochar after all
536 result = LegalityTest(boards[yyboardindex], PosFlags(yyboardindex), fromY, fromX, toY, toX, cl.promoCharIn);
537 if (currentMoveString[4] == NULLCHAR) { // suppy missing mandatory promotion character
538 if(result == WhitePromotion || result == BlackPromotion) {
539 switch(gameInfo.variant) {
541 case VariantShatranj: currentMoveString[4] = PieceToChar(BlackFerz); break;
542 case VariantGreat: currentMoveString[4] = PieceToChar(BlackMan); break;
543 case VariantShogi: currentMoveString[4] = '+'; break;
544 default: currentMoveString[4] = PieceToChar(BlackQueen);
546 } else if(result == WhiteNonPromotion || result == BlackNonPromotion) {
547 currentMoveString[4] = '=';
549 } else if(appData.testLegality && gameInfo.variant != VariantSChess && // strip off unnecessary and false promo characters
550 !(result == WhitePromotion || result == BlackPromotion ||
551 result == WhiteNonPromotion || result == BlackNonPromotion)) currentMoveString[4] = NULLCHAR;
553 } else if(cl.pieceIn == EmptySquare) cl.pieceIn = wom ? WhitePawn : BlackPawn;
554 cl.ffIn = type[0] == NOTHING ? -1 : coord[0] + 'a' - AAA;
555 cl.rfIn = type[1] == NOTHING ? -1 : coord[1] + '0' - ONE;
557 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
559 if(cl.kind == ImpossibleMove && !piece && type[1] == NOTHING // fxg5 type
560 && toY == (wom ? 4 : 3)) { // could be improperly written e.p.
561 cl.rtIn += wom ? 1 : -1; // shift target square to e.p. square
562 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
563 if((cl.kind != WhiteCapturesEnPassant && cl.kind != BlackCapturesEnPassant))
564 return ImpossibleMove; // nice try, but no cigar
567 currentMoveString[0] = cl.ff + AAA;
568 currentMoveString[1] = cl.rf + ONE;
569 currentMoveString[3] = cl.rt + ONE;
570 if(killX < 0) // [HGM] lion: do not overwrite kill-square suffix
571 currentMoveString[4] = cl.promoChar;
573 if((cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) && (Match("ep", p) || Match("e.p.", p)));
575 return (int) cl.kind;
578 badMove:// we failed to find algebraic move
582 // Next we do some common symbols where the first character commits us to things that cannot possibly be a move
584 // ********* PGN tags ******************************************
586 oldp = ++(*p); kifu = 0;
587 if(Match("--", p)) { // "[--" could be start of position diagram
588 if(!Scan(']', p) && (*p)[-3] == '-' && (*p)[-2] == '-') return PositionDiagram;
592 if(isdigit(**p) || isalpha(**p)) {
593 do (*p)++; while(isdigit(**p) || isalpha(**p) || **p == '+' ||
594 **p == '-' || **p == '=' || **p == '_' || **p == '#');
598 while(**p != '\n' && (*(*p)++ != '"'|| (*p)[-2] == '\\')); // look for unescaped quote
599 if((*p)[-1] !='"') { *p = oldp; Scan(']', p); return Comment; } // string closing delimiter missing
600 SkipWhite(p); if(*(*p)++ == ']') return PGNTag;
603 Scan(']', p); return Comment;
606 // ********* SAN Castings *************************************
607 if(**p == 'O' || **p == 'o' || **p == '0' && !Match("00:", p)) { // exclude 00 in time stamps
608 int castlingType = 0;
609 if(Match("O-O-O", p) || Match("o-o-o", p) || Match("0-0-0", p) ||
610 Match("OOO", p) || Match("ooo", p) || Match("000", p)) castlingType = 2;
611 else if(Match("O-O", p) || Match("o-o", p) || Match("0-0", p) ||
612 Match("OO", p) || Match("oo", p) || Match("00", p)) castlingType = 1;
613 if(castlingType) { //code from old parser, collapsed for both castling types, and streamlined a bit
614 int rf, ff, rt, ft; ChessSquare king;
617 if(gameInfo.variant == VariantSChess) promo = PromoSuffix(p);
619 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
630 ff = (BOARD_WIDTH-1)>>1; // this would be d-file
631 if (boards[yyboardindex][rf][ff] == king) {
632 /* ICS wild castling */
633 ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
635 ff = BOARD_WIDTH>>1; // e-file
636 ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
638 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
640 ff = initialRights[2];
641 ft = initialRights[castlingType-1];
643 ff = initialRights[5];
644 ft = initialRights[castlingType+2];
646 if (appData.debugMode) fprintf(debugFP, "Parser FRC (type=%d) %d %d\n", castlingType, ff, ft);
647 if(ff == NoRights || ft == NoRights) return ImpossibleMove;
649 sprintf(currentMoveString, "%c%c%c%c%c",ff+AAA,rf+ONE,ft+AAA,rt+ONE,promo);
650 if (appData.debugMode) fprintf(debugFP, "(%d-type) castling %d %d\n", castlingType, ff, ft);
652 return (int) LegalityTest(boards[yyboardindex],
653 PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
654 rf, ff, rt, ft, promo);
655 } else if(Match("01", p)) return Nothing; // prevent this from being mistaken for move number 1
659 // ********* variations (nesting) ******************************
661 if(RdTime(')', p)) return ElapsedTime;
664 if(**p ==')') { (*p)++; return Close; }
665 if(**p == ';') { while(**p != '\n') (*p)++; return Comment; }
668 // ********* Comments and result messages **********************
669 *p = oldp; commentEnd = NULL; result = 0;
671 if(RdTime('}', p)) return ElapsedTime;
672 if(lastChar == '\n' && Match("--------------\n", p)) {
674 i = Scan ('}', p); q = *p - 16;
675 if(Match("\n--------------}\n", &q)) return PositionDiagram;
676 } else i = Scan('}', p);
677 commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
679 if(commentEnd) SkipWhite(p);
680 if(kifu && **p == '*') { // .kif comment
682 while(**p && **p != '\n') { if(q < yytext + 10*MSG_SIZ-3) *q++ = **p; (*p)++; }
683 parseStart = yytext; *yytext = '{'; strcpy(q, "}\n"); // wrap in braces
686 if(Match("*", p)) result = GameUnfinished;
687 else if(**p == '0') {
688 if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
689 Match("0 - 1", p) || Match("0 / 1", p) || Match("0 : 1", p)) result = BlackWins;
690 } else if(**p == '1') {
691 if( Match("1-0", p) || Match("1/0", p) || Match("1:0", p) ||
692 Match("1 - 0", p) || Match("1 / 0", p) || Match("1 : 0", p)) result = WhiteWins;
693 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) ||
694 Match("1 / 2 : 1 / 2", p) || Match("1/2", p) || Match("1 / 2", p)) result = GameIsDrawn;
697 if(Match(" (", p) && !Scan(')', p) || Match(" {", p) && !Scan('}', p)) { // there is a comment after the PGN result!
698 if(commentEnd) { *p = commentEnd; return Comment; } // so comment before it is normal comment; return that first
700 return result; // this returns a possible preceeding comment as result details
702 if(commentEnd) { *p = commentEnd; return Comment; } // there was no PGN result following, so return as normal comment
705 // ********* Move numbers (after castlings or PGN results!) ***********
706 if((i = Number(p)) != BADNUMBER) { // a single number was read as part of our attempt to read a move
708 if(**p == '.') (*p)++; SkipWhite(p);
709 if(**p == '+' || isalpha(**p) || gameInfo.variant == VariantShogi && *p != numEnd && isdigit(**p)) {
711 return i == 1 ? MoveNumberOne : Nothing;
713 *p = numEnd; return Nothing;
717 // ********* non-compliant game-result indicators *********************
718 if(Match("+-+", p) || Word("stalemate", p)) return GameIsDrawn;
719 if(Match("++", p) || Verb("resign", p) || (Word("check", p) || 1) && Word("mate", p) )
720 return (wom ? BlackWins : WhiteWins);
722 if(Word("w", p) && (Match("hite", p) || 1) || Word("b", p) && (Match("lack", p) || 1) ) {
723 if(**p != ' ') return Nothing;
725 if(Verb("disconnect", p)) return GameUnfinished;
726 if(Verb("resign", p) || Verb("forfeit", p) || Word("mated", p) || Word("lost", p) || Word("loses", p))
727 return (c == 'W' ? BlackWins : WhiteWins);
728 if(Word("mates", p) || Word("wins", p) || Word("won", p))
729 return (c != 'W' ? BlackWins : WhiteWins);
732 if(Word("draw", p)) {
733 if(**p == 'n') (*p)++;
734 if(**p != ' ') return GameIsDrawn;
736 if(Word("agreed", p)) return GameIsDrawn;
737 if(Match("by ", p) && (Word("repetition", p) || Word("agreement", p)) ) return GameIsDrawn;
740 while(**p != '\n') if(*(*p)++ == ')') break;
741 if((*p)[-1] == ')') return GameIsDrawn;
743 *p = oldp - 1; return GameIsDrawn;
747 // ********* Numeric annotation glyph **********************************
748 if(**p == '$') { (*p)++; if(Number(p) != BADNUMBER) return NAG; return Nothing; }
751 // ********** by now we are getting down to the silly stuff ************
752 if(Word("gnu", p) || Match("GNU", p)) {
753 if(**p == ' ') (*p)++;
754 if(Word("chess", p) || Match("CHESS", p)) {
756 if((q = strstr(*p, "game")) || (q = strstr(*p, "GAME")) || (q = strstr(*p, "Game"))) {
757 (*p) = q + 4; return GNUChessGame;
762 if(lastChar == '\n' && (Match("# ", p) || Match("; ", p) || Match("% ", p))) {
763 while(**p != '\n' && **p != ' ') (*p)++;
764 if(**p == ' ' && (Match(" game file", p) || Match(" position file", p))) {
765 while(**p != '\n') (*p)++; // skip to EOLN
768 *p = oldp; // we might need to re-match the skipped stuff
771 if(Match("---", p)) { while(**p == '-') (*p)++; return Nothing; } // prevent separators parsing as null move
772 if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
773 strncpy(currentMoveString, "@@@@", 5);
774 return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
777 // ********* Efficient skipping of (mostly) alphabetic chatter **********
778 while(isdigit(**p) || isalpha(**p) || **p == '-') (*p)++;
781 while(isdigit(**p) || isalpha(**p) || **p == '-' || **p == '\'') (*p)++;
782 return Nothing; // random word
784 if(lastChar == '\n' && Match(": ", p)) { // mail header, skip indented lines
786 while(**p != '\n') (*p)++;
787 if(!ReadLine()) return Nothing; // append next line if not EOF
788 } while(Match("\n ", p) || Match("\n\t", p));
793 // ********* Prevent 00 in unprotected time stamps to be mistaken for castling *******
794 if(Match(":00", p)) return Nothing;
796 // ********* Could not match to anything. Return offending character ****
802 Return offset of next pattern in the current file.
807 return ftell(inputFile) - (inPtr - parsePtr); // subtract what is read but not yet parsed
812 { // prepare parse buffer for reading file
814 inPtr = parsePtr = inputBuf;
817 *inPtr = NULLCHAR; // make sure we will start by reading a line
821 yynewstr P((char *s))
830 { // this replaces the flex-generated parser
831 int result = NextUnit(&parsePtr);
832 char *p = parseStart, *q = yytext;
833 if(p == yytext) return result; // kludge to allow kanji expansion
834 while(p < parsePtr) *q++ = *p++; // copy the matched text to yytext[]
842 { // [HGM] wrapper for yylex, which treats nesting of parentheses
843 int symbol, nestingLevel = 0, i=0;
845 static char buf[256*MSG_SIZ];
847 do { // eat away anything not at level 0
849 if(symbol == Open) nestingLevel++;
850 if(nestingLevel) { // save all parsed text between (and including) the ()
851 for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
854 if(symbol == 0) break; // ran into EOF
855 if(symbol == Close) symbol = Comment, nestingLevel--;
856 } while(nestingLevel || symbol == Nothing);
857 yy_text = buf[0] ? buf : (char*)yytext;
862 yylexstr (int boardIndex, char *s, char *buf, int buflen)
865 char *savPP = parsePtr;
867 yyboardindex = boardIndex;
869 ret = (ChessMove) Myylex();
870 strncpy(buf, yy_text, buflen-1);
871 buf[buflen-1] = NULLCHAR;