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 \begin_layout Standard
As the last step, save your work sheet to a file: F)ile, S)ave. 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 Up to now, your sheet does not have a name, so you will be prompted for
one: one:
\end_layout \end_layout
@ -4872,8 +4872,26 @@ Integer Integer values are exact, their range depends on the C type long
An example is: An example is:
\family typewriter \family typewriter
42 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 \end_layout
\begin_layout Description \begin_layout Description
@ -4919,6 +4937,93 @@ Unlike other spreadsheets, the operators in teapot check the type of the
\end_layout \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 \begin_layout Description
\series bold \series bold
@ -4932,7 +5037,7 @@ x
y y
\series default \series default
\emph default \emph default
evaluates to 1 if evaluates to Boolean true if
\emph on \emph on
x x
\emph default \emph default
@ -4979,7 +5084,7 @@ x
y y
\series default \series default
\emph default \emph default
evaluates to 1 if evaluates to true if
\emph on \emph on
x x
\emph default \emph default
@ -5004,7 +5109,7 @@ x
y y
\series default \series default
\emph default \emph default
evaluates to 1 if evaluates to true if
\emph on \emph on
x x
\emph default \emph default
@ -5029,7 +5134,7 @@ x
y y
\series default \series default
\emph default \emph default
evaluates to 1 if evaluates to true if
\emph on \emph on
x x
\emph default \emph default
@ -5055,7 +5160,7 @@ x
y y
\series default \series default
\emph default \emph default
evaluates to 1 if evaluates to true if
\emph on \emph on
x x
\emph default \emph default
@ -5079,7 +5184,7 @@ x
y y
\series default \series default
\emph default \emph default
evaluates to 1 if the floating point value evaluates to true if the floating point value
\emph on \emph on
x x
\emph default \emph default
@ -5088,8 +5193,9 @@ x
y y
\emph default \emph default
. .
Almost equal means, the numbers are at most neighbours. Almost equal means that the numbers are equal or neighbor each other in
It is an error to use this with any type but float. the floating point representation being used.
It is an error to use this comparison with any type but float.
\end_layout \end_layout
\begin_layout Description \begin_layout Description
@ -5116,6 +5222,26 @@ y
. .
\end_layout \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 \begin_layout Description
\series bold \series bold
@ -5148,16 +5274,6 @@ y
\emph default \emph default
are strings, the result is the concatenated string. are strings, the result is the concatenated string.
If they are locations, this operates componentwise. 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 \end_layout
\begin_layout Description \begin_layout Description
@ -5207,15 +5323,6 @@ x
y y
\emph default \emph default
are numbers. 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 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). coordinates of two locations (although the use case for that is unclear).
\end_layout \end_layout
@ -5273,24 +5380,6 @@ y
\begin_layout Description \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 \family typewriter
\series bold \series bold
- -
@ -5310,13 +5399,38 @@ x
\emph on \emph on
x x
\emph default \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 If
\emph on \emph on
x x
\emph default \emph default
is empty, the result will be empty, too. 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 \end_layout
\begin_layout Description \begin_layout Description
@ -5517,8 +5631,8 @@ the arguments are interpreted in exactly the same way as for
\series bold \series bold
& &
\series default \series default
with no arguments or even parentheses evaluates to the location of the with no arguments (with or without parentheses) evaluates to the location
current cell. of the current cell.
\end_layout \end_layout
\begin_layout Description \begin_layout Description
@ -5603,14 +5717,21 @@ displaced (by).
\begin_inset Quotes erd \begin_inset Quotes erd
\end_inset \end_inset
Thus, Thus, both
\family sans \family sans
\series bold \series bold
D D
\series default \series default
(-1) (-1)
\family default \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 \family sans
\series bold \series bold
D D
@ -5690,12 +5811,19 @@ R
\series default \series default
(,,1) (,,1)
\family default \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 \family sans
\series bold \series bold
R R
\series default \series default
(&(0,0,1) (below
\family default \family default
), but ), but
\family sans \family sans
@ -5703,7 +5831,8 @@ R
R R
\family default \family default
\series 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 \begin_inset Quotes eld
\end_inset \end_inset
@ -6553,6 +6682,18 @@ location
\begin_layout Description \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 \series medium
float float
\begin_inset space ~ \begin_inset space ~
@ -8003,6 +8144,18 @@ status open
\begin_layout Description \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 \series medium
float float
\begin_inset space ~ \begin_inset space ~
@ -8456,7 +8609,11 @@ negterm
\end_layout \end_layout
\begin_layout Description \begin_layout Description
powterm::= powterm::= [
\family typewriter
-
\family default
]
\emph on \emph on
primary primary
\emph default \emph default
@ -8516,7 +8673,7 @@ mathterm
\end_layout \end_layout
\begin_layout Description \begin_layout Description
term::= relterm::=
\emph on \emph on
factor factor
\emph default \emph default
@ -8551,6 +8708,48 @@ factor
} }
\end_layout \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 \begin_layout Subsection
Implementation notes Implementation notes
\end_layout \end_layout
@ -8634,6 +8833,20 @@ round
to be used as keywords for the int() function. to be used as keywords for the int() function.
\end_layout \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 \begin_layout Section
Frequently Asked Questions Frequently Asked Questions
\end_layout \end_layout

View File

@ -425,7 +425,44 @@ size_t ptokatprec(char* dest, size_t size, size_t field_width, StringFormat sf,
default: /* infix argument */ default: /* infix argument */
assert(tok->u.funcall.argc > 1); assert(tok->u.funcall.argc > 1);
if (fp < atprec && cur < size) dest[cur++] = '('; if (fp < atprec && cur < size) dest[cur++] = '(';
for (size_t ai = 0; ai < tok->u.funcall.argc && cur < size-1; ++ ai) /* 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, 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; bool parenarg = false;
if (ai > 0) { if (ai > 0) {
@ -449,11 +486,21 @@ size_t ptokatprec(char* dest, size_t size, size_t field_width, StringFormat sf,
digits, ef, tok->u.funcall.argv + ai, fp); digits, ef, tok->u.funcall.argv + ai, fp);
if (parenarg && cur < size) dest[cur++] = ')'; if (parenarg && cur < size) dest[cur++] = ')';
} }
}
if (fp < atprec && cur < size) dest[cur++] = ')'; if (fp < atprec && cur < size) dest[cur++] = ')';
break; break;
} }
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 */ /*{{{*/ /* EEK */ /*{{{*/
case 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 */ /*{{{*/ /* print -- print token sequence */ /*{{{*/
void print(char *s, size_t size, size_t chars, StringFormat sf, FloatFormat ff, void print(char *s, size_t size, size_t chars, StringFormat sf, FloatFormat ff,
int digits, Token **n) 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; typedef enum {TRUNCATED_ERROR, VERBOSE_ERROR, RESTORE_ERROR} ErrorFormat;
size_t printtok(char *dest, size_t size, size_t field_width, StringFormat sf, size_t printtok(char *dest, size_t size, size_t field_width, StringFormat sf,
FloatFormat ff, int digits, ErrorFormat ef, const Token *tok); 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, void print(char *s, size_t size, size_t chars, StringFormat sf, FloatFormat ff,
int digits, Token **n); int digits, Token **n);
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -88,8 +88,18 @@ void tfree_protected(Token *n, const Token dontfree)
case FUNCALL: case FUNCALL:
if (difftype || n->u.funcall.argv != dontfree.u.funcall.argv) if (difftype || n->u.funcall.argv != dontfree.u.funcall.argv)
{ {
for (int ai = 0; ai < n->u.funcall.argc; ++ai) for (int ai = n->u.funcall.argc-1; ai >= 0; --ai)
tfree_protected(n->u.funcall.argv + ai, dontfree); 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) { if (n->u.funcall.argv != NULLTOKEN) {
free(n->u.funcall.argv); free(n->u.funcall.argv);
n->u.funcall.argv = NULLTOKEN; n->u.funcall.argv = NULLTOKEN;
@ -587,36 +597,20 @@ Token tmul(Token l, Token r)
/* tneg -- monadic - operator */ /*{{{*/ /* tneg -- monadic - operator */ /*{{{*/
Token tneg(Token x) Token tneg(Token x)
{ {
/* variables */ /*{{{*/
Token result; Token result;
int len; result.type = x.type;
/*}}}*/
if (x.type == EEK) return tcopy(x); switch (x.type) {
if (x.type == EMPTY) return x; case EEK: return tcopy(x);
if (x.type == INT) case EMPTY: return x;
/* result is negated int argument */ /*{{{*/ case INT: result.u.integer = -x.u.integer; return result;
{ case FLOAT: result.u.flt = -x.u.flt; return result;
result.type = INT; case LOCATION:
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) for (size_t len = 0; len < 3; ++len)
result.u.location[len] = -x.u.location[len]; result.u.location[len] = -x.u.location[len];
return result; return result;
case BOOL: result.u.bl = !x.u.bl; return result;
default: break;
} }
result.type = EEK; result.type = EEK;
const char* templ = "wrong type for - operator: %s"; const char* templ = "wrong type for - operator: %s";
@ -716,428 +710,189 @@ static Token relational_type_mismatch(Type l, Type r)
/* tlt -- < operator */ /*{{{*/ /* tlt -- < operator */ /*{{{*/
Token tlt(Token l, Token r) Token tlt(Token l, Token r)
{ {
/* variables */ /*{{{*/
Token result; Token result;
static char empty[]=""; result.type = BOOL;
int len; result.u.bl = false;
/*}}}*/ static char empty[] = "";
if (l.type==EEK) /* return left error argument */ /*{{{*/ if (l.type == EEK) return tcopy(l);
return tcopy(l); if (r.type == EEK) return tcopy(r);
/*}}}*/ if (l.type == EMPTY) /* try to assign 0 element of r.type */ /*{{{*/
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; if (r.type == EMPTY) return result;
switch (r.type) switch (r.type)
{ {
case INT: l.u.integer=0; break; case INT: l.u.integer = 0; break;
case FLOAT: l.u.flt=0.0; break; case FLOAT: l.u.flt = 0.0; break;
case STRING: l.u.string=empty; break; case STRING: l.u.string = empty; break;
default: ; 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) switch (l.type)
{ {
case INT: r.u.integer=0; break; case INT: r.u.integer = 0; break;
case FLOAT: r.u.flt=0.0; break; case FLOAT: r.u.flt = 0.0; break;
case STRING: r.u.string=empty; 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==INT && r.type==INT) /* return left int < right int */ /*{{{*/ if (l.type == INT) /* handle left operand integer */ /*{{{*/
{ {
result.type=INT; if (r.type == INT) /* return left int < right 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.bl = l.u.integer < r.u.integer;
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 < 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 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);
}
/*}}}*/
if (l.type == STRING && r.type == STRING) /* return left string < right string */ /*{{{*/
{
result.u.bl = (strcmp(l.u.string,r.u.string)<0);
return result;
}
/*}}}*/
if (l.type == FLOAT) /* handle left operand float */
{
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;
}
/*}}}*/
return relational_type_mismatch(l.type, r.type);
} }
/*}}}*/ /*}}}*/
/* tle -- <= operator */ /*{{{*/ /* tle -- <= operator */ /*{{{*/
Token tle(Token l, Token r) Token tle(Token l, Token r)
{ {
/* variables */ /*{{{*/ return tor(tlt(l, r), teq(l, r));
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;
} }
/*}}}*/ /*}}}*/
/* tge -- >= operator */ /*{{{*/ /* tge -- >= operator */ /*{{{*/
Token tge(Token l, Token r) Token tge(Token l, Token r)
{ {
/* variables */ /*{{{*/ return tor(tgt(l, r), teq(l, r));
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;
} }
/*}}}*/ /*}}}*/
/* tgt -- <= operator */ /*{{{*/
/* tgt -- < operator */ /*{{{*/
Token tgt(Token l, Token r) Token tgt(Token l, Token r)
{ {
/* variables */ /*{{{*/ return tlt(r, l);
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;
} }
/*}}}*/ /*}}}*/
/* teq -- == operator */ /*{{{*/ /* teq -- == operator */ /*{{{*/
Token teq(Token l, Token r) Token teq(Token l, Token r)
{ {
/* variables */ /*{{{*/
Token result; Token result;
result.type = BOOL;
result.u.bl = true;
static char empty[]=""; static char empty[]="";
int len;
/*}}}*/
if (l.type==EEK) return tcopy(l); if (l.type == EEK) return tcopy(l);
else if (r.type==EEK) return tcopy(r); if (r.type == EEK) return tcopy(r);
if (l.type==EMPTY) if (l.type == EMPTY)
/* try to assign 0 element of r.type */ /*{{{*/ /* try to assign 0 element of r.type */ /*{{{*/
{ {
l.type=r.type; if (r.type == EMPTY) return result;
switch (r.type) switch (r.type)
{ {
case INT: l.u.integer=0; break; case INT: l.u.integer = 0; break;
case FLOAT: l.u.flt=0.0; break; case FLOAT: l.u.flt = 0.0; break;
case STRING: l.u.string=empty; break; case STRING: l.u.string = empty; break;
default: ; case BOOL: l.u.bl = false; break;
default: return relational_type_mismatch(l.type, r.type);
} }
l.type = r.type;
} }
/*}}}*/ /*}}}*/
if (r.type==EMPTY) if (r.type==EMPTY)
/* try to assign 0 element of l.type */ /*{{{*/ /* try to assign 0 element of l.type */ /*{{{*/
{ {
r.type=l.type;
switch (l.type) switch (l.type)
{ {
case INT: r.u.integer=0; break; case INT: r.u.integer=0; break;
case FLOAT: r.u.flt=0.0; break; case FLOAT: r.u.flt=0.0; break;
case STRING: r.u.string=empty; 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.bl = (l.u.flt == ((FltT)r.u.integer));
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 = 0;
}
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)
{
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 result;
}
if (l.type == INT && r.type == FLOAT)
{
result.u.bl = (((FltT)l.u.integer) == r.u.flt);
return result;
}
if (l.type != r.type)
{
result.u.bl = false;
return result;
}
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;
}
return relational_type_mismatch(l.type, r.type);
} }
/*}}}*/ /*}}}*/
@ -1160,127 +915,64 @@ static bool nearly_equal(FltT a1, FltT a2)
/* tabouteq -- ~= operator */ /*{{{*/ /* tabouteq -- ~= operator */ /*{{{*/
Token tabouteq(Token l, Token r) Token tabouteq(Token l, Token r)
{ {
/* variables */ /*{{{*/
Token result; Token result;
/*}}}*/ result.type = BOOL;
if (l.type==EEK) return tcopy(l); if (l.type == EEK) return tcopy(l);
if (r.type==EEK) return tcopy(r); if (r.type == EEK) return tcopy(r);
if (l.type==EMPTY) if (l.type == EMPTY && r.type == FLOAT)
/* try to assign 0 element of r.type */ /*{{{*/
{ {
l.type=r.type; l.type = FLOAT;
switch (r.type) l.u.flt = 0.0;
}
if (r.type == EMPTY && l.type == FLOAT)
{ {
case FLOAT: l.u.flt=0.0; break; r.type = FLOAT;
default: ; r.u.flt = 0.0;
} }
} if (l.type == FLOAT && r.type == FLOAT)
/*}}}*/
if (r.type==EMPTY)
/* try to assign 0 element of l.type */ /*{{{*/
{ {
r.type=l.type; result.u.bl = nearly_equal(l.u.flt, r.u.flt);
switch (l.type)
{
case FLOAT: r.u.flt=0.0; break;
default: ;
}
}
/*}}}*/
if (l.type==FLOAT && r.type==FLOAT)
{
result.type=INT;
result.u.integer = (int)nearly_equal(l.u.flt, r.u.flt);
}
else
{
result.type=EEK;
result.u.err = strdup(_("Usage: ~= only compares float values"));
}
return result; return result;
}
return duperror(&result, _("Usage: ~= only compares float values"));
} }
/*}}}*/ /*}}}*/
/* tne -- != operator */ /*{{{*/ /* tne -- != operator */ /*{{{*/
Token tne(Token l, Token r) Token tne(Token l, Token r)
{ {
/* variables */ /*{{{*/ return tneg(teq(l,r));
Token result; }
static char empty[]=""; /*}}}*/
int len;
/*}}}*/
if (l.type==EEK) return tcopy(l); /* tor -- logical or of two tokens */ /*{{{*/
if (r.type==EEK) return tcopy(r); Token tor(Token l, Token r)
if (l.type==EMPTY) {
/* try to assign 0 element of r.type */ /*{{{*/ switch (l.type) {
{ case EEK: return tcopy(l);
l.type=r.type; case EMPTY : return tcopy(r);
switch (r.type) case BOOL:
{ if (r.type == EMPTY || l.u.bl) return l;
case INT: l.u.integer=0; break; return r;
case FLOAT: l.u.flt=0.0; break; default: break;
case STRING: l.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;
} }
/*}}}*/ return operand_type_error("and: ", 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: ;
}
}
/*}}}*/
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;
} }
/*}}}*/ /*}}}*/

View File

@ -25,5 +25,6 @@ Token tgt(Token l, Token r);
Token teq(Token l, Token r); Token teq(Token l, Token r);
Token tabouteq(Token l, Token r); Token tabouteq(Token l, Token r);
Token tne(Token l, Token r); Token tne(Token l, Token r);
Token tor(Token l, Token r);
Token tand(Token l, Token r);
#endif #endif

View File

@ -598,6 +598,45 @@ static Token error_func(FunctionIdentifier self, int argc, const Token argv[])
return result; 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 */ /*{{{*/ /* string */ /*{{{*/
static Token string_func(FunctionIdentifier self, int argc, const Token argv[]) 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]); } if (start > 0) { result = tcopy(argv[start-1]); }
for (int i = start; i < argc; ++i) { for (int i = start; i < argc; ++i) {
Token tmp = tbin(result, argv[i]); Token tmp = tbin(result, argv[i]);
tfree(&result); tfree_protected(&result, tmp);
result = tmp; result = tmp;
if (result.type == EEK) return result; if (result.type == EEK) return result;
} }
@ -737,7 +776,7 @@ static void sum_updt(FunctionIdentifier id, Location *loc, Token *tok,
{ {
assert(id == FUNC_SUM); assert(id == FUNC_SUM);
Token tmp = tadd(*tok, *newtok); Token tmp = tadd(*tok, *newtok);
tfree(tok); tfree_protected(tok, tmp);
*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[]) static Token int_func(FunctionIdentifier self, int argc, const Token argv[])
{ {
assert(self == FUNC_INT); assert(self == FUNC_INT);
/* variables */ /*{{{*/ const char *usage = _("Usage: int(string|bool|float[,rounding_function])");
Token result; Token result;
/*}}}*/
if (argc == 1 && argv[0].type == FLOAT)
/* result is integer with cutoff fractional part */ /*{{{*/
{
result.type = INT; 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; result.u.integer = (IntT)argv[0].u.flt;
return result; return result;
} } else { /* result is integer with given conversion */ /*{{{*/
/*}}}*/
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) switch (argv[1].u.fident)
{ {
case FUNC_TRUNC: result.u.integer = (IntT)argv[0].u.flt; break; case FUNC_TRUNC:
case FUNC_ROUND: result.u.integer = ROUNDFLTINT(argv[0].u.flt); break; result.u.integer = (IntT)argv[0].u.flt; break;
case FUNC_FLOOR: result.u.integer = (IntT)FLOORFLT(argv[0].u.flt); break; case FUNC_ROUND:
case FUNC_CEIL: result.u.integer = (IntT) CEILFLT(argv[0].u.flt); break; 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); default: assert(0);
} }
return result; return result;
} }
/*}}}*/ case STRING:
if (argc == 1 && argv[0].type == STRING)
/* result is integer */ /*{{{*/
{ {
char *s; char *s;
if (argc > 1) return duperror(&result, usage);
errno=0; errno=0;
result.u.integer = STRTOINT(argv[0].u.string, &s, 10); result.u.integer = STRTOINT(argv[0].u.string, &s, 10);
if (s==(char*)0 || *s) duperror(&result, _("int(string): invalid string")); if (s==(char*)0 || *s)
else if (errno==ERANGE && return duperror(&result, _("int(string): invalid string"));
if (errno==ERANGE &&
(result.u.integer==LONG_MAX || result.u.integer==LONG_MIN)) (result.u.integer==LONG_MAX || result.u.integer==LONG_MIN))
duperror(&result, _("int(string): domain error")); return duperror(&result, _("int(string): domain error"));
else result.type = INT;
return result; return result;
} }
/*}}}*/ case BOOL:
return duperror(&result, _("Usage: int(string|float[,rounding_function])")); 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;
}
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) for (size_t i = 2; i < argc; ++i)
{ {
tmp = tmul(result,argv[0]); tmp = tmul(result,argv[0]);
tfree(&result); tfree_protected(&result, tmp);
result = tmp; result = tmp;
tmp = tadd(result,argv[i]); tmp = tadd(result,argv[i]);
tfree(&result); tfree_protected(&result, tmp);
result = tmp; result = tmp;
if (result.type == EEK) return result; if (result.type == EEK) return result;
} }
@ -1386,6 +1427,12 @@ Tfunc tfunc[]=
[FUNC_ERROR] = { "error", error_func, PREFIX_FUNC, FUNCT, 0 }, [FUNC_ERROR] = { "error", error_func, PREFIX_FUNC, FUNCT, 0 },
[FUNC_EVAL] = { "eval", eval_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 */ /* Type conversion functions */
[FUNC_FLOAT] = { "float", float_func, PREFIX_FUNC, FUNCT, 0 }, [FUNC_FLOAT] = { "float", float_func, PREFIX_FUNC, FUNCT, 0 },
[FUNC_INT] = { "int", int_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_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_R, FUNC_D, FUNC_CAP_X, FUNC_X_AMPERSAND, FUNC_NEGATE, FUNC_PLUS_SYMBOL,
FUNC_MINUS_SYMBOL, FUNC_ASTERISK, FUNC_SLASH, 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_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_PER_CENT, FUNC_CONCAT, FUNC_TAU, FUNC_SQRT, FUNC_FLOOR, FUNC_CEIL,
FUNC_TRUNC, FUNC_ROUND, FUNC_DECIMAL, FUNC_SCIENTIFIC, FUNC_COMPACT, 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_HEXACT, FUNC_RIGHT, FUNC_LEFT, FUNC_DOWN, FUNC_UP, FUNC_BELOW, FUNC_ABOVE,
FUNC_TRUE, FUNC_FALSE, FUNC_AND, FUNC_OR,
N_FUNCTION_IDS N_FUNCTION_IDS
} FunctionIdentifier; } 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 */ /* Forward declaration of Token, since this header is used in scanner.h */
typedef struct Token_struc Token; typedef struct Token_struc Token;
typedef enum /* In increasing order of precedence */ 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 PREFIX_NEG, INFIX_POW, PREFIX_FUNC
} FunctionPrecedence; } FunctionPrecedence;

View File

@ -32,9 +32,14 @@ extern char *strdup(const char* s);
/*}}}*/ /*}}}*/
/* prototypes */ /*{{{*/ /* 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 /* full_eval_funcall -- evaluate the args of a funcall token and then
call the function on them call the function on them
*/ */
@ -43,16 +48,23 @@ static Token full_eval_funcall(Token *t)
assert(t->type == FUNCALL); assert(t->type == FUNCALL);
if (t->u.funcall.argc < 1) if (t->u.funcall.argc < 1)
return tfuncall(t->u.funcall.fident, t->u.funcall.argc, 0); return tfuncall(t->u.funcall.fident, t->u.funcall.argc, 0);
Token *eval_argv = malloc(t->u.funcall.argc*sizeof(Token)); 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) { for (size_t ai = 0; ai < t->u.funcall.argc; ++ai) {
eval_argv[ai] = evaltoken(t->u.funcall.argv[ai], FULL); eval_argv[ai] = evaltoken(t->u.funcall.argv[ai], FULL);
} }
}
Token result = tfuncall(t->u.funcall.fident, t->u.funcall.argc, eval_argv); Token result = tfuncall(t->u.funcall.fident, t->u.funcall.argc, eval_argv);
if (tfunc[t->u.funcall.fident].eval_as == FUNCT) {
/* To allow a function to return one of its arguments, we need /* To allow a function to return one of its arguments, we need
to be sure not to free that argument: */ to be sure not to free that argument: */
for (size_t ai = 0; ai < t->u.funcall.argc; ++ai) for (size_t ai = 0; ai < t->u.funcall.argc; ++ai)
tfree_protected(&eval_argv[ai], result); tfree_protected(&eval_argv[ai], result);
free(eval_argv); free(eval_argv);
}
return result; return result;
} }
@ -285,8 +297,8 @@ static Token powterm(Token *n[], int *i, EvalMethod meth)
if (meth == FULL) if (meth == FULL)
{ {
Token result = tpow(l,r); Token result = tpow(l,r);
tfree(&l); tfree_protected(&l, result);
tfree(&r); tfree_protected(&r, result);
if (result.type == EEK) return result; if (result.type == EEK) return result;
l = result; l = result;
} else { } else {
@ -347,8 +359,8 @@ static Token powterm(Token *n[], int *i, EvalMethod meth)
case MOD: result = tmod(l,r); break; case MOD: result = tmod(l,r); break;
default: assert(0); default: assert(0);
} }
tfree(&l); tfree_protected(&l, result);
tfree(&r); tfree_protected(&r, result);
if (result.type == EEK) return result; if (result.type == EEK) return result;
l = result; l = result;
} else { } else {
@ -389,26 +401,24 @@ static Token powterm(Token *n[], int *i, EvalMethod meth)
/* factor -- parse and evaluate a factor of sums/differences */ /*{{{*/ /* factor -- parse and evaluate a factor of sums/differences */ /*{{{*/
static Token factor(Token *n[], int *i, EvalMethod meth) 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; FunctionIdentifier plusident = FUNC_PLUS_SYMBOL;
Token l;
Operator op = CP; Operator op = CP;
bool first_funcall = true; 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; if (n[*i] != NULLTOKEN && n[*i]->type == OPERATOR) op = n[*i]->u.op;
while (op == PLUS || op == MINUS) while (op == PLUS || op == MINUS)
{ {
Token r;
++(*i); ++(*i);
r = piterm(n, i, meth); Token r = piterm(n, i, meth);
if (meth == FULL) if (meth == FULL)
{ {
Token result = (op==PLUS ? tadd(l,r) : tsub(l,r)); Token result = (op==PLUS ? tadd(l,r) : tsub(l,r));
tfree(&l); tfree_protected(&l, result);
tfree(&r); tfree_protected(&r, result);
if (result.type == EEK) return result; if (result.type == EEK) return result;
l = result; l = result;
} else { } else {
@ -433,8 +443,7 @@ static Token factor(Token *n[], int *i, EvalMethod meth)
{ {
tfree(&r); tfree(&r);
tfree(&l); tfree(&l);
duperror(&l, _("Exceeded maximum sequence length of +")); return duperror(&l, _("Exceeded maximum sequence length of +"));
return l;
} }
l.u.funcall.argv[(l.u.funcall.argc)++] = r; 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 */ /*{{{*/ /* relterm -- parse and evaluate a relational term */ /*{{{*/
static Token term(Token *n[], int *i, EvalMethod meth) static Token relterm(Token *n[], int *i, EvalMethod meth)
{ {
Token l = factor(n, i, meth); Token l = factor(n, i, meth);
if (l.type == EEK) return l; 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); ++(*i);
r = factor(n, i, meth); Token r = factor(n, i, LITERAL);
if (meth == FULL) if (meth == FULL)
{ {
if (!firstcomp && result.type == BOOL && !result.u.bl) {
tfree(&r);
} else {
Token tmp = evaltoken(r, FULL);
tfree_protected(&r, tmp);
r = tmp;
switch (op) switch (op)
{ {
case LT: result=tlt(l,r); break; case LT: tmp = tlt(l,r); break;
case LE: result=tle(l,r); break; case LE: tmp = tle(l,r); break;
case GE: result=tge(l,r); break; case GE: tmp = tge(l,r); break;
case GT: result=tgt(l,r); break; case GT: tmp = tgt(l,r); break;
case ISEQUAL: result=teq(l,r); break; case ISEQUAL: tmp = teq(l,r); break;
case ABOUTEQ: result=tabouteq(l,r); break; case ABOUTEQ: tmp = tabouteq(l,r); break;
case NE: result=tne(l,r); break; case NE: tmp = tne(l,r); break;
default: assert(0); 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); 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); tfree(&r);
if (result.type == EEK) return result; else {
l = result; 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;
}
} else { } else {
if (r.type == EEK) if (r.type == EEK)
{ {
tfree(&l); tfree(&l);
return r; return r;
} }
if (first_funcall)
{
first_funcall = false;
Token tmp = l; Token tmp = l;
l.type = FUNCALL; l.type = FUNCALL;
l.u.funcall.fident = identcode(Op_Name[op], strlen(Op_Name[op])); l.u.funcall.fident = identcode(Op_Name[op], strlen(Op_Name[op]));
l.u.funcall.argc = 2; l.u.funcall.argc = 2;
l.u.funcall.argv = malloc(2*sizeof(Token)); l.u.funcall.argv = malloc(MAXARGC * sizeof(Token));
l.u.funcall.argv[0] = tmp; l.u.funcall.argv[0] = tmp;
l.u.funcall.argv[1] = r; 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; return l;
} }
@ -518,8 +637,8 @@ Token eval(Token **n, EvalMethod meth)
if (meth == FULL) if (meth == FULL)
{ {
Token result = tconcat(l,r); Token result = tconcat(l,r);
tfree(&l); tfree_protected(&l, result);
tfree(&r); tfree_protected(&r, result);
if (result.type == EEK) return result; if (result.type == EEK) return result;
l = result; l = result;
} else { } else {

View File

@ -28,7 +28,8 @@ extern long double strtold(const char *nptr, char **endptr); /* SunOS 4 hack */
const char *Type_Name[] = const char *Type_Name[] =
{ [EMPTY] = "EMPTY", [STRING] = "STRING", [FLOAT] = "FLOAT", [INT] = "INT", { [EMPTY] = "EMPTY", [STRING] = "STRING", [FLOAT] = "FLOAT", [INT] = "INT",
[OPERATOR] = "OPERATOR", [LIDENT] = "LABEL", [FIDENT] = "FUNCTION", [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[] = const char *Op_Name[] =
@ -36,7 +37,7 @@ const char *Op_Name[] =
[OP] = "(", [CP] = ")", [COMMA] = ",", [OP] = "(", [CP] = ")", [COMMA] = ",",
[LT] = "<", [LE] = "<=", [GE] = ">=", [GT] = ">", [LT] = "<", [LE] = "<=", [GE] = ">=", [GT] = ">",
[ISEQUAL] = "==", [ABOUTEQ] = "~=", [NE] = "!=", [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 */ /* 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) void cleartoken(Token* tok)
{ {
tok->type = EMPTY; tok->type = EMPTY;
tok->u.flt = 0.0;
tok->u.location[0] = 0; tok->u.location[0] = 0;
tok->u.location[1] = 0; tok->u.location[1] = 0;
tok->u.location[2] = 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 */ /* duperror - Sets tok to an error and strdups the message into place */
Token duperror(Token* tok, const char* erro) Token duperror(Token* tok, const char* erro)
{ {
@ -191,22 +221,32 @@ static Token *ident(const char **s)
begin=*s; ++(*s); begin=*s; ++(*s);
while (isalpha((int)**s) || **s=='_' || **s=='@' || **s=='&' || **s=='.' || **s=='$' || isdigit((int)**s)) ++(*s); while (isalpha((int)**s) || **s=='_' || **s=='@' || **s=='&' || **s=='.' || **s=='$' || isdigit((int)**s)) ++(*s);
result=malloc(sizeof(Token)); result = malloc(sizeof(Token));
if ((fident = identcode(begin,(size_t)(*s-begin))) == NOT_A_FUNCTION) 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)); result->u.lident=malloc((size_t)(*s-begin+1));
(void)strncpy(result->u.lident,begin,(size_t)(*s-begin)); (void)strncpy(result->u.lident,begin,(size_t)(*s-begin));
result->u.lident[*s-begin]='\0'; result->u.lident[*s-begin]='\0';
} }
else else
{ {
result->type=FIDENT; result->type = FIDENT;
result->u.fident=fident; result->u.fident = fident;
} }
return result; return result;
} }
return (Token*)0; return NULLTOKEN;
} }
/*}}}*/ /*}}}*/

View File

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