Add BOOL token_type, macros, and boolean operators

For clarity, it is valuable for the results of comparisons to show as "true"
  and "false" rather than "1" and "0". This change implements a BOOL type
  to facilitate that. Since we want logical operators to short-circuit, this
  change also obliges the introduction of macros, which are just like functions
  except their arguments are not evaluated before control is passed to the
  routine implementing the macro.
This commit is contained in:
Glen Whitney 2019-08-26 23:56:19 -07:00
parent 7e0ba7370d
commit 892fdcdb75
10 changed files with 871 additions and 688 deletions

View File

@ -647,7 +647,7 @@ Paper
\begin_layout Standard
As the last step, save your work sheet to a file: F)ile, S)ave.
The native file format is XDR, so choose that.
The native file format is Teapot ASCII, so choose that.
Up to now, your sheet does not have a name, so you will be prompted for
one:
\end_layout
@ -4872,8 +4872,26 @@ Integer Integer values are exact, their range depends on the C type long
An example is:
\family typewriter
42
\family default
\end_layout
\begin_layout Description
Boolean
\begin_inset Quotes eld
\end_inset
true
\begin_inset Quotes erd
\end_inset
or
\begin_inset Quotes eld
\end_inset
false,
\begin_inset Quotes erd
\end_inset
used for logical conditions.
\end_layout
\begin_layout Description
@ -4919,6 +4937,93 @@ Unlike other spreadsheets, the operators in teapot check the type of the
\end_layout
\begin_layout Description
x
\series medium
\emph on
\begin_inset space ~
\end_inset
\family typewriter
\series default
\emph default
and
\family default
\series medium
\emph on
\begin_inset space ~
\end_inset
\series default
\emph default
y evaluates to the logical conjunction of boolean values
\emph on
x
\emph default
and
\emph on
y
\emph default
.
Note that this evaluation short-circuits: if
\emph on
x
\emph default
is false, then
\emph on
y
\emph default
is never evaluated, and so does not affect the value even if it is an error,
for example.
\end_layout
\begin_layout Description
x
\series medium
\emph on
\begin_inset space ~
\end_inset
\family typewriter
\series default
\emph default
or
\family default
\series medium
\emph on
\begin_inset space ~
\end_inset
\series default
\emph default
y evaluates to the logical disjunction of boolean values
\emph on
x
\emph default
and
\emph on
y
\emph default
.
As with
\family typewriter
and
\family default
, this evaluation short-circuits if
\emph on
x
\emph default
is true.
\end_layout
\begin_layout Description
\series bold
@ -4932,7 +5037,7 @@ x
y
\series default
\emph default
evaluates to 1 if
evaluates to Boolean true if
\emph on
x
\emph default
@ -4979,7 +5084,7 @@ x
y
\series default
\emph default
evaluates to 1 if
evaluates to true if
\emph on
x
\emph default
@ -5004,7 +5109,7 @@ x
y
\series default
\emph default
evaluates to 1 if
evaluates to true if
\emph on
x
\emph default
@ -5029,7 +5134,7 @@ x
y
\series default
\emph default
evaluates to 1 if
evaluates to true if
\emph on
x
\emph default
@ -5055,7 +5160,7 @@ x
y
\series default
\emph default
evaluates to 1 if
evaluates to true if
\emph on
x
\emph default
@ -5079,7 +5184,7 @@ x
y
\series default
\emph default
evaluates to 1 if the floating point value
evaluates to true if the floating point value
\emph on
x
\emph default
@ -5088,8 +5193,9 @@ x
y
\emph default
.
Almost equal means, the numbers are at most neighbours.
It is an error to use this with any type but float.
Almost equal means that the numbers are equal or neighbor each other in
the floating point representation being used.
It is an error to use this comparison with any type but float.
\end_layout
\begin_layout Description
@ -5116,6 +5222,26 @@ y
.
\end_layout
\begin_layout Description
Note: a string of consecutive relational operators is interpreted as the
conjunction of each consecutive (overlapping) pair.
Thus
\family typewriter
2 <= y() <= 10
\family default
will evaluate to true precisely in rows 2 through 10, inclusive (it is
shorthand for
\family typewriter
2 <= y() and y() <= 10
\family default
).
Similarly
\family typewriter
@(cell1) == @(cell2) == @(cell3)
\family default
will return true exactly when all three cells have the same value.
\end_layout
\begin_layout Description
\series bold
@ -5129,7 +5255,7 @@ x
y
\series default
\emph default
evaluates to the sum if
evaluates to the sum if
\emph on
x
\emph default
@ -5148,16 +5274,6 @@ y
\emph default
are strings, the result is the concatenated string.
If they are locations, this operates componentwise.
There is no dedicated logical
\noun on
or
\noun default
operation, so use
\family typewriter
+
\family default
for that.
\end_layout
\begin_layout Description
@ -5173,7 +5289,7 @@ x
y
\series default
\emph default
evaluates to the difference if
evaluates to the difference if
\emph on
x
\emph default
@ -5198,7 +5314,7 @@ x
y
\series default
\emph default
evaluates to the product if
evaluates to the product if
\emph on
x
\emph default
@ -5207,15 +5323,6 @@ x
y
\emph default
are numbers.
There is no dedicated logical
\noun on
and
\noun default
operation, so use
\family typewriter
*
\family default
for that.
You can multiply a location by an integer, or take the dot product of the
coordinates of two locations (although the use case for that is unclear).
\end_layout
@ -5233,7 +5340,7 @@ x
y
\series default
\emph default
evaluates to the quotient if
evaluates to the quotient if
\emph on
x
\emph default
@ -5258,7 +5365,7 @@ x
y
\series default
\emph default
evaluates to the remainder of the division if
evaluates to the remainder of the division if
\emph on
x
\emph default
@ -5273,24 +5380,6 @@ y
\begin_layout Description
\series bold
\emph on
x^y
\series default
\emph default
evaluates to
\emph on
x
\emph default
to the power of
\emph on
y
\emph default
.
\end_layout
\begin_layout Description
\family typewriter
\series bold
-
@ -5310,13 +5399,38 @@ x
\emph on
x
\emph default
is a number or location.
is a number or location, or the negation of
\emph on
x
\emph default
if
\emph on
x
\emph default
is Boolean.
If
\emph on
x
\emph default
is empty, the result will be empty, too.
\end_layout
\begin_layout Description
\series bold
\emph on
x^y
\series default
\emph default
evaluates to
\emph on
x
\emph default
to the power of
\emph on
y
\emph default
.
\end_layout
\begin_layout Description
@ -5517,8 +5631,8 @@ the arguments are interpreted in exactly the same way as for
\series bold
&
\series default
with no arguments or even parentheses evaluates to the location of the
current cell.
with no arguments (with or without parentheses) evaluates to the location
of the current cell.
\end_layout
\begin_layout Description
@ -5603,14 +5717,21 @@ displaced (by).
\begin_inset Quotes erd
\end_inset
Thus,
Thus, both
\family sans
\series bold
D
\series default
(-1)
\family default
returns the location of the cell immediately to the left of this one, and
and
\family sans
\series bold
D
\series default
(left)
\family default
return the location of the cell immediately to the left of this one, and
\family sans
\series bold
D
@ -5690,12 +5811,19 @@ R
\series default
(,,1)
\family default
returns the same cell as this one but on the following layer, as does
returns the same cell as this one but on the following layer, as do
\family sans
\series bold
R
\series default
(&(0,0,1)
\family default
) and
\family sans
\series bold
R
\series default
(&(0,0,1)
(below
\family default
), but
\family sans
@ -5703,7 +5831,8 @@ R
R
\family default
\series default
(TABLE,,1) returns the value of the cell just below the one labeled
(TABLE,,1) returns the value of the cell immediately down from the one labeled
\begin_inset Quotes eld
\end_inset
@ -6553,6 +6682,18 @@ location
\begin_layout Description
\series medium
boolean
\begin_inset space ~
\end_inset
\series default
false represents the false Boolean value.
\end_layout
\begin_layout Description
\series medium
float
\begin_inset space ~
@ -8003,6 +8144,18 @@ status open
\begin_layout Description
\series medium
boolean
\begin_inset space ~
\end_inset
\series default
true represents the true Boolean value.
\end_layout
\begin_layout Description
\series medium
float
\begin_inset space ~
@ -8456,7 +8609,11 @@ negterm
\end_layout
\begin_layout Description
powterm::=
powterm::= [
\family typewriter
-
\family default
]
\emph on
primary
\emph default
@ -8516,7 +8673,7 @@ mathterm
\end_layout
\begin_layout Description
term::=
relterm::=
\emph on
factor
\emph default
@ -8551,6 +8708,48 @@ factor
}
\end_layout
\begin_layout Description
conjterm::=
\emph on
relterm
\family sans
\emph default
{
\family typewriter
and
\family sans
\family default
\emph on
relterm
\family sans
\emph default
}
\end_layout
\begin_layout Description
term::=
\emph on
conjterm
\family sans
\emph default
{
\family typewriter
or
\family sans
\family default
\emph on
conjterm
\family sans
\emph default
}
\end_layout
\begin_layout Subsection
Implementation notes
\end_layout
@ -8634,6 +8833,20 @@ round
to be used as keywords for the int() function.
\end_layout
\begin_layout Standard
Note also that some functions are actually implemented as
\emph on
macros
\emph default
, which are just like functions but receive their arguments unevaluated.
The difference is meant to be transparent to usage of the function, so
it is not mentioned elsewhere in this documentation.
However, the macro facility is necessary when the function would need to
control the evaluation of the arguments, as in the short-circuiting logical
operations, or takes an expression as an argument, say to evaluate it on
other cells.
\end_layout
\begin_layout Section
Frequently Asked Questions
\end_layout

View File

@ -425,35 +425,82 @@ size_t ptokatprec(char* dest, size_t size, size_t field_width, StringFormat sf,
default: /* infix argument */
assert(tok->u.funcall.argc > 1);
if (fp < atprec && cur < size) dest[cur++] = '(';
for (size_t ai = 0; ai < tok->u.funcall.argc && cur < size-1; ++ ai)
{
bool parenarg = false;
if (ai > 0) {
if (fp < INFIX_MUL && cur < size) dest[cur++] = ' ';
const char *use = tfunc[fid].display_symbol;
if (use == NULL) use = tfunc[fid].name;
strncpy(dest+cur, use, size-cur-1);
cur += strlen(use);
if (fp < INFIX_MUL && cur < size) dest[cur++] = ' ';
} else if (fp > PREFIX_NEG) {
char powbuf[4096];
size_t arglen = ptokatprec(powbuf, sizeof(powbuf), 0, sf, ff,
digits, ef, tok->u.funcall.argv, fp);
assert(arglen < sizeof(powbuf)-1);
if (powbuf[0] == '-') {
parenarg = true;
if (cur < size) dest[cur++] = '(';
/* Check for the special relation sequence notation */
bool specialrel = true;
if (fid != FUNC_AND || tok->u.funcall.argc < 2) specialrel = false;
else {
for (int ai = 0; ai < tok->u.funcall.argc; ++ai) {
if (tok->u.funcall.argv[ai].type != FUNCALL
|| tok->u.funcall.argv[ai].u.funcall.argc != 2
|| !IS_RELATION_FUNC(tok->u.funcall.argv[ai].u.funcall.fident))
{
specialrel = false;
break;
}
if (ai > 0
&& !tok_matches(tok->u.funcall.argv[ai].u.funcall.argv,
tok->u.funcall.argv[ai-1].u.funcall.argv+1))
{
specialrel = false;
break;
}
}
}
if (specialrel) {
cur += ptokatprec(dest+cur, size-cur-1, field_width-cur, sf, ff,
digits, ef, tok->u.funcall.argv + ai, fp);
if (parenarg && cur < size) dest[cur++] = ')';
digits, ef, tok->u.funcall.argv, INFIX_BOOL);
for (int ai = 1; ai < tok->u.funcall.argc && cur < size-1; ++ ai) {
dest[cur++] = ' ';
const char *use =
tfunc[tok->u.funcall.argv[ai].u.funcall.fident].name;
strncpy(dest+cur, use, size-cur-1);
cur += strlen(use);
if (cur < size) dest[cur++] = ' ';
cur += ptokatprec(dest+cur, size-cur-1, field_width-cur, sf, ff,
digits, ef,
tok->u.funcall.argv[ai].u.funcall.argv + 1,
INFIX_REL);
}
} else {
for (int ai = 0; ai < tok->u.funcall.argc && cur < size-1; ++ai)
{
bool parenarg = false;
if (ai > 0) {
if (fp < INFIX_MUL && cur < size) dest[cur++] = ' ';
const char *use = tfunc[fid].display_symbol;
if (use == NULL) use = tfunc[fid].name;
strncpy(dest+cur, use, size-cur-1);
cur += strlen(use);
if (fp < INFIX_MUL && cur < size) dest[cur++] = ' ';
} else if (fp > PREFIX_NEG) {
char powbuf[4096];
size_t arglen = ptokatprec(powbuf, sizeof(powbuf), 0, sf, ff,
digits, ef, tok->u.funcall.argv, fp);
assert(arglen < sizeof(powbuf)-1);
if (powbuf[0] == '-') {
parenarg = true;
if (cur < size) dest[cur++] = '(';
}
}
cur += ptokatprec(dest+cur, size-cur-1, field_width-cur, sf, ff,
digits, ef, tok->u.funcall.argv + ai, fp);
if (parenarg && cur < size) dest[cur++] = ')';
}
}
if (fp < atprec && cur < size) dest[cur++] = ')';
break;
}
break;
}
/* BOOL */ /*{{{*/
case BOOL:
{
const char *rep = "false";
if (tok->u.bl) rep = "true";
strncpy(dest+cur, rep, size-cur-1);
cur += strlen(rep);
break;
}
/* EEK */ /*{{{*/
case EEK:
{
@ -508,6 +555,18 @@ size_t printtok(char* dest, size_t size, size_t field_width,
}
/*}}}*/
static char dbgpbuf[4096];
/* dbgprint -- simple on the fly print for in the debugger */
const char *dbgprint(const Token* tok) {
size_t used = printtok(dbgpbuf, sizeof(dbgpbuf), 0, QUOTE_STRING, FLT_COMPACT,
0, VERBOSE_ERROR, tok);
if (used > sizeof(dbgpbuf) - 2) {
printf(_("Warning: buffer overflow in dbgprint"));
}
return dbgpbuf;
}
/* print -- print token sequence */ /*{{{*/
void print(char *s, size_t size, size_t chars, StringFormat sf, FloatFormat ff,
int digits, Token **n)

View File

@ -77,6 +77,7 @@ typedef enum {DIRECT_STRING, QUOTE_STRING} StringFormat;
typedef enum {TRUNCATED_ERROR, VERBOSE_ERROR, RESTORE_ERROR} ErrorFormat;
size_t printtok(char *dest, size_t size, size_t field_width, StringFormat sf,
FloatFormat ff, int digits, ErrorFormat ef, const Token *tok);
const char *dbgprint(const Token *tok); /* for use in debugging */
void print(char *s, size_t size, size_t chars, StringFormat sf, FloatFormat ff,
int digits, Token **n);
#ifdef __cplusplus

View File

@ -88,8 +88,18 @@ void tfree_protected(Token *n, const Token dontfree)
case FUNCALL:
if (difftype || n->u.funcall.argv != dontfree.u.funcall.argv)
{
for (int ai = 0; ai < n->u.funcall.argc; ++ai)
tfree_protected(n->u.funcall.argv + ai, dontfree);
for (int ai = n->u.funcall.argc-1; ai >= 0; --ai)
if (n->u.funcall.fident = FUNC_AND && ai > 0
&& n->u.funcall.argv[ai].type == FUNCALL
&& n->u.funcall.argv[ai-1].type == FUNCALL
&& n->u.funcall.argv[ai].u.funcall.argc == 2
&& n->u.funcall.argv[ai-1].u.funcall.argc == 2
&& IS_RELATION_FUNC(n->u.funcall.argv[ai].u.funcall.fident)
&& IS_RELATION_FUNC(n->u.funcall.argv[ai-1].u.funcall.fident))
{
tfree_protected(n->u.funcall.argv + ai,
n->u.funcall.argv[ai-1].u.funcall.argv[1]);
} else tfree_protected(n->u.funcall.argv + ai, dontfree);
if (n->u.funcall.argv != NULLTOKEN) {
free(n->u.funcall.argv);
n->u.funcall.argv = NULLTOKEN;
@ -587,36 +597,20 @@ Token tmul(Token l, Token r)
/* tneg -- monadic - operator */ /*{{{*/
Token tneg(Token x)
{
/* variables */ /*{{{*/
Token result;
int len;
/*}}}*/
result.type = x.type;
if (x.type == EEK) return tcopy(x);
if (x.type == EMPTY) return x;
if (x.type == INT)
/* result is negated int argument */ /*{{{*/
{
result.type = INT;
result.u.integer = -x.u.integer;
return result;
}
/*}}}*/
if (x.type == FLOAT)
/* result is negated float argument */ /*{{{*/
{
result.type = FLOAT;
result.u.flt = -x.u.flt;
return result;
}
/*}}}*/
if (x.type == LOCATION)
/* result is component-wise negation of location */ /*{{{*/
{
result.type = LOCATION;
for (size_t len = 0; len < 3; ++len)
result.u.location[len] = -x.u.location[len];
return result;
switch (x.type) {
case EEK: return tcopy(x);
case EMPTY: return x;
case INT: result.u.integer = -x.u.integer; return result;
case FLOAT: result.u.flt = -x.u.flt; return result;
case LOCATION:
for (size_t len = 0; len < 3; ++len)
result.u.location[len] = -x.u.location[len];
return result;
case BOOL: result.u.bl = !x.u.bl; return result;
default: break;
}
result.type = EEK;
const char* templ = "wrong type for - operator: %s";
@ -716,428 +710,189 @@ static Token relational_type_mismatch(Type l, Type r)
/* tlt -- < operator */ /*{{{*/
Token tlt(Token l, Token r)
{
/* variables */ /*{{{*/
Token result;
static char empty[]="";
int len;
/*}}}*/
result.type = BOOL;
result.u.bl = false;
static char empty[] = "";
if (l.type==EEK) /* return left error argument */ /*{{{*/
return tcopy(l);
/*}}}*/
if (r.type==EEK) /* return right error argument */ /*{{{*/
return tcopy(r);
/*}}}*/
if (l.type==EMPTY) /* try to assign 0 element of r.type */ /*{{{*/
if (l.type == EEK) return tcopy(l);
if (r.type == EEK) return tcopy(r);
if (l.type == EMPTY) /* try to assign 0 element of r.type */ /*{{{*/
{
l.type=r.type;
if (r.type == EMPTY) return result;
switch (r.type)
{
case INT: l.u.integer=0; break;
case FLOAT: l.u.flt=0.0; break;
case STRING: l.u.string=empty; break;
default: ;
case INT: l.u.integer = 0; break;
case FLOAT: l.u.flt = 0.0; break;
case STRING: l.u.string = empty; break;
case BOOL: l.u.bl = false; break;
default: return relational_type_mismatch(l.type, r.type);
}
l.type = r.type;
}
/*}}}*/
if (r.type==EMPTY) /* try to assign 0 element of l.type */ /*{{{*/
if (r.type == EMPTY) /* try to assign 0 element of l.type */ /*{{{*/
{
r.type=l.type;
switch (l.type)
{
case INT: r.u.integer=0; break;
case FLOAT: r.u.flt=0.0; break;
case STRING: r.u.string=empty; break;
default: ;
case INT: r.u.integer = 0; break;
case FLOAT: r.u.flt = 0.0; break;
case STRING: r.u.string = empty; break;
case BOOL: r.u.bl = false; break;
default: return relational_type_mismatch(l.type, r.type);;
}
r.type = l.type;
}
/*}}}*/
if (l.type==INT && r.type==INT) /* return left int < right int */ /*{{{*/
if (l.type == INT) /* handle left operand integer */ /*{{{*/
{
result.type=INT;
result.u.integer=l.u.integer<r.u.integer;
if (r.type == INT) /* return left int < right int */ /*{{{*/
{
result.u.bl = l.u.integer < r.u.integer;
return result;
} /*}}}*/
if (r.type == FLOAT) /* return left int < right float */ /*{{{*/
{
result.u.bl = ((FltT)l.u.integer) < r.u.flt;
return result;
} /*}}}*/
return relational_type_mismatch(l.type, r.type);
}
/*}}}*/
else if (l.type==STRING && r.type==STRING) /* return left string < right string */ /*{{{*/
if (l.type == STRING && r.type == STRING) /* return left string < right string */ /*{{{*/
{
result.type=INT;
result.u.integer=(strcmp(l.u.string,r.u.string)<0);
result.u.bl = (strcmp(l.u.string,r.u.string)<0);
return result;
}
/*}}}*/
else if (l.type==FLOAT && r.type==FLOAT) /* return left float < right float */ /*{{{*/
if (l.type == FLOAT) /* handle left operand float */
{
result.type=INT;
result.u.integer=l.u.flt<r.u.flt;
if (r.type == FLOAT) /* return left float < right float */ /*{{{*/
{
result.u.bl = l.u.flt < r.u.flt;
return result;
}
/*}}}*/
if (r.type == INT) /* return left float < right float */ /*{{{*/
{
result.u.bl = l.u.flt < ((FltT)r.u.integer);
return result;
}
/*}}}*/
return relational_type_mismatch(l.type, r.type);
}
if (l.type == LOCATION && r.type == LOCATION)
/* result is true if l is strictly in rectangle from origin to r */ /*{{{*/
{
Dimensions dim = X;
while (dim < HYPER)
{
if (l.u.location[dim] > r.u.location[dim]) break;
else if (l.u.location[dim] < r.u.location[dim]) result.u.bl = true;
++dim;
}
if (dim < HYPER) result.u.bl = false;
return result;
}
/*}}}*/
else if (l.type==FLOAT && r.type==INT) /* return left float < right float */ /*{{{*/
{
result.type = INT;
result.u.integer = l.u.flt < ((FltT)r.u.integer);
}
/*}}}*/
else if (l.type==INT && r.type==FLOAT) /* return left int < right float */ /*{{{*/
{
result.type = INT;
result.u.integer = ((FltT)l.u.integer) < r.u.flt;
}
/*}}}*/
else if (l.type == LOCATION && r.type == LOCATION)
/* result is true if l is strictly in rectangle from origin to r
In addition the value tells how many coordinates were strictly less */ /*{{{*/
{
result.type = INT;
result.u.integer = 0;
for (len = 0; len < 3; ++len)
if (l.u.location[len] > r.u.location[len]) break;
else if (l.u.location[len] < r.u.location[len]) ++result.u.integer;
if (len < 3) result.u.integer = 0;
}
/*}}}*/
else return relational_type_mismatch(l.type, r.type);
return result;
return relational_type_mismatch(l.type, r.type);
}
/*}}}*/
/* tle -- <= operator */ /*{{{*/
Token tle(Token l, Token r)
{
/* variables */ /*{{{*/
Token result;
static char empty[]="";
int len;
/*}}}*/
if (l.type==EEK) /* return left error argument */ /*{{{*/
return tcopy(l);
/*}}}*/
if (r.type==EEK) /* return right error argument */ /*{{{*/
return tcopy(r);
/*}}}*/
if (l.type==EMPTY) /* try to assign 0 element of r.type */ /*{{{*/
{
l.type=r.type;
switch (r.type)
{
case INT: l.u.integer=0; break;
case FLOAT: l.u.flt=0.0; break;
case STRING: l.u.string=empty; break;
default: ;
}
}
/*}}}*/
if (r.type==EMPTY) /* try to assign 0 element of l.type */ /*{{{*/
{
r.type=l.type;
switch (l.type)
{
case INT: r.u.integer=0; break;
case FLOAT: r.u.flt=0.0; break;
case STRING: r.u.string=empty; break;
default: ;
}
}
/*}}}*/
if (l.type==INT && r.type==INT) /* result is left int <= right int */ /*{{{*/
{
result.type=INT;
result.u.integer=l.u.integer<=r.u.integer;
}
/*}}}*/
else if (l.type==STRING && r.type==STRING) /* result is left string <= right string */ /*{{{*/
{
result.type=INT;
result.u.integer=(strcmp(l.u.string,r.u.string)<=0);
}
/*}}}*/
else if (l.type==FLOAT && r.type==FLOAT) /* result is left float <= right float */ /*{{{*/
{
result.type=INT;
result.u.integer=l.u.flt<=r.u.flt;
}
/*}}}*/
else if (l.type==FLOAT && r.type==INT) /* result is left float <= (FltT)right int */ /*{{{*/
{
result.type = INT;
result.u.integer = l.u.flt <= ((FltT)r.u.integer);
}
/*}}}*/
else if (l.type==INT && r.type==FLOAT) /* result is (FltT)left int <= right float */ /*{{{*/
{
result.type = INT;
result.u.integer = ((FltT)l.u.integer) <= r.u.flt;
}
/*}}}*/
else if (l.type==EMPTY && r.type==EMPTY) /* result is 1 */ /*{{{*/
{
result.type = INT;
result.u.integer = 1;
}
/*}}}*/
else if (l.type == LOCATION && r.type == LOCATION)
/* result is true if l is in rectangle from origin to r */ /*{{{*/
{
result.type = INT;
result.u.integer = 1;
for (len = 0; len < 3; ++len)
if (l.u.location[len] > r.u.location[len]) break;
if (len < 3) result.u.integer = 0;
}
/*}}}*/
else return relational_type_mismatch(l.type, r.type);
return result;
return tor(tlt(l, r), teq(l, r));
}
/*}}}*/
/* tge -- >= operator */ /*{{{*/
Token tge(Token l, Token r)
{
/* variables */ /*{{{*/
Token result;
static char empty[]="";
int len;
/*}}}*/
if (l.type==EEK) /* return left error argument */ /*{{{*/
return tcopy(l);
/*}}}*/
if (r.type==EEK) /* return right error argument */ /*{{{*/
return tcopy(r);
/*}}}*/
if (l.type==EMPTY) /* try to assign 0 element of r.type */ /*{{{*/
{
l.type=r.type;
switch (r.type)
{
case INT: l.u.integer=0; break;
case FLOAT: l.u.flt=0.0; break;
case STRING: l.u.string=empty; break;
default: ;
}
}
/*}}}*/
if (r.type==EMPTY) /* try to assign 0 element of l.type */ /*{{{*/
{
r.type=l.type;
switch (l.type)
{
case INT: r.u.integer=0; break;
case FLOAT: r.u.flt=0.0; break;
case STRING: r.u.string=empty; break;
default: ;
}
}
/*}}}*/
if (l.type==INT && r.type==INT) /* result is left int >= right int */ /*{{{*/
{
result.type=INT;
result.u.integer=l.u.integer>=r.u.integer;
}
/*}}}*/
else if (l.type==STRING && r.type==STRING) /* return left string >= right string */ /*{{{*/
{
result.type=INT;
result.u.integer=(strcmp(l.u.string,r.u.string)>=0);
}
/*}}}*/
else if (l.type==FLOAT && r.type==FLOAT) /* return left float >= right float */ /*{{{*/
{
result.type=INT;
result.u.integer=l.u.flt>=r.u.flt;
}
/*}}}*/
else if (l.type==FLOAT && r.type==INT) /* return left float >= (FltT) right int */ /*{{{*/
{
result.type = INT;
result.u.integer = l.u.flt >= ((FltT)r.u.integer);
}
/*}}}*/
else if (l.type==INT && r.type==FLOAT) /* return (FltT) left int >= right float */ /*{{{*/
{
result.type = INT;
result.u.integer = ((FltT)l.u.integer) >= r.u.flt;
}
/*}}}*/
else if (l.type == LOCATION && r.type == LOCATION)
/* result is true if r is in rectangle from origin to l */ /*{{{*/
{
result.type = INT;
result.u.integer = 1;
for (len = 0; len < 3; ++len)
if (r.u.location[len] > l.u.location[len]) break;
if (len < 3) result.u.integer = 0;
}
/*}}}*/
else return relational_type_mismatch(l.type, r.type);
return result;
return tor(tgt(l, r), teq(l, r));
}
/*}}}*/
/* tgt -- <= operator */ /*{{{*/
/* tgt -- < operator */ /*{{{*/
Token tgt(Token l, Token r)
{
/* variables */ /*{{{*/
Token result;
static char empty[]="";
int len;
/*}}}*/
if (l.type==EEK) /* return left error argument */ /*{{{*/
return tcopy(l);
/*}}}*/
if (r.type==EEK) /* return right error argument */ /*{{{*/
return tcopy(r);
/*}}}*/
if (l.type==EMPTY) /* try to assign 0 element of r.type */ /*{{{*/
{
l.type=r.type;
switch (r.type)
{
case INT: l.u.integer=0; break;
case FLOAT: l.u.flt=0.0; break;
case STRING: l.u.string=empty; break;
default: ;
}
}
/*}}}*/
if (r.type==EMPTY) /* try to assign 0 element of l.type */ /*{{{*/
{
r.type=l.type;
switch (l.type)
{
case INT: r.u.integer=0; break;
case FLOAT: r.u.flt=0.0; break;
case STRING: r.u.string=empty; break;
default: ;
}
}
/*}}}*/
if (l.type==INT && r.type==INT) /* result is left int > right int */ /*{{{*/
{
result.type=INT;
result.u.integer=l.u.integer>r.u.integer;
}
/*}}}*/
else if (l.type==STRING && r.type==STRING) /* result is left string > right string */ /*{{{*/
{
result.type=INT;
result.u.integer=(strcmp(l.u.string,r.u.string)>0);
}
/*}}}*/
else if (l.type==FLOAT && r.type==FLOAT) /* result is left float > right float */ /*{{{*/
{
result.type=INT;
result.u.integer=l.u.flt>r.u.flt;
}
/*}}}*/
else if (l.type==FLOAT && r.type==INT) /* result is left float > (FltT) right int */ /*{{{*/
{
result.type = INT;
result.u.integer = l.u.flt > ((FltT)r.u.integer);
}
/*}}}*/
else if (l.type==INT && r.type==FLOAT) /* result is left int > right float */ /*{{{*/
{
result.type = INT;
result.u.integer = ((FltT)l.u.integer) > r.u.flt;
}
/*}}}*/
else if (l.type == LOCATION && r.type == LOCATION)
/* result is true if r is strictly in rectangle from origin to l
In addition the value tells how many coordinates were strictly greater */ /*{{{*/
{
result.type = INT;
result.u.integer = 0;
for (len = 0; len < 3; ++len)
if (r.u.location[len] > l.u.location[len]) break;
else if (r.u.location[len] < l.u.location[len]) ++result.u.integer;
if (len < 3) result.u.integer = 0;
}
else return relational_type_mismatch(l.type, r.type);
return result;
return tlt(r, l);
}
/*}}}*/
/* teq -- == operator */ /*{{{*/
Token teq(Token l, Token r)
{
/* variables */ /*{{{*/
Token result;
result.type = BOOL;
result.u.bl = true;
static char empty[]="";
int len;
/*}}}*/
if (l.type==EEK) return tcopy(l);
else if (r.type==EEK) return tcopy(r);
if (l.type==EMPTY)
if (l.type == EEK) return tcopy(l);
if (r.type == EEK) return tcopy(r);
if (l.type == EMPTY)
/* try to assign 0 element of r.type */ /*{{{*/
{
l.type=r.type;
if (r.type == EMPTY) return result;
switch (r.type)
{
case INT: l.u.integer=0; break;
case FLOAT: l.u.flt=0.0; break;
case STRING: l.u.string=empty; break;
default: ;
case INT: l.u.integer = 0; break;
case FLOAT: l.u.flt = 0.0; break;
case STRING: l.u.string = empty; break;
case BOOL: l.u.bl = false; break;
default: return relational_type_mismatch(l.type, r.type);
}
l.type = r.type;
}
/*}}}*/
if (r.type==EMPTY)
/* try to assign 0 element of l.type */ /*{{{*/
{
r.type=l.type;
switch (l.type)
{
case INT: r.u.integer=0; break;
case FLOAT: r.u.flt=0.0; break;
case STRING: r.u.string=empty; break;
default: ;
case BOOL: r.u.bl = false; break;
default: return relational_type_mismatch(l.type, r.type);
}
r.type=l.type;
}
/*}}}*/
if (l.type==FLOAT && r.type==INT)
if (l.type == FLOAT && r.type == INT)
{
result.type = INT;
result.u.integer = (l.u.flt == ((FltT)r.u.integer));
result.u.bl = (l.u.flt == ((FltT)r.u.integer));
return result;
}
else if (l.type==INT && r.type==FLOAT)
if (l.type == INT && r.type == FLOAT)
{
result.type = INT;
result.u.integer = (((FltT)l.u.integer) == r.u.flt);
}
else if (l.type != r.type)
{
result.type = INT;
result.u.integer = 0;
result.u.bl = (((FltT)l.u.integer) == r.u.flt);
return result;
}
else if (l.type==INT)
if (l.type != r.type)
{
result.type=INT;
result.u.integer=l.u.integer==r.u.integer;
result.u.bl = false;
return result;
}
else if (l.type==STRING)
{
result.type=INT;
result.u.integer=(strcmp(l.u.string,r.u.string)==0);
switch (l.type) {
case INT:
result.u.bl = l.u.integer == r.u.integer; return result;
case STRING:
result.u.bl = (strcmp(l.u.string,r.u.string)==0); return result;
case FLOAT:
result.u.bl = l.u.flt == r.u.flt; return result;
case BOOL:
result.u.bl = l.u.bl == r.u.bl; return result;
case LOCATION: {
/* result is true if locations are component-wise equal */
Dimensions dim = X;
while (dim < HYPER && l.u.location[dim] == r.u.location[dim]) ++dim;
if (dim < HYPER) result.u.bl = false;
return result;
}
default: break;
}
else if (l.type==FLOAT)
{
result.type=INT;
result.u.integer=l.u.flt==r.u.flt;
}
else if (l.type==EMPTY)
{
result.type=INT;
result.u.integer=1;
}
else if (l.type == LOCATION)
/* result is true if locations are component-wise equal */ /*{{{*/
{
result.type = INT;
result.u.integer = 1;
for (len = 0; len < 3 && l.u.location[len] == r.u.location[len]; ++len);
if (len < 3) result.u.integer = 0;
}
/*}}}*/
else return relational_type_mismatch(l.type, r.type);
return result;
return relational_type_mismatch(l.type, r.type);
}
/*}}}*/
@ -1160,127 +915,64 @@ static bool nearly_equal(FltT a1, FltT a2)
/* tabouteq -- ~= operator */ /*{{{*/
Token tabouteq(Token l, Token r)
{
/* variables */ /*{{{*/
Token result;
/*}}}*/
result.type = BOOL;
if (l.type==EEK) return tcopy(l);
if (r.type==EEK) return tcopy(r);
if (l.type==EMPTY)
/* try to assign 0 element of r.type */ /*{{{*/
if (l.type == EEK) return tcopy(l);
if (r.type == EEK) return tcopy(r);
if (l.type == EMPTY && r.type == FLOAT)
{
l.type=r.type;
switch (r.type)
{
case FLOAT: l.u.flt=0.0; break;
default: ;
}
l.type = FLOAT;
l.u.flt = 0.0;
}
/*}}}*/
if (r.type==EMPTY)
/* try to assign 0 element of l.type */ /*{{{*/
if (r.type == EMPTY && l.type == FLOAT)
{
r.type=l.type;
switch (l.type)
{
case FLOAT: r.u.flt=0.0; break;
default: ;
}
r.type = FLOAT;
r.u.flt = 0.0;
}
/*}}}*/
if (l.type==FLOAT && r.type==FLOAT)
if (l.type == FLOAT && r.type == FLOAT)
{
result.type=INT;
result.u.integer = (int)nearly_equal(l.u.flt, r.u.flt);
result.u.bl = nearly_equal(l.u.flt, r.u.flt);
return result;
}
else
{
result.type=EEK;
result.u.err = strdup(_("Usage: ~= only compares float values"));
}
return result;
return duperror(&result, _("Usage: ~= only compares float values"));
}
/*}}}*/
/* tne -- != operator */ /*{{{*/
Token tne(Token l, Token r)
{
/* variables */ /*{{{*/
Token result;
static char empty[]="";
int len;
/*}}}*/
if (l.type==EEK) return tcopy(l);
if (r.type==EEK) return tcopy(r);
if (l.type==EMPTY)
/* try to assign 0 element of r.type */ /*{{{*/
{
l.type=r.type;
switch (r.type)
{
case INT: l.u.integer=0; break;
case FLOAT: l.u.flt=0.0; break;
case STRING: l.u.string=empty; break;
default: ;
}
return tneg(teq(l,r));
}
/*}}}*/
/* 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;
}
/*}}}*/
if (r.type==EMPTY)
/* try to assign 0 element of l.type */ /*{{{*/
{
r.type=l.type;
switch (l.type)
{
case INT: r.u.integer=0; break;
case FLOAT: r.u.flt=0.0; break;
case STRING: r.u.string=empty; break;
default: ;
}
return operand_type_error("or: ", l.type, r.type);
}
/*}}}*/
/* 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;
}
/*}}}*/
if (l.type==FLOAT && r.type==INT)
{
result.type = INT;
result.u.integer = l.u.flt != ((FltT)r.u.integer);
}
else if (l.type==INT && r.type==FLOAT)
{
result.type = INT;
result.u.integer = ((FltT)l.u.integer)!=r.u.flt;
}
else if (l.type != r.type)
{
result.type = INT;
result.u.integer = 1;
}
else if (l.type==INT)
{
result.type=INT;
result.u.integer=l.u.integer!=r.u.integer;
}
else if (l.type==STRING)
{
result.type=INT;
result.u.integer=(strcmp(l.u.string,r.u.string)!=0);
}
else if (l.type==FLOAT)
{
result.type=INT;
result.u.integer=l.u.flt!=r.u.flt;
}
else if (l.type==EMPTY) /* both empty, so equal, i.e. not inequal */
{
result.type=INT;
result.u.integer=0;
}
else if (l.type == LOCATION) {
result.type = INT;
result.u.integer = 0;
for (len = 0; len < 3 && l.u.location[len] == r.u.location[len]; ++len);
if (len < 3) result.u.integer = 1;
}
else return relational_type_mismatch(l.type, r.type);
return result;
return operand_type_error("and: ", l.type, r.type);
}
/*}}}*/

View File

@ -25,5 +25,6 @@ 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 tor(Token l, Token r);
Token tand(Token l, Token r);
#endif

View File

@ -598,6 +598,45 @@ static Token error_func(FunctionIdentifier self, int argc, const Token argv[])
return result;
}
/*}}}*/
/* blcnst_func -- common implementation of true/false */ /*{{{*/
static Token blcnst_func(FunctionIdentifier self, int argc, const Token argv[])
{
assert(self == FUNC_FALSE || self == FUNC_TRUE);
if (argc >= 0) {
Token err;
const char *templ = _("'%s' must be used as a bare word");
err.type = EEK;
err.u.err = malloc(strlen(templ) + MAX_FUNC_NAME_LENGTH + 1);
sprintf(err.u.err, templ, tfunc[self].name);
return err;
}
Token res;
res.type = BOOL;
res.u.bl = (self == FUNC_TRUE);
return res;
}
/*}}}*/
/* blop_macro -- common handler for short-circuiting boolean ops */ /*{{{*/
static Token blop_macro(FunctionIdentifier self, int argc, const Token argv[])
{
assert(self == FUNC_AND || self == FUNC_OR);
Token result;
result.type = BOOL;
result.u.bl = (self == FUNC_AND);
for (int i = 0; i < argc; ++i) {
Token argres = evaltoken(argv[i], FULL);
Token tmp = (self == FUNC_AND) ? tand(result, argres) : tor(result, argres);
tfree_protected(&argres, tmp);
tfree_protected(&result, tmp);
result = tmp;
if (result.type == EEK) return result;
}
return result;
}
/*}}}*/
/* string */ /*{{{*/
static Token string_func(FunctionIdentifier self, int argc, const Token argv[])
{
@ -658,7 +697,7 @@ static Token accum_func(FunctionIdentifier self, int argc, const Token argv[])
if (start > 0) { result = tcopy(argv[start-1]); }
for (int i = start; i < argc; ++i) {
Token tmp = tbin(result, argv[i]);
tfree(&result);
tfree_protected(&result, tmp);
result = tmp;
if (result.type == EEK) return result;
}
@ -737,7 +776,7 @@ static void sum_updt(FunctionIdentifier id, Location *loc, Token *tok,
{
assert(id == FUNC_SUM);
Token tmp = tadd(*tok, *newtok);
tfree(tok);
tfree_protected(tok, tmp);
*tok = tmp;
}
@ -877,49 +916,51 @@ static Token binop_func(FunctionIdentifier self, int argc, const Token argv[])
static Token int_func(FunctionIdentifier self, int argc, const Token argv[])
{
assert(self == FUNC_INT);
/* variables */ /*{{{*/
const char *usage = _("Usage: int(string|bool|float[,rounding_function])");
Token result;
/*}}}*/
if (argc == 1 && argv[0].type == FLOAT)
/* result is integer with cutoff fractional part */ /*{{{*/
{
result.type = INT;
result.u.integer = (IntT)argv[0].u.flt;
return result;
}
/*}}}*/
if (argc == 2 && argv[0].type==FLOAT && argv[1].type == FIDENT)
/* result is integer with given conversion */ /*{{{*/
{
result.type = INT;
switch (argv[1].u.fident)
result.type = INT;
if (argc < 1 || argc > 2) return duperror(&result, usage);
switch (argv[0].type) {
case FLOAT:
if (argc == 1) {
result.u.integer = (IntT)argv[0].u.flt;
return result;
} else { /* result is integer with given conversion */ /*{{{*/
switch (argv[1].u.fident)
{
case FUNC_TRUNC:
result.u.integer = (IntT)argv[0].u.flt; break;
case FUNC_ROUND:
result.u.integer = ROUNDFLTINT(argv[0].u.flt); break;
case FUNC_FLOOR:
result.u.integer = (IntT)FLOORFLT(argv[0].u.flt); break;
case FUNC_CEIL:
result.u.integer = (IntT) CEILFLT(argv[0].u.flt); break;
default: assert(0);
}
return result;
}
case STRING:
{
case FUNC_TRUNC: result.u.integer = (IntT)argv[0].u.flt; break;
case FUNC_ROUND: result.u.integer = ROUNDFLTINT(argv[0].u.flt); break;
case FUNC_FLOOR: result.u.integer = (IntT)FLOORFLT(argv[0].u.flt); break;
case FUNC_CEIL: result.u.integer = (IntT) CEILFLT(argv[0].u.flt); break;
default: assert(0);
char *s;
if (argc > 1) return duperror(&result, usage);
errno=0;
result.u.integer = STRTOINT(argv[0].u.string, &s, 10);
if (s==(char*)0 || *s)
return duperror(&result, _("int(string): invalid string"));
if (errno==ERANGE &&
(result.u.integer==LONG_MAX || result.u.integer==LONG_MIN))
return duperror(&result, _("int(string): domain error"));
return result;
}
return result;
case BOOL:
if (argc > 1) return duperror(&result, usage);
if (argv[0].u.bl) result.u.integer = 1;
else result.u.integer = 0;
return result;
default: break;
}
/*}}}*/
if (argc == 1 && argv[0].type == STRING)
/* result is integer */ /*{{{*/
{
char *s;
errno=0;
result.u.integer = STRTOINT(argv[0].u.string, &s, 10);
if (s==(char*)0 || *s) duperror(&result, _("int(string): invalid string"));
else if (errno==ERANGE &&
(result.u.integer==LONG_MAX || result.u.integer==LONG_MIN))
duperror(&result, _("int(string): domain error"));
else result.type = INT;
return result;
}
/*}}}*/
return duperror(&result, _("Usage: int(string|float[,rounding_function])"));
return duperror(&result, usage);
}
/*}}}*/
@ -1161,10 +1202,10 @@ static Token poly_func(FunctionIdentifier self, int argc, const Token argv[])
for (size_t i = 2; i < argc; ++i)
{
tmp = tmul(result,argv[0]);
tfree(&result);
tfree_protected(&result, tmp);
result = tmp;
tmp = tadd(result,argv[i]);
tfree(&result);
tfree_protected(&result, tmp);
result = tmp;
if (result.type == EEK) return result;
}
@ -1386,6 +1427,12 @@ Tfunc tfunc[]=
[FUNC_ERROR] = { "error", error_func, PREFIX_FUNC, FUNCT, 0 },
[FUNC_EVAL] = { "eval", eval_func, PREFIX_FUNC, FUNCT, 0 },
/* Boolean functions */
[FUNC_AND] = { "and", blop_macro, INFIX_BOOL, MACRO, 0 },
[FUNC_FALSE] = { "false", blcnst_func, PREFIX_FUNC, FUNCT, 0 },
[FUNC_OR] = { "or", blop_macro, INFIX_BOOL, MACRO, 0 },
[FUNC_TRUE] = { "true", blcnst_func, PREFIX_FUNC, FUNCT, 0 },
/* Type conversion functions */
[FUNC_FLOAT] = { "float", float_func, PREFIX_FUNC, FUNCT, 0 },
[FUNC_INT] = { "int", int_func, PREFIX_FUNC, FUNCT, 0 },

View File

@ -22,21 +22,27 @@ typedef enum
FUNC_RND, FUNC_SUBSTR, FUNC_STRPTIME, FUNC_TIME, FUNC_BITAND, FUNC_BITOR,
FUNC_R, FUNC_D, FUNC_CAP_X, FUNC_X_AMPERSAND, FUNC_NEGATE, FUNC_PLUS_SYMBOL,
FUNC_MINUS_SYMBOL, FUNC_ASTERISK, FUNC_SLASH,
/* Note use of thes in the macro below */
FUNC_LESS_EQUAL, FUNC_GREATER_EQUAL, FUNC_LESS_THAN, FUNC_GREATER_THAN,
FUNC_EQUAL_EQUAL, FUNC_TILDE_EQUAL, FUNC_BANG_EQUAL, FUNC_CARET,
FUNC_EQUAL_EQUAL, FUNC_TILDE_EQUAL, FUNC_BANG_EQUAL,
FUNC_CARET,
FUNC_PER_CENT, FUNC_CONCAT, FUNC_TAU, FUNC_SQRT, FUNC_FLOOR, FUNC_CEIL,
FUNC_TRUNC, FUNC_ROUND, FUNC_DECIMAL, FUNC_SCIENTIFIC, FUNC_COMPACT,
FUNC_HEXACT, FUNC_RIGHT, FUNC_LEFT, FUNC_DOWN, FUNC_UP, FUNC_BELOW, FUNC_ABOVE,
FUNC_TRUE, FUNC_FALSE, FUNC_AND, FUNC_OR,
N_FUNCTION_IDS
} FunctionIdentifier;
#define IS_RELATION_FUNC(f) (((f) >= FUNC_LESS_EQUAL) && ((f) <= FUNC_BANG_EQUAL))
/* Forward declaration of Token, since this header is used in scanner.h */
typedef struct Token_struc Token;
typedef enum /* In increasing order of precedence */
{
NO_PRECEDENCE, INFIX_CONC, INFIX_REL, INFIX_PLUS, INFIX_MUL,
NO_PRECEDENCE, INFIX_CONC, INFIX_BOOL, INFIX_REL, INFIX_PLUS, INFIX_MUL,
PREFIX_NEG, INFIX_POW, PREFIX_FUNC
} FunctionPrecedence;

View File

@ -32,9 +32,14 @@ extern char *strdup(const char* s);
/*}}}*/
/* prototypes */ /*{{{*/
static Token term(Token *n[], int *i, EvalMethod meth);
static Token boolterm(Operator bop, Token *n[], int *i, EvalMethod meth);
/*}}}*/
static Token term(Token *n[], int* i, EvalMethod meth)
{
return boolterm(LOR, n, i, meth);
}
/* full_eval_funcall -- evaluate the args of a funcall token and then
call the function on them
*/
@ -43,16 +48,23 @@ static Token full_eval_funcall(Token *t)
assert(t->type == FUNCALL);
if (t->u.funcall.argc < 1)
return tfuncall(t->u.funcall.fident, t->u.funcall.argc, 0);
Token *eval_argv = malloc(t->u.funcall.argc*sizeof(Token));
for (size_t ai = 0; ai < t->u.funcall.argc; ++ai) {
eval_argv[ai] = evaltoken(t->u.funcall.argv[ai], FULL);
Token *eval_argv;
if (tfunc[t->u.funcall.fident].eval_as == MACRO)
eval_argv = t->u.funcall.argv;
else {
eval_argv = malloc(t->u.funcall.argc*sizeof(Token));
for (size_t ai = 0; ai < t->u.funcall.argc; ++ai) {
eval_argv[ai] = evaltoken(t->u.funcall.argv[ai], FULL);
}
}
Token result = tfuncall(t->u.funcall.fident, t->u.funcall.argc, eval_argv);
/* To allow a function to return one of its arguments, we need
to be sure not to free that argument: */
for (size_t ai = 0; ai < t->u.funcall.argc; ++ai)
tfree_protected(&eval_argv[ai], result);
free(eval_argv);
if (tfunc[t->u.funcall.fident].eval_as == FUNCT) {
/* To allow a function to return one of its arguments, we need
to be sure not to free that argument: */
for (size_t ai = 0; ai < t->u.funcall.argc; ++ai)
tfree_protected(&eval_argv[ai], result);
free(eval_argv);
}
return result;
}
@ -285,8 +297,8 @@ static Token powterm(Token *n[], int *i, EvalMethod meth)
if (meth == FULL)
{
Token result = tpow(l,r);
tfree(&l);
tfree(&r);
tfree_protected(&l, result);
tfree_protected(&r, result);
if (result.type == EEK) return result;
l = result;
} else {
@ -347,8 +359,8 @@ static Token powterm(Token *n[], int *i, EvalMethod meth)
case MOD: result = tmod(l,r); break;
default: assert(0);
}
tfree(&l);
tfree(&r);
tfree_protected(&l, result);
tfree_protected(&r, result);
if (result.type == EEK) return result;
l = result;
} else {
@ -389,26 +401,24 @@ static Token powterm(Token *n[], int *i, EvalMethod meth)
/* factor -- parse and evaluate a factor of sums/differences */ /*{{{*/
static Token factor(Token *n[], int *i, EvalMethod meth)
{
Token l = piterm(n, i, meth);
if (l.type == EEK) return l;
FunctionIdentifier plusident = FUNC_PLUS_SYMBOL;
Token l;
Operator op = CP;
bool first_funcall = true;
l = piterm(n, i, meth);
if (l.type == EEK) return l;
if (n[*i] != NULLTOKEN && n[*i]->type == OPERATOR) op = n[*i]->u.op;
while (op == PLUS || op == MINUS)
{
Token r;
++(*i);
r = piterm(n, i, meth);
Token r = piterm(n, i, meth);
if (meth == FULL)
{
Token result = (op==PLUS ? tadd(l,r) : tsub(l,r));
tfree(&l);
tfree(&r);
tfree_protected(&l, result);
tfree_protected(&r, result);
if (result.type == EEK) return result;
l = result;
} else {
@ -433,8 +443,7 @@ static Token factor(Token *n[], int *i, EvalMethod meth)
{
tfree(&r);
tfree(&l);
duperror(&l, _("Exceeded maximum sequence length of +"));
return l;
return duperror(&l, _("Exceeded maximum sequence length of +"));
}
l.u.funcall.argv[(l.u.funcall.argc)++] = r;
}
@ -446,54 +455,164 @@ static Token factor(Token *n[], int *i, EvalMethod meth)
}
/*}}}*/
/* term -- parse and evaluate a relational term */ /*{{{*/
static Token term(Token *n[], int *i, EvalMethod meth)
/* relterm -- parse and evaluate a relational term */ /*{{{*/
static Token relterm(Token *n[], int *i, EvalMethod meth)
{
Token l = factor(n, i, meth);
if (l.type == EEK) return l;
/* a < b < c used to mean (a < b) < c, but that does not make sense really
because there is not an ordering on bools (if we had a separate bool
type). So restrict to a single binary relation; one can still use parens
to get the old, odd behavior */
if (n[*i] != NULLTOKEN && n[*i]->type == OPERATOR
&& n[*i]->u.op >= LT && n[*i]->u.op <= NE)
{
Operator op = n[*i]->u.op;
Token result, r;
bool firstcomp = true;
Token result = l;
Operator op = CP;
/* a < b < c now means a < b and b < c, so we have to save both the running
result and the last term
*/
if (n[*i] != NULLTOKEN && n[*i]->type == OPERATOR) op = n[*i]->u.op;
while (IS_RELATION_OP(op))
{
++(*i);
r = factor(n, i, meth);
Token r = factor(n, i, LITERAL);
if (meth == FULL)
{
switch (op)
{
case LT: result=tlt(l,r); break;
case LE: result=tle(l,r); break;
case GE: result=tge(l,r); break;
case GT: result=tgt(l,r); break;
case ISEQUAL: result=teq(l,r); break;
case ABOUTEQ: result=tabouteq(l,r); break;
case NE: result=tne(l,r); break;
default: assert(0);
if (!firstcomp && result.type == BOOL && !result.u.bl) {
tfree(&r);
} else {
Token tmp = evaltoken(r, FULL);
tfree_protected(&r, tmp);
r = tmp;
switch (op)
{
case LT: tmp = tlt(l,r); break;
case LE: tmp = tle(l,r); break;
case GE: tmp = tge(l,r); break;
case GT: tmp = tgt(l,r); break;
case ISEQUAL: tmp = teq(l,r); break;
case ABOUTEQ: tmp = tabouteq(l,r); break;
case NE: tmp = tne(l,r); break;
default: assert(0);
}
tfree_protected(&l, result);
l = r;
if (firstcomp) {
result = tmp;
} else {
Token newres = tand(result, tmp);
tfree_protected(&result, newres);
tfree_protected(&tmp, newres);
result = newres;
}
if (result.type == EEK) {
tfree_protected(&l, result);
return result;
}
}
} else { /* meth = LITERAL */
if (r.type == EEK)
{
tfree(&l);
return r;
}
Token newcomp;
newcomp.type = FUNCALL;
newcomp.u.funcall.fident = identcode(Op_Name[op], strlen(Op_Name[op]));
newcomp.u.funcall.argc = 2;
newcomp.u.funcall.argv = malloc(2*sizeof(Token));
newcomp.u.funcall.argv[0] = l;
newcomp.u.funcall.argv[1] = r;
l = r;
if (firstcomp) {
result = newcomp;
firstcomp = false;
} else if (result.u.fident != FUNC_AND) {
Token holdres = result;
result.u.funcall.fident = FUNC_AND;
result.u.funcall.argc = 2;
result.u.funcall.argv = malloc(MAXARGC*sizeof(Token));
result.u.funcall.argv[0] = holdres;
result.u.funcall.argv[1] = newcomp;
} else if (result.u.funcall.argc >= MAXARGC) {
tfree(&result);
return
duperror(&result,
_("Exeeded maximum sequence length of comparisons"));
} else {
result.u.funcall.argv[(result.u.funcall.argc)++] = newcomp;
}
}
if (n[*i] != NULLTOKEN && n[*i]->type == OPERATOR) op = n[*i] -> u.op;
else op = CP;
} /* while next op is a comparison */
return result;
}
/*}}}*/
/* boolterm -- parse and evaluate a boolean term */ /*{{{*/
static Token boolterm(Operator bop, Token *n[], int *i, EvalMethod meth)
{
assert (bop == LAND || bop == LOR);
Token l;
Operator op = CP;
bool first_funcall = true;
l = (bop == LAND) ? relterm(n, i, meth) : boolterm(LAND, n, i, meth);
if (l.type == EEK) return l;
if (n[*i] != NULLTOKEN && n[*i]->type == OPERATOR) op = n[*i]->u.op;
while (op == bop)
{
++(*i);
/* Need to evaluate right operand literally for the sake of short
circuiting
*/
Token r =
(bop == LAND) ? relterm(n, i, LITERAL) : boolterm(LAND, n, i, LITERAL);
if (meth == FULL) {
if (l.type == BOOL
&& ((bop == LAND && !l.u.bl) || (bop == LOR && l.u.bl)))
tfree(&r);
else {
Token result = evaltoken(r, FULL);
tfree_protected(&r, result);
Token tmp = (bop == LAND) ? tand(l, result) : tor(r, result);
tfree_protected(&result, tmp);
tfree_protected(&l, tmp);
if (tmp.type == EEK) return tmp;
l = tmp;
}
tfree(&l);
tfree(&r);
if (result.type == EEK) return result;
l = result;
} else {
if (r.type == EEK)
{
tfree(&l);
return r;
}
Token tmp = l;
l.type = FUNCALL;
l.u.funcall.fident = identcode(Op_Name[op], strlen(Op_Name[op]));
l.u.funcall.argc = 2;
l.u.funcall.argv = malloc(2*sizeof(Token));
l.u.funcall.argv[0] = tmp;
l.u.funcall.argv[1] = r;
if (first_funcall)
{
first_funcall = false;
Token tmp = l;
l.type = FUNCALL;
l.u.funcall.fident = identcode(Op_Name[op], strlen(Op_Name[op]));
l.u.funcall.argc = 2;
l.u.funcall.argv = malloc(MAXARGC * sizeof(Token));
l.u.funcall.argv[0] = tmp;
l.u.funcall.argv[1] = r;
} else {
if (l.u.funcall.argc >= MAXARGC)
{
tfree(&r);
tfree(&l);
const char* templ = _("Exceeded max sequence length of %s");
l.type = EEK;
l.u.err = malloc(strlen(templ) + MAX_OP_NAME_LENGTH + 1);
sprintf(l.u.err, templ, Op_Name[op]);
return l;
}
l.u.funcall.argv[(l.u.funcall.argc)++] = r;
}
}
if (n[*i] != NULLTOKEN && n[*i]->type == OPERATOR) op = n[*i]->u.op;
else op = CP;
}
return l;
}
@ -518,8 +637,8 @@ Token eval(Token **n, EvalMethod meth)
if (meth == FULL)
{
Token result = tconcat(l,r);
tfree(&l);
tfree(&r);
tfree_protected(&l, result);
tfree_protected(&r, result);
if (result.type == EEK) return result;
l = result;
} else {

View File

@ -28,7 +28,8 @@ extern long double strtold(const char *nptr, char **endptr); /* SunOS 4 hack */
const char *Type_Name[] =
{ [EMPTY] = "EMPTY", [STRING] = "STRING", [FLOAT] = "FLOAT", [INT] = "INT",
[OPERATOR] = "OPERATOR", [LIDENT] = "LABEL", [FIDENT] = "FUNCTION",
[LOCATION] = "LOCATION", [FUNCALL] = "FUNCTION-CALL", [EEK] = "ERROR"
[LOCATION] = "LOCATION", [FUNCALL] = "FUNCTION-CALL", [EEK] = "ERROR",
[BOOL] = "BOOL"
};
const char *Op_Name[] =
@ -36,7 +37,7 @@ const char *Op_Name[] =
[OP] = "(", [CP] = ")", [COMMA] = ",",
[LT] = "<", [LE] = "<=", [GE] = ">=", [GT] = ">",
[ISEQUAL] = "==", [ABOUTEQ] = "~=", [NE] = "!=",
[POW] = "^", [MOD] = "%"
[POW] = "^", [MOD] = "%", [LAND] = "and", [LOR] = "or"
};
/* loc_in_box -- returns true if test is in the box determined by b and c */
@ -55,11 +56,40 @@ bool loc_in_box(const Location test,
void cleartoken(Token* tok)
{
tok->type = EMPTY;
tok->u.flt = 0.0;
tok->u.location[0] = 0;
tok->u.location[1] = 0;
tok->u.location[2] = 0;
}
/* tok_matches - return true if l and r appear to be the same token */ /*{{{*/
bool tok_matches(const Token* l, const Token *r)
{
if (l->type != r->type) return false;
switch (l->type) {
case EMPTY: return true;
case STRING: return l->u.string == r->u.string;
case FLOAT: return l->u.flt == r->u.flt;
case INT: return l->u.integer == r->u.integer;
case OPERATOR: return l->u.op == r->u.op;
case LIDENT: return l->u.lident == r->u.lident;
case FIDENT: return l->u.fident == r->u.fident;
case LOCATION:
return l->u.location[X] == r->u.location[X]
&& l->u.location[Y] == r->u.location[Y]
&& l->u.location[Z] == r->u.location[Z];
case EEK: return l->u.err == r->u.err;
case FUNCALL:
return l->u.funcall.fident == r->u.funcall.fident
&& l->u.funcall.argc == r->u.funcall.argc
&& l->u.funcall.argv == r->u.funcall.argv;
case BOOL:
return l->u.bl == r->u.bl;
}
assert(0);
return false;
}
/* duperror - Sets tok to an error and strdups the message into place */
Token duperror(Token* tok, const char* erro)
{
@ -191,22 +221,32 @@ static Token *ident(const char **s)
begin=*s; ++(*s);
while (isalpha((int)**s) || **s=='_' || **s=='@' || **s=='&' || **s=='.' || **s=='$' || isdigit((int)**s)) ++(*s);
result=malloc(sizeof(Token));
if ((fident = identcode(begin,(size_t)(*s-begin))) == NOT_A_FUNCTION)
result = malloc(sizeof(Token));
if (*s-begin == 3 && strncmp(begin, "and", 3) == 0)
{
result->type=LIDENT;
result->type = OPERATOR;
result->u.op = LAND;
}
else if (*s-begin == 2 && strncmp(begin, "or", 2) == 0)
{
result->type = OPERATOR;
result->u.op = LOR;
}
else if ((fident = identcode(begin,(size_t)(*s-begin))) == NOT_A_FUNCTION)
{
result->type = LIDENT;
result->u.lident=malloc((size_t)(*s-begin+1));
(void)strncpy(result->u.lident,begin,(size_t)(*s-begin));
result->u.lident[*s-begin]='\0';
}
else
{
result->type=FIDENT;
result->u.fident=fident;
result->type = FIDENT;
result->u.fident = fident;
}
return result;
}
return (Token*)0;
return NULLTOKEN;
}
/*}}}*/

View File

@ -15,7 +15,8 @@ extern "C" {
typedef enum {
EMPTY
#ifndef __cplusplus
, STRING, FLOAT, INT, OPERATOR, LIDENT, FIDENT, LOCATION, EEK, FUNCALL
, STRING, FLOAT, INT, OPERATOR, LIDENT, FIDENT, LOCATION, EEK,
FUNCALL, BOOL
#endif
} Type;
@ -25,13 +26,15 @@ extern const char *Type_Name[];
typedef enum
{
PLUS, MINUS, MUL, DIV, OP, CP, COMMA,
LT, /* MUST be the first relational operation for parsing to work */
LE, GE, GT, ISEQUAL, ABOUTEQ,
NE, /* MUST be the last relational operation for parsing to work */
POW, MOD
LT, /* Take care with the macros just below */
LE, GE, GT, ISEQUAL, ABOUTEQ, NE,
POW, MOD, LAND, LOR
} Operator;
#define MAX_OP_NAME_LENGTH 3
#define IS_RELATION_OP(op) (((op)>= LT) && ((op)<=NE))
extern const char *Op_Name[];
typedef int Location[3]; /* NOTE: Locations are passed by REFERENCE not value */
@ -159,6 +162,7 @@ typedef struct Token_struc
Location location;
FunctionCall funcall;
char *err;
bool bl;
} u;
} Token;
@ -166,6 +170,7 @@ typedef struct Token_struc
#define EMPTY_TVEC ((Token**)0)
#define TOKISNUM(t) ((t).type == INT || (t).type == FLOAT || (t).type == EMPTY)
bool tok_matches(const Token *l, const Token *r);
Token duperror(Token* tok, const char* erro);
Token **scan(const char **s);
void cleartoken(Token* tok);