From 06938ec494badf16abcc078c88d0d4ef72b50077 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Wed, 28 Aug 2019 12:40:50 -0700 Subject: [PATCH] Implement a find() macro This macro looks at each cell along some stride through the sheet, returning the location of the first cell encountered at which a given expression evaluates to true, or boolean false otherwise. To make this more useful, this change also revamps the handling of boolean values, making them much more permissive and coercing to boolean type in fewer circumstances. It adds the bool() type conversion for explicitly coercing to boolean type, and fixes a small issue with reading hex-formatted floating-point values ('p' can be used as an exponent indicator). Closes #64. --- doc/teapot.lyx | 145 +++++++++++++++++++++++++++++---- src/common/eval.c | 45 ++++++---- src/common/eval.h | 1 + src/common/func.c | 190 +++++++++++++++++++++++++++++-------------- src/common/func.h | 2 +- src/common/scanner.c | 18 ++-- src/common/sheet.c | 16 ++++ src/common/sheet.h | 1 + 8 files changed, 318 insertions(+), 100 deletions(-) diff --git a/doc/teapot.lyx b/doc/teapot.lyx index daa22c1..f7dce0a 100644 --- a/doc/teapot.lyx +++ b/doc/teapot.lyx @@ -4960,7 +4960,7 @@ and \series default \emph default -y evaluates to the logical conjunction of boolean values +y evaluates to the logical conjunction of values \emph on x \emph default @@ -4979,6 +4979,32 @@ y \emph default is never evaluated, and so does not affect the value even if it is an error, for example. + In determining the value, the boolean conversions of +\emph on +x +\emph default + and +\emph on +y +\emph default + are used (see the description of the bool() function below), but the actual + value returned is either that of +\emph on +x +\emph default + or +\emph on +y +\emph default +, namely, +\emph on +x +\emph default + if it corresponds to a boolean false value or +\emph on +y +\emph default + otherwise. \end_layout \begin_layout Description @@ -5024,6 +5050,15 @@ and x \emph default is true. + Also in similar fashion, the value is +\emph on +x +\emph default + if it corresponds to a boolean true value and +\emph on +y +\emph default + otherwise. \end_layout \begin_layout Description @@ -6442,24 +6477,41 @@ or \end_layout \begin_layout Description -bool currently only acts as a keyword to + +\series medium +bool +\begin_inset space ~ +\end_inset + + +\series default +bool +\series medium +[([ +\emph on +x +\emph default +])] +\series default + Converts +\emph on +x +\emph default +to a boolean value. + This is very permissive; error values are unaffected; empty, integer 0, + float 0.0, and boolean false values are false, and everything else is true. + If +\emph on +x +\emph default + is omitted, defaults to the value of the current cell. + If the parentheses are omitted as well, acts as a keyword, for example + for testing types with \family sans \series bold is \series default -(); -\family default -there are not currently any conversions to boolean type. - Use e.g. - -\family sans - -\shape italic -expr -\shape default - != 0 -\family default -to obtain a boolean value from a numerical expression. +(). \end_layout \begin_layout Description @@ -6755,6 +6807,69 @@ function identifier, There is probably little practical call for this type. \end_layout +\begin_layout Description +find +\series medium +( +\emph on +expr +\emph default +, +\emph on + +\begin_inset space ~ +\end_inset + + +\emph default +location +\emph on + +\begin_inset space ~ +\end_inset + +stride +\emph default +[,location +\emph on + +\begin_inset space ~ +\end_inset + +start +\emph default +]) examines cells in turn, returning the location of the first one at which + +\series default +\emph on +expr +\emph default + evaluates to true, or boolean false if the boundary of the sheet is encountered. + The second argument +\emph on +stride +\emph default + must not have all zero components, and is added to the location being examined + at each iteration. + Often it is useful to use a direction constant like +\family sans +\series bold +up +\family default +\series default + for the stride. + The search begins at location +\emph on +start +\emph default +, which defaults to the current location plus the +\emph on +stride +\emph default + if it is not specified. + +\end_layout + \begin_layout Description \series medium diff --git a/src/common/eval.c b/src/common/eval.c index e9d0288..448654c 100644 --- a/src/common/eval.c +++ b/src/common/eval.c @@ -946,33 +946,42 @@ Token tne(Token l, Token r) } /*}}}*/ +/* tbool -- token considered as a boolean value */ /*{{{*/ +Token tbool(Token x) +{ + Token result; + result.type = BOOL; + result.u.bl = true; + switch (x.type) { + case EEK: return x; + case EMPTY: result.u.bl = false; return result; + case BOOL: return x; + case INT: result.u.bl = (x.u.integer != 0); return result; + case STRING: result.u.bl = (strlen(x.u.string) > 0); return result; + case FLOAT: result.u.bl = x.u.flt != 0.0; return result; + default: break; /* everything else is true */ + } + return result; +} +/*}}}*/ + /* tor -- logical or of two tokens */ /*{{{*/ Token tor(Token l, Token r) { - switch (l.type) { - case EEK: return tcopy(l); - case EMPTY : return tcopy(r); - case BOOL: - if (r.type == EMPTY || l.u.bl) return l; - return r; - default: break; - } - return operand_type_error("or: ", l.type, r.type); + if (l.type == EEK) return tcopy(l); + Token lbool = tbool(l); + if (lbool.u.bl) return tcopy(l); + return tcopy(r); } /*}}}*/ /* tand -- logical and of two tokens */ /*{{{*/ Token tand(Token l, Token r) { - switch (l.type) { - case EEK: return tcopy(l); - case EMPTY : return l; - case BOOL: - if (!l.u.bl) return l; - return r; - default: break; - } - return operand_type_error("and: ", l.type, r.type); + if (l.type == EEK) return tcopy(l); + Token lbool = tbool(l); + if (!lbool.u.bl) return tcopy(l); + return tcopy(r); } /*}}}*/ diff --git a/src/common/eval.h b/src/common/eval.h index e2660af..b32f134 100644 --- a/src/common/eval.h +++ b/src/common/eval.h @@ -25,6 +25,7 @@ Token tgt(Token l, Token r); Token teq(Token l, Token r); Token tabouteq(Token l, Token r); Token tne(Token l, Token r); +Token tbool(Token x); Token tor(Token l, Token r); Token tand(Token l, Token r); #endif diff --git a/src/common/func.c b/src/common/func.c index dae1ff9..52bd696 100644 --- a/src/common/func.c +++ b/src/common/func.c @@ -512,6 +512,54 @@ static Token dim_func(FunctionIdentifier self, int argc, const Token argv[]) } /*}}}/ + /* find_macro -- search for a cell satisfying some expression */ /*{{{*/ +static Token find_macro(FunctionIdentifier self, int argc, const Token argv[]) +{ + const char *usage = _("Usage: find(expr, loc_stride[, loc_start])"); + Token result; + if (argc < 2 || argc > 3) return duperror(&result, usage); + Token tstride = evaltoken(argv[1], FULL); + if (tstride.type == EEK) return tstride; + if (tstride.type != LOCATION) { + tfree(&tstride); + return duperror(&result, usage); + } + Location stride; + LOCATION_GETS(stride, tstride.u.location); + if (stride[X] == 0 && stride[Y] == 0 && stride[Z] == 0) + return duperror(&result, _("Stride in find() must be nonzero.")); + Location current; + if (argc == 3) { + Token tstart = evaltoken(argv[2], FULL); + if (tstart.type == EEK) return tstart; + if (tstart.type != LOCATION) { + tfree(&tstart); + return duperror(&result, usage); + } + LOCATION_GETS(current, tstart.u.location); + } else { + LOCATION_GETS(current, upd_l); + LOCATION_ADD(current, stride); + } + result.type = BOOL; + result.u.bl = false; + while (IN_OCTANT(current) && LOC_WITHIN(upd_sheet, current)) { + Token tprobe = evaluate_at(argv[0], upd_sheet, current); + Token probe = tbool(tprobe); + tfree_protected(&tprobe, probe); + if (probe.type == EEK) return probe; + assert(probe.type == BOOL); + if (probe.u.bl) { + result.type = LOCATION; + LOCATION_GETS(result.u.location, current); + return result; + } + LOCATION_ADD(current, stride); + } + return result; +} +/*}}}*/ + /* bit_func -- common implementation of the bitwise functions */ /*{{{*/ static Token bit_func(FunctionIdentifier self, int argc, const Token argv[]) { @@ -637,6 +685,21 @@ static Token self_func(FunctionIdentifier self, int argc, const Token argv[]) } /*}}}*/ +static Token bool_func(FunctionIdentifier self, int argc, const Token argv[]) +{ + assert(self == FUNC_BOOL); + Token arg; + switch (argc) { + case -1: return self_func(self, argc, argv); + case 0: arg = recompvalue(upd_sheet, upd_l); break; + case 1: arg = tcopy(argv[0]); break; + default: return duperror(&arg, _("Usage:bool[([val])]")); + } + Token result = tbool(arg); + tfree_protected(&arg, result); + return result; +} + /* empty -- ignores arguments and returns empty value */ /*{{{*/ static Token empty_func(FunctionIdentifier self, int argc, const Token argv[]) { @@ -688,48 +751,6 @@ static Token float_func(FunctionIdentifier self, int argc, const Token argv[]) } /*}}}*/ -/* number -- convert to most appropriate number type */ /*{{{*/ -static Token number_func(FunctionIdentifier self, int argc, const Token argv[]) -{ - assert(self == FUNC_NUMBER); - const char *usage = _("Usage: number[([bool|empty|int|float|string x])]"); - Token result, arg; - result.type = INT; - result.u.integer = 0; - - switch (argc) { - case -1: return self_func(self, argc, argv); - case 0: arg = recompvalue(upd_sheet, upd_l); break; - case 1: arg = tcopy(argv[0]); break; - default: return duperror(&result, usage); - } - switch (arg.type) { - case INT: return arg; - case FLOAT: return arg; - case BOOL: if (arg.u.bl) result.u.integer = 1; /* FALL THROUGH */ - case EMPTY: return result; - case STRING: { - char *p = arg.u.string; - Token *st = scan_integer(&p); - if (st != NULLTOKEN && *p == '\0') result = *st; - else { - p = arg.u.string; - st = scan_flt(&p); - if (st == NULLTOKEN || *p != '\0') - duperror(&result, _("string does not represent a number")); - else result = *st; - } - free(st); - tfree(&arg); - return result; - } - default: break; - } - tfree(&arg); - return duperror(&result, usage); -} -/*}}}*/ - /* int */ /*{{{*/ static Token int_func(FunctionIdentifier self, int argc, const Token argv[]) { @@ -786,6 +807,47 @@ static Token int_func(FunctionIdentifier self, int argc, const Token argv[]) } /*}}}*/ +/* number -- convert to most appropriate number type */ /*{{{*/ +static Token number_func(FunctionIdentifier self, int argc, const Token argv[]) +{ + assert(self == FUNC_NUMBER); + const char *usage = _("Usage: number[([bool|empty|int|float|string x])]"); + Token result, arg; + result.type = INT; + result.u.integer = 0; + + switch (argc) { + case -1: return self_func(self, argc, argv); + case 0: arg = recompvalue(upd_sheet, upd_l); break; + case 1: arg = tcopy(argv[0]); break; + default: return duperror(&result, usage); + } + switch (arg.type) { + case INT: return arg; + case FLOAT: return arg; + case BOOL: if (arg.u.bl) result.u.integer = 1; /* FALL THROUGH */ + case EMPTY: return result; + case STRING: { + char *p = arg.u.string; + Token *st = scan_integer(&p); + if (st != NULLTOKEN && *p == '\0') result = *st; + else { + p = arg.u.string; + st = scan_flt(&p); + if (st == NULLTOKEN || *p != '\0') + duperror(&result, _("string does not represent a number")); + else result = *st; + } + free(st); + tfree(&arg); + return result; + } + default: break; + } + tfree(&arg); + return duperror(&result, usage); +} +/*}}}*/ /* string */ /*{{{*/ static Token string_func(FunctionIdentifier self, int argc, const Token argv[]) @@ -972,7 +1034,16 @@ static Token region_func(RegFuncInit init, RegFuncUpdt updt, RegFuncFinl finl, Token tmp = recompvalue(upd_sheet, w); updt(id, &l, &t, &w, &tmp); tfree_protected(&tmp, t); - if (t.type == EEK) return t; + if (t.type == EEK) { + const char *templ = _("While computing %s() at &(%d,%d,%d): %s"); + Token report; + report.type = EEK; + report.u.err = + malloc(strlen(templ) + MAX_FUNC_NAME_LENGTH + 3*20 + strlen(t.u.err)); + sprintf(report.u.err, templ, tfunc[id].name, w[X], w[Y], w[Z], t.u.err); + tfree(&t); + return report; + } } if (finl != (RegFuncFinl)0) finl(id, &l, &t); return t; @@ -1539,21 +1610,22 @@ Tfunc tfunc[]= [FUNC_CARET] = { "^", accum_func, INFIX_POW, FUNCT, 0 }, /* Addressing/cell fetching/cell-position functions */ - [FUNC_AMPERSAND] = { "&", adr_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_D] = { "D", adr_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_X_AMPERSAND] = { "X&", adr_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_AT_SYMBOL] = { "@", at_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_R] = { "R", at_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_CAP_X] = { "X", at_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_RIGHT] = { "right", disp_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_LEFT] = { "left", disp_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_DOWN] = { "down", disp_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_UP] = { "up", disp_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_BELOW] = { "below", disp_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_ABOVE] = { "above", disp_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_X] = { "x", dim_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_Y] = { "y", dim_func, PREFIX_FUNC, FUNCT, 0 }, - [FUNC_Z] = { "z", dim_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_AMPERSAND] = { "&", adr_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_D] = { "D", adr_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_X_AMPERSAND] = { "X&", adr_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_AT_SYMBOL] = { "@", at_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_R] = { "R", at_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_CAP_X] = { "X", at_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_RIGHT] = { "right", disp_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_LEFT] = { "left", disp_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_DOWN] = { "down", disp_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_UP] = { "up", disp_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_BELOW] = { "below", disp_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_ABOVE] = { "above", disp_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_X] = { "x", dim_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_Y] = { "y", dim_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_Z] = { "z", dim_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_FIND] = { "find", find_macro, PREFIX_FUNC, MACRO, 0 }, /* Evaluation control functions */ [FUNC_CLOCK] = { "clock", clock_func, PREFIX_FUNC, FUNCT, 0 }, @@ -1566,7 +1638,7 @@ Tfunc tfunc[]= [FUNC_TRUE] = { "true", blcnst_func, PREFIX_FUNC, FUNCT, 0 }, /* Type conversion/testing functions and keywords */ - [FUNC_BOOL] = { "bool", self_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_BOOL] = { "bool", bool_func, PREFIX_FUNC, FUNCT, 0 }, [FUNC_EMPTY] = { "empty", empty_func, PREFIX_FUNC, FUNCT, 0 }, [FUNC_ERROR] = { "error", error_func, PREFIX_FUNC, FUNCT, 0 }, [FUNC_FIDENT] = { "fident", self_func, PREFIX_FUNC, FUNCT, 0 }, diff --git a/src/common/func.h b/src/common/func.h index 4166580..ee4128e 100644 --- a/src/common/func.h +++ b/src/common/func.h @@ -37,7 +37,7 @@ typedef enum FUNC_BOOL, FUNC_EMPTY, FUNC_FIDENT, FUNC_FUNCALL, FUNC_LIDENT, FUNC_LOCATION, FUNC_NUMBER, FUNC_OPERATOR, - FUNC_IS, + FUNC_IS, FUNC_FIND, N_FUNCTION_IDS } FunctionIdentifier; diff --git a/src/common/scanner.c b/src/common/scanner.c index bfd2c33..7a44d4c 100644 --- a/src/common/scanner.c +++ b/src/common/scanner.c @@ -133,15 +133,19 @@ static Token *charstring(char **s) Token *scan_integer(char **s) { char *r = *s; + bool ishex = (*s)[0] == '0' && (*s)[1] == 'x'; IntT i = STRTOINT(r, s, 0); - if (*s != r && **s != '.' && **s != 'e') - { - Token *n = malloc(sizeof(Token)); - n->type = INT; - n->u.integer = i; - return n; + if (*s == r + || **s == '.' || **s == 'e' || **s == 'E' || (ishex && **s == 'p')) + { /* either doesn't look like a number, or looks like a float, not an int */ + *s = r; + return NULLTOKEN; } - else { *s = r; return NULLTOKEN; } + /* looks like an int */ + Token *n = malloc(sizeof(Token)); + n->type = INT; + n->u.integer = i; + return n; } /*}}}*/ diff --git a/src/common/sheet.c b/src/common/sheet.c index 5ca7f6b..84ac653 100644 --- a/src/common/sheet.c +++ b/src/common/sheet.c @@ -611,6 +611,22 @@ Token recompvalue(Sheet *sheet, const Location at) } /*}}}*/ +/* evaluate_at -- evaluate a token in a sheet at a location */ /*{{{*/ +Token evaluate_at(Token t, Sheet *sheet, const Location at) +{ + assert(sheet != (Sheet *)0); + Sheet *oldsheet = upd_sheet; + upd_sheet = sheet; + Location old_l; + LOCATION_GETS(old_l, upd_l); + LOCATION_GETS(upd_l, at); + Token result = evaltoken(t, FULL); + LOCATION_GETS(upd_l, old_l); + upd_sheet = oldsheet; + return result; +} +/*}}}*/ + /* update -- update all cells that need it */ /*{{{*/ void update(Sheet *sheet) { diff --git a/src/common/sheet.h b/src/common/sheet.h index 35ca5c5..3c622a4 100644 --- a/src/common/sheet.h +++ b/src/common/sheet.h @@ -91,6 +91,7 @@ bool setwidth(Sheet *sheet, int x, int z, int width); int cellwidth(Sheet *sheet, const Location at); void puttok(Sheet *sheet, const Location at, Token t, TokVariety v); Token recompvalue(Sheet *sheet, const Location at); +Token evaluate_at(Token t, Sheet *sheet, const Location at); void update(Sheet *sheet); char *geterror(Sheet *sheet, const Location at); void printvalue(char *s, size_t size, size_t chars, StringFormat sf,