Allow arbitrary nesting of sub-variations in PGN input
[xboard.git] / parser.l
index 0e4f898..4ac67dc 100644 (file)
--- a/parser.l
+++ b/parser.l
@@ -178,7 +178,7 @@ extern void CopyBoard P((Board to, Board from));
 %}
 %%
 
-"+"?[A-Z][/]?[a-l][0-9][xX:-]?[a-l][0-9]((=?\(?[A-Z]\)?)|=)? {
+"+"?[A-Z][/]?[a-l][0-9][xX:-]?[a-l][0-9]((=?\(?[A-Z]\)?)|[=+])? {
     /*
      * Fully-qualified algebraic move, possibly with promotion
      */
@@ -215,9 +215,8 @@ extern void CopyBoard P((Board to, Board from));
        } else {
             c = currentMoveString[4] = ToLower(yytext[yyleng-1]);
        }
+        if(c == '+' && gameInfo.variant != VariantShogi) c = currentMoveString[4] = NULLCHAR; // + means check outside Shogi
        currentMoveString[5] = NULLCHAR;
-        if(c != '=' && c != '+' && CharToPiece(c) == EmptySquare)
-            return IllegalMove; /* [HGM] promotion to invalid piece */
     }
 
     if (appData.debugMode) {
@@ -236,6 +235,7 @@ extern void CopyBoard P((Board to, Board from));
 
     piece = boards[yyboardindex]
       [currentMoveString[1] - ONE][currentMoveString[0] - AAA];
+    if(PieceToChar(piece) == '+' && appData.icsActive) promoted = 1, yytext[skip3] = PieceToChar(DEMOTED piece); // trust ICS
     if(promoted) piece = (ChessSquare) (DEMOTED piece);
     c = PieceToChar(piece);
     if(c == '~') c = PieceToChar((ChessSquare) (DEMOTED piece));
@@ -250,8 +250,8 @@ extern void CopyBoard P((Board to, Board from));
                           currentMoveString[2] - AAA,
                          currentMoveString[4]);
 
-    if (currentMoveString[4] == NULLCHAR &&
-        (result == WhitePromotion  || result == BlackPromotion)) {
+    if (currentMoveString[4] == NULLCHAR) {
+      if(result == WhitePromotion  || result == BlackPromotion) {
         if(gameInfo.variant == VariantCourier || gameInfo.variant == VariantShatranj)
             currentMoveString[4] = PieceToChar(BlackFerz);
         else if(gameInfo.variant == VariantGreat)
@@ -260,13 +260,15 @@ extern void CopyBoard P((Board to, Board from));
             currentMoveString[4] = '+';
         else
             currentMoveString[4] = PieceToChar(BlackQueen);
-       currentMoveString[5] = NULLCHAR;
+      } else if(result == WhiteNonPromotion  || result == BlackNonPromotion)
+            currentMoveString[4] = '=';
+      currentMoveString[5] = NULLCHAR;
     }
 
     return (int) result;
 }
 
-[a-l][0-9][xX:-]?[a-l][0-9]((=?\(?[A-Za-z]\)?)|=)?      {
+[a-l][0-9][xX:-]?[a-l][0-9]((=?\(?[A-Za-z]\)?)|[=+])?      {
     /*
      * Simple algebraic move, possibly with promotion
      * [HGM] Engine moves are received in this format, with lower-case promoChar!
@@ -292,9 +294,8 @@ extern void CopyBoard P((Board to, Board from));
        } else {
             c = currentMoveString[4] = ToLower(yytext[yyleng-1]);
        }
+        if(c == '+' && gameInfo.variant != VariantShogi) c = currentMoveString[4] = NULLCHAR; // + means check outside Shogi
        currentMoveString[5] = NULLCHAR;
-        if(c != '=' && c != '+' && CharToPiece(c) == EmptySquare)
-            return IllegalMove;
     }
 
     /* [HGM] do not allow values beyond board size */
@@ -326,8 +327,9 @@ extern void CopyBoard P((Board to, Board from));
             currentMoveString[4] = '+'; // Queen might not be defined in mini variants!
         else
             currentMoveString[4] = PieceToChar(BlackQueen);
-       currentMoveString[5] = NULLCHAR;
-      }
+      } else if(result == WhiteNonPromotion  || result == BlackNonPromotion)
+            currentMoveString[4] = '=';
+      currentMoveString[5] = NULLCHAR;
     } else if(appData.testLegality && // strip off unnecessary and false promo characters
        !(result == WhitePromotion  || result == BlackPromotion ||
          result == WhiteNonPromotion || result == BlackNonPromotion)) currentMoveString[4] = NULLCHAR;
@@ -338,7 +340,7 @@ extern void CopyBoard P((Board to, Board from));
 [A-L][0-9][xX:-]?[A-L][0-9]      {
     /*
      * Simple algebraic move, in capitals
-     * [HGM] Engine moves are received in this format, with lower-case promoChar!
+     * [HGM] Some Xiangqi engines use this format ('ICCS notation'). So no promotions!
      */
     int skip = 0;
     ChessMove result;
@@ -374,21 +376,10 @@ extern void CopyBoard P((Board to, Board from));
                           currentMoveString[2] - AAA,
                          currentMoveString[4]);
 
-    if (currentMoveString[4] == NULLCHAR &&
-        (result == WhitePromotion  || result == BlackPromotion)) {
-        if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
-            currentMoveString[4] = PieceToChar(BlackFerz);
-        else if(gameInfo.variant == VariantGreat)
-            currentMoveString[4] = PieceToChar(BlackMan);
-        else
-            currentMoveString[4] = PieceToChar(BlackQueen);
-       currentMoveString[5] = NULLCHAR;
-    }
-
     return (int) result;
 }
 
-[a-l][0-9]((=?\(?[A-Za-z]\)?)|=)?       {
+[a-l][0-9]((=?\(?[A-Za-z]\)?)|[=+])?       {
     /*
      * Pawn move, possibly with promotion
      */
@@ -406,7 +397,8 @@ extern void CopyBoard P((Board to, Board from));
     cl.ffIn = yytext[0] - AAA;
     cl.rtIn = yytext[1] - ONE;
     cl.ftIn = yytext[0] - AAA;
-    c = cl.promoCharIn = ToLower(yytext[2+skip]);
+    cl.promoCharIn = ToLower(yytext[2+skip]);
+    if(cl.promoCharIn == '+' && gameInfo.variant != VariantShogi) cl.promoCharIn = NULLCHAR; // + means check outside Shogi
 
     /* [HGM] do not allow values beyond board size */
     if(cl.rtIn >= BOARD_HEIGHT ||
@@ -415,10 +407,6 @@ extern void CopyBoard P((Board to, Board from));
        cl.ftIn <  BOARD_LEFT     )
       return ImpossibleMove;
 
-    if(c != '=' && c != '+' && c != NULLCHAR && CharToPiece(c) == EmptySquare)
-      return IllegalMove;
-
-
     Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
 
     currentMoveString[0] = cl.ff + AAA;
@@ -461,7 +449,8 @@ extern void CopyBoard P((Board to, Board from));
     cl.ffIn = yytext[0] - AAA;
     cl.rtIn = -1;
     cl.ftIn = yytext[1+skip1] - AAA;
-    c = cl.promoCharIn = yytext[2+skip1+skip2];
+    cl.promoCharIn = yytext[2+skip1+skip2];
+    if(cl.promoCharIn == '+' && gameInfo.variant != VariantShogi) cl.promoCharIn = NULLCHAR; // + means check outside Shogi
 
     /* [HGM] do not allow values beyond board size */
     if(cl.ffIn >= BOARD_RGHT  ||
@@ -470,9 +459,6 @@ extern void CopyBoard P((Board to, Board from));
        cl.ftIn <  BOARD_LEFT     )
       return ImpossibleMove;
 
-    if(c != '=' && c != '+' && c != NULLCHAR && CharToPiece(c) == EmptySquare)
-      return IllegalMove;
-
     Disambiguate(boards[yyboardindex], PosFlags(yyboardindex), &cl);
 
     currentMoveString[0] = cl.ff + AAA;
@@ -485,7 +471,7 @@ extern void CopyBoard P((Board to, Board from));
     return (int) cl.kind;
 }
 
-[a-l][xX:]?[a-l][0-9]((=?\(?[A-Z]\)?)|ep|"e.p."|=)? {
+[a-l][xX:]?[a-l][0-9]((=?\(?[A-Z]\)?)|ep|"e.p."|[=+])? {
     /*
      * unambiguously abbreviated Pawn capture, possibly with promotion
      */
@@ -541,8 +527,7 @@ extern void CopyBoard P((Board to, Board from));
        else
           c = currentMoveString[4] = ToLower(yytext[yyleng-1]);
        currentMoveString[5] = NULLCHAR;
-        if(c != '=' && c != '+' && CharToPiece(c) == EmptySquare)
-            return IllegalMove;
+        if(c == '+' && gameInfo.variant != VariantShogi) c = currentMoveString[4] = NULLCHAR; // + means check outside Shogi
     } else {
        currentMoveString[4] = NULLCHAR;
     }
@@ -555,15 +540,19 @@ extern void CopyBoard P((Board to, Board from));
                           currentMoveString[2] - AAA,
                          currentMoveString[4]);
 
-    if (currentMoveString[4] == NULLCHAR &&
-        (result == WhitePromotion  || result == BlackPromotion)) {
+    if (currentMoveString[4] == NULLCHAR) {
+      if(result == WhitePromotion  || result == BlackPromotion) {
         currentMoveString[4] = PieceToChar(BlackQueen);
        // [HGM] shatranj: take care of variants without Queen
        if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
             currentMoveString[4] = PieceToChar(BlackFerz);
        if(gameInfo.variant == VariantGreat)
             currentMoveString[4] = PieceToChar(BlackMan);
-       currentMoveString[5] = NULLCHAR;
+       if(gameInfo.variant == VariantShogi)
+            currentMoveString[4] = '+';
+      } else if(result == WhiteNonPromotion  || result == BlackNonPromotion)
+            currentMoveString[4] = '=';
+      currentMoveString[5] = NULLCHAR;
     }
 
     if (result != IllegalMove) return (int) result;
@@ -599,7 +588,7 @@ extern void CopyBoard P((Board to, Board from));
       return (int) IllegalMove;
 }
 
-"+"?[A-Z][xX:-]?[a-l][0-9]=?  {
+"+"?[A-Z][xX:-]?[a-l][0-9]((=?\(?[A-Z]\)?)|[=+])?  {
     /*
      * piece move, possibly ambiguous
      */
@@ -627,8 +616,9 @@ extern void CopyBoard P((Board to, Board from));
     cl.ftIn = yytext[1+skip] - AAA;
     cl.promoCharIn = NULLCHAR;
 
-    if(yyleng-skip > 3) /* [HGM] can have Shogi-style promotion */
-        cl.promoCharIn = yytext[yyleng-1];
+    if(yyleng-skip > 3 && gameInfo.variant == VariantShogi) /* [HGM] can have Shogi-style promotion */
+        cl.promoCharIn = yytext[yyleng-1-(yytext[yyleng-1]==')')];
+    if(cl.promoCharIn == '+' && gameInfo.variant != VariantShogi) cl.promoCharIn = NULLCHAR; // + means check outside Shogi
 
     if (appData.debugMode) {
         fprintf(debugFP, "Parser Qa1: yyleng=%d,  %d(%d,%d)-(%d,%d) = %d (%c)\n",
@@ -655,7 +645,7 @@ extern void CopyBoard P((Board to, Board from));
     return (int) cl.kind;
 }
 
-"+"?[A-Z][a-l0-9][xX:-]?[a-l][0-9]=?   {
+"+"?[A-Z][a-l0-9][xX:-]?[a-l][0-9]((=?\(?[A-Z]\)?)|[=+])?   {
     /*
      * piece move with rank or file disambiguator
      */
@@ -694,7 +684,8 @@ extern void CopyBoard P((Board to, Board from));
     cl.promoCharIn = NULLCHAR;
 
     if(yyleng-skip > 4) /* [HGM] can have Shogi-style promotion */
-        cl.promoCharIn = yytext[yyleng-1];
+        cl.promoCharIn = yytext[yyleng-1-(yytext[yyleng-1]==')')];
+    if(cl.promoCharIn == '+' && gameInfo.variant != VariantShogi) cl.promoCharIn = NULLCHAR; // + means check outside Shogi
 
     /* [HGM] do not allow values beyond board size */
     if(cl.rtIn >= BOARD_HEIGHT ||
@@ -827,7 +818,7 @@ extern void CopyBoard P((Board to, Board from));
                              rf, ff, rt, ft, NULLCHAR);
 }
 
-[A-Z][@*][a-l][0-9] {
+[A-Za-z][@*][a-l][0-9] {
     /* Bughouse piece drop. */
     currentMoveString[1] = '@';
     currentMoveString[2] = yytext[2];
@@ -920,10 +911,11 @@ extern void CopyBoard P((Board to, Board from));
     return (int) GameUnfinished;
 }
 
-[1-9][0-9]*/"."?[ \t\n]*[a-lNnPpRrBQqKACFEWDGHOo]    {
+[1-9][0-9]*/"."?[ \t\n]*[a-lnprqoA-Z+]    {
     /* move numbers */
     if ((yyleng == 1) && (yytext[0] == '1'))
       return (int) MoveNumberOne;
+    else return (int) Nothing; // [HGM] make sure something is returned, for gathering parsed text
 }
 
 \([0-9]+:[0-9][0-9](\.[0-9]+)?\)|\{[0-9]+:[0-9][0-9](\.[0-9]+)?\} {
@@ -969,24 +961,24 @@ extern void CopyBoard P((Board to, Board from));
     return (int) Comment; 
 }
 
-\([^()]*(\([^()]*(\([^()]*(\([^()]*\)[^()]*)*\)[^()]*)*\)[^()]*)+[^()]*\)  { /* very nested () */
-    return (int) Comment; 
+\(  {                               /* Opening parentheses */
+    return (int) Open; 
 }
 
-\([^)][^)]+\)   {                              /* >=2 chars in () */
-    return (int) Comment; 
+\)   {                                       /* closing parentheses */
+    return (int) Close; 
 }       
 
 ^[-a-zA-Z0-9]+:" ".*(\n[ \t]+.*)*  {
-        /* Skip mail headers */
+    return (int) Nothing;                 /* Skip mail headers */
 }
 
 [a-zA-Z0-9'-]+                 {
-        /* Skip random words */
+    return (int) Nothing;                 /* Skip random words */
 }
 
 .|\n                           {
-        /* Skip everything else */
+    return (int) Nothing;                 /* Skip everything else */
 }
 
 %%
@@ -1167,7 +1159,7 @@ ChessMove yylexstr(boardIndex, s, text, len)
     yy_switch_to_buffer(buffer);
 #endif /*FLEX_SCANNER*/
 
-    ret = (ChessMove) yylex();
+    ret = (ChessMove) Myylex();
      strncpy(text, yy_text, len-1); // [HGM] vari: yy_text is not available to caller after buffer switch ?!?
      text[len-1] = NULLCHAR;
 
@@ -1180,3 +1172,23 @@ ChessMove yylexstr(boardIndex, s, text, len)
 
     return ret;
 }
+
+int Myylex()
+{   // [HGM] wrapper for yylex, which treats nesting of parentheses
+    int symbol, nestingLevel = 0, i=0;
+    char *p;
+    static char buf[256*MSG_SIZ];
+    buf[0] = NULLCHAR;
+    do { // eat away anything not at level 0
+        symbol = yylex();
+        if(symbol == Open) nestingLevel++;
+        if(nestingLevel) { // save all parsed text between (and including) the ()
+            for(p=yytext; *p && i<256*MSG_SIZ-2;) buf[i++] = *p++;
+            buf[i] = NULLCHAR;
+        }
+        if(symbol == 0) break; // ran into EOF
+        if(symbol == Close) symbol = Comment, nestingLevel--;
+    } while(nestingLevel || symbol == Nothing);
+    yy_text = buf[0] ? buf : (char*)yytext;
+    return symbol;
+}