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 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 piece = *(*p)++; // Note we could test for 2-byte non-ascii names here
422 if(**p == '/') slash = *(*p)++;
425 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
426 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
428 if(n == 2 && type[0] == type[1]) { // if two identical types, the opposite type in between must have been missing
429 type[2] = type[1]; coord[2] = coord[1];
430 type[1] = NOTHING; coord[1] = -1; n++;
433 // we always get here, and might have read a +, a piece, and upto 4 potential coordinates
434 if(n <= 2) { // could be from-square or disambiguator, when -:xX follow, or drop with @ directly after piece, but also to-square
435 if(**p == '-' || **p == ':' || **p == 'x' || **p == 'X' || // these cannot be move suffix, so to-square must follow
436 (**p == '@' || **p == '*') && n == 0 && !promoted && piece) { // P@ must also be followed by to-square
438 if(n == 1) coord[1] = coord[0]; // must be disambiguator, but we do not know which yet
440 while(n < 4) { // attempt to read to-square
441 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
442 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
445 } else if((**p == '+' || **p == '=') && n == 1 && piece && type[0] == NUMERIC) { // can be traditional Xiangqi notation
448 if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
449 } else if(n == 2) { // only one square mentioned, must be to-square
450 while(n < 4) { coord[n] = coord[n-2], type[n] = type[n-2], coord[n-2] = -1, type[n-2] = NOTHING; n++; }
452 } else if(n == 3 && type[1] != NOTHING) { // must be hyphenless disambiguator + to-square
453 for(i=3; i>0; i--) coord[i] = coord[i-1], type[i] = type[i-1]; // move to-square to where it belongs
454 type[1] = NOTHING; // disambiguator goes in first two positions
457 // we always get here; move must be completely read now, with to-square coord(s) at end
458 if(n == 3) { // incomplete to-square. Could be Xiangqi traditional, or stuff like fxg
459 if(piece && type[1] == NOTHING && type[0] == NUMERIC && type[2] == NUMERIC &&
460 (separator == '+' || separator == '=' || separator == '-')) {
461 // Xiangqi traditional
463 return ImpossibleMove; // for now treat as invalid
465 // fxg stuff, but also things like 0-0, 0-1 and 1-0
466 if(!piece && type[1] == NOTHING && type[0] == ALPHABETIC && type[2] == ALPHABETIC
467 && (coord[0] != 14 || coord[2] != 14) /* reserve oo for castling! */ ) {
468 piece = 'P'; n = 4; // kludge alert: fake full to-square
470 } else if(n == 1 && type[0] == NUMERIC && coord[0] > 1) { while(**p == '.') (*p)++; return Nothing; } // fast exit for move numbers
471 if(n == 4 && type[2] != type[3] && // we have a valid to-square (kludge: type[3] can be NOTHING on fxg type move)
472 (piece || !promoted) && // promoted indicator only valid on named piece type
473 (type[2] == ALPHABETIC || IS_SHOGI(gameInfo.variant))) { // in Shogi also allow alphabetic rank
474 DisambiguateClosure cl;
475 int fromX, fromY, toX, toY;
477 if(slash && (!piece || type[1] == NOTHING)) goto badMove; // slash after piece only in ICS long format
478 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
480 if(type[2] == NUMERIC) { // alpha-rank
481 coord[2] = BOARD_RGHT - BOARD_LEFT - coord[2];
482 coord[3] = BOARD_HEIGHT - coord[3];
483 if(coord[0] >= 0) coord[0] = BOARD_RGHT - BOARD_LEFT - coord[0];
484 if(coord[1] >= 0) coord[1] = BOARD_HEIGHT - coord[1];
486 toX = cl.ftIn = (currentMoveString[2] = coord[2] + 'a') - AAA;
487 toY = cl.rtIn = (currentMoveString[3] = coord[3] + '0') - ONE;
488 if(type[3] == NOTHING) cl.rtIn = -1; // for fxg type moves ask for toY disambiguation
489 else if(toY >= BOARD_HEIGHT || toY < 0) return ImpossibleMove; // vert off-board to-square
490 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return ImpossibleMove;
492 cl.pieceIn = CharToPiece(wom ? piece : ToLower(piece));
493 if(cl.pieceIn == EmptySquare) return ImpossibleMove; // non-existent piece
494 if(promoted) cl.pieceIn = (ChessSquare) (CHUPROMOTED cl.pieceIn);
495 } else cl.pieceIn = EmptySquare;
496 if(separator == '@' || separator == '*') { // drop move. We only get here without from-square or promoted piece
497 fromY = DROP_RANK; fromX = cl.pieceIn;
498 currentMoveString[0] = piece;
499 currentMoveString[1] = '@';
500 currentMoveString[4] = NULLCHAR;
501 return LegalityTest(boards[yyboardindex], PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, fromY, fromX, toY, toX, NULLCHAR);
503 if(type[1] == NOTHING && type[0] != NOTHING) { // there is a disambiguator
504 if(type[0] != type[2]) coord[0] = -1, type[1] = type[0], type[0] = NOTHING; // it was a rank-disambiguator
506 if( type[1] != type[2] && // means fromY is of opposite type as ToX, or NOTHING
507 (type[0] == NOTHING || type[0] == type[2]) ) { // well formed
509 fromX = (currentMoveString[0] = coord[0] + 'a') - AAA;
510 fromY = (currentMoveString[1] = coord[1] + '0') - ONE;
511 currentMoveString[4] = cl.promoCharIn = PromoSuffix(p);
512 currentMoveString[5] = NULLCHAR;
513 if(!cl.promoCharIn && (**p == '-' || **p == 'x')) { // Lion-type multi-leg move
514 currentMoveString[5] = (killX = toX) + AAA; // what we thought was to-square is in fact kill-square
515 currentMoveString[6] = (killY = toY) + ONE; // append it as suffix behind long algebraic move
516 currentMoveString[4] = ';';
517 currentMoveString[7] = NULLCHAR;
518 // read new to-square (VERY non-robust! Assumes correct (non-alpha-rank) syntax, and messes up on errors)
519 toX = cl.ftIn = (currentMoveString[2] = *++*p) - AAA; ++*p;
520 toY = cl.rtIn = (currentMoveString[3] = Number(p) + '0') - ONE;
522 if(type[0] != NOTHING && type[1] != NOTHING && type[3] != NOTHING) { // fully specified.
523 ChessSquare realPiece = boards[yyboardindex][fromY][fromX];
524 // Note that Disambiguate does not work for illegal moves, but flags them as impossible
525 if(piece) { // check if correct piece indicated
526 if(PieceToChar(realPiece) == '~') realPiece = (ChessSquare) (DEMOTED realPiece);
527 if(!(appData.icsActive && PieceToChar(realPiece) == '+') && // trust ICS if it moves promoted pieces
528 piece && realPiece != cl.pieceIn) return ImpossibleMove;
529 } else if(!separator && **p == '+') { // could be a protocol move, where bare '+' suffix means shogi-style promotion
530 if(realPiece < (wom ? WhiteCannon : BlackCannon) && PieceToChar(PROMOTED realPiece) == '+') // seems to be that
531 currentMoveString[4] = cl.promoCharIn = *(*p)++; // append promochar after all
533 result = LegalityTest(boards[yyboardindex], PosFlags(yyboardindex), fromY, fromX, toY, toX, cl.promoCharIn);
534 if (currentMoveString[4] == NULLCHAR) { // suppy missing mandatory promotion character
535 if(result == WhitePromotion || result == BlackPromotion) {
536 switch(gameInfo.variant) {
538 case VariantShatranj: currentMoveString[4] = PieceToChar(BlackFerz); break;
539 case VariantGreat: currentMoveString[4] = PieceToChar(BlackMan); break;
540 case VariantShogi: currentMoveString[4] = '+'; break;
541 default: currentMoveString[4] = PieceToChar(BlackQueen);
543 } else if(result == WhiteNonPromotion || result == BlackNonPromotion) {
544 currentMoveString[4] = '=';
546 } else if(appData.testLegality && gameInfo.variant != VariantSChess && // strip off unnecessary and false promo characters
547 !(result == WhitePromotion || result == BlackPromotion ||
548 result == WhiteNonPromotion || result == BlackNonPromotion)) currentMoveString[4] = NULLCHAR;
550 } else if(cl.pieceIn == EmptySquare) cl.pieceIn = wom ? WhitePawn : BlackPawn;
551 cl.ffIn = type[0] == NOTHING ? -1 : coord[0] + 'a' - AAA;
552 cl.rfIn = type[1] == NOTHING ? -1 : coord[1] + '0' - ONE;
554 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
556 if(cl.kind == ImpossibleMove && !piece && type[1] == NOTHING // fxg5 type
557 && toY == (wom ? 4 : 3)) { // could be improperly written e.p.
558 cl.rtIn += wom ? 1 : -1; // shift target square to e.p. square
559 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
560 if((cl.kind != WhiteCapturesEnPassant && cl.kind != BlackCapturesEnPassant))
561 return ImpossibleMove; // nice try, but no cigar
564 currentMoveString[0] = cl.ff + AAA;
565 currentMoveString[1] = cl.rf + ONE;
566 currentMoveString[3] = cl.rt + ONE;
567 if(killX < 0) // [HGM] lion: do not overwrite kill-square suffix
568 currentMoveString[4] = cl.promoChar;
570 if((cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) && (Match("ep", p) || Match("e.p.", p)));
572 return (int) cl.kind;
575 badMove:// we failed to find algebraic move
579 // Next we do some common symbols where the first character commits us to things that cannot possibly be a move
581 // ********* PGN tags ******************************************
583 oldp = ++(*p); kifu = 0;
584 if(Match("--", p)) { // "[--" could be start of position diagram
585 if(!Scan(']', p) && (*p)[-3] == '-' && (*p)[-2] == '-') return PositionDiagram;
589 if(isdigit(**p) || isalpha(**p)) {
590 do (*p)++; while(isdigit(**p) || isalpha(**p) || **p == '+' ||
591 **p == '-' || **p == '=' || **p == '_' || **p == '#');
595 while(**p != '\n' && (*(*p)++ != '"'|| (*p)[-2] == '\\')); // look for unescaped quote
596 if((*p)[-1] !='"') { *p = oldp; Scan(']', p); return Comment; } // string closing delimiter missing
597 SkipWhite(p); if(*(*p)++ == ']') return PGNTag;
600 Scan(']', p); return Comment;
603 // ********* SAN Castings *************************************
604 if(**p == 'O' || **p == 'o' || **p == '0' && !Match("00:", p)) { // exclude 00 in time stamps
605 int castlingType = 0;
606 if(Match("O-O-O", p) || Match("o-o-o", p) || Match("0-0-0", p) ||
607 Match("OOO", p) || Match("ooo", p) || Match("000", p)) castlingType = 2;
608 else if(Match("O-O", p) || Match("o-o", p) || Match("0-0", p) ||
609 Match("OO", p) || Match("oo", p) || Match("00", p)) castlingType = 1;
610 if(castlingType) { //code from old parser, collapsed for both castling types, and streamlined a bit
611 int rf, ff, rt, ft; ChessSquare king;
614 if(gameInfo.variant == VariantSChess) promo = PromoSuffix(p);
616 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
627 ff = (BOARD_WIDTH-1)>>1; // this would be d-file
628 if (boards[yyboardindex][rf][ff] == king) {
629 /* ICS wild castling */
630 ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
632 ff = BOARD_WIDTH>>1; // e-file
633 ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
635 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
637 ff = initialRights[2];
638 ft = initialRights[castlingType-1];
640 ff = initialRights[5];
641 ft = initialRights[castlingType+2];
643 if (appData.debugMode) fprintf(debugFP, "Parser FRC (type=%d) %d %d\n", castlingType, ff, ft);
644 if(ff == NoRights || ft == NoRights) return ImpossibleMove;
646 sprintf(currentMoveString, "%c%c%c%c%c",ff+AAA,rf+ONE,ft+AAA,rt+ONE,promo);
647 if (appData.debugMode) fprintf(debugFP, "(%d-type) castling %d %d\n", castlingType, ff, ft);
649 return (int) LegalityTest(boards[yyboardindex],
650 PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
651 rf, ff, rt, ft, promo);
652 } else if(Match("01", p)) return Nothing; // prevent this from being mistaken for move number 1
656 // ********* variations (nesting) ******************************
658 if(RdTime(')', p)) return ElapsedTime;
661 if(**p ==')') { (*p)++; return Close; }
662 if(**p == ';') { while(**p != '\n') (*p)++; return Comment; }
665 // ********* Comments and result messages **********************
666 *p = oldp; commentEnd = NULL; result = 0;
668 if(RdTime('}', p)) return ElapsedTime;
669 if(lastChar == '\n' && Match("--------------\n", p)) {
671 i = Scan ('}', p); q = *p - 16;
672 if(Match("\n--------------}\n", &q)) return PositionDiagram;
673 } else i = Scan('}', p);
674 commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
676 if(commentEnd) SkipWhite(p);
677 if(kifu && **p == '*') { // .kif comment
679 while(**p && **p != '\n') { if(q < yytext + 10*MSG_SIZ-3) *q++ = **p; (*p)++; }
680 parseStart = yytext; *yytext = '{'; strcpy(q, "}\n"); // wrap in braces
683 if(Match("*", p)) result = GameUnfinished;
684 else if(**p == '0') {
685 if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
686 Match("0 - 1", p) || Match("0 / 1", p) || Match("0 : 1", p)) result = BlackWins;
687 } else if(**p == '1') {
688 if( Match("1-0", p) || Match("1/0", p) || Match("1:0", p) ||
689 Match("1 - 0", p) || Match("1 / 0", p) || Match("1 : 0", p)) result = WhiteWins;
690 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) ||
691 Match("1 / 2 : 1 / 2", p) || Match("1/2", p) || Match("1 / 2", p)) result = GameIsDrawn;
694 if(Match(" (", p) && !Scan(')', p) || Match(" {", p) && !Scan('}', p)) { // there is a comment after the PGN result!
695 if(commentEnd) { *p = commentEnd; return Comment; } // so comment before it is normal comment; return that first
697 return result; // this returns a possible preceeding comment as result details
699 if(commentEnd) { *p = commentEnd; return Comment; } // there was no PGN result following, so return as normal comment
702 // ********* Move numbers (after castlings or PGN results!) ***********
703 if((i = Number(p)) != BADNUMBER) { // a single number was read as part of our attempt to read a move
705 if(**p == '.') (*p)++; SkipWhite(p);
706 if(**p == '+' || isalpha(**p) || gameInfo.variant == VariantShogi && *p != numEnd && isdigit(**p)) {
708 return i == 1 ? MoveNumberOne : Nothing;
710 *p = numEnd; return Nothing;
714 // ********* non-compliant game-result indicators *********************
715 if(Match("+-+", p) || Word("stalemate", p)) return GameIsDrawn;
716 if(Match("++", p) || Verb("resign", p) || (Word("check", p) || 1) && Word("mate", p) )
717 return (wom ? BlackWins : WhiteWins);
719 if(Word("w", p) && (Match("hite", p) || 1) || Word("b", p) && (Match("lack", p) || 1) ) {
720 if(**p != ' ') return Nothing;
722 if(Verb("disconnect", p)) return GameUnfinished;
723 if(Verb("resign", p) || Verb("forfeit", p) || Word("mated", p) || Word("lost", p) || Word("loses", p))
724 return (c == 'W' ? BlackWins : WhiteWins);
725 if(Word("mates", p) || Word("wins", p) || Word("won", p))
726 return (c != 'W' ? BlackWins : WhiteWins);
729 if(Word("draw", p)) {
730 if(**p == 'n') (*p)++;
731 if(**p != ' ') return GameIsDrawn;
733 if(Word("agreed", p)) return GameIsDrawn;
734 if(Match("by ", p) && (Word("repetition", p) || Word("agreement", p)) ) return GameIsDrawn;
737 while(**p != '\n') if(*(*p)++ == ')') break;
738 if((*p)[-1] == ')') return GameIsDrawn;
740 *p = oldp - 1; return GameIsDrawn;
744 // ********* Numeric annotation glyph **********************************
745 if(**p == '$') { (*p)++; if(Number(p) != BADNUMBER) return NAG; return Nothing; }
748 // ********** by now we are getting down to the silly stuff ************
749 if(Word("gnu", p) || Match("GNU", p)) {
750 if(**p == ' ') (*p)++;
751 if(Word("chess", p) || Match("CHESS", p)) {
753 if((q = strstr(*p, "game")) || (q = strstr(*p, "GAME")) || (q = strstr(*p, "Game"))) {
754 (*p) = q + 4; return GNUChessGame;
759 if(lastChar == '\n' && (Match("# ", p) || Match("; ", p) || Match("% ", p))) {
760 while(**p != '\n' && **p != ' ') (*p)++;
761 if(**p == ' ' && (Match(" game file", p) || Match(" position file", p))) {
762 while(**p != '\n') (*p)++; // skip to EOLN
765 *p = oldp; // we might need to re-match the skipped stuff
768 if(Match("---", p)) { while(**p == '-') (*p)++; return Nothing; } // prevent separators parsing as null move
769 if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
770 strncpy(currentMoveString, "@@@@", 5);
771 return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
774 // ********* Efficient skipping of (mostly) alphabetic chatter **********
775 while(isdigit(**p) || isalpha(**p) || **p == '-') (*p)++;
778 while(isdigit(**p) || isalpha(**p) || **p == '-' || **p == '\'') (*p)++;
779 return Nothing; // random word
781 if(lastChar == '\n' && Match(": ", p)) { // mail header, skip indented lines
783 while(**p != '\n') (*p)++;
784 if(!ReadLine()) return Nothing; // append next line if not EOF
785 } while(Match("\n ", p) || Match("\n\t", p));
790 // ********* Prevent 00 in unprotected time stamps to be mistaken for castling *******
791 if(Match(":00", p)) return Nothing;
793 // ********* Could not match to anything. Return offending character ****
799 Return offset of next pattern in the current file.
804 return ftell(inputFile) - (inPtr - parsePtr); // subtract what is read but not yet parsed
809 { // prepare parse buffer for reading file
811 inPtr = parsePtr = inputBuf;
814 *inPtr = NULLCHAR; // make sure we will start by reading a line
818 yynewstr P((char *s))
827 { // this replaces the flex-generated parser
828 int result = NextUnit(&parsePtr);
829 char *p = parseStart, *q = yytext;
830 if(p == yytext) return result; // kludge to allow kanji expansion
831 while(p < parsePtr) *q++ = *p++; // copy the matched text to yytext[]
839 { // [HGM] wrapper for yylex, which treats nesting of parentheses
840 int symbol, nestingLevel = 0, i=0;
842 static char buf[256*MSG_SIZ];
844 do { // eat away anything not at level 0
846 if(symbol == Open) nestingLevel++;
847 if(nestingLevel) { // save all parsed text between (and including) the ()
848 for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
851 if(symbol == 0) break; // ran into EOF
852 if(symbol == Close) symbol = Comment, nestingLevel--;
853 } while(nestingLevel || symbol == Nothing);
854 yy_text = buf[0] ? buf : (char*)yytext;
859 yylexstr (int boardIndex, char *s, char *buf, int buflen)
862 char *savPP = parsePtr;
864 yyboardindex = boardIndex;
866 ret = (ChessMove) Myylex();
867 strncpy(buf, yy_text, buflen-1);
868 buf[buflen-1] = NULLCHAR;