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)
59 unsigned char kanjiTab[] = {
60 '1', 0357, 0274, 0221, // kanji notation for arabic digits
61 '2', 0357, 0274, 0222,
62 '3', 0357, 0274, 0223,
63 '4', 0357, 0274, 0224,
64 '5', 0357, 0274, 0225,
65 '6', 0357, 0274, 0226,
66 '7', 0357, 0274, 0227,
67 '8', 0357, 0274, 0230,
68 '9', 0357, 0274, 0231,
69 'x', 0345, 0220, 0214,
70 's', 0345, 0205, 0210, // sente
71 'g', 0345, 0276, 0214, // gote
72 '-', 0346, 0212, 0225, // resign
74 'a', 0344, 0270, 0200, // in reality these are numbers in Japanese a=1, b=2 etc.
75 'b', 0344, 0272, 0214,
76 'c', 0344, 0270, 0211,
77 'd', 0345, 0233, 0233,
78 'e', 0344, 0272, 0224,
79 'f', 0345, 0205, 0255,
80 'g', 0344, 0270, 0203,
81 'h', 0345, 0205, 0253,
82 'i', 0344, 0271, 0235,
83 ' ', 0343, 0200, 0200,
85 'K', 0347, 0216, 0211, // piece names
86 'K', 0347, 0216, 0213,
87 'G', 0351, 0207, 0221,
88 'S', 0351, 0212, 0200,
89 'R', 0351, 0243, 0233,
90 'B', 0350, 0247, 0222,
91 'N', 0346, 0241, 0202,
92 'L', 0351, 0246, 0231,
93 'P', 0346, 0255, 0251,
94 'D', 0351, 0276, 0215,
95 'H', 0351, 0246, 0254,
101 '+', 0346, 0210, 0220, // helper
102 '@', 0346, 0211, 0223,
103 'p', 0346, 0211, 0213, // player
104 ':', 0357, 0274, 0232,
105 '-', 0344, 0272, 0206,
108 int NextUnit P((char **p));
114 GetKanji (char **p, int start)
116 unsigned char *q = *(unsigned char **) p;
118 for(i=start; kanjiTab[i]; i+=4) {
119 if(q[0] == kanjiTab[i+1] && q[1] == kanjiTab[i+2] && q[2] == kanjiTab[i+3]) {
125 if((q[0] & 0xE0) == 0xC0 && (q[1] & 0xC0) == 0x80) (*p) += 2; else // for now skip unrecognized utf-8 characters
126 if((q[0] & 0xF0) == 0xE0 && (q[1] & 0xC0) == 0x80 && (q[2] & 0xC0) == 0x80) (*p) += 3; else
127 if((q[0] & 0xF8) == 0xF0 && (q[1] & 0xC0) == 0x80 && (q[2] & 0xC0) == 0x80 && (q[3] & 0xC0) == 0x80) (*p) += 4;
128 else if(**p & 0x80) return -1; // not valid utf-8
130 return 0; // unrecognized but valid kanji (skipped), or plain ASCII
136 static char buf[MSG_SIZ];
137 char *ptr = buf+2, *q, k, first = **p;
138 k = GetKanji(p, XCO);
139 if(k < 0) { (*p)++; return Nothing; } // must try shift-JIS here
140 if(k >= '1' && k <= '9') {
141 buf[0] = k; buf[1] = GetKanji(p, YCO); // to-square coords
142 } else if(k == 'x') {
143 if(GetKanji(p, YCO) != ' ') (*p) -= 3; // skip spacer kanji after recapture
144 } else if((k == 's' || k == 'g') && GetKanji(p, MISC) == 'p' && GetKanji(p, MISC) == ':') { // player name
145 snprintf(yytext, MSG_SIZ, "[%s \"", k == 's' ? "White" : "Black"); // construct PGN tag
146 for(q=yytext+8; **p && **p != '\n' && q < yytext + MSG_SIZ; ) *q++ = *(*p)++;
147 strcpy(q, "\"]\n"); parseStart = yytext; lastChar = '\n';
149 } else if(k == '-' && GetKanji(p, MISC) == '-') { // resign
152 if(quickFlag ? quickFlag&1 : WhiteOnMove(yyboardindex))
153 res = BlackWins, strcpy(yytext, "0-1 {resign}");
154 else res = WhiteWins, strcpy(yytext, "1-0 {resign}");
157 if((first & 255) >= 0343) { kifu = 1; while(**p && **p != '\n') (*p)++; } // unrecognized Japanese kanji: skip to end of line
160 k = GetKanji(p, PIECE);
161 buf[2] = k; // piece ID
162 k = GetKanji(p, MISC);
163 // here we must handle traditional disambiguation
164 if(k == '@') { // drop move
165 buf[3] = '@', buf[4] = buf[0], buf[5] = buf[1]; buf[6] = NULLCHAR;
166 if(appData.debugMode) fprintf(debugFP, "kifu drop %s\n", ptr);
167 return NextUnit(&ptr);
169 // k should be either 0 or '+' here
170 if(**p == '(' && (*p)[3] == ')') { // kif disambiguation
171 buf[3] = (*p)[1]; buf[4] = (*p)[2] + 'a' - '1'; buf[5] = buf[0]; buf[6] = buf[1]; buf[7] = k; buf[8] = NULLCHAR;
172 (*p) += 4; ptr++; // strip off piece name if we know full from-square
173 if(appData.debugMode) fprintf(debugFP, "kifu move %s\n", ptr);
174 return NextUnit(&ptr);
176 buf[3] = buf[0]; buf[4] = buf[1]; buf[5] = k; buf[6] = NULLCHAR;
177 if(appData.debugMode) fprintf(debugFP, "kif2 move %s\n", ptr);
178 return NextUnit(&ptr);
184 { // Read one line from the input file, and append to the buffer
185 char c, *start = inPtr;
186 if(fromString) return 0; // parsing string, so the end is a hard end
187 if(!inputFile) return 0;
188 while((c = fgetc(inputFile)) != EOF) {
190 if(c == '\n') { *inPtr = NULLCHAR; return 1; }
191 if(inPtr - inputBuf > PARSEBUFSIZE-2) inPtr--; //prevent crash on overflow
193 if(inPtr == start) return 0;
194 *inPtr++ = '\n', *inPtr = NULLCHAR; // repair missing linefeed at EOF
199 Scan (char c, char **p)
200 { // line-spanning skip to mentioned character or EOF
202 while(**p) if(*(*p)++ == c) return 0;
204 // no closing bracket; force match for entire rest of file.
210 { // skip spaces tabs and newlines; return 1 if anything was skipped
213 while(**p == ' ' || **p == '\t' || **p == '\n' || **p == '\r') (*p)++;
214 } while(**p == NULLCHAR && ReadLine()); // continue as long as ReadLine reads something
219 Match (char *pattern, char **ptr)
221 char *p = pattern, *s = *ptr;
222 while(*p && (*p == *s++ || s[-1] == '\r' && *p--)) p++;
227 return 0; // no match, no ptr update
231 Word (char *pattern, char **p)
233 if(Match(pattern, p)) return 1;
234 if(*pattern >= 'a' && *pattern <= 'z' && *pattern - **p == 'a' - 'A') { // capitalized
236 if(Match(pattern + 1, p)) return 1;
243 Verb (char *pattern, char **p)
245 int res = Word(pattern, p);
246 if(res && !Match("s", p)) Match("ed", p); // eat conjugation suffix, if any
254 if(**p < '0' || **p > '9') return BADNUMBER;
255 while(**p >= '0' && **p <= '9') {
256 val = 10*val + *(*p)++ - '0';
262 RdTime (char c, char **p)
264 char *start = ++(*p), *sec; // increment *p, as it was pointing to the opening ( or {
265 if(Number(p) == BADNUMBER) return 0;
267 if(Match(":", p) && Number(p) != BADNUMBER && *p - sec == 3) { // well formed
269 if(Match(".", p) && Number(p) != BADNUMBER && *(*p)++ == c) return 1; // well-formed fraction
271 if(*(*p)++ == c) return 1; // matching bracket without fraction
273 *p = start; // failure
278 PromoSuffix (char **p)
281 if(**p == 'e' && (Match("ep", p) || Match("e.p.", p))) { *p = start; return NULLCHAR; } // non-compliant e.p. suffix is no promoChar!
282 if(**p == '+' && IS_SHOGI(gameInfo.variant)) { (*p)++; return '+'; }
283 if(**p == '=' || (gameInfo.variant == VariantSChess) && **p == '/') (*p)++; // optional = (or / for Seirawan gating)
284 if(**p == '(' && (*p)[2] == ')' && isalpha( (*p)[1] )) { (*p) += 3; return ToLower((*p)[-2]); }
285 if(isalpha(**p) && **p != 'x') return ToLower(*(*p)++); // reserve 'x' for multi-leg captures?
286 if(*p != start) return **p == '+' ? *(*p)++ : '='; // must be the optional = (or =+)
287 return NULLCHAR; // no suffix detected
292 { // Main parser routine
293 int coord[4], n, result, piece, i;
294 char type[4], promoted, separator, slash, *oldp, *commentEnd, c;
295 int wom = quickFlag ? quickFlag&1 : WhiteOnMove(yyboardindex);
297 // ********* try white first, because it is so common **************************
298 if(**p == ' ' || **p == '\n' || **p == '\t') { parseStart = (*p)++; return Nothing; }
301 if(**p == NULLCHAR) { // make sure there is something to parse
302 if(fromString) return 0; // we are parsing string, so the end is really the end
303 *p = inPtr = inputBuf;
304 if(!ReadLine()) return 0; // EOF
305 } else if(inPtr > inputBuf + PARSEBUFSIZE/2) { // buffer fills up with already parsed stuff
306 char *q = *p, *r = inputBuf;
308 *p = inputBuf; inPtr = r - 1;
310 parseStart = oldp = *p; // remember where we begin
312 // ********* attempt to recognize a SAN move in the leading non-blank text *****
313 piece = separator = promoted = slash = n = 0;
314 for(i=0; i<4; i++) coord[i] = -1, type[i] = NOTHING;
315 if(**p & 0x80) return KifuMove(p); // non-ascii. Could be some kanj notation for Shogi or Xiangqi
316 if(**p == '+') (*p)++, promoted++;
317 if(**p >= 'a' && **p <= 'z' && (*p)[1]== '@') piece =*(*p)++ + 'A' - 'a'; else
318 if(**p >= 'A' && **p <= 'Z') {
319 piece = *(*p)++; // Note we could test for 2-byte non-ascii names here
320 if(**p == '/') slash = *(*p)++;
323 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
324 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
326 if(n == 2 && type[0] == type[1]) { // if two identical types, the opposite type in between must have been missing
327 type[2] = type[1]; coord[2] = coord[1];
328 type[1] = NOTHING; coord[1] = -1; n++;
331 // we always get here, and might have read a +, a piece, and upto 4 potential coordinates
332 if(n <= 2) { // could be from-square or disambiguator, when -:xX follow, or drop with @ directly after piece, but also to-square
333 if(**p == '-' || **p == ':' || **p == 'x' || **p == 'X' || // these cannot be move suffix, so to-square must follow
334 (**p == '@' || **p == '*') && n == 0 && !promoted && piece) { // P@ must also be followed by to-square
336 if(n == 1) coord[1] = coord[0]; // must be disambiguator, but we do not know which yet
338 while(n < 4) { // attempt to read to-square
339 if(**p >= 'a' && **p < 'x') coord[n] = *(*p)++ - 'a', type[n++] = ALPHABETIC;
340 else if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
343 } else if((**p == '+' || **p == '=') && n == 1 && piece && type[0] == NUMERIC) { // can be traditional Xiangqi notation
346 if((i = Number(p)) != BADNUMBER) coord[n] = i, type[n++] = NUMERIC;
347 } else if(n == 2) { // only one square mentioned, must be to-square
348 while(n < 4) { coord[n] = coord[n-2], type[n] = type[n-2], coord[n-2] = -1, type[n-2] = NOTHING; n++; }
350 } else if(n == 3 && type[1] != NOTHING) { // must be hyphenless disambiguator + to-square
351 for(i=3; i>0; i--) coord[i] = coord[i-1], type[i] = type[i-1]; // move to-square to where it belongs
352 type[1] = NOTHING; // disambiguator goes in first two positions
355 // we always get here; move must be completely read now, with to-square coord(s) at end
356 if(n == 3) { // incomplete to-square. Could be Xiangqi traditional, or stuff like fxg
357 if(piece && type[1] == NOTHING && type[0] == NUMERIC && type[2] == NUMERIC &&
358 (separator == '+' || separator == '=' || separator == '-')) {
359 // Xiangqi traditional
361 return ImpossibleMove; // for now treat as invalid
363 // fxg stuff, but also things like 0-0, 0-1 and 1-0
364 if(!piece && type[1] == NOTHING && type[0] == ALPHABETIC && type[2] == ALPHABETIC
365 && (coord[0] != 14 || coord[2] != 14) /* reserve oo for castling! */ ) {
366 piece = 'P'; n = 4; // kludge alert: fake full to-square
368 } else if(n == 1 && type[0] == NUMERIC && coord[0] > 1) { while(**p == '.') (*p)++; return Nothing; } // fast exit for move numbers
369 if(n == 4 && type[2] != type[3] && // we have a valid to-square (kludge: type[3] can be NOTHING on fxg type move)
370 (piece || !promoted) && // promoted indicator only valid on named piece type
371 (type[2] == ALPHABETIC || IS_SHOGI(gameInfo.variant))) { // in Shogi also allow alphabetic rank
372 DisambiguateClosure cl;
373 int fromX, fromY, toX, toY;
375 if(slash && (!piece || type[1] == NOTHING)) goto badMove; // slash after piece only in ICS long format
376 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
378 if(type[2] == NUMERIC) { // alpha-rank
379 coord[2] = BOARD_RGHT - BOARD_LEFT - coord[2];
380 coord[3] = BOARD_HEIGHT - coord[3];
381 if(coord[0] >= 0) coord[0] = BOARD_RGHT - BOARD_LEFT - coord[0];
382 if(coord[1] >= 0) coord[1] = BOARD_HEIGHT - coord[1];
384 toX = cl.ftIn = (currentMoveString[2] = coord[2] + 'a') - AAA;
385 toY = cl.rtIn = (currentMoveString[3] = coord[3] + '0') - ONE;
386 if(type[3] == NOTHING) cl.rtIn = -1; // for fxg type moves ask for toY disambiguation
387 else if(toY >= BOARD_HEIGHT || toY < 0) return ImpossibleMove; // vert off-board to-square
388 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return ImpossibleMove;
390 cl.pieceIn = CharToPiece(wom ? piece : ToLower(piece));
391 if(cl.pieceIn == EmptySquare) return ImpossibleMove; // non-existent piece
392 if(promoted) cl.pieceIn = (ChessSquare) (CHUPROMOTED cl.pieceIn);
393 } else cl.pieceIn = EmptySquare;
394 if(separator == '@' || separator == '*') { // drop move. We only get here without from-square or promoted piece
395 fromY = DROP_RANK; fromX = cl.pieceIn;
396 currentMoveString[0] = piece;
397 currentMoveString[1] = '@';
398 currentMoveString[4] = NULLCHAR;
399 return LegalityTest(boards[yyboardindex], PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, fromY, fromX, toY, toX, NULLCHAR);
401 if(type[1] == NOTHING && type[0] != NOTHING) { // there is a disambiguator
402 if(type[0] != type[2]) coord[0] = -1, type[1] = type[0], type[0] = NOTHING; // it was a rank-disambiguator
404 if( type[1] != type[2] && // means fromY is of opposite type as ToX, or NOTHING
405 (type[0] == NOTHING || type[0] == type[2]) ) { // well formed
407 fromX = (currentMoveString[0] = coord[0] + 'a') - AAA;
408 fromY = (currentMoveString[1] = coord[1] + '0') - ONE;
409 currentMoveString[4] = cl.promoCharIn = PromoSuffix(p);
410 currentMoveString[5] = NULLCHAR;
411 if(!cl.promoCharIn && (**p == '-' || **p == 'x')) { // Lion-type multi-leg move
412 currentMoveString[5] = (killX = toX) + AAA; // what we thought was to-square is in fact kill-square
413 currentMoveString[6] = (killY = toY) + ONE; // append it as suffix behind long algebraic move
414 currentMoveString[4] = ';';
415 currentMoveString[7] = NULLCHAR;
416 // read new to-square (VERY non-robust! Assumes correct (non-alpha-rank) syntax, and messes up on errors)
417 toX = cl.ftIn = (currentMoveString[2] = *++*p) - AAA; ++*p;
418 toY = cl.rtIn = (currentMoveString[3] = Number(p) + '0') - ONE;
420 if(type[0] != NOTHING && type[1] != NOTHING && type[3] != NOTHING) { // fully specified.
421 ChessSquare realPiece = boards[yyboardindex][fromY][fromX];
422 // Note that Disambiguate does not work for illegal moves, but flags them as impossible
423 if(piece) { // check if correct piece indicated
424 if(PieceToChar(realPiece) == '~') realPiece = (ChessSquare) (DEMOTED realPiece);
425 if(!(appData.icsActive && PieceToChar(realPiece) == '+') && // trust ICS if it moves promoted pieces
426 piece && realPiece != cl.pieceIn) return ImpossibleMove;
427 } else if(!separator && **p == '+') { // could be a protocol move, where bare '+' suffix means shogi-style promotion
428 if(realPiece < (wom ? WhiteCannon : BlackCannon) && PieceToChar(PROMOTED realPiece) == '+') // seems to be that
429 currentMoveString[4] = cl.promoCharIn = *(*p)++; // append promochar after all
431 result = LegalityTest(boards[yyboardindex], PosFlags(yyboardindex), fromY, fromX, toY, toX, cl.promoCharIn);
432 if (currentMoveString[4] == NULLCHAR) { // suppy missing mandatory promotion character
433 if(result == WhitePromotion || result == BlackPromotion) {
434 switch(gameInfo.variant) {
436 case VariantShatranj: currentMoveString[4] = PieceToChar(BlackFerz); break;
437 case VariantGreat: currentMoveString[4] = PieceToChar(BlackMan); break;
438 case VariantShogi: currentMoveString[4] = '+'; break;
439 default: currentMoveString[4] = PieceToChar(BlackQueen);
441 } else if(result == WhiteNonPromotion || result == BlackNonPromotion) {
442 currentMoveString[4] = '=';
444 } else if(appData.testLegality && gameInfo.variant != VariantSChess && // strip off unnecessary and false promo characters
445 !(result == WhitePromotion || result == BlackPromotion ||
446 result == WhiteNonPromotion || result == BlackNonPromotion)) currentMoveString[4] = NULLCHAR;
448 } else if(cl.pieceIn == EmptySquare) cl.pieceIn = wom ? WhitePawn : BlackPawn;
449 cl.ffIn = type[0] == NOTHING ? -1 : coord[0] + 'a' - AAA;
450 cl.rfIn = type[1] == NOTHING ? -1 : coord[1] + '0' - ONE;
452 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
454 if(cl.kind == ImpossibleMove && !piece && type[1] == NOTHING // fxg5 type
455 && toY == (wom ? 4 : 3)) { // could be improperly written e.p.
456 cl.rtIn += wom ? 1 : -1; // shift target square to e.p. square
457 Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
458 if((cl.kind != WhiteCapturesEnPassant && cl.kind != BlackCapturesEnPassant))
459 return ImpossibleMove; // nice try, but no cigar
462 currentMoveString[0] = cl.ff + AAA;
463 currentMoveString[1] = cl.rf + ONE;
464 currentMoveString[3] = cl.rt + ONE;
465 if(killX < 0) // [HGM] lion: do not overwrite kill-square suffix
466 currentMoveString[4] = cl.promoChar;
468 if((cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) && (Match("ep", p) || Match("e.p.", p)));
470 return (int) cl.kind;
473 badMove:// we failed to find algebraic move
477 // Next we do some common symbols where the first character commits us to things that cannot possibly be a move
479 // ********* PGN tags ******************************************
481 oldp = ++(*p); kifu = 0;
482 if(Match("--", p)) { // "[--" could be start of position diagram
483 if(!Scan(']', p) && (*p)[-3] == '-' && (*p)[-2] == '-') return PositionDiagram;
487 if(isdigit(**p) || isalpha(**p)) {
488 do (*p)++; while(isdigit(**p) || isalpha(**p) || **p == '+' ||
489 **p == '-' || **p == '=' || **p == '_' || **p == '#');
493 while(**p != '\n' && (*(*p)++ != '"'|| (*p)[-2] == '\\')); // look for unescaped quote
494 if((*p)[-1] !='"') { *p = oldp; Scan(']', p); return Comment; } // string closing delimiter missing
495 SkipWhite(p); if(*(*p)++ == ']') return PGNTag;
498 Scan(']', p); return Comment;
501 // ********* SAN Castings *************************************
502 if(**p == 'O' || **p == 'o' || **p == '0' && !Match("00:", p)) { // exclude 00 in time stamps
503 int castlingType = 0;
504 if(Match("O-O-O", p) || Match("o-o-o", p) || Match("0-0-0", p) ||
505 Match("OOO", p) || Match("ooo", p) || Match("000", p)) castlingType = 2;
506 else if(Match("O-O", p) || Match("o-o", p) || Match("0-0", p) ||
507 Match("OO", p) || Match("oo", p) || Match("00", p)) castlingType = 1;
508 if(castlingType) { //code from old parser, collapsed for both castling types, and streamlined a bit
509 int rf, ff, rt, ft; ChessSquare king;
512 if(gameInfo.variant == VariantSChess) promo = PromoSuffix(p);
514 if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
525 ff = (BOARD_WIDTH-1)>>1; // this would be d-file
526 if (boards[yyboardindex][rf][ff] == king) {
527 /* ICS wild castling */
528 ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
530 ff = BOARD_WIDTH>>1; // e-file
531 ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
533 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
535 ff = initialRights[2];
536 ft = initialRights[castlingType-1];
538 ff = initialRights[5];
539 ft = initialRights[castlingType+2];
541 if (appData.debugMode) fprintf(debugFP, "Parser FRC (type=%d) %d %d\n", castlingType, ff, ft);
542 if(ff == NoRights || ft == NoRights) return ImpossibleMove;
544 sprintf(currentMoveString, "%c%c%c%c%c",ff+AAA,rf+ONE,ft+AAA,rt+ONE,promo);
545 if (appData.debugMode) fprintf(debugFP, "(%d-type) castling %d %d\n", castlingType, ff, ft);
547 return (int) LegalityTest(boards[yyboardindex],
548 PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
549 rf, ff, rt, ft, promo);
554 // ********* variations (nesting) ******************************
556 if(RdTime(')', p)) return ElapsedTime;
559 if(**p ==')') { (*p)++; return Close; }
560 if(**p == ';') { while(**p != '\n') (*p)++; return Comment; }
563 // ********* Comments and result messages **********************
564 *p = oldp; commentEnd = NULL; result = 0;
566 if(RdTime('}', p)) return ElapsedTime;
567 if(lastChar == '\n' && Match("--------------\n", p)) {
569 i = Scan ('}', p); q = *p - 16;
570 if(Match("\n--------------}\n", &q)) return PositionDiagram;
571 } else i = Scan('}', p);
572 commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
574 if(commentEnd) SkipWhite(p);
575 if(lastChar == '\n' && kifu && **p == '*') { while(**p && **p != '\n') (*p)++; return Comment; } // .kif comment
576 if(Match("*", p)) result = GameUnfinished;
577 else if(**p == '0') {
578 if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
579 Match("0 - 1", p) || Match("0 / 1", p) || Match("0 : 1", p)) result = BlackWins;
580 } else if(**p == '1') {
581 if( Match("1-0", p) || Match("1/0", p) || Match("1:0", p) ||
582 Match("1 - 0", p) || Match("1 / 0", p) || Match("1 : 0", p)) result = WhiteWins;
583 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) ||
584 Match("1 / 2 : 1 / 2", p) || Match("1/2", p) || Match("1 / 2", p)) result = GameIsDrawn;
587 if(Match(" (", p) && !Scan(')', p) || Match(" {", p) && !Scan('}', p)) { // there is a comment after the PGN result!
588 if(commentEnd) { *p = commentEnd; return Comment; } // so comment before it is normal comment; return that first
590 return result; // this returns a possible preceeding comment as result details
592 if(commentEnd) { *p = commentEnd; return Comment; } // there was no PGN result following, so return as normal comment
595 // ********* Move numbers (after castlings or PGN results!) ***********
596 if((i = Number(p)) != BADNUMBER) { // a single number was read as part of our attempt to read a move
598 if(**p == '.') (*p)++; SkipWhite(p);
599 if(**p == '+' || isalpha(**p) || gameInfo.variant == VariantShogi && *p != numEnd && isdigit(**p)) {
601 return i == 1 ? MoveNumberOne : Nothing;
603 *p = numEnd; return Nothing;
607 // ********* non-compliant game-result indicators *********************
608 if(Match("+-+", p) || Word("stalemate", p)) return GameIsDrawn;
609 if(Match("++", p) || Verb("resign", p) || (Word("check", p) || 1) && Word("mate", p) )
610 return (wom ? BlackWins : WhiteWins);
612 if(Word("w", p) && (Match("hite", p) || 1) || Word("b", p) && (Match("lack", p) || 1) ) {
613 if(**p != ' ') return Nothing;
615 if(Verb("disconnect", p)) return GameUnfinished;
616 if(Verb("resign", p) || Verb("forfeit", p) || Word("mated", p) || Word("lost", p) || Word("loses", p))
617 return (c == 'W' ? BlackWins : WhiteWins);
618 if(Word("mates", p) || Word("wins", p) || Word("won", p))
619 return (c != 'W' ? BlackWins : WhiteWins);
622 if(Word("draw", p)) {
623 if(**p == 'n') (*p)++;
624 if(**p != ' ') return GameIsDrawn;
626 if(Word("agreed", p)) return GameIsDrawn;
627 if(Match("by ", p) && (Word("repetition", p) || Word("agreement", p)) ) return GameIsDrawn;
630 while(**p != '\n') if(*(*p)++ == ')') break;
631 if((*p)[-1] == ')') return GameIsDrawn;
633 *p = oldp - 1; return GameIsDrawn;
637 // ********* Numeric annotation glyph **********************************
638 if(**p == '$') { (*p)++; if(Number(p) != BADNUMBER) return NAG; return Nothing; }
641 // ********** by now we are getting down to the silly stuff ************
642 if(Word("gnu", p) || Match("GNU", p)) {
643 if(**p == ' ') (*p)++;
644 if(Word("chess", p) || Match("CHESS", p)) {
646 if((q = strstr(*p, "game")) || (q = strstr(*p, "GAME")) || (q = strstr(*p, "Game"))) {
647 (*p) = q + 4; return GNUChessGame;
652 if(lastChar == '\n' && (Match("# ", p) || Match("; ", p) || Match("% ", p))) {
653 while(**p != '\n' && **p != ' ') (*p)++;
654 if(**p == ' ' && (Match(" game file", p) || Match(" position file", p))) {
655 while(**p != '\n') (*p)++; // skip to EOLN
658 *p = oldp; // we might need to re-match the skipped stuff
661 if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
662 strncpy(currentMoveString, "@@@@", 5);
663 return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;
666 // ********* Efficient skipping of (mostly) alphabetic chatter **********
667 while(isdigit(**p) || isalpha(**p) || **p == '-') (*p)++;
670 while(isdigit(**p) || isalpha(**p) || **p == '-' || **p == '\'') (*p)++;
671 return Nothing; // random word
673 if(lastChar == '\n' && Match(": ", p)) { // mail header, skip indented lines
675 while(**p != '\n') (*p)++;
676 if(!ReadLine()) return Nothing; // append next line if not EOF
677 } while(Match("\n ", p) || Match("\n\t", p));
682 // ********* Prevent 00 in unprotected time stamps to be mistaken for castling *******
683 if(Match(":00", p)) return Nothing;
685 // ********* Could not match to anything. Return offending character ****
691 Return offset of next pattern in the current file.
696 return ftell(inputFile) - (inPtr - parsePtr); // subtract what is read but not yet parsed
701 { // prepare parse buffer for reading file
703 inPtr = parsePtr = inputBuf;
706 *inPtr = NULLCHAR; // make sure we will start by reading a line
710 yynewstr P((char *s))
719 { // this replaces the flex-generated parser
720 int result = NextUnit(&parsePtr);
721 char *p = parseStart, *q = yytext;
722 if(p == yytext) return result; // kludge to allow kanji expansion
723 while(p < parsePtr) *q++ = *p++; // copy the matched text to yytext[]
731 { // [HGM] wrapper for yylex, which treats nesting of parentheses
732 int symbol, nestingLevel = 0, i=0;
734 static char buf[256*MSG_SIZ];
736 do { // eat away anything not at level 0
738 if(symbol == Open) nestingLevel++;
739 if(nestingLevel) { // save all parsed text between (and including) the ()
740 for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
743 if(symbol == 0) break; // ran into EOF
744 if(symbol == Close) symbol = Comment, nestingLevel--;
745 } while(nestingLevel || symbol == Nothing);
746 yy_text = buf[0] ? buf : (char*)yytext;
751 yylexstr (int boardIndex, char *s, char *buf, int buflen)
754 char *savPP = parsePtr;
756 yyboardindex = boardIndex;
758 ret = (ChessMove) Myylex();
759 strncpy(buf, yy_text, buflen-1);
760 buf[buflen-1] = NULLCHAR;