diff --git a/doc/teapot.lyx b/doc/teapot.lyx index 7c55552..a429083 100644 --- a/doc/teapot.lyx +++ b/doc/teapot.lyx @@ -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 diff --git a/src/common/cell.c b/src/common/cell.c index 5599c35..cfeb7c9 100644 --- a/src/common/cell.c +++ b/src/common/cell.c @@ -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) diff --git a/src/common/cell.h b/src/common/cell.h index c54e5b6..997b21a 100644 --- a/src/common/cell.h +++ b/src/common/cell.h @@ -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 diff --git a/src/common/eval.c b/src/common/eval.c index e53bb49..e9d0288 100644 --- a/src/common/eval.c +++ b/src/common/eval.c @@ -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.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); } /*}}}*/ diff --git a/src/common/eval.h b/src/common/eval.h index b508353..e2660af 100644 --- a/src/common/eval.h +++ b/src/common/eval.h @@ -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 diff --git a/src/common/func.c b/src/common/func.c index 83657e2..3d2c430 100644 --- a/src/common/func.c +++ b/src/common/func.c @@ -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 }, diff --git a/src/common/func.h b/src/common/func.h index ba20381..fdba1ea 100644 --- a/src/common/func.h +++ b/src/common/func.h @@ -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; diff --git a/src/common/parser.c b/src/common/parser.c index fe9840a..1fc738d 100644 --- a/src/common/parser.c +++ b/src/common/parser.c @@ -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 { diff --git a/src/common/scanner.c b/src/common/scanner.c index 9401d2a..0d9b392 100644 --- a/src/common/scanner.c +++ b/src/common/scanner.c @@ -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; } /*}}}*/ diff --git a/src/common/scanner.h b/src/common/scanner.h index c72f0d4..b932a15 100644 --- a/src/common/scanner.h +++ b/src/common/scanner.h @@ -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);