/* * CHEST, chess analyst. For Copyright notice read file "COPYRIGHT". * * $Source: /home/heiner/ca/chest/RCS/epdio.c,v $ * $Id: epdio.c,v 1.11 1999/12/04 21:54:41 heiner Exp $ * * EPD input and output * * FFS: "c0"..."c9" and "v0"..."v9" need processing? */ #include "bsd.h" /* bzero, qsort */ #include "str.h" #include "types.h" #include "board.h" #include "job.h" #include "input.h" #include "output.h" #include "epdio.h" #include #include /* atoi */ #ifndef EPD_DEBUG # define EPD_DEBUG 0 #endif #ifndef EPD_COPY_COMMENT_CHARS # define EPD_COPY_COMMENT_CHARS "%;" /* CF: copy these comments */ #endif #ifndef EPD_STRIP_COMMENT # define EPD_STRIP_COMMENT 0 /* CF: reduce copied comment lines */ #endif #ifndef CDECL #ifdef _WIN32 #define CDECL __cdecl #else #define CDECL #endif #endif /*---------------------------------------------------------------------------*/ /* * Error handling */ static int g_epd_errs = 0; static char *g_epd_phase = ""; static void epd_tell_error(const char *what) { ++g_epd_errs; inp_tell_error(what); /* FFS: stderr instead stdout? */ } static void epd_tell__err(const char *what, const char *also, int val) { char buf[202]; ++g_epd_errs; if (what) { if (also && *also) { sprintf(buf, "%.99s %.99s", what, also); } else { sprintf(buf, "%.99s %d", what, val); } what = buf; } inp_tell_error(what); } static void epd_tell_err_S(const char *what, const char *also) { if (also && *also) { epd_tell__err(what, also, 0); } else { epd_tell_error(what); } } static void epd_tell_err_I(const char *what, int val) { epd_tell__err(what, (char *) 0, 0); } #if EPD_DEBUG static void epd_dump(EpdJob * ejp) { printf("EPD dump: {\n"); if (ejp) { int i; int cur; int nxt; #define LSTsizarr(arr) (int)sizeof(arr), arr printf(" fys '%-.*s'\n", LSTsizarr(ejp->fys)); printf(" tom '%-.*s'\n", LSTsizarr(ejp->tom)); printf(" castle '%-.*s'\n", LSTsizarr(ejp->castle)); printf(" ep '%-.*s'\n", LSTsizarr(ejp->ep)); printf(" opfull %4d, strfull %4d\n", ejp->opfull, ejp->strfull); for (i = 0; i < NELEMS(ejp->oparr); ++i) { if (i >= ejp->opfull) break; printf(" OP%3d '%-.*s' %d\n", i, LSTsizarr(ejp->oparr[i].op), ejp->oparr[i].rest); } cur = 0; while ((cur < NELEMS(ejp->strbuf)) && (cur < ejp->strfull)) { for (nxt = cur; nxt < NELEMS(ejp->strbuf); ++nxt) { if (nxt >= ejp->strfull) break; if (!ejp->strbuf[nxt]) break; } printf(" STR %4d [%2d] '%-.*s'\n", cur, nxt - cur, nxt - cur, &(ejp->strbuf[cur])); cur = nxt + 1; } if (cur > ejp->strfull) { printf(" *** unterminated\n"); } #undef LSTsizarr } else { printf("NIL\n"); } printf("}\n"); } #else /* ! EPD_DEBUG */ # define epd_dump(ejp) /* empty */ #endif /* ! EPD_DEBUG */ /*---------------------------------------------------------------------------*/ /* Basic support */ Eximpl void epd_clear(EpdJob * ejp) { if (ejp) { bzero((char *) ejp, sizeof(*ejp)); } } static int epd_len_tok(const char *p) { const char *p0 = p; while (*p && !IS_SPACE(*p) && (*p != ';')) { ++p; } return (int) (p - p0); } static int epd_len_opnd(const char *p) { if (*p == '"') { const char *p0 = p; for (++p; *p; ++p) { if (*p == '"') { ++p; break; } if ((p[0] == '\\') && p[1]) { ++p; /* additional extra skip */ } } return (int) (p - p0); } return epd_len_tok(p); } static void epd__stuff(char *dst, int siz, const char *src, int len) { if (dst && (siz > 0)) { register int i; if (len >= siz) { len = siz - 1; } for (i = 0; i < len; ++i) { dst[i] = src[i]; } dst[i] = 0; } } static const char * epd_fill_carr(const char *inp, char *carr, int siz, const char *nam) { int len; len = epd_len_tok(inp); if (len <= 0) { epd_tell_err_S("missing EPD part:", nam); /* we continue to fill in an empty string */ len = 0; } epd__stuff(carr, siz, inp, len); return inp + len; } #define EPD_FILL_CARR(inp,carr,nam) \ epd_fill_carr(inp, carr, (int) sizeof(carr), nam) static EpdStr epd_new_str(EpdJob * ejp) { if (ejp->strfull <= 0) { ejp->strbuf[0] = 0; /* at NIL assert an empty string */ ejp->strfull = 1; } return ejp->strfull; } static Bool /* succ */ epd_has_space(EpdJob * ejp, int siz) { /* NB: we also reserve the last char as garanteed terminator. */ if (((int) NELEMS(ejp->strbuf) - ejp->strfull) > siz) { return TRUE; } if (g_epd_errs < 2) { epd_tell_err_S("Too much text in EPD", g_epd_phase); } return FALSE; /* sorry */ } static Bool /* succ */ epd_app_chr(EpdJob * ejp, char c) { if (!epd_has_space(ejp, 1)) { return FALSE; /* sorry */ } ejp->strbuf[ejp->strfull++] = c; return TRUE; /* ok */ } static Bool /* succ */ epd_app_mem(EpdJob * ejp, const char *src, int siz) { if (src && (siz > 0)) { if (!epd_has_space(ejp, siz)) { return FALSE; /* sorry */ } do { ejp->strbuf[ejp->strfull++] = *src++; } while (--siz > 0); } return TRUE; /* ok */ } static Bool /* succ */ epd_app_str(EpdJob * ejp, const char *src) { return src ? epd_app_mem(ejp, src, str_len(src)) : TRUE; } static Bool /* success */ epd_mk_str(EpdJob * ejp, const char *src, EpdStr * dstp) { if (src && src[0]) { EpdStr dst; dst = epd_new_str(ejp); if (!epd_app_mem(ejp, src, 1 + str_len(src))) { return FALSE; /* sorry */ } *dstp = dst; } else { *dstp = EPD_STR_NIL; } return TRUE; /* did it */ } static int /* -1 | index into "oparr[]" */ epd_find_op_inx(EpdJob * ejp, const char *op) { register int i; for (i = 0; i < ejp->opfull; ++i) { if (str_equal(ejp->oparr[i].op, op)) return i; } return -1; /* not found */ } static EpdOp * epd_find_op(EpdJob * ejp, const char *op) { int i = epd_find_op_inx(ejp, op); return (i >= 0) ? &(ejp->oparr[i]) : 0; } static char * /* 0 | opnds string */ epd_find_op_opnds(EpdJob * ejp, const char *op) { EpdOp *eop; eop = epd_find_op(ejp, op); if (eop && !EPD_IS_NIL(eop->rest)) { return EPD_STR(ejp, eop->rest); } return 0; } /*---------------------------------------------------------------------------*/ static void epd_del_str(EpdJob * ejp, EpdStr * restp) { if (ejp && restp) { EpdStr rest; rest = *restp; *restp = EPD_STR_NIL; /* clear out reference */ #if 1 /* FFS */ if (!EPD_IS_NIL(rest) && (rest < ejp->strfull)) { EpdStr next; int siz; int i; siz = 1 + str_len(EPD_STR(ejp, rest)); next = rest + siz; if (next < ejp->strfull) { /* something behind it */ /* reduce references */ for (i = 0; i < ejp->opfull; ++i) { EpdOp *eop; eop = &(ejp->oparr[i]); if (!EPD_IS_NIL(eop->rest) && (eop->rest >= next)) { eop->rest -= siz; } } /* copy down strings */ for (; next < ejp->strfull; ++rest, ++next) { EPD_STR(ejp, rest)[0] = EPD_STR(ejp, next)[0]; } /* free string space */ ejp->strfull -= siz; } } #endif } } static void epd_del_op(EpdJob * ejp, const char *op) { int i; i = epd_find_op_inx(ejp, op); if (i >= 0) { epd_del_str(ejp, &(ejp->oparr[i].rest)); /* copy down further ops */ ejp->opfull -= 1; /* one op less */ for (; i < ejp->opfull; ++i) { ejp->oparr[i] = ejp->oparr[i + 1]; } } } static EpdOp * /* 0 | freshly allocated */ epd_new_op(EpdJob * ejp) { EpdOp *eop; if (ejp->opfull >= NELEMS(ejp->oparr)) { epd_tell_err_I("too many EPD-ops, max =", (int) NELEMS(ejp->oparr)); return 0; } eop = &(ejp->oparr[ejp->opfull++]); eop->op[0] = 0; eop->rest = EPD_STR_NIL; return eop; } /*---------------------------------------------------------------------------*/ /* Read input */ Eximpl int /* -1 | success */ epd_fill_line(EpdJob * ejp, const char *linbuf) { register const char *p; /* scanning input */ char c; g_epd_errs = 0; g_epd_phase = "input"; p = linbuf; SKIP_SPACE(p); /* we accept leading spaces */ switch ((c = *p)) { case '.': /* end of input */ return -1; /* codes end-of-input */ case 0: /* empty line */ c = ' '; /* represented by blank in copy-string */ /* FALLTHROUGH */ case '%': /* PGN escape, accepted as comment */ case ';': /* PGN comment */ case '#': /* another widely used explicit comment */ if (str_pos(EPD_COPY_COMMENT_CHARS, c) >= 0) { #if EPD_STRIP_COMMENT printf("%s\n", p); /* copy reduced line */ #else printf("%s\n", linbuf); /* copy complete line */ #endif } return FALSE; /* no error, but no job read */ } p = EPD_FILL_CARR(p, ejp->fys, "board"); SKIP_SPACE(p); p = EPD_FILL_CARR(p, ejp->tom, "side to move"); SKIP_SPACE(p); p = EPD_FILL_CARR(p, ejp->castle, "castle rights"); SKIP_SPACE(p); p = EPD_FILL_CARR(p, ejp->ep, "ep target"); SKIP_SPACE(p); for (;;) { register const char *p0; register int i; EpdOp *eop; SKIP_SPACE(p); if (!*p) break; /* FFS: dup opcode: should overwrite first? */ /* allocate next EpdOp */ if (!(eop = epd_new_op(ejp))) { return FALSE; } /* fill in opcode */ p0 = p; p = EPD_FILL_CARR(p, eop->op, "opcode"); if (p <= p0) { break; } /* copy opnd-list */ SKIP_SPACE(p); if (*p == ';') { eop->rest = EPD_STR_NIL; } else { eop->rest = epd_new_str(ejp); for (i = 0; i < EPD_MAX_BUF_SIZ; ++i) { register int len; len = epd_len_opnd(p); if (len <= 0) break; if (i && !epd_app_chr(ejp, ' ')) break; if (!epd_app_mem(ejp, p, len)) break; p += len; if (!IS_SPACE(*p)) break; SKIP_SPACE(p); } if (!epd_app_chr(ejp, 0)) break; /* terminate string */ } /* check proper termination */ if (*p == ';') { ++p; } else { epd_tell_err_S("missing ';' at end of EPD-op", eop->op); } } epd_dump(ejp); /* debugging */ return g_epd_errs == 0; /* ok, done */ } /*---------------------------------------------------------------------------*/ /* * Sorting and Output/Printing */ static char * /* empty string replaced by "-" */ epd_ne_str(char *str) { return (str && *str) ? str : "-"; } static int CDECL epd__cmp_op(const void *vp1, const void *vp2) { return str_cmp(((EpdOp *) vp1)->op, ((EpdOp *) vp2)->op); } static void epd_sort_ops(EpdJob * ejp) { arr_Q_sort(ejp->oparr, ejp->opfull, epd__cmp_op); } static int CDECL epd__cmp_cp(const void *vp1, const void *vp2) { return str_cmp(*((char **) vp1), *((char **) vp2)); } static void epd_sort_opndstr(char *str) { /* Input operands are seperated by a single space. Optional leading * space. */ char strbuf[EPD_MAX_BUF_SIZ]; char *ptrarr[EPD_MAX_BUF_SIZ]; int nstrs; register char *inp; register char *outp; char *ep; int i; nstrs = 0; inp = str; outp = strbuf; /* split into array of opnds */ if (*inp == ' ') ++inp; while (*inp) { ptrarr[nstrs++] = outp; for (ep = inp + epd_len_opnd(inp); inp < ep;) *outp++ = *inp++; *outp++ = 0; if (*inp == ' ') ++inp; } if (nstrs > 1) { arr_Q_sort(ptrarr, nstrs, epd__cmp_cp); /* copy back opnd array sorted */ outp = str; for (i = 0; i < nstrs; ++i) { if (i) *outp++ = ' '; for (inp = ptrarr[i]; *inp;) *outp++ = *inp++; } *outp = 0; } } static void epd_sort_in_op(EpdJob * ejp, const char *op) { EpdOp *eop; eop = epd_find_op(ejp, op); if (eop && !EPD_IS_NIL(eop->rest) && EPD_STR(ejp, eop->rest)[0]) { epd_sort_opndstr(EPD_STR(ejp, eop->rest)); } } Eximpl void epd_print(EpdJob * ejp) { /* Produce a single line of EPD output. The data need not be * standardized/sorted, yet. */ int i; g_epd_errs = 0; g_epd_phase = "output"; epd_dump(ejp); /* debugging */ if (!ejp) return; printf("%s %s %s %s", epd_ne_str(ejp->fys), epd_ne_str(ejp->tom), epd_ne_str(ejp->castle), epd_ne_str(ejp->ep)); epd_sort_in_op(ejp, "am"); epd_sort_in_op(ejp, "bm"); epd_sort_ops(ejp); for (i = 0; i < ejp->opfull; ++i) { EpdOp *eop; char *restp; eop = &(ejp->oparr[i]); if (eop->op[0]) { /* has an opcode */ printf(" %s", eop->op); if (!EPD_IS_NIL(eop->rest)) { restp = EPD_STR(ejp, eop->rest); while (*restp == ' ') ++restp; if (*restp) { printf(" %s", restp); } } printf(";"); /* terminate opcode */ } /* has an opcode */ } /* for opcode list */ printf("\n"); fflush(stdout); } /*---------------------------------------------------------------------------*/ /* * Examine EPD to derive a job. */ static int /* 0 | numeric value */ epd_op_numval(EpdJob * ejp, const char *op) { char *str; str = epd_find_op_opnds(ejp, op); return str ? atoi(str) : 0; /* FFS: atoi */ } static int /* 0 | ply depth of pv ending with '#' */ epd_pv_plies(EpdJob * ejp) { register char *p; register char *q; int plies; plies = 0; q = 0; p = epd_find_op_opnds(ejp, "pv"); if (p) { SKIP_SPACE(p); while (*p) { plies += 1; while (*p && !IS_SPACE(*p)) { q = p; ++p; } SKIP_SPACE(p); } if (!q || (q[0] != '#')) { /* no mate indicator at end ... */ plies = 0; /* ... so we know nothing */ } } return plies; } #ifndef EPD_CE_BIG # define EPD_CE_BIG 600 /* CF: going to win */ #endif Eximpl int /* 0 | depth */ epd_get_dep_prog(EpdJob * ejp, int dftdep) { /* If |ce| > 32000 --> either normal with depth or prove lost with depth * If dm > 0 --> normal with depth If pv ends in # --> normal or lost * with depth |ce| >> 0 --> normal or lost with default depth else: Try * normal and lost, default depth FFS: jobtype other than normal? We * derive a depth, and a "jobprog", or fail. */ int ce; int plies; int dm; int pv; #define RR(dep, prog) { f_jobprog = (prog); return (dep); } ce = epd_op_numval(ejp, "ce"); dm = epd_op_numval(ejp, "dm"); pv = epd_pv_plies(ejp); if (ce > 32000) { /* ce = 32767 - plies; plies = 32767 - ce plies = 2D-1; plies+1 = 2D; * D = (plies+1)/2 */ plies = 32767 - ce; if ((plies > 0) && (plies % 2)) { RR((plies + 1) / 2, JTP__normal) } } else if (ce < -32000) { /* ce = -32767 + plies; plies = ce + 32767 */ plies = ce + 32767; if ((plies > 0) && !(plies % 2)) { RR(plies / 2, JTP__lost) } } else if (dm > 0) { RR(dm, JTP__normal) } else if (pv > 0) { plies = pv; if (plies % 2) { /* odd: normal mate */ RR((plies + 1) / 2, JTP__normal) } else { RR(plies / 2, JTP__lost) } } else if (ce > EPD_CE_BIG) { RR(dftdep, JTP__normal) } else if (ce < -EPD_CE_BIG) { RR(dftdep, JTP__lost) } else { RR(dftdep, JTP__botheasy) } RR(0, 0) #undef RR } /*---------------------------------------------------------------------------*/ /* Clone/Modify EPD for move. */ Eximpl void epd_copy_move(EpdJob * src, Board * bp, const Move * mp, EpdJob * dst) { if (dst) { if (!src) { epd_clear(dst); } else { *dst = *src; dst->fys[0] = 0; dst->tom[0] = 0; dst->castle[0] = 0; dst->ep[0] = 0; if (mp) { /* We would like to mark the EPD to the effect, that this * move has been executed (if necessary). */ EpdOp *srcop; EpdOp *dstop; g_epd_errs = 0; /* FFS */ g_epd_phase = "output"; /* FFS */ srcop = epd_find_op(src, "id"); if (srcop && !EPD_IS_NIL(srcop->rest)) { dstop = epd_find_op(dst, "id"); if (dstop) { epd_del_str(dst, &(dstop->rest)); dstop->rest = epd_new_str(dst); if (!epd_app_str(dst, EPD_STR(src, srcop->rest)) || !epd_app_str(dst, " & ") || !epd_app_str(dst, mvc_SAN(bp, mp)) || !epd_app_chr(dst, 0)) { EPD_STR(dst, dstop->rest)[0] = 0; /* FFS */ } } } } } } } Eximpl void epd_use_board(EpdJob * ejp, const Board * bp) { if (ejp) { (void) app_fen_board(bp, ejp->fys, LANG_ENGLISH); (void) app_fen_tom(bp, ejp->tom, LANG_ENGLISH); (void) app_fen_castle(bp, ejp->castle, LANG_ENGLISH); (void) app_fen_ep(bp, ejp->ep, LANG_ENGLISH); } } /*---------------------------------------------------------------------------*/ /* Construct EPD from results. */ static void epd_del_our_ops(EpdJob * ejp) { /* Delete those, which we are going to produce, or have to be consistent * with those. */ epd_del_op(ejp, "dm"); epd_del_op(ejp, "ce"); epd_del_op(ejp, "acs"); epd_del_op(ejp, "acn"); epd_del_op(ejp, "am"); epd_del_op(ejp, "bm"); epd_del_op(ejp, "pv"); epd_del_op(ejp, "pm"); /* consistent with "pv" */ /* FFS: "acd" */ } static int epd_ce_for_mate(int depth) { int ce = 0; /* default, when not codable */ int plies = 2 * depth - 1; if (plies >= 0) { ce = 32767 - plies; if (ce <= 32000) ce = 0; /* out of decisive range */ } return ce; } static int epd_ce_for_lost(int depth) { int ce = 0; /* default, when not codable */ int plies = 2 * depth; /* inclusive pre-ply */ if (plies > 0) { /* note: -32767 is reserved */ ce = -32767 + plies; if (ce >= -32000) ce = 0; /* out of decisive range */ } return ce; } static EpdOp * /* 0 | --> op to use */ epd_find_or_make(EpdJob * ejp, const char *op) { EpdOp *eop; eop = epd_find_op(ejp, op); if (eop) { epd_del_str(ejp, &(eop->rest)); } else { eop = epd_new_op(ejp); if (eop && op) { epd__stuff(eop->op, (int) sizeof(eop->op), op, str_len(op)); } } return eop; } #define MX_DBL_VAL 9.0E15 /* max double we accept as exact int */ #define MX_DBL_BUF (1+15+1+1) static Bool /* success */ epd_setop_str(EpdJob * ejp, const char *op, const char *val) { EpdOp *eop; eop = epd_find_or_make(ejp, op); if (!eop) return FALSE; return epd_mk_str(ejp, val, &(eop->rest)); } static Bool /* success */ epd_setop_dbl(EpdJob * ejp, const char *op, double val) { char buf[MX_DBL_BUF + 50 /* paranoia */ ]; if ((val > MX_DBL_VAL) || (val < -MX_DBL_VAL)) { return FALSE; } sprintf(buf, "%.0f", val); return epd_setop_str(ejp, op, buf); } static Bool /* succ: whether all fit into */ epd_setop_list(EpdJob * ejp, const char *op, Board * bp, const Movelist * lp) { EpdOp *eop; const Move *mp; int cnt; if (!lp || empty_list(lp)) { return TRUE; /* FFS */ } cnt = 0; eop = epd_find_or_make(ejp, op); if (!eop) return FALSE; eop->rest = epd_new_str(ejp); formoves(lp, mp) { if (cnt && !epd_app_chr(ejp, ' ')) { return FALSE; } if (!epd_app_str(ejp, mvc_SAN(bp, mp))) { return FALSE; } ++cnt; } return epd_app_chr(ejp, 0); /* terminate */ } Eximpl Bool /* whether all fit into ... */ epd_set_result( EpdJob * ejp, /* ... this job */ Bool ismate, /* otherwise "proven * lost" */ int depth, /* of mate [without * preply] */ const char *pv, /* 0 | PV */ double secs, /* seconds used */ double nodes) { int ce = 0; Bool ok = TRUE; if (!ejp) return FALSE; g_epd_errs = 0; g_epd_phase = "output"; epd_del_our_ops(ejp); /* Start filling with the most important (in case we fail somehow). FFS: * "pm" FFS: "am"/"bm" missing */ if (ismate) { /* can force mate */ ok &= epd_setop_dbl(ejp, "dm", (double) depth); ce = epd_ce_for_mate(depth); } else { /* proven to be lost */ ce = epd_ce_for_lost(depth); } if (ce) { ok &= epd_setop_dbl(ejp, "ce", (double) ce); } if (pv && pv[0]) { ok &= epd_setop_str(ejp, "pv", pv); } if (nodes >= 0) { ok &= epd_setop_dbl(ejp, "acn", nodes); } if (secs >= 0) { ok &= epd_setop_dbl(ejp, "acs", secs); } /* FFS: we could add a comment (c0/c1...) identifying the version of * CHEST, which did produce this result. */ return ok; } Eximpl Bool /* succ: whether all fit into */ epd_set_bm(EpdJob * ejp, Board * bp, const Movelist * lp) { return epd_setop_list(ejp, "bm", bp, lp); } /*---------------------------------------------------------------------------*/