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.
This commit is contained in:
Glen Whitney 2019-08-28 12:40:50 -07:00
parent 6534aec2fd
commit 06938ec494
8 changed files with 318 additions and 100 deletions

View file

@ -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);
}
/*}}}*/

View file

@ -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

View file

@ -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 },

View file

@ -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;

View file

@ -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;
}
/*}}}*/

View file

@ -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)
{

View file

@ -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,