aboutsummaryrefslogtreecommitdiffstats
path: root/FICS/formula.c
diff options
context:
space:
mode:
authorMarkus Uhlin <markus@nifty-networks.net>2023-12-07 21:31:49 +0100
committerMarkus Uhlin <markus@nifty-networks.net>2023-12-07 21:31:49 +0100
commit79b59f9b30fb6a1fdf8c3efb446271f7cb00d434 (patch)
treef6ade4ccbc3af20d825edacfd12b5da8ded8d240 /FICS/formula.c
FICS 1.6.2
Diffstat (limited to 'FICS/formula.c')
-rw-r--r--FICS/formula.c593
1 files changed, 593 insertions, 0 deletions
diff --git a/FICS/formula.c b/FICS/formula.c
new file mode 100644
index 0000000..dbdd798
--- /dev/null
+++ b/FICS/formula.c
@@ -0,0 +1,593 @@
+/* Formula program for FICS. Written by Dave Herscovici
+ (dhersco@math.nps.navy.mil)
+ Edited by DAV to include wild,non-std and untimed
+ lightning flags
+
+ Operators allowed, in order of precedence:
+ ! (not), - (unary),
+ *, /,
+ +, -,
+ <, <=, =<, >, >=, =>, (<= and =< are equivalent, as are >= and =>)
+ =, ==, !=, <>, (two ways of saying both equals and not equals)
+ &, &&, (both "and")
+ |, ||, (both "or").
+
+ Parentheses () are also allowed, as is a pound sign '#' for comments.
+ The program will divide by a fudge factor of .001 instead of 0.
+
+ Variables allowed:
+ time, inc, rating, myrating, rated, blitz, standard, lightning, registered,
+ assesswin, assessdraw, assessloss, timeseal,
+ ratingdiff = rating - myrating, private, wild, untimed, nonstandard,
+ maxtime(n) = maximum time n moves will take (in seconds),
+ mymaxtime(n) = same, but just count my time.
+ f1, f2, f3, f4, f5, f6, f7, f8, f9.
+
+ The code for translating blitz and standard variables may have to be
+ redone. F1 through f9 are user-defined formula variables. They can
+ be used to avoid having to retype your entire formula when you want
+ to change one part of it, or to compensate for the lack of a 'mood'
+ variable.
+
+ For example:
+ set f1 rated & time=5 & inc=0 # rated 5 minute games
+ set f2 rating - myrating
+ set f3 # this line is a random comment.
+ set f4 f2>400 # I want a REAL fight
+
+ Then you can type:
+ set formula f1 # rated 5 min. games only
+ set formula etime >= 10 & f2 > -100 # long games, decent competition
+ set formula f1 & !f4
+ or set formula f2 >= 0 | blitz
+ depending on your mood.
+
+ Further modifications could account for starting positions, time
+ odds games, provisional or established opponents, etc. Maybe f0
+ should be reserved for your formula upon logging in; i.e. if f0
+ is set, your formula is automatically set to f0 when you log in.
+*/
+
+#include <ctype.h>
+#include "config.h"
+#include "formula.h"
+#include "playerdb.h"
+#include "gamedb.h"
+#include "command.h"
+#include "common.h"
+#include "stdinclude.h"
+#include "utils.h"
+#include "rmalloc.h"
+#include "lists.h"
+#include "network.h"
+#include "ratings.h"
+
+extern player parray[];
+
+PRIVATE char *GetPlayersFormula (player *pWho, int clause)
+{
+ if (clause==MAX_FORMULA) return (pWho->formula);
+ else return (pWho->formulaLines[clause]);
+}
+
+/* In MoveIndexPastString, *i is treated as the index into string[];
+ this function returns 1 if the character string beginning with
+ string[*i] match *text, and 0 otherwise. In the former case, *i
+ is incremented to move the index past *text.
+*/
+int MoveIndexPastString (char *string, int *i, char *text)
+{
+ int n = strlen(text);
+ if (strncasecmp(text, string + *i, n)) return (0);
+ *i += n;
+ return (n);
+} /* end of function MoveIndexPastString. */
+
+/* GetRating simply chooses between blitz, standard and ratings. */
+int GetRating(player *p, int gametype)
+{
+ if (gametype == TYPE_BLITZ) return (p->b_stats.rating);
+ else if (gametype == TYPE_STAND) return (p->s_stats.rating);
+ else if (gametype == TYPE_WILD) return (p->w_stats.rating);
+ else if (gametype == TYPE_LIGHT) return (p->l_stats.rating);
+ else if (gametype == TYPE_BUGHOUSE) return (p->bug_stats.rating);
+ else return 0;
+} /* end of function GetRating. */
+
+int GetNumberInsideParens (game *g, int clause, int *i, int *token,
+ int eval)
+{
+ char *string = GetPlayersFormula(&parray[g->black], clause);
+ int ret;
+
+ while (string[*i] != '\0' && isspace(string[*i])) (*i)++;
+ if (!MoveIndexPastString(string, i, "(")) return (ERR_BADCHAR);
+
+ ret = CheckFormula(g, clause, i, OPTYPE_PAREN, token, eval);
+ if (ret != ERR_NONE)
+ return (ret);
+ if (MoveIndexPastString(string, i, ")")) return (ERR_NONE);
+ else return (ERR_PAREN);
+}
+
+int Maxtime (game *g, int numMoves, int numPlayers)
+{
+ int max;
+
+ if ((g->bInitTime == g->wInitTime) && (g->bIncrement == g->wIncrement)) {
+ max = numPlayers * (60 * g->wInitTime + numMoves * g->wIncrement);
+ if ((g->type != TYPE_UNTIMED) && (g->wInitTime == 0))
+ max +=10 * numPlayers; /* 0 x start with ten secs */
+
+ } else if (numPlayers == 2) {
+ max = 60 * (g->wInitTime + g->bInitTime)
+ + numMoves * (g->wIncrement + g->bIncrement);
+ if ((g->type != TYPE_UNTIMED) && (g->wInitTime == 0))
+ max +=10; /* 0 x start with ten secs */
+ if ((g->type != TYPE_UNTIMED) && (g->bInitTime == 0))
+ max +=10; /* 0 x start with ten secs */
+
+ } else {
+ max = 60 * g->bInitTime + numMoves * g->bIncrement;
+ if ((g->type != TYPE_UNTIMED) && (g->bInitTime == 0))
+ max +=10; /* 0 x start with ten secs */
+ }
+
+ return max;
+}
+
+
+/* The black player in game *g has been challenged. S/he has a list
+ of formulas, and we're checking the one given by the index clause
+ (MAX_FORMULA denotes the actual formula; if clause is smaller,
+ we're looking at one of the user-defined formulas). We're at
+ position *i in the formula. If we find a legitimate variable there
+ and eval is 1, VarToToken puts its value in *tok; if eval is 0,
+ just move past the variable. Returns 1 or 0, depending on whether
+ a legitimate variable was found.
+*/
+int VarToToken (game *g, int clause, char *string, int *i, int *tok, int eval)
+{
+ player *me = &parray[g->black];
+ player *you = &parray[g->white];
+ int index=0, c;
+ double dummy_sterr;
+
+ /* We list possible variables with the longest names first. */
+ if (MoveIndexPastString(string, i, "registered")) *tok=you->registered;
+ else if (MoveIndexPastString(string, i, "ratingdiff"))
+ *tok = GetRating(you, g->type) - GetRating(me, g->type);
+ else if (MoveIndexPastString(string, i, "assessloss"))
+ {
+ if (g->rated)
+ rating_sterr_delta(g->black, g->white, g->type,
+ time(0), RESULT_LOSS, tok, &dummy_sterr);
+ else *tok = 0;
+ }
+ else if (MoveIndexPastString(string, i, "assessdraw"))
+ {
+ if (g->rated)
+ rating_sterr_delta(g->black, g->white, g->type,
+ time(0), RESULT_DRAW, tok, &dummy_sterr);
+ else *tok = 0;
+ }
+ else if (MoveIndexPastString(string, i, "assesswin"))
+ {
+ if (g->rated)
+ rating_sterr_delta(g->black, g->white, g->type,
+ time(0), RESULT_WIN, tok, &dummy_sterr);
+ else *tok = 0;
+ }
+ else if (MoveIndexPastString(string, i, "mymaxtime"))
+ {
+ if (GetNumberInsideParens(g, clause, i, tok, eval) != ERR_NONE)
+ return (0);
+ *tok = Maxtime (g, *tok, 1);
+ }
+#ifdef TIMESEAL
+ else if (MoveIndexPastString(string, i, "timeseal"))
+ *tok = con[you->socket].timeseal;
+#endif
+ else if (MoveIndexPastString(string, i, "myrating"))
+ *tok = GetRating(me, g->type);
+ else if (MoveIndexPastString(string, i, "computer"))
+ *tok = in_list(-1, L_COMPUTER, you->name);
+ else if (MoveIndexPastString(string, i, "standard"))
+ *tok = (g->type == TYPE_STAND);
+ else if (MoveIndexPastString(string, i, "private"))
+ *tok = you->private;
+ else if (MoveIndexPastString(string, i, "maxtime"))
+ {
+ if (GetNumberInsideParens(g, clause, i, tok, eval) != ERR_NONE)
+ return (0);
+ *tok = Maxtime (g, *tok, 2);
+ }
+ else if (MoveIndexPastString(string, i, "abuser"))
+ *tok = in_list(-1, L_ABUSER, you->name);
+ else if (MoveIndexPastString(string, i, "rating"))
+ *tok = GetRating(you, g->type);
+ else if (MoveIndexPastString(string, i, "rated")) *tok=g->rated;
+ else if (MoveIndexPastString(string, i, "blitz")) *tok = (g->type == TYPE_BLITZ);
+ else if (MoveIndexPastString(string, i, "wild")) *tok = (g->type == TYPE_WILD);
+ else if (MoveIndexPastString(string, i, "lightning")) *tok = (g->type == TYPE_LIGHT);
+ else if (MoveIndexPastString(string, i, "nonstandard")) *tok = (g->type == TYPE_NONSTANDARD);
+ else if (MoveIndexPastString(string, i, "untimed")) *tok = (g->type == TYPE_UNTIMED);
+ else if (MoveIndexPastString(string, i, "time")) *tok=g->wInitTime;
+ else if (MoveIndexPastString(string, i, "inc")) *tok=g->wIncrement;
+ else if (tolower(string[*i])=='f' && isdigit(string[*i+1]) &&
+ (c = (string[*i+1]-'1')) >= 0 && clause > c)
+ {
+ *i += 2;
+ return (!CheckFormula (g, c, &index, OPTYPE_NONE, tok, eval));
+ }
+ else return (0);
+ return (1);
+} /* end of function VarToToken. */
+
+/* ScanForOp checks for an operator at position *i in string[]. */
+int ScanForOp (char *string, int *i)
+{
+ while (string[*i] != '\0' && isspace(string[*i])) (*i)++;
+
+ if (string[*i] == '\0') return (OP_NONE);
+ if (string[*i] == '#') return (OP_NONE);
+ if (string[*i] == ')') return (OP_RTPAREN);
+
+ /* Two char operators and longer first. */
+ if (MoveIndexPastString(string, i, "and")) return (OP_AND);
+ if (MoveIndexPastString(string, i, "or")) return (OP_OR);
+ if (MoveIndexPastString(string, i, "||")) return (OP_OR);
+ if (MoveIndexPastString(string, i, "&&")) return (OP_AND);
+ if (MoveIndexPastString(string, i, "==")) return (OP_EQ);
+ if (MoveIndexPastString(string, i, "!=")) return (OP_NEQ);
+ if (MoveIndexPastString(string, i, "<>")) return (OP_NEQ);
+ if (MoveIndexPastString(string, i, ">=")) return (OP_GE);
+ if (MoveIndexPastString(string, i, "=>")) return (OP_GE);
+ if (MoveIndexPastString(string, i, "<=")) return (OP_LE);
+ if (MoveIndexPastString(string, i, "=<")) return (OP_LE);
+
+ /* One char operators now. */
+ if (MoveIndexPastString(string, i, "|")) return (OP_OR);
+ if (MoveIndexPastString(string, i, "&")) return (OP_AND);
+ if (MoveIndexPastString(string, i, ">")) return (OP_GT);
+ if (MoveIndexPastString(string, i, "<")) return (OP_LT);
+ if (MoveIndexPastString(string, i, "=")) return (OP_EQ);
+ if (MoveIndexPastString(string, i, "+")) return (OP_PLUS);
+ if (MoveIndexPastString(string, i, "-")) return (OP_MINUS);
+ if (MoveIndexPastString(string, i, "*")) return (OP_MULT);
+ if (MoveIndexPastString(string, i, "/")) return (OP_DIV);
+ return (OP_BAD);
+} /* end of function ScanForOp. */
+
+/* OpType returns the precedence of the operator op. */
+int OpType (int op)
+{
+ switch (op)
+ {
+ case OP_BAD: return (OPTYPE_BAD);
+ case OP_NONE: return (OPTYPE_NONE);
+ case OP_RTPAREN: return (OPTYPE_RPAREN);
+ case OP_OR: return (OPTYPE_OR);
+ case OP_AND: return (OPTYPE_AND);
+ case OP_EQ: case OP_NEQ: return (OPTYPE_COMPEQ);
+ case OP_GT: case OP_GE: case OP_LT: case OP_LE: return (OPTYPE_COMPGL);
+ case OP_PLUS: case OP_MINUS: return (OPTYPE_ADD);
+ case OP_MULT: case OP_DIV: return (OPTYPE_MULT);
+ case OP_NEGATE: case OP_NOT: return (OPTYPE_UNARY);
+ default: return (OPTYPE_BAD);
+ }
+} /* end of function OpType. */
+
+/* In EvalBinaryOp, *left is the left operand; and op is the
+ current operator. *g and clause specify which formula string to
+ look at (we're checking parray[g->black].formulaLines[clause]),
+ and *i tells us where we are in the string. We look for a right
+ operand from position *i in the string, and place the expression
+ (*left op right) in *left. For example, if *left=6, op =
+ OP_MULT, and we pull off right = 4, we replace *left with
+ 6*4=24. Returns 0 if no error; otherwise indicates the error.
+*/
+int EvalBinaryOp (int *left, int op, game *g, int clause, int *i)
+{
+ int right, ret;
+ if ((op==OP_OR) && (*left != 0)) /* Nothing to decide. */
+ {
+ *left = 1;
+ return (CheckFormula (g, clause, i, OpType(op), &right, 0));
+ }
+ else if ((op==OP_AND) && (*left == 0)) /* Nothing to decide. */
+ return (CheckFormula (g, clause, i, OpType(op), &right, 0));
+
+ else {
+ ret = CheckFormula (g, clause, i, OpType(op), &right, 1);
+ if (ret != ERR_NONE) return (ret);
+ }
+ switch (op)
+ {
+ default: case OP_BAD: return (ERR_BADOP);
+ case OP_NONE: case OP_RTPAREN: return (ERR_NONE);
+ case OP_OR: *left = (*left || right); return (ERR_NONE);
+ case OP_AND: *left = (*left && right); return (ERR_NONE);
+ case OP_EQ: *left = (*left == right); return (ERR_NONE);
+ case OP_NEQ: *left = (*left != right); return (ERR_NONE);
+ case OP_GT: *left = (*left > right); return (ERR_NONE);
+ case OP_GE: *left = (*left >= right); return (ERR_NONE);
+ case OP_LT: *left = (*left < right); return (ERR_NONE);
+ case OP_LE: *left = (*left <= right); return (ERR_NONE);
+ case OP_PLUS: *left += right; return (ERR_NONE);
+ case OP_MINUS: *left -= right; return (ERR_NONE);
+ case OP_MULT: *left *= right; return (ERR_NONE);
+ case OP_DIV:
+ if (right != 0)
+ {
+ *left /= right;
+ return (ERR_NONE);
+ }
+ else
+ {
+ pprintf(g->black, "Dividing by %lf instead or zero in formula.\n",
+FUDGE_FACTOR);
+ *left /= FUDGE_FACTOR;
+ return (ERR_NONE);
+ }
+ }
+} /* end of function EvalBinaryOp. */
+
+/* If eval is 1, ScanForNumber evaluates the number at position *i
+ in the formula given by *g and clause, and place this value in
+ *token. op_type is the precedence of the operator preceding the
+ *i'th position. If we come to an operator of higher precedence,
+ we must keep going before we can leave this function. If eval
+ is 0, just move past the number we would evaluate. Returns 0 if
+ no error; otherwise return code indicates the error.
+*/
+int ScanForNumber (game *g, int clause, int *i, int op_type,
+ int *token, int eval)
+{
+ char *string = GetPlayersFormula (&parray[g->black], clause);
+ char c;
+ int ret;
+
+ while ((string[*i] != '\0') && (isspace(string[*i]))) (*i)++;
+ switch (c = string[*i])
+ {
+ case '\0': case '#':
+ if (op_type != OPTYPE_NONE) return (ERR_EOF);
+ else *token=1;
+ return (ERR_NONE);
+
+ case ')':
+ if (op_type != OPTYPE_PAREN) return (ERR_PAREN);
+ else *token=1;
+ return(ERR_NONE);
+
+ case '(': return GetNumberInsideParens(g,clause,i,token,eval);
+
+ case '-': case '!':
+ ++(*i);
+ if (c==string[*i]) return(ERR_UNARY);
+ ret = ScanForNumber(g,clause,i,OPTYPE_UNARY,token,eval);
+ if (ret != ERR_NONE)
+ return (ret);
+ if (c == '-') *token = -(*token);
+ else if (c == '!') *token = !(*token);
+ return (ERR_NONE);
+
+ default:
+ if (isdigit(string[*i]))
+ {
+ *token = 0;
+
+ do *token = 10 * (*token) + string[(*i)++] - '0';
+ while (isdigit(string[*i]));
+
+ while (string[*i] != '\0' && isspace(string[*i])) (*i)++;
+ if (MoveIndexPastString (string, i, "minutes"))
+ *token *= 60;
+ return (ERR_NONE);
+ }
+ else if (isalpha(string[*i]))
+ {
+ if (!VarToToken (g, clause, string, i, token, eval))
+ return (ERR_BADVAR);
+ return(ERR_NONE);
+ }
+ else return (ERR_NONESENSE);
+ }
+} /* end of function ScanForNumber. */
+
+/* If eval is 1, CheckFormula looks for the next token in the
+ formula given by *g, clause, and *i; usually this is the right
+ side of an expression whose operator has precedence OpType; if
+ eval is 0, just go to the end of an expression. Return 0 if no
+ error; otherwise, return error type.
+*/
+int CheckFormula (game *g, int clause, int *i, int op_type,
+ int *result, int eval)
+{
+ int token, ret, nextOp, lastPos;
+ char *string = GetPlayersFormula (&parray[g->black], clause);
+
+ if (string == NULL)
+ {
+ *result = 1;
+ return (ERR_NONE);
+ }
+ ret = ScanForNumber (g, clause, i, op_type, &token, eval);
+ if (ret != ERR_NONE)
+ return (ret);
+ lastPos = *i;
+ nextOp = ScanForOp (string, i);
+ while (OpType(nextOp) > op_type) /* higher precedence. */
+ {
+ if (nextOp==OP_RTPAREN) return (ERR_PAREN);
+ if (!eval)
+ ret = CheckFormula(g, clause, i, OpType(nextOp), &token, 0);
+ else ret = EvalBinaryOp (&token, nextOp, g, clause, i);
+ if (ret != ERR_NONE) return (ret);
+ lastPos = *i;
+ nextOp = ScanForOp (string, i);
+ }
+ if (nextOp == OP_BAD) return(ERR_BADOP);
+ *result = token;
+
+ /* move back unless we're at the end or at a right paren, in which
+ case we never went forward. */
+ if (nextOp != OP_NONE && nextOp != OP_RTPAREN) *i=lastPos;
+ return (ERR_NONE);
+} /* end of function CheckFormula. */
+
+/* which clauses are relevant for a player's formula. */
+PRIVATE int ChooseClauses(player *who, char *formula)
+{
+ int i, which, ret=0;
+
+ if (formula == NULL)
+ return ret;
+ for (i=0; formula[i] != '\0' && formula[i] != '#'; i++) {
+ if (formula[i] != 'f' || (i > 0 && isalnum(formula[i-1]))
+ || !isdigit(formula[i+1]))
+ continue;
+ sscanf(&formula[i], "f%d", &which);
+ ret |= (1 << (which - 1));
+ }
+ /* now scan clauses found as part of the formula. */
+ for (i = MAX_FORMULA - 1; i >= 0; i--) {
+ if (ret & (1 << i)) {
+ ret |= ChooseClauses(who, who->formulaLines[i]);
+ }
+ }
+ return ret;
+} /* end of function ChooseClauses. */
+
+void ExplainFormula (game *g, textlist **clauses)
+{
+ int i, which, dummy_index, value;
+ char txt[20];
+ player *challenged = &parray[g->black];
+ textlist **Cur = clauses;
+
+ which = ChooseClauses(challenged, challenged->formula);
+
+ for (i = 0; i < MAX_FORMULA; i++) {
+ if ((which & (1 << i)) == 0)
+ continue;
+ dummy_index = 0;
+ CheckFormula (g, i, &dummy_index, OPTYPE_NONE, &value, 1);
+ sprintf(txt, "%d", value);
+ SaveTextListEntry (Cur, txt, i);
+ Cur = &(*Cur)->next;
+ }
+}
+
+#if 0
+void ExplainFormula (game *g, textlist *eval)
+{
+ int i, newPos, value;
+ char Head[MAX_STRING_LENGTH],
+ Tail[MAX_STRING_LENGTH];
+
+ strcpy (strRet, GetPlayersFormula(&parray[g->black], clause));
+ for (i=0; strRet[i] != '\0'; i++)
+ {
+ if (strRet[i] == '#')
+ {
+ strRet[i] = '\0';
+ break;
+ }
+ if (!isalpha(strRet[i])) continue;
+ newPos = i;
+ if (!VarToToken(g, clause, strRet, &newPos, &value, 1))
+ i = newPos;
+ else
+ {
+ strcpy (Head, strRet);
+ strcpy (Tail, strRet + newPos);
+ Head[i] = '\0';
+
+ sprintf(strRet, "%s%d%s", Head, value, Tail);
+ while (isalpha(strRet[i])) i++;
+ }
+ }
+}
+#endif
+
+/* GameMatchesFormula converts parameters to a game structure
+ and passes a pointer to this game to CheckFormula for
+ evaluation. It returns the evaluated formula.
+*/
+PUBLIC int GameMatchesFormula (int w, int b, int wTime, int wInc, int bTime,
+ int bInc, int rated, int gametype, textlist **clauseList)
+{
+ game g;
+ int index=0, ret;
+
+ g.white = w; g.black = b;
+ g.wInitTime = wTime; g.bInitTime = bTime;
+ g.wIncrement = wInc; g.bIncrement = bInc;
+ g.rated = rated;
+ g.type = gametype;
+
+ if (CheckFormula(&g, MAX_FORMULA, &index, OPTYPE_NONE, &ret, 1) != ERR_NONE)
+ return (0);
+ if (ret == 0) ExplainFormula (&g, clauseList);
+ return (ret);
+} /* end of function GameMatchesFormula. */
+
+/* SetValidFormula sets a clause of player p and creates a game structure
+ to check whether that new formula is legitimate. If so, return 1;
+ otherwise, reset the formula and return 0.
+*/
+int SetValidFormula (int p, int clause, char *string)
+{
+ game g;
+ int index=0, ret, err=ERR_NONE;
+ char *Old=NULL, **Cur;
+ player *me = &parray[p];
+
+ if (clause==MAX_FORMULA)
+ Cur = &me->formula;
+ else
+ Cur = &me->formulaLines[clause];
+
+ Old = *Cur;
+
+ if (string != NULL) {
+ string = eatwhite(string);
+ *Cur = (*string != '\0' ? strdup (string) : NULL);
+ }
+ else *Cur = NULL;
+
+ if (*Cur==NULL) {
+ if (Old != NULL) rfree(Old);
+ return 1;
+ }
+
+ g.white = g.black = p;
+ g.wInitTime = g.bInitTime = me->d_time;
+ g.wIncrement = g.bIncrement = me->d_inc;
+ g.rated = me->rated;
+ g.type = TYPE_BLITZ;
+ err = CheckFormula (&g, clause, &index, OPTYPE_NONE, &ret, 0);
+
+ if (err != ERR_NONE) {
+ /* Bad formula; reset it. */
+ rfree(*Cur);
+ *Cur = Old;
+ } else {
+ if (Old != NULL) rfree(Old);
+ }
+ return (err == ERR_NONE);
+}
+
+PUBLIC void ShowClauses (int p, int p1, textlist *clauses)
+{
+ textlist *Cur;
+
+ if (clauses != NULL)
+ pprintf(p, "\n");
+ for (Cur = clauses; Cur != NULL; Cur = Cur->next) {
+ pprintf(p, "f%d=%s: %s\n", Cur->index + 1, Cur->text,
+ parray[p1].formulaLines[Cur->index]);
+ }
+}