Use GTK color picker instead of R, G, B and D buttons
[xboard.git] / parser.c
index d175b62..e325005 100644 (file)
--- a/parser.c
+++ b/parser.c
@@ -1,7 +1,7 @@
 /*
  * parser.c --
  *
- * Copyright 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
+ * Copyright 2011, 2012, 2013, 2014, 2015, 2016 Free Software Foundation, Inc.
  * ------------------------------------------------------------------------
  *
  * GNU XBoard is free software: you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 
 #include "config.h"
 #include <stdio.h>
+#include <stdlib.h>
 #include <ctype.h>
 #include <string.h>
 #include "common.h"
@@ -55,6 +56,7 @@ static char fromString = 0, lastChar = '\n';
 #define YCO   53
 #define PIECE 94
 #define MISC 155
+#define JIS  200
 
 unsigned char kanjiTab[] = {
   '1', 0357, 0274, 0221, // kanji notation for arabic digits
@@ -91,11 +93,11 @@ unsigned char kanjiTab[] = {
   'N', 0346, 0241, 0202,
   'L', 0351, 0246, 0231,
   'P', 0346, 0255, 0251,
-  'D', 0351, 0276, 0215,
-  'H', 0351, 0246, 0254,
-  'G', 0, 0, 0,
-  'G', 0, 0, 0,
-  'G', 0, 0, 0,
+  'r', 0351, 0276, 0215,
+  'b', 0351, 0246, 0254,
+  'p', 0343, 0201, 0250,
+  'r', 0347, 0253, 0234,
+  '+', 0346, 0210, 0220,
   'G', 0, 0, 0,
    0,
   '+', 0346, 0210, 0220, // helper
@@ -103,10 +105,72 @@ unsigned char kanjiTab[] = {
   'p', 0346, 0211, 0213, // player
   ':', 0357, 0274, 0232,
   '-', 0344, 0272, 0206,
-   0
+  'f', 0344, 0270, 0212,
+  's', 0345, 0257, 0204,
+  'b', 0345, 0274, 0225,
+  'r', 0345, 0267, 0246,
+  'l', 0345, 0217, 0263,
+  'v', 0347, 0233, 0264,
+   0,
+   // shift-JIS
+  '1', 0202, 0120, 0,
+  '2', 0202, 0121, 0,
+  '3', 0202, 0122, 0,
+  '4', 0202, 0123, 0,
+  '5', 0202, 0124, 0,
+  '6', 0202, 0125, 0,
+  '7', 0202, 0126, 0,
+  '8', 0202, 0127, 0,
+  '9', 0202, 0130, 0,
+  'x', 0223, 0257, 0,
+  's', 0220, 0346, 0,
+  'g', 0214, 0343, 0,
+  '-', 0223, 0212, 0,
+   0,
+  'a', 0210, 0352, 0, 
+  'b', 0223, 0361, 0, 
+  'c', 0216, 0117, 0, 
+  'd', 0216, 0154, 0, 
+  'e', 0214, 0334, 0, 
+  'f', 0230, 0132, 0, 
+  'g', 0216, 0265, 0, 
+  'h', 0224, 0252, 0, 
+  'i', 0213, 0343, 0, 
+  ' ', 0201, 0100, 0, 
+   0,
+  'K', 0213, 0312, 0, 
+  'K', 0213, 0312, 0, 
+  'G', 0213, 0340, 0, 
+  'S', 0213, 0342, 0, 
+  'R', 0224, 0362, 0, 
+  'B', 0212, 0160, 0,
+  'N', 0214, 0152, 0, 
+  'L', 0215, 0201, 0, 
+  'P', 0225, 0340, 0, 
+  'r', 0227, 0264, 0, 
+  'b', 0224, 0156, 0, 
+  'p', 0202, 0306, 0, 
+  'r', 0227, 0263, 0, 
+  '+', 0220, 0254, 0, 
+  'G', 0, 0, 0, 
+   0,
+  '+', 0220, 0254, 0, 
+  '@', 0221, 0305, 0, 
+//  'p', 0214, 0343, 0,
+  'p', 0216, 0350, 0,
+  ':', 0201, 0106, 0,
+  '-', 0227, 0271, 0,
+  'f', 0217, 0343, 0,
+  's', 0212, 0361, 0,
+  'b', 0210, 0370, 0,
+  'r', 0215, 0266, 0,
+  'l', 0211, 0105, 0,
+  'v', 0222, 0274, 0,
+   0,
+     
 };
-int NextUnit P((char **p));
 
+int NextUnit P((char **p));
 
 int kifu = 0;
 
@@ -115,9 +179,22 @@ GetKanji (char **p, int start)
 {
     unsigned char *q = *(unsigned char **) p;
     int i;
+
+    if((*q & 0x80) == 0) return 0; // plain ASCII, refuse to parse
+    if((**p & 0xC0) == 0x80) { // this is an illegal starting code in utf-8, so assume shift-JIS
+       for(i=start+JIS; kanjiTab[i]; i+=4) {
+           if(q[0] == kanjiTab[i+1] && q[1] == kanjiTab[i+2]) {
+               (*p) += 2; kifu = 0x80;
+               return kanjiTab[i];
+           }
+       }
+       (*p) += (kifu ? 2 : 1); // assume this is an unrecognized kanji when reading kif files
+       return 0;
+    }
+
     for(i=start; kanjiTab[i]; i+=4) {
        if(q[0] == kanjiTab[i+1] && q[1] == kanjiTab[i+2] && q[2] == kanjiTab[i+3]) {
-           (*p) += 3;
+           (*p) += 3; kifu = 0x80;
            return kanjiTab[i];
        }
     }
@@ -134,7 +211,8 @@ int
 KifuMove (char **p)
 {
     static char buf[MSG_SIZ];
-    char *ptr = buf+2, *q, k, first = **p;
+    char *ptr = buf+3, *q, k;
+    int wom = quickFlag ? quickFlag&1 : WhiteOnMove(yyboardindex);
     k = GetKanji(p, XCO);
     if(k < 0) { (*p)++; return Nothing; } // must try shift-JIS here
     if(k >= '1' && k <= '9') {
@@ -143,37 +221,62 @@ KifuMove (char **p)
        if(GetKanji(p, YCO) != ' ') (*p) -= 3; // skip spacer kanji after recapture 
     } else if((k == 's' || k == 'g') && GetKanji(p, MISC) == 'p' && GetKanji(p, MISC) == ':') { // player name
        snprintf(yytext, MSG_SIZ, "[%s \"", k == 's' ? "White" : "Black"); // construct PGN tag
-       for(q=yytext+8; **p && **p != '\n' && q < yytext + MSG_SIZ; ) *q++ = *(*p)++;
+       for(q=yytext+8; **p && **p != '\n' && **p != '\r' && q < yytext + MSG_SIZ; ) *q++ = *(*p)++;
        strcpy(q, "\"]\n"); parseStart = yytext; lastChar = '\n';
        return PGNTag;
     } else if(k == '-' && GetKanji(p, MISC) == '-') { // resign
        int res;
        parseStart = yytext;
-       if(quickFlag ? quickFlag&1 : WhiteOnMove(yyboardindex))
-            res = BlackWins, strcpy(yytext, "0-1 {resign}"); 
-       else res = WhiteWins, strcpy(yytext, "1-0 {resign}");
+       if(wom)
+            res = BlackWins, strcpy(yytext, "{sente resigns} 0-1"); 
+       else res = WhiteWins, strcpy(yytext, "{gote resigns} 1-0");
        return res;
     } else {
-       if((first & 255) >= 0343) { kifu = 1; while(**p && **p != '\n') (*p)++; } // unrecognized Japanese kanji: skip to end of line
+       while(**p && **p != '\n') (*p)++; // unrecognized Japanese kanji: skip to end of line
        return Nothing;
     }
-    k = GetKanji(p, PIECE);
-    buf[2] = k; // piece ID
+    buf[3] = GetKanji(p, PIECE); // piece ID
+    if(buf[3] == '+') buf[2] = '+', buf[3] = GetKanji(p, PIECE); // +N, +L, +S
     k = GetKanji(p, MISC);
-    // here we must handle traditional disambiguation
     if(k == '@') { // drop move
-       buf[3] = '@', buf[4] = buf[0], buf[5] = buf[1]; buf[6] = NULLCHAR;
+       buf[4] = '@', buf[5] = buf[0], buf[6] = buf[1]; buf[7] = NULLCHAR;
        if(appData.debugMode) fprintf(debugFP, "kifu drop %s\n", ptr);
        return NextUnit(&ptr);
     }
-    // k should be either 0 or '+' here
+
+    kifu = 0x80;
+    do { // read disambiguation (and promotion) kanji
+       switch(k) {
+         case '+': kifu |= 1; break;
+         case 'f': kifu |= 2; break;
+         case 'b': kifu |= 4; break;
+         case 's': kifu |= 8; break;
+         case 'l': kifu |= 0x10; break;
+         case 'r': kifu |= 0x20; break;
+         case 'v': kifu |= 0x40; break;
+       }
+    } while(k = GetKanji(p, MISC));
+
     if(**p == '(' && (*p)[3] == ')') { // kif disambiguation
-       buf[3] = (*p)[1]; buf[4] = (*p)[2] + 'a' - '1'; buf[5] = buf[0]; buf[6] = buf[1]; buf[7] = k; buf[8] = NULLCHAR;
+       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;
        (*p) += 4; ptr++; // strip off piece name if we know full from-square
        if(appData.debugMode) fprintf(debugFP, "kifu move %s\n", ptr);
        return NextUnit(&ptr);
-    } else {
-       buf[3] = buf[0]; buf[4] = buf[1]; buf[5] = k; buf[6] = NULLCHAR;
+    } else { // kif2
+       char *q = buf+4;
+       if(islower(buf[3])) // kludge: kanji for promoted types translate as lower case
+           buf[3] += 'A' - 'a', buf[2] = '+', ptr--;        // so prefix with '+'
+       if(kifu * ~1) { // disambiguation was given, and thus is probably needed
+           if(buf[3] != 'B' && buf[3] != 'R') {                // stepper, so distance must be <= 1 (N or L never need vertical disambiguation!)
+               if(kifu & 0x10) *q++ = buf[0] - (wom ? -1 : 1); // translate left/right/straight to PSN file disambiguators
+               if(kifu & 0x20) *q++ = buf[0] + (wom ? -1 : 1);
+               if(kifu & 0x40) *q++ = buf[0], kifu |= 2;       // kludge: 'straight' only needs disambiguation if forward!
+               if(kifu & 2) *q++ = buf[1] + (wom ? -1 : 1);    // translate forward/backward/sideway to PSN rank disambiguators
+               if(kifu & 4) *q++ = buf[1] - (wom ? -1 : 1);
+               if(kifu & 8) *q++ = buf[1];
+           } // 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'
+       }
+       *q++ = buf[0]; *q++ = buf[1]; *q++ = (kifu & 1)*'+'; *q = NULLCHAR;
        if(appData.debugMode) fprintf(debugFP, "kif2 move %s\n", ptr);
        return NextUnit(&ptr);
     }
@@ -278,6 +381,7 @@ char
 PromoSuffix (char **p)
 {
     char *start = *p;
+    if(**p == ' ') return NULLCHAR; // common case, test explicitly for speed
     if(**p == 'e' && (Match("ep", p) || Match("e.p.", p))) { *p = start; return NULLCHAR; } // non-compliant e.p. suffix is no promoChar!
     if(**p == '+' && IS_SHOGI(gameInfo.variant)) { (*p)++; return '+'; }
     if(**p == '=' || (gameInfo.variant == VariantSChess) && **p == '/') (*p)++; // optional = (or / for Seirawan gating)
@@ -300,7 +404,7 @@ NextUnit (char **p)
 
        if(**p == NULLCHAR) { // make sure there is something to parse
            if(fromString) return 0; // we are parsing string, so the end is really the end
-           *p = inPtr = inputBuf;
+           *p = inPtr = parseStart = inputBuf;
            if(!ReadLine()) return 0; // EOF
        } else if(inPtr > inputBuf + PARSEBUFSIZE/2) { // buffer fills up with already parsed stuff
            char *q = *p, *r = inputBuf;
@@ -316,7 +420,10 @@ NextUnit (char **p)
        if(**p == '+') (*p)++, promoted++;
        if(**p >= 'a' && **p <= 'z' && (*p)[1]== '@') piece =*(*p)++ + 'A' - 'a'; else
        if(**p >= 'A' && **p <= 'Z') {
+            static char s[] = SUFFIXES;
+            char *q;
             piece = *(*p)++; // Note we could test for 2-byte non-ascii names here
+            if(q = strchr(s, **p)) (*p)++, piece += 64*(q - s + 1);
             if(**p == '/') slash = *(*p)++;
        }
         while(n < 4) {
@@ -387,9 +494,9 @@ NextUnit (char **p)
            else if(toY >= BOARD_HEIGHT || toY < 0)   return ImpossibleMove; // vert off-board to-square
            if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return ImpossibleMove;
            if(piece) {
-               cl.pieceIn = CharToPiece(wom ? piece : ToLower(piece));
+               cl.pieceIn = CharToPiece(wom ? piece : piece + 'a' - 'A');
                if(cl.pieceIn == EmptySquare) return ImpossibleMove; // non-existent piece
-               if(promoted) cl.pieceIn = (ChessSquare) (CHUPROMOTED cl.pieceIn);
+               if(promoted) cl.pieceIn = (ChessSquare) (CHUPROMOTED(cl.pieceIn));
            } else cl.pieceIn = EmptySquare;
            if(separator == '@' || separator == '*') { // drop move. We only get here without from-square or promoted piece
                fromY = DROP_RANK; fromX = cl.pieceIn;
@@ -403,29 +510,42 @@ NextUnit (char **p)
            }
            if(  type[1] != type[2] && // means fromY is of opposite type as ToX, or NOTHING
                (type[0] == NOTHING || type[0] == type[2]) ) { // well formed
-
+               int suffix = 7;
                fromX = (currentMoveString[0] = coord[0] + 'a') - AAA;
                fromY = (currentMoveString[1] = coord[1] + '0') - ONE;
                currentMoveString[4] = cl.promoCharIn = PromoSuffix(p);
                currentMoveString[5] = NULLCHAR;
+               if(**p == 'x' && !cl.promoCharIn) { // other leg follows
+                   char *q = *p;
+                   int x = *++*p, y;
+                   ++*p; y = Number(p);
+                   if(**p == '-' || **p == 'x') {  // 3-leg move!
+                       currentMoveString[7] = (kill2X = toX) + AAA; // what we thought was to-square is in fact 1st kill-square of two
+                       currentMoveString[8] = (kill2Y = toY) + ONE; // append it after 2nd kill-square
+                       toX = x - AAA;       // kludge alert: this will become 2nd kill square
+                       toY = y + '0' - ONE;
+                       suffix += 2;
+                   } else *p = q; // 2-leg move, rewind to leave reading of 2nd leg to code below
+               }
                if(!cl.promoCharIn && (**p == '-' || **p == 'x')) { // Lion-type multi-leg move
                    currentMoveString[5] = (killX = toX) + AAA; // what we thought was to-square is in fact kill-square
                    currentMoveString[6] = (killY = toY) + ONE; // append it as suffix behind long algebraic move
                    currentMoveString[4] = ';';
-                   currentMoveString[7] = NULLCHAR;
+                   currentMoveString[suffix+1] = NULLCHAR;
                    // read new to-square (VERY non-robust! Assumes correct (non-alpha-rank) syntax, and messes up on errors)
                    toX = cl.ftIn = (currentMoveString[2] = *++*p) - AAA; ++*p;
                    toY = cl.rtIn = (currentMoveString[3] = Number(p) + '0') - ONE;
+                   currentMoveString[suffix] = cl.promoCharIn = PromoSuffix(p);
                }
                if(type[0] != NOTHING && type[1] != NOTHING && type[3] != NOTHING) { // fully specified.
                    ChessSquare realPiece = boards[yyboardindex][fromY][fromX];
                    // Note that Disambiguate does not work for illegal moves, but flags them as impossible
                    if(piece) { // check if correct piece indicated
-                       if(PieceToChar(realPiece) == '~') realPiece = (ChessSquare) (DEMOTED realPiece);
+                       if(PieceToChar(realPiece) == '~') realPiece = (ChessSquare) (DEMOTED(realPiece));
                        if(!(appData.icsActive && PieceToChar(realPiece) == '+') && // trust ICS if it moves promoted pieces
                           piece && realPiece != cl.pieceIn) return ImpossibleMove;
                    } else if(!separator && **p == '+') { // could be a protocol move, where bare '+' suffix means shogi-style promotion
-                       if(realPiece < (wom ?  WhiteCannon : BlackCannon) && PieceToChar(PROMOTED realPiece) == '+') // seems to be that
+                       if(realPiece < (wom ?  WhiteCannon : BlackCannon) && PieceToChar(PROMOTED(realPiece)) == '+') // seems to be that
                           currentMoveString[4] = cl.promoCharIn = *(*p)++; // append promochar after all
                    }
                    result = LegalityTest(boards[yyboardindex], PosFlags(yyboardindex), fromY, fromX, toY, toX, cl.promoCharIn);
@@ -514,12 +634,12 @@ badMove:// we failed to find algebraic move
                if (yyskipmoves) return (int) AmbiguousMove; /* not disambiguated */
 
                if (wom) {
-                   rf = 0;
-                   rt = 0;
+                   rf = castlingRank[0];
+                   rt = castlingRank[0];
                    king = WhiteKing;
                } else {
-                   rf = BOARD_HEIGHT-1;
-                   rt = BOARD_HEIGHT-1;
+                   rf = castlingRank[3];
+                   rt = castlingRank[3];
                    king = BlackKing;
                }
                ff = (BOARD_WIDTH-1)>>1; // this would be d-file
@@ -527,8 +647,12 @@ badMove:// we failed to find algebraic move
                    /* ICS wild castling */
                    ft = castlingType == 1 ? BOARD_LEFT+1 : (gameInfo.variant == VariantJanus ? BOARD_RGHT-2 : BOARD_RGHT-3);
                } else {
+                   char *q;
                    ff = BOARD_WIDTH>>1; // e-file
                    ft = castlingType == 1 ? BOARD_RGHT-2 : BOARD_LEFT+2;
+                   if(pieceDesc[king] && (q = strchr(pieceDesc[king], 'O'))) { // redefined to non-default King stride
+                       ft = (castlingType == 1 ? ff + atoi(q+1) : ff - atoi(q+1));
+                   }
                }
                if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
                    if (wom) {
@@ -547,7 +671,7 @@ badMove:// we failed to find algebraic move
                return (int) LegalityTest(boards[yyboardindex],
                              PosFlags(yyboardindex)&~F_MANDATORY_CAPTURE, // [HGM] losers: e.p.!
                              rf, ff, rt, ft, promo);
-           }
+           } else if(Match("01", p)) return Nothing; // prevent this from being mistaken for move number 1
        }
 
 
@@ -572,7 +696,12 @@ badMove:// we failed to find algebraic move
            commentEnd = *p; if(i) return Comment; // return comment that runs to EOF immediately
        }
         if(commentEnd) SkipWhite(p);
-       if(lastChar == '\n' && kifu && **p == '*') { while(**p && **p != '\n') (*p)++; return Comment; } // .kif comment
+       if(kifu && **p == '*') { // .kif comment
+           char *q = yytext;
+           while(**p && **p != '\n') { if(q < yytext + 10*MSG_SIZ-3) *q++ = **p; (*p)++; }
+           parseStart = yytext; *yytext = '{'; strcpy(q, "}\n"); // wrap in braces
+           return Comment;
+       }
        if(Match("*", p)) result = GameUnfinished;
        else if(**p == '0') {
            if( Match("0-1", p) || Match("0/1", p) || Match("0:1", p) ||
@@ -658,6 +787,7 @@ badMove:// we failed to find algebraic move
            *p = oldp; // we might need to re-match the skipped stuff
        }
 
+       if(Match("---", p)) { while(**p == '-') (*p)++; return Nothing; } // prevent separators parsing as null move
        if(Match("@@@@", p) || Match("--", p) || Match("Z0", p) || Match("pass", p) || Match("null", p)) {
            strncpy(currentMoveString, "@@@@", 5);
            return yyboardindex & F_WHITE_ON_MOVE ? WhiteDrop : BlackDrop;