mirror of
git://git.code.sf.net/p/zsh/code
synced 2024-12-29 16:25:35 +01:00
1677 lines
37 KiB
C
1677 lines
37 KiB
C
/*
|
|
* math.c - mathematical expression evaluation
|
|
*
|
|
* This file is part of zsh, the Z shell.
|
|
*
|
|
* Copyright (c) 1992-1997 Paul Falstad
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, without written agreement and without
|
|
* license or royalty fees, to use, copy, modify, and distribute this
|
|
* software and to distribute modified versions of this software for any
|
|
* purpose, provided that the above copyright notice and the following
|
|
* two paragraphs appear in all copies of this software.
|
|
*
|
|
* In no event shall Paul Falstad or the Zsh Development Group be liable
|
|
* to any party for direct, indirect, special, incidental, or consequential
|
|
* damages arising out of the use of this software and its documentation,
|
|
* even if Paul Falstad and the Zsh Development Group have been advised of
|
|
* the possibility of such damage.
|
|
*
|
|
* Paul Falstad and the Zsh Development Group specifically disclaim any
|
|
* warranties, including, but not limited to, the implied warranties of
|
|
* merchantability and fitness for a particular purpose. The software
|
|
* provided hereunder is on an "as is" basis, and Paul Falstad and the
|
|
* Zsh Development Group have no obligation to provide maintenance,
|
|
* support, updates, enhancements, or modifications.
|
|
*
|
|
*/
|
|
|
|
struct mathvalue;
|
|
|
|
#include "zsh.mdh"
|
|
#include "math.pro"
|
|
|
|
#include <math.h>
|
|
|
|
/* nonzero means we are not evaluating, just parsing */
|
|
|
|
/**/
|
|
int noeval;
|
|
|
|
/* integer zero */
|
|
|
|
/**/
|
|
mod_export mnumber zero_mnumber;
|
|
|
|
/*
|
|
* The last value we computed: note this isn't cleared
|
|
* until the next computation, unlike yyval.
|
|
* Everything else is saved and returned to allow recursive calls.
|
|
*/
|
|
/**/
|
|
mnumber lastmathval;
|
|
|
|
/* last input base we used */
|
|
|
|
/**/
|
|
int lastbase;
|
|
|
|
static char *ptr;
|
|
|
|
static mnumber yyval;
|
|
static char *yylval;
|
|
|
|
#define MAX_MLEVEL 256
|
|
|
|
static int mlevel = 0;
|
|
|
|
/* != 0 means recognize unary plus, minus, etc. */
|
|
|
|
static int unary = 1;
|
|
|
|
/* LR = left-to-right associativity *
|
|
* RL = right-to-left associativity *
|
|
* BOOL = short-circuiting boolean */
|
|
|
|
#define LR 0x0000
|
|
#define RL 0x0001
|
|
#define BOOL 0x0002
|
|
|
|
#define MTYPE(x) ((x) & 3)
|
|
|
|
/*
|
|
* OP_A2 2 arguments
|
|
* OP_A2IR 2 arguments, return integer
|
|
* OP_A2IO 2 arguments, must be integer, return integer
|
|
* OP_E2 2 arguments with assignment
|
|
* OP_E2IO 2 arguments with assignment, must be integer, return integer
|
|
* OP_OP None of the above, but occurs where we are expecting an operator
|
|
* rather than an operand.
|
|
* OP_OPF Followed by an operator, not an operand.
|
|
*
|
|
* OP_A2*, OP_E2*, OP_OP*:
|
|
* Occur when we need an operator; the next object must be an operand,
|
|
* unless OP_OPF is also supplied.
|
|
*
|
|
* Others:
|
|
* Occur when we need an operand; the next object must also be an operand,
|
|
* unless OP_OPF is also supplied.
|
|
*/
|
|
#define OP_A2 0x0004
|
|
#define OP_A2IR 0x0008
|
|
#define OP_A2IO 0x0010
|
|
#define OP_E2 0x0020
|
|
#define OP_E2IO 0x0040
|
|
#define OP_OP 0x0080
|
|
#define OP_OPF 0x0100
|
|
|
|
#define M_INPAR 0
|
|
#define M_OUTPAR 1
|
|
#define NOT 2
|
|
#define COMP 3
|
|
#define POSTPLUS 4
|
|
#define POSTMINUS 5
|
|
#define UPLUS 6
|
|
#define UMINUS 7
|
|
#define AND 8
|
|
#define XOR 9
|
|
#define OR 10
|
|
#define MUL 11
|
|
#define DIV 12
|
|
#define MOD 13
|
|
#define PLUS 14
|
|
#define MINUS 15
|
|
#define SHLEFT 16
|
|
#define SHRIGHT 17
|
|
#define LES 18
|
|
#define LEQ 19
|
|
#define GRE 20
|
|
#define GEQ 21
|
|
#define DEQ 22
|
|
#define NEQ 23
|
|
#define DAND 24
|
|
#define DOR 25
|
|
#define DXOR 26
|
|
#define QUEST 27
|
|
#define COLON 28
|
|
#define EQ 29
|
|
#define PLUSEQ 30
|
|
#define MINUSEQ 31
|
|
#define MULEQ 32
|
|
#define DIVEQ 33
|
|
#define MODEQ 34
|
|
#define ANDEQ 35
|
|
#define XOREQ 36
|
|
#define OREQ 37
|
|
#define SHLEFTEQ 38
|
|
#define SHRIGHTEQ 39
|
|
#define DANDEQ 40
|
|
#define DOREQ 41
|
|
#define DXOREQ 42
|
|
#define COMMA 43
|
|
#define EOI 44
|
|
#define PREPLUS 45
|
|
#define PREMINUS 46
|
|
#define NUM 47
|
|
#define ID 48
|
|
#define POWER 49
|
|
#define CID 50
|
|
#define POWEREQ 51
|
|
#define FUNC 52
|
|
#define TOKCOUNT 53
|
|
|
|
/*
|
|
* Operator precedences: in reverse order, i.e. lower number, high precedence.
|
|
* These are the C precedences.
|
|
*
|
|
* 0 Non-operators: NUM (numeric constant), ID (identifier),
|
|
* CID (identifier with '#'), FUNC (math function)
|
|
* 1 Opening parenthesis: M_INPAR '(' (for convenience, not an operator)
|
|
* 2 Unary operators: PREPLUS/POSTPLUS '++', PREMINUS/POSTMINUS '--',
|
|
* NOT '!', COMP '~', UPLUS '+', UMINUS '-'
|
|
* 3 POWER '**' (not in C but at high precedence in Perl)
|
|
* 4 MUL '*', DIV '/', MOD '%'
|
|
* 5 PLUS '+', MINUS '-'
|
|
* 6 SHLEFT '<<', SHRIGHT '>>'
|
|
* 7 GRE '>', 'GEQ' '>=', LES '<', LEQ '<='
|
|
* 8 DEQ '==', NEQ '!='
|
|
* 9 AND '&'
|
|
* 10 XOR '^'
|
|
* 11 OR '|'
|
|
* 12 DAND '&&'
|
|
* 13 DXOR '^^' (not in C)
|
|
* 14 DOR '||'
|
|
* 15 QUEST '?'
|
|
* 16 COLON ':'
|
|
* 17 EQ '=', PLUSEQ '+=', MINUSEQ '-=', MULEQ '*=', DIVEQ '/=',
|
|
* MODEQ '%=', ANDEQ '&=', XOREQ '^=', OREQ '|=',
|
|
* SHFLEFTEQ '<<=', SHRIGHTEQ '>>=', DANDEQ '&&=', DOREQ '||=',
|
|
* DXOREQ '^^='
|
|
* 18 COMMA ','
|
|
* 137 M_OUTPAR ')' (for convenience, not an operator)
|
|
* 200 EOI (end of input: for convenience, not an operator)
|
|
*/
|
|
static int c_prec[TOKCOUNT] =
|
|
{
|
|
/* M_INPAR M_OUTPAR NOT COMP POSTPLUS */
|
|
/* 0 */ 1, 137, 2, 2, 2,
|
|
/* POSTMINUS UPLUS UMINUS AND XOR */
|
|
/* 5 */ 2, 2, 2, 9, 10,
|
|
/* OR MUL DIV MOD PLUS */
|
|
/* 10 */ 11, 4, 4, 4, 5,
|
|
/* MINUS SHLEFT SHRIGHT LES LEQ */
|
|
/* 15 */ 5, 6, 6, 7, 7,
|
|
/* GRE GEQ DEQ NEQ DAND */
|
|
/* 20 */ 7, 7, 8, 8, 12,
|
|
/* DOR DXOR QUEST COLON EQ */
|
|
/* 25 */ 14, 13, 15, 16, 17,
|
|
/* PLUSEQ MINUSEQ MULEQ DIVEQ MODEQ */
|
|
/* 30 */ 17, 17, 17, 17, 17,
|
|
/* ANDEQ XOREQ OREQ SHLEFTEQ SHRIGHTEQ */
|
|
/* 35 */ 17, 17, 17, 17, 17,
|
|
/* DANDEQ DOREQ DXOREQ COMMA EOI */
|
|
/* 40 */ 17, 17, 17, 18, 200,
|
|
/* PREPLUS PREMINUS NUM ID POWER */
|
|
/* 45 */ 2, 2, 0, 0, 3,
|
|
/* CID POWEREQ FUNC */
|
|
/* 50 */ 0, 17, 0
|
|
};
|
|
|
|
/*
|
|
* Operator precedences: in reverse order, i.e. lower number, high precedence.
|
|
* These are the default zsh precedences.
|
|
*
|
|
* 0 Non-operators: NUM (numeric constant), ID (identifier),
|
|
* CID (identifier with '#'), FUNC (math function)
|
|
* 1 Opening parenthesis: M_INPAR '(' (for convenience, not an operator)
|
|
* 2 Unary operators: PREPLUS/POSTPLUS '++', PREMINUS/POSTMINUS '--',
|
|
* NOT '!', COMP '~', UPLUS '+', UMINUS '-'
|
|
* 3 SHLEFT '<<', SHRIGHT '>>'
|
|
* 4 AND '&'
|
|
* 5 XOR '^'
|
|
* 6 OR '|'
|
|
* 7 POWER '**' (not in C but at high precedence in Perl)
|
|
* 8 MUL '*', DIV '/', MOD '%'
|
|
* 9 PLUS '+', MINUS '-'
|
|
* 10 GRE '>', 'GEQ' '>=', LES '<', LEQ '<='
|
|
* 11 DEQ '==', NEQ '!='
|
|
* 12 DAND '&&'
|
|
* 13 DOR '||', DXOR '^^' (not in C)
|
|
* 14 QUEST '?'
|
|
* 15 COLON ':'
|
|
* 16 EQ '=', PLUSEQ '+=', MINUSEQ '-=', MULEQ '*=', DIVEQ '/=',
|
|
* MODEQ '%=', ANDEQ '&=', XOREQ '^=', OREQ '|=',
|
|
* SHFLEFTEQ '<<=', SHRIGHTEQ '>>=', DANDEQ '&&=', DOREQ '||=',
|
|
* DXOREQ '^^='
|
|
* 17 COMMA ','
|
|
* 137 M_OUTPAR ')' (for convenience, not an operator)
|
|
* 200 EOI (end of input: for convenience, not an operator)
|
|
*/
|
|
static int z_prec[TOKCOUNT] =
|
|
{
|
|
/* M_INPAR M_OUTPAR NOT COMP POSTPLUS */
|
|
/* 0 */ 1, 137, 2, 2, 2,
|
|
/* POSTMINUS UPLUS UMINUS AND XOR */
|
|
/* 5 */ 2, 2, 2, 4, 5,
|
|
/* OR MUL DIV MOD PLUS */
|
|
/* 10 */ 6, 8, 8, 8, 9,
|
|
/* MINUS SHLEFT SHRIGHT LES LEQ */
|
|
/* 15 */ 9, 3, 3, 10, 10,
|
|
/* GRE GEQ DEQ NEQ DAND */
|
|
/* 20 */ 10, 10, 11, 11, 12,
|
|
/* DOR DXOR QUEST COLON EQ */
|
|
/* 25 */ 13, 13, 14, 15, 16,
|
|
/* PLUSEQ MINUSEQ MULEQ DIVEQ MODEQ */
|
|
/* 30 */ 16, 16, 16, 16, 16,
|
|
/* ANDEQ XOREQ OREQ SHLEFTEQ SHRIGHTEQ */
|
|
/* 35 */ 16, 16, 16, 16, 16,
|
|
/* DANDEQ DOREQ DXOREQ COMMA EOI */
|
|
/* 40 */ 16, 16, 16, 17, 200,
|
|
/* PREPLUS PREMINUS NUM ID POWER */
|
|
/* 45 */ 2, 2, 0, 0, 7,
|
|
/* CID POWEREQ FUNC */
|
|
/* 50 */ 0, 16, 0
|
|
};
|
|
|
|
/* Option-selectable preference table */
|
|
static int *prec;
|
|
|
|
/*
|
|
* Precedences for top and argument evaluation. Careful:
|
|
* prec needs to be set before we use these.
|
|
*/
|
|
#define TOPPREC (prec[COMMA]+1)
|
|
#define ARGPREC (prec[COMMA]-1)
|
|
|
|
static int type[TOKCOUNT] =
|
|
{
|
|
/* 0 */ LR, LR|OP_OP|OP_OPF, RL, RL, RL|OP_OP|OP_OPF,
|
|
/* 5 */ RL|OP_OP|OP_OPF, RL, RL, LR|OP_A2IO, LR|OP_A2IO,
|
|
/* 10 */ LR|OP_A2IO, LR|OP_A2, LR|OP_A2, LR|OP_A2, LR|OP_A2,
|
|
/* 15 */ LR|OP_A2, LR|OP_A2IO, LR|OP_A2IO, LR|OP_A2IR, LR|OP_A2IR,
|
|
/* 20 */ LR|OP_A2IR, LR|OP_A2IR, LR|OP_A2IR, LR|OP_A2IR, BOOL|OP_A2IO,
|
|
/* 25 */ BOOL|OP_A2IO, LR|OP_A2IO, RL|OP_OP, RL|OP_OP, RL|OP_E2,
|
|
/* 30 */ RL|OP_E2, RL|OP_E2, RL|OP_E2, RL|OP_E2, RL|OP_E2,
|
|
/* 35 */ RL|OP_E2IO, RL|OP_E2IO, RL|OP_E2IO, RL|OP_E2IO, RL|OP_E2IO,
|
|
/* 40 */ BOOL|OP_E2IO, BOOL|OP_E2IO, RL|OP_A2IO, RL|OP_A2, RL|OP_OP,
|
|
/* 45 */ RL, RL, LR|OP_OPF, LR|OP_OPF, RL|OP_A2,
|
|
/* 50 */ LR|OP_OPF, RL|OP_E2, LR|OP_OPF
|
|
};
|
|
|
|
/* the value stack */
|
|
|
|
#define STACKSZ 100
|
|
static int mtok; /* last token */
|
|
static int sp = -1; /* stack pointer */
|
|
|
|
struct mathvalue {
|
|
/*
|
|
* If we need to get a variable, this is the string to be passed
|
|
* to the parameter code. It may include a subscript.
|
|
*/
|
|
char *lval;
|
|
/*
|
|
* If this is not zero, we've retrieved a variable and this
|
|
* stores a reference to it.
|
|
*/
|
|
Value pval;
|
|
mnumber val;
|
|
};
|
|
|
|
static struct mathvalue *stack;
|
|
|
|
enum prec_type {
|
|
/* Evaluating a top-level expression */
|
|
MPREC_TOP,
|
|
/* Evaluating a function argument */
|
|
MPREC_ARG
|
|
};
|
|
|
|
|
|
/*
|
|
* Get a number from a variable.
|
|
* Try to be clever about reusing subscripts by caching the Value structure.
|
|
*/
|
|
static mnumber
|
|
getmathparam(struct mathvalue *mptr)
|
|
{
|
|
mnumber result;
|
|
if (!mptr->pval) {
|
|
char *s = mptr->lval;
|
|
mptr->pval = (Value)zhalloc(sizeof(struct value));
|
|
if (!getvalue(mptr->pval, &s, 1))
|
|
{
|
|
if (unset(UNSET))
|
|
zerr("%s: parameter not set", mptr->lval);
|
|
mptr->pval = NULL;
|
|
if (isset(FORCEFLOAT)) {
|
|
result.type = MN_FLOAT;
|
|
result.u.d = 0.0;
|
|
return result;
|
|
}
|
|
return zero_mnumber;
|
|
}
|
|
if (errflag)
|
|
return zero_mnumber;
|
|
}
|
|
result = getnumvalue(mptr->pval);
|
|
if (isset(FORCEFLOAT) && result.type == MN_INTEGER) {
|
|
result.type = MN_FLOAT;
|
|
result.u.d = (double)result.u.l;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static mnumber
|
|
mathevall(char *s, enum prec_type prec_tp, char **ep)
|
|
{
|
|
int xlastbase, xnoeval, xunary, *xprec;
|
|
char *xptr;
|
|
mnumber xyyval;
|
|
char *xyylval;
|
|
int xsp;
|
|
struct mathvalue *xstack = 0, nstack[STACKSZ];
|
|
mnumber ret;
|
|
|
|
if (mlevel >= MAX_MLEVEL) {
|
|
xyyval.type = MN_INTEGER;
|
|
xyyval.u.l = 0;
|
|
*ep = s;
|
|
|
|
zerr("math recursion limit exceeded: %s", *ep);
|
|
|
|
return xyyval;
|
|
}
|
|
if (mlevel++) {
|
|
xlastbase = lastbase;
|
|
xnoeval = noeval;
|
|
xunary = unary;
|
|
xptr = ptr;
|
|
xyyval = yyval;
|
|
xyylval = yylval;
|
|
|
|
xsp = sp;
|
|
xstack = stack;
|
|
xprec = prec;
|
|
} else {
|
|
xlastbase = xnoeval = xunary = xsp = 0;
|
|
xyyval.type = MN_INTEGER;
|
|
xyyval.u.l = 0;
|
|
xyylval = NULL;
|
|
xptr = NULL;
|
|
xprec = NULL;
|
|
}
|
|
prec = isset(CPRECEDENCES) ? c_prec : z_prec;
|
|
stack = nstack;
|
|
lastbase = -1;
|
|
ptr = s;
|
|
sp = -1;
|
|
unary = 1;
|
|
stack[0].val.type = MN_INTEGER;
|
|
stack[0].val.u.l = 0;
|
|
mathparse(prec_tp == MPREC_TOP ? TOPPREC : ARGPREC);
|
|
/*
|
|
* Internally, we parse the contents of parentheses at top
|
|
* precedence... so we can return a parenthesis here if
|
|
* there are too many at the end.
|
|
*/
|
|
if (mtok == M_OUTPAR && !errflag)
|
|
zerr("bad math expression: unexpected ')'");
|
|
*ep = ptr;
|
|
DPUTS(!errflag && sp > 0,
|
|
"BUG: math: wallabies roaming too freely in outback");
|
|
|
|
if (errflag) {
|
|
/*
|
|
* This used to set the return value to errflag.
|
|
* I don't understand how that could be useful; the
|
|
* caller doesn't know that's what's happened and
|
|
* may not get a value at all.
|
|
* Worse, we reset errflag in execarith() and setting
|
|
* this explicitly non-zero means a (( ... )) returns
|
|
* status 0 if there's an error. That surely can't
|
|
* be right. execarith() now detects an error and returns
|
|
* status 2.
|
|
*/
|
|
ret.type = MN_INTEGER;
|
|
ret.u.l = 0;
|
|
} else {
|
|
if (stack[0].val.type == MN_UNSET)
|
|
ret = getmathparam(stack);
|
|
else
|
|
ret = stack[0].val;
|
|
}
|
|
|
|
if (--mlevel) {
|
|
lastbase = xlastbase;
|
|
noeval = xnoeval;
|
|
unary = xunary;
|
|
ptr = xptr;
|
|
yyval = xyyval;
|
|
yylval = xyylval;
|
|
|
|
sp = xsp;
|
|
stack = xstack;
|
|
prec = xprec;
|
|
}
|
|
return lastmathval = ret;
|
|
}
|
|
|
|
static int
|
|
lexconstant(void)
|
|
{
|
|
#ifdef USE_LOCALE
|
|
char *prev_locale;
|
|
#endif
|
|
char *nptr;
|
|
|
|
nptr = ptr;
|
|
if (IS_DASH(*nptr))
|
|
nptr++;
|
|
|
|
if (*nptr == '0') {
|
|
int lowchar;
|
|
nptr++;
|
|
lowchar = tolower(*nptr);
|
|
if (lowchar == 'x' || lowchar == 'b') {
|
|
/* Let zstrtol parse number with base */
|
|
yyval.u.l = zstrtol_underscore(ptr, &ptr, 0, 1);
|
|
/* Should we set lastbase here? */
|
|
lastbase = (lowchar == 'b') ? 2 : 16;
|
|
if (isset(FORCEFLOAT))
|
|
{
|
|
yyval.type = MN_FLOAT;
|
|
yyval.u.d = (double)yyval.u.l;
|
|
}
|
|
return NUM;
|
|
}
|
|
else if (isset(OCTALZEROES))
|
|
{
|
|
char *ptr2;
|
|
|
|
/*
|
|
* Make sure this is a real octal constant;
|
|
* it can't be a base indication (always decimal)
|
|
* or a floating point number.
|
|
*/
|
|
for (ptr2 = nptr; idigit(*ptr2) || *ptr2 == '_'; ptr2++)
|
|
;
|
|
|
|
if (ptr2 > nptr && *ptr2 != '.' && *ptr2 != 'e' &&
|
|
*ptr2 != 'E' && *ptr2 != '#')
|
|
{
|
|
yyval.u.l = zstrtol_underscore(ptr, &ptr, 0, 1);
|
|
lastbase = 8;
|
|
if (isset(FORCEFLOAT))
|
|
{
|
|
yyval.type = MN_FLOAT;
|
|
yyval.u.d = (double)yyval.u.l;
|
|
}
|
|
return NUM;
|
|
}
|
|
nptr = ptr2;
|
|
}
|
|
}
|
|
while (idigit(*nptr) || *nptr == '_')
|
|
nptr++;
|
|
|
|
if (*nptr == '.' || *nptr == 'e' || *nptr == 'E') {
|
|
char *ptr2;
|
|
/* it's a float */
|
|
yyval.type = MN_FLOAT;
|
|
#ifdef USE_LOCALE
|
|
prev_locale = dupstring(setlocale(LC_NUMERIC, NULL));
|
|
setlocale(LC_NUMERIC, "POSIX");
|
|
#endif
|
|
if (*nptr == '.') {
|
|
nptr++;
|
|
while (idigit(*nptr) || *nptr == '_')
|
|
nptr++;
|
|
}
|
|
if (*nptr == 'e' || *nptr == 'E') {
|
|
nptr++;
|
|
if (*nptr == '+' || IS_DASH(*nptr))
|
|
nptr++;
|
|
while (idigit(*nptr) || *nptr == '_')
|
|
nptr++;
|
|
}
|
|
for (ptr2 = ptr; ptr2 < nptr; ptr2++) {
|
|
if (*ptr2 == '_') {
|
|
int len = nptr - ptr;
|
|
ptr = dupstring(ptr);
|
|
for (ptr2 = ptr; len; len--) {
|
|
if (*ptr2 == '_')
|
|
chuck(ptr2);
|
|
else
|
|
ptr2++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
yyval.u.d = strtod(ptr, &nptr);
|
|
#ifdef USE_LOCALE
|
|
if (prev_locale) setlocale(LC_NUMERIC, prev_locale);
|
|
#endif
|
|
if (ptr == nptr || *nptr == '.') {
|
|
zerr("bad floating point constant");
|
|
return EOI;
|
|
}
|
|
ptr = nptr;
|
|
} else {
|
|
/* it's an integer */
|
|
yyval.u.l = zstrtol_underscore(ptr, &ptr, 10, 1);
|
|
|
|
if (*ptr == '#') {
|
|
ptr++;
|
|
lastbase = yyval.u.l;
|
|
yyval.u.l = zstrtol_underscore(ptr, &ptr, lastbase, 1);
|
|
}
|
|
if (isset(FORCEFLOAT))
|
|
{
|
|
yyval.type = MN_FLOAT;
|
|
yyval.u.d = (double)yyval.u.l;
|
|
}
|
|
}
|
|
return NUM;
|
|
}
|
|
|
|
/**/
|
|
int outputradix;
|
|
|
|
/**/
|
|
int outputunderscore;
|
|
|
|
#ifndef HAVE_ISINF
|
|
/**/
|
|
int
|
|
isinf(double x)
|
|
{
|
|
if ((-1.0 < x) && (x < 1.0)) /* x is small, and thus finite */
|
|
return (0);
|
|
else if ((x + x) == x) /* only true if x == Infinity */
|
|
return (1);
|
|
else /* must be finite (normal or subnormal), or NaN */
|
|
return (0);
|
|
}
|
|
#endif
|
|
|
|
#if !defined(HAVE_ISNAN)
|
|
static double
|
|
store(double *x)
|
|
{
|
|
return (*x);
|
|
}
|
|
|
|
/**/
|
|
int
|
|
isnan(double x)
|
|
{
|
|
/* (x != x) should be sufficient, but some compilers incorrectly optimize it away */
|
|
return (store(&x) != store(&x));
|
|
}
|
|
#endif
|
|
|
|
/**/
|
|
static int
|
|
zzlex(void)
|
|
{
|
|
int cct = 0;
|
|
char *ie;
|
|
yyval.type = MN_INTEGER;
|
|
|
|
for (;; cct = 0)
|
|
switch (*ptr++) {
|
|
case '+':
|
|
if (*ptr == '+') {
|
|
ptr++;
|
|
return (unary) ? PREPLUS : POSTPLUS;
|
|
}
|
|
if (*ptr == '=') {
|
|
ptr++;
|
|
return PLUSEQ;
|
|
}
|
|
return (unary) ? UPLUS : PLUS;
|
|
case '-':
|
|
case Dash:
|
|
if (IS_DASH(*ptr)) {
|
|
ptr++;
|
|
return (unary) ? PREMINUS : POSTMINUS;
|
|
}
|
|
if (*ptr == '=') {
|
|
ptr++;
|
|
return MINUSEQ;
|
|
}
|
|
if (unary) {
|
|
if (idigit(*ptr) ||
|
|
(*ptr == '.' &&
|
|
(idigit(ptr[1]) || !itype_end(ptr, INAMESPC, 0)))) {
|
|
int ctype = lexconstant();
|
|
if (ctype == NUM)
|
|
{
|
|
if (yyval.type == MN_FLOAT)
|
|
{
|
|
yyval.u.d = -yyval.u.d;
|
|
}
|
|
else
|
|
{
|
|
yyval.u.l = -yyval.u.l;
|
|
}
|
|
}
|
|
return ctype;
|
|
} else
|
|
return UMINUS;
|
|
} else
|
|
return MINUS;
|
|
case '(':
|
|
return M_INPAR;
|
|
case ')':
|
|
return M_OUTPAR;
|
|
case '!':
|
|
if (*ptr == '=') {
|
|
ptr++;
|
|
return NEQ;
|
|
}
|
|
return NOT;
|
|
case '~':
|
|
return COMP;
|
|
case '&':
|
|
if (*ptr == '&') {
|
|
if (*++ptr == '=') {
|
|
ptr++;
|
|
return DANDEQ;
|
|
}
|
|
return DAND;
|
|
} else if (*ptr == '=') {
|
|
ptr++;
|
|
return ANDEQ;
|
|
}
|
|
return AND;
|
|
case '|':
|
|
if (*ptr == '|') {
|
|
if (*++ptr == '=') {
|
|
ptr++;
|
|
return DOREQ;
|
|
}
|
|
return DOR;
|
|
} else if (*ptr == '=') {
|
|
ptr++;
|
|
return OREQ;
|
|
}
|
|
return OR;
|
|
case '^':
|
|
if (*ptr == '^') {
|
|
if (*++ptr == '=') {
|
|
ptr++;
|
|
return DXOREQ;
|
|
}
|
|
return DXOR;
|
|
} else if (*ptr == '=') {
|
|
ptr++;
|
|
return XOREQ;
|
|
}
|
|
return XOR;
|
|
case '*':
|
|
if (*ptr == '*') {
|
|
if (*++ptr == '=') {
|
|
ptr++;
|
|
return POWEREQ;
|
|
}
|
|
return POWER;
|
|
}
|
|
if (*ptr == '=') {
|
|
ptr++;
|
|
return MULEQ;
|
|
}
|
|
return MUL;
|
|
case '/':
|
|
if (*ptr == '=') {
|
|
ptr++;
|
|
return DIVEQ;
|
|
}
|
|
return DIV;
|
|
case '%':
|
|
if (*ptr == '=') {
|
|
ptr++;
|
|
return MODEQ;
|
|
}
|
|
return MOD;
|
|
case '<':
|
|
if (*ptr == '<') {
|
|
if (*++ptr == '=') {
|
|
ptr++;
|
|
return SHLEFTEQ;
|
|
}
|
|
return SHLEFT;
|
|
} else if (*ptr == '=') {
|
|
ptr++;
|
|
return LEQ;
|
|
}
|
|
return LES;
|
|
case '>':
|
|
if (*ptr == '>') {
|
|
if (*++ptr == '=') {
|
|
ptr++;
|
|
return SHRIGHTEQ;
|
|
}
|
|
return SHRIGHT;
|
|
} else if (*ptr == '=') {
|
|
ptr++;
|
|
return GEQ;
|
|
}
|
|
return GRE;
|
|
case '=':
|
|
if (*ptr == '=') {
|
|
ptr++;
|
|
return DEQ;
|
|
}
|
|
return EQ;
|
|
case '$':
|
|
yyval.u.l = mypid;
|
|
return NUM;
|
|
case '?':
|
|
if (unary) {
|
|
yyval.u.l = lastval;
|
|
return NUM;
|
|
}
|
|
return QUEST;
|
|
case ':':
|
|
return COLON;
|
|
case ',':
|
|
return COMMA;
|
|
case '\0':
|
|
ptr--;
|
|
return EOI;
|
|
case '[':
|
|
{
|
|
int n, checkradix = 0;
|
|
|
|
if (idigit(*ptr)) {
|
|
n = zstrtol(ptr, &ptr, 10);
|
|
if (*ptr != ']' || !idigit(*++ptr)) {
|
|
zerr("bad base syntax");
|
|
return EOI;
|
|
}
|
|
yyval.u.l = zstrtol(ptr, &ptr, lastbase = n);
|
|
return NUM;
|
|
}
|
|
if (*ptr == '#') {
|
|
n = 1;
|
|
if (*++ptr == '#') {
|
|
n = -1;
|
|
ptr++;
|
|
}
|
|
if (!idigit(*ptr) && *ptr != '_')
|
|
goto bofs;
|
|
if (idigit(*ptr)) {
|
|
outputradix = n * zstrtol(ptr, &ptr, 10);
|
|
checkradix = 1;
|
|
}
|
|
if (*ptr == '_') {
|
|
ptr++;
|
|
if (idigit(*ptr))
|
|
outputunderscore = zstrtol(ptr, &ptr, 10);
|
|
else
|
|
outputunderscore = 3;
|
|
}
|
|
} else {
|
|
bofs:
|
|
zerr("bad output format specification");
|
|
return EOI;
|
|
}
|
|
if(*ptr != ']')
|
|
goto bofs;
|
|
if (checkradix) {
|
|
n = (outputradix < 0) ? -outputradix : outputradix;
|
|
if (n < 2 || n > 36) {
|
|
zerr("invalid base (must be 2 to 36 inclusive): %d",
|
|
outputradix);
|
|
return EOI;
|
|
}
|
|
}
|
|
ptr++;
|
|
break;
|
|
}
|
|
case ' ': /* Fall through! */
|
|
case '\t':
|
|
case '\n':
|
|
case '"': /* POSIX says ignore these */
|
|
case Dnull:
|
|
break;
|
|
default:
|
|
if (idigit(*--ptr) ||
|
|
(*ptr == '.' &&
|
|
(idigit(ptr[1]) || !itype_end(ptr, INAMESPC, 0))))
|
|
return lexconstant();
|
|
if (*ptr == '#') {
|
|
if (*++ptr == '\\' || *ptr == '#') {
|
|
int v;
|
|
char *optr = ptr;
|
|
|
|
ptr++;
|
|
if (!*ptr) {
|
|
zerr("bad math expression: character missing after ##");
|
|
return EOI;
|
|
}
|
|
if(!(ptr = getkeystring(ptr, NULL, GETKEYS_MATH, &v))) {
|
|
zerr("bad math expression: bad character after ##");
|
|
ptr = optr;
|
|
return EOI;
|
|
}
|
|
yyval.u.l = v;
|
|
return NUM;
|
|
}
|
|
cct = 1;
|
|
}
|
|
if ((ie = itype_end(ptr, INAMESPC, 0)) != ptr) {
|
|
int func = 0;
|
|
char *p;
|
|
|
|
p = ptr;
|
|
ptr = ie;
|
|
if (ie - p == 3 && !EMULATION(EMULATE_SH)) {
|
|
if ((p[0] == 'N' || p[0] == 'n') &&
|
|
(p[1] == 'A' || p[1] == 'a') &&
|
|
(p[2] == 'N' || p[2] == 'n')) {
|
|
yyval.type = MN_FLOAT;
|
|
yyval.u.d = 0.0;
|
|
yyval.u.d /= yyval.u.d;
|
|
return NUM;
|
|
}
|
|
else if ((p[0] == 'I' || p[0] == 'i') &&
|
|
(p[1] == 'N' || p[1] == 'n') &&
|
|
(p[2] == 'F' || p[2] == 'f')) {
|
|
yyval.type = MN_FLOAT;
|
|
yyval.u.d = 0.0;
|
|
yyval.u.d = 1.0 / yyval.u.d;
|
|
return NUM;
|
|
}
|
|
}
|
|
if (*ptr == '[' || (!cct && *ptr == '(')) {
|
|
char op = *ptr, cp = ((*ptr == '[') ? ']' : ')');
|
|
int l;
|
|
func = (op == '(');
|
|
for (ptr++, l = 1; *ptr && l; ptr++) {
|
|
if (*ptr == op)
|
|
l++;
|
|
if (*ptr == cp)
|
|
l--;
|
|
if (*ptr == '\\' && ptr[1])
|
|
ptr++;
|
|
}
|
|
}
|
|
yylval = dupstrpfx(p, ptr - p);
|
|
return (func ? FUNC : (cct ? CID : ID));
|
|
}
|
|
else if (cct) {
|
|
yyval.u.l = poundgetfn(NULL);
|
|
return NUM;
|
|
}
|
|
return EOI;
|
|
}
|
|
}
|
|
|
|
/**/
|
|
static void
|
|
push(mnumber val, char *lval, int getme)
|
|
{
|
|
if (sp == STACKSZ - 1)
|
|
zerr("stack overflow");
|
|
else
|
|
sp++;
|
|
stack[sp].val = val;
|
|
stack[sp].lval = lval;
|
|
stack[sp].pval = NULL;
|
|
if (getme)
|
|
stack[sp].val.type = MN_UNSET;
|
|
}
|
|
|
|
/**/
|
|
static mnumber
|
|
pop(int noget)
|
|
{
|
|
struct mathvalue *mv = stack+sp;
|
|
|
|
if (mv->val.type == MN_UNSET && !noget)
|
|
mv->val = getmathparam(mv);
|
|
sp--;
|
|
return errflag ? zero_mnumber : mv->val;
|
|
}
|
|
|
|
/**/
|
|
static mnumber
|
|
getcvar(char *s)
|
|
{
|
|
char *t;
|
|
mnumber mn;
|
|
mn.type = MN_INTEGER;
|
|
|
|
queue_signals();
|
|
if (!(t = getsparam(s)))
|
|
mn.u.l = 0;
|
|
else {
|
|
#ifdef MULTIBYTE_SUPPORT
|
|
if (isset(MULTIBYTE)) {
|
|
wint_t wc;
|
|
(void)mb_metacharlenconv(t, &wc);
|
|
if (wc != WEOF) {
|
|
mn.u.l = (zlong)wc;
|
|
unqueue_signals();
|
|
return mn;
|
|
}
|
|
}
|
|
#endif
|
|
mn.u.l = (unsigned char) (*t == Meta ? t[1] ^ 32 : *t);
|
|
}
|
|
unqueue_signals();
|
|
return mn;
|
|
}
|
|
|
|
/**/
|
|
static mnumber
|
|
setmathvar(struct mathvalue *mvp, mnumber v)
|
|
{
|
|
Param pm;
|
|
|
|
if (mvp->pval) {
|
|
/*
|
|
* This value may have been hanging around for a while.
|
|
* Be ultra-paranoid in checking the variable is still valid.
|
|
*/
|
|
char *s = mvp->lval, *ptr;
|
|
Param pm;
|
|
DPUTS(!mvp->lval, "no variable name but variable value in math");
|
|
if ((ptr = strchr(s, '[')))
|
|
s = dupstrpfx(s, ptr - s);
|
|
pm = (Param) paramtab->getnode(paramtab, s);
|
|
if (pm == mvp->pval->pm) {
|
|
if (noeval)
|
|
return v;
|
|
setnumvalue(mvp->pval, v);
|
|
return v;
|
|
}
|
|
/* Different parameter, start again from scratch */
|
|
mvp->pval = NULL;
|
|
}
|
|
if (!mvp->lval) {
|
|
zerr("bad math expression: lvalue required");
|
|
v.type = MN_INTEGER;
|
|
v.u.l = 0;
|
|
return v;
|
|
}
|
|
if (noeval)
|
|
return v;
|
|
untokenize(mvp->lval);
|
|
pm = setnparam(mvp->lval, v);
|
|
if (pm) {
|
|
/*
|
|
* If we are performing an assignment, we return the
|
|
* number with the same type as the parameter we are
|
|
* assigning to, in the spirit of the way assignments
|
|
* in C work. Note this was a change to long-standing
|
|
* zsh behaviour.
|
|
*/
|
|
switch (PM_TYPE(pm->node.flags)) {
|
|
case PM_INTEGER:
|
|
if (v.type != MN_INTEGER) {
|
|
v.u.l = (zlong)v.u.d;
|
|
v.type = MN_INTEGER;
|
|
}
|
|
break;
|
|
|
|
case PM_EFLOAT:
|
|
case PM_FFLOAT:
|
|
if (v.type != MN_FLOAT) {
|
|
v.u.d = (double)v.u.l;
|
|
v.type = MN_FLOAT;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return v;
|
|
}
|
|
|
|
|
|
/**/
|
|
static mnumber
|
|
callmathfunc(char *o)
|
|
{
|
|
MathFunc f;
|
|
char *a, *n;
|
|
static mnumber dummy;
|
|
|
|
n = a = dupstring(o);
|
|
|
|
while (*a != '(')
|
|
a++;
|
|
*a++ = '\0';
|
|
a[strlen(a) - 1] = '\0';
|
|
|
|
if ((f = getmathfunc(n, 1))) {
|
|
if ((f->flags & (MFF_STR|MFF_USERFUNC)) == MFF_STR) {
|
|
return f->sfunc(n, a, f->funcid);
|
|
} else {
|
|
int argc = 0;
|
|
mnumber *argv = NULL, *q, marg;
|
|
LinkList l = newlinklist();
|
|
LinkNode node;
|
|
|
|
if (f->flags & MFF_USERFUNC) {
|
|
/* first argument is function name: always use mathfunc */
|
|
addlinknode(l, n);
|
|
}
|
|
|
|
if (f->flags & MFF_STR) {
|
|
if (!*a) {
|
|
addlinknode(l, dupstring(""));
|
|
argc++;
|
|
}
|
|
} else {
|
|
while (iblank(*a))
|
|
a++;
|
|
}
|
|
while (*a) {
|
|
if (*a) {
|
|
argc++;
|
|
if (f->flags & MFF_USERFUNC) {
|
|
/* need to pass strings */
|
|
char *str;
|
|
if (f->flags & MFF_STR) {
|
|
str = dupstring(a);
|
|
a = "";
|
|
} else {
|
|
marg = mathevall(a, MPREC_ARG, &a);
|
|
if (marg.type & MN_FLOAT) {
|
|
/* convfloat is off the heap */
|
|
str = convfloat(marg.u.d, 0, 0, NULL);
|
|
} else {
|
|
char buf[BDIGBUFSIZE];
|
|
convbase(buf, marg.u.l, 10);
|
|
str = dupstring(buf);
|
|
}
|
|
}
|
|
addlinknode(l, str);
|
|
} else {
|
|
q = (mnumber *) zhalloc(sizeof(mnumber));
|
|
*q = mathevall(a, MPREC_ARG, &a);
|
|
addlinknode(l, q);
|
|
}
|
|
if (errflag || mtok != COMMA)
|
|
break;
|
|
}
|
|
}
|
|
if (*a && !errflag)
|
|
zerr("bad math expression: illegal character: %c", *a);
|
|
if (!errflag) {
|
|
if (argc >= f->minargs && (f->maxargs < 0 ||
|
|
argc <= f->maxargs)) {
|
|
if (f->flags & MFF_USERFUNC) {
|
|
char *shfnam = f->module ? f->module : n;
|
|
Shfunc shfunc = getshfunc(shfnam);
|
|
if (!shfunc)
|
|
zerr("no such function: %s", shfnam);
|
|
else {
|
|
doshfunc(shfunc, l, 1);
|
|
return lastmathval;
|
|
}
|
|
} else {
|
|
if (argc) {
|
|
q = argv =
|
|
(mnumber *)zhalloc(argc * sizeof(mnumber));
|
|
for (node = firstnode(l); node; incnode(node))
|
|
*q++ = *(mnumber *)getdata(node);
|
|
}
|
|
return f->nfunc(n, argc, argv, f->funcid);
|
|
}
|
|
} else
|
|
zerr("wrong number of arguments: %s", o);
|
|
}
|
|
}
|
|
} else {
|
|
zerr("unknown function: %s", n);
|
|
}
|
|
|
|
dummy.type = MN_INTEGER;
|
|
dummy.u.l = 0;
|
|
|
|
return dummy;
|
|
}
|
|
|
|
/**/
|
|
static int
|
|
notzero(mnumber a)
|
|
{
|
|
if ((a.type & MN_INTEGER) && a.u.l == 0) {
|
|
zerr("division by zero");
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* macro to pop three values off the value stack */
|
|
|
|
static void
|
|
op(int what)
|
|
{
|
|
mnumber a, b, c, *spval;
|
|
int tp = type[what];
|
|
|
|
if (errflag)
|
|
return;
|
|
if (sp < 0) {
|
|
zerr("bad math expression: stack empty");
|
|
return;
|
|
}
|
|
|
|
if (tp & (OP_A2|OP_A2IR|OP_A2IO|OP_E2|OP_E2IO)) {
|
|
/* Make sure anyone seeing this message reports it. */
|
|
DPUTS(sp < 1, "BUG: math: not enough wallabies in outback.");
|
|
b = pop(0);
|
|
a = pop(what == EQ);
|
|
if (errflag)
|
|
return;
|
|
|
|
if (tp & (OP_A2IO|OP_E2IO)) {
|
|
/* coerce to integers */
|
|
if (a.type & MN_FLOAT) {
|
|
a.type = MN_INTEGER;
|
|
a.u.l = (zlong)a.u.d;
|
|
}
|
|
if (b.type & MN_FLOAT) {
|
|
b.type = MN_INTEGER;
|
|
b.u.l = (zlong)b.u.d;
|
|
}
|
|
} else if (a.type != b.type && what != COMMA &&
|
|
(a.type != MN_UNSET || what != EQ)) {
|
|
/*
|
|
* Different types, so coerce to float.
|
|
* It may happen during an assignment that the LHS
|
|
* variable is actually an integer, but there's still
|
|
* no harm in doing the arithmetic in floating point;
|
|
* the assignment will do the correct conversion.
|
|
* This way, if the parameter is actually a scalar, but
|
|
* used to contain an integer, we can write a float into it.
|
|
*/
|
|
if (a.type & MN_INTEGER) {
|
|
a.type = MN_FLOAT;
|
|
a.u.d = (double)a.u.l;
|
|
}
|
|
if (b.type & MN_INTEGER) {
|
|
b.type = MN_FLOAT;
|
|
b.u.d = (double)b.u.l;
|
|
}
|
|
}
|
|
|
|
if (noeval) {
|
|
c.type = MN_INTEGER;
|
|
c.u.l = 0;
|
|
} else {
|
|
/*
|
|
* type for operation: usually same as operands, but e.g.
|
|
* (a == b) returns int.
|
|
*/
|
|
c.type = (tp & OP_A2IR) ? MN_INTEGER : a.type;
|
|
|
|
switch(what) {
|
|
case AND:
|
|
case ANDEQ:
|
|
c.u.l = a.u.l & b.u.l;
|
|
break;
|
|
case XOR:
|
|
case XOREQ:
|
|
c.u.l = a.u.l ^ b.u.l;
|
|
break;
|
|
case OR:
|
|
case OREQ:
|
|
c.u.l = a.u.l | b.u.l;
|
|
break;
|
|
case MUL:
|
|
case MULEQ:
|
|
if (c.type == MN_FLOAT)
|
|
c.u.d = a.u.d * b.u.d;
|
|
else
|
|
c.u.l = a.u.l * b.u.l;
|
|
break;
|
|
case DIV:
|
|
case DIVEQ:
|
|
if (!notzero(b))
|
|
return;
|
|
if (c.type == MN_FLOAT)
|
|
c.u.d = a.u.d / b.u.d;
|
|
else {
|
|
/*
|
|
* Avoid exception when dividing the smallest
|
|
* negative integer by -1. Always treat it the
|
|
* same as multiplication. This still doesn't give
|
|
* numerically the right answer in two's complement,
|
|
* but treating both these in the same way seems
|
|
* reasonable.
|
|
*/
|
|
if (b.u.l == -1)
|
|
c.u.l = - a.u.l;
|
|
else
|
|
c.u.l = a.u.l / b.u.l;
|
|
}
|
|
break;
|
|
case MOD:
|
|
case MODEQ:
|
|
if (!notzero(b))
|
|
return;
|
|
/*
|
|
* Avoid exception as above.
|
|
* Any integer mod -1 is the same as any integer mod 1
|
|
* i.e. zero.
|
|
*/
|
|
if (c.type == MN_FLOAT)
|
|
c.u.d = fmod(a.u.d, b.u.d);
|
|
else if (b.u.l == -1)
|
|
c.u.l = 0;
|
|
else
|
|
c.u.l = a.u.l % b.u.l;
|
|
break;
|
|
case PLUS:
|
|
case PLUSEQ:
|
|
if (c.type == MN_FLOAT)
|
|
c.u.d = a.u.d + b.u.d;
|
|
else
|
|
c.u.l = a.u.l + b.u.l;
|
|
break;
|
|
case MINUS:
|
|
case MINUSEQ:
|
|
if (c.type == MN_FLOAT)
|
|
c.u.d = a.u.d - b.u.d;
|
|
else
|
|
c.u.l = a.u.l - b.u.l;
|
|
break;
|
|
case SHLEFT:
|
|
case SHLEFTEQ:
|
|
c.u.l = a.u.l << b.u.l;
|
|
break;
|
|
case SHRIGHT:
|
|
case SHRIGHTEQ:
|
|
c.u.l = a.u.l >> b.u.l;
|
|
break;
|
|
case LES:
|
|
c.u.l = (zlong)
|
|
(a.type == MN_FLOAT ? (a.u.d < b.u.d) : (a.u.l < b.u.l));
|
|
break;
|
|
case LEQ:
|
|
c.u.l = (zlong)
|
|
(a.type == MN_FLOAT ? (a.u.d <= b.u.d) : (a.u.l <= b.u.l));
|
|
break;
|
|
case GRE:
|
|
c.u.l = (zlong)
|
|
(a.type == MN_FLOAT ? (a.u.d > b.u.d) : (a.u.l > b.u.l));
|
|
break;
|
|
case GEQ:
|
|
c.u.l = (zlong)
|
|
(a.type == MN_FLOAT ? (a.u.d >= b.u.d) : (a.u.l >= b.u.l));
|
|
break;
|
|
case DEQ:
|
|
c.u.l = (zlong)
|
|
(a.type == MN_FLOAT ? (a.u.d == b.u.d) : (a.u.l == b.u.l));
|
|
break;
|
|
case NEQ:
|
|
c.u.l = (zlong)
|
|
(a.type == MN_FLOAT ? (a.u.d != b.u.d) : (a.u.l != b.u.l));
|
|
break;
|
|
case DAND:
|
|
case DANDEQ:
|
|
c.u.l = (zlong)(a.u.l && b.u.l);
|
|
break;
|
|
case DOR:
|
|
case DOREQ:
|
|
c.u.l = (zlong)(a.u.l || b.u.l);
|
|
break;
|
|
case DXOR:
|
|
case DXOREQ:
|
|
c.u.l = (zlong)((a.u.l && !b.u.l) || (!a.u.l && b.u.l));
|
|
break;
|
|
case COMMA:
|
|
c = b;
|
|
break;
|
|
case POWER:
|
|
case POWEREQ:
|
|
if (c.type == MN_INTEGER && b.u.l < 0) {
|
|
/* produces a real result, so cast to real. */
|
|
a.type = b.type = c.type = MN_FLOAT;
|
|
a.u.d = (double) a.u.l;
|
|
b.u.d = (double) b.u.l;
|
|
}
|
|
if (c.type == MN_INTEGER) {
|
|
for (c.u.l = 1; b.u.l--; c.u.l *= a.u.l);
|
|
} else {
|
|
if (b.u.d <= 0 && !notzero(a))
|
|
return;
|
|
if (a.u.d < 0) {
|
|
/* Error if (-num ** b) and b is not an integer */
|
|
double tst = (double)(zlong)b.u.d;
|
|
if (tst != b.u.d) {
|
|
zerr("bad math expression: imaginary power");
|
|
return;
|
|
}
|
|
}
|
|
c.u.d = pow(a.u.d, b.u.d);
|
|
}
|
|
break;
|
|
case EQ:
|
|
c = b;
|
|
break;
|
|
}
|
|
}
|
|
if (tp & (OP_E2|OP_E2IO)) {
|
|
struct mathvalue *mvp = stack + sp + 1;
|
|
c = setmathvar(mvp, c);
|
|
push(c, mvp->lval, 0);
|
|
} else
|
|
push(c,NULL, 0);
|
|
return;
|
|
}
|
|
|
|
spval = &stack[sp].val;
|
|
if (stack[sp].val.type == MN_UNSET) {
|
|
*spval = getmathparam(stack + sp);
|
|
if (errflag)
|
|
return;
|
|
}
|
|
switch (what) {
|
|
case NOT:
|
|
if (spval->type & MN_FLOAT) {
|
|
spval->u.l = !spval->u.d;
|
|
spval->type = MN_INTEGER;
|
|
} else
|
|
spval->u.l = !spval->u.l;
|
|
break;
|
|
case COMP:
|
|
if (spval->type & MN_FLOAT) {
|
|
spval->u.l = ~((zlong)spval->u.d);
|
|
spval->type = MN_INTEGER;
|
|
} else
|
|
spval->u.l = ~spval->u.l;
|
|
break;
|
|
case POSTPLUS:
|
|
a = *spval;
|
|
if (spval->type & MN_FLOAT)
|
|
a.u.d++;
|
|
else
|
|
a.u.l++;
|
|
(void)setmathvar(stack + sp, a);
|
|
break;
|
|
case POSTMINUS:
|
|
a = *spval;
|
|
if (spval->type & MN_FLOAT)
|
|
a.u.d--;
|
|
else
|
|
a.u.l--;
|
|
(void)setmathvar(stack + sp, a);
|
|
break;
|
|
case UPLUS:
|
|
break;
|
|
case UMINUS:
|
|
if (spval->type & MN_FLOAT)
|
|
spval->u.d = -spval->u.d;
|
|
else
|
|
spval->u.l = -spval->u.l;
|
|
break;
|
|
case QUEST:
|
|
DPUTS(sp < 2, "BUG: math: three shall be the number of the counting.");
|
|
c = pop(0);
|
|
b = pop(0);
|
|
a = pop(0);
|
|
if (errflag)
|
|
return;
|
|
/* b and c can stay different types in this case. */
|
|
push(((a.type & MN_FLOAT) ? a.u.d : a.u.l) ? b : c, NULL, 0);
|
|
break;
|
|
case COLON:
|
|
zerr("bad math expression: ':' without '?'");
|
|
break;
|
|
case PREPLUS:
|
|
if (spval->type & MN_FLOAT)
|
|
spval->u.d++;
|
|
else
|
|
spval->u.l++;
|
|
setmathvar(stack + sp, *spval);
|
|
break;
|
|
case PREMINUS:
|
|
if (spval->type & MN_FLOAT)
|
|
spval->u.d--;
|
|
else
|
|
spval->u.l--;
|
|
setmathvar(stack + sp, *spval);
|
|
break;
|
|
default:
|
|
zerr("bad math expression: out of integers");
|
|
return;
|
|
}
|
|
stack[sp].lval = NULL;
|
|
stack[sp].pval = NULL;
|
|
}
|
|
|
|
|
|
/**/
|
|
static void
|
|
bop(int tk)
|
|
{
|
|
mnumber *spval = &stack[sp].val;
|
|
int tst;
|
|
|
|
if (stack[sp].val.type == MN_UNSET)
|
|
*spval = getmathparam(stack + sp);
|
|
tst = (spval->type & MN_FLOAT) ? (zlong)spval->u.d : spval->u.l;
|
|
|
|
switch (tk) {
|
|
case DAND:
|
|
case DANDEQ:
|
|
if (!tst)
|
|
noeval++;
|
|
break;
|
|
case DOR:
|
|
case DOREQ:
|
|
if (tst)
|
|
noeval++;
|
|
break;
|
|
};
|
|
}
|
|
|
|
|
|
/**/
|
|
mod_export mnumber
|
|
matheval(char *s)
|
|
{
|
|
char *junk;
|
|
mnumber x;
|
|
int xmtok = mtok;
|
|
/* maintain outputradix and outputunderscore across levels of evaluation */
|
|
if (!mlevel)
|
|
outputradix = outputunderscore = 0;
|
|
|
|
if (*s == Nularg)
|
|
s++;
|
|
if (!*s) {
|
|
x.type = MN_INTEGER;
|
|
x.u.l = 0;
|
|
return x;
|
|
}
|
|
x = mathevall(s, MPREC_TOP, &junk);
|
|
mtok = xmtok;
|
|
if (*junk)
|
|
zerr("bad math expression: illegal character: %c", *junk);
|
|
return x;
|
|
}
|
|
|
|
/**/
|
|
mod_export zlong
|
|
mathevali(char *s)
|
|
{
|
|
mnumber x = matheval(s);
|
|
return (x.type & MN_FLOAT) ? (zlong)x.u.d : x.u.l;
|
|
}
|
|
|
|
|
|
/**/
|
|
zlong
|
|
mathevalarg(char *s, char **ss)
|
|
{
|
|
mnumber x;
|
|
int xmtok = mtok;
|
|
|
|
/*
|
|
* At this entry point we don't allow an empty expression,
|
|
* whereas we do with matheval(). I'm not sure if this
|
|
* difference is deliberate, but it does mean that e.g.
|
|
* $array[$ind] where ind hasn't been set produces an error,
|
|
* which is probably safe.
|
|
*
|
|
* To avoid a more opaque error further in, bail out here.
|
|
*/
|
|
if (*s == Nularg)
|
|
s++;
|
|
if (!*s) {
|
|
zerr("bad math expression: empty string");
|
|
return (zlong)0;
|
|
}
|
|
x = mathevall(s, MPREC_ARG, ss);
|
|
if (mtok == COMMA)
|
|
(*ss)--;
|
|
mtok = xmtok;
|
|
return (x.type & MN_FLOAT) ? (zlong)x.u.d : x.u.l;
|
|
}
|
|
|
|
/*
|
|
* Make sure we have an operator or an operand, whatever is expected.
|
|
* For this purpose, unary operators constitute part of an operand.
|
|
*/
|
|
|
|
/**/
|
|
static void
|
|
checkunary(int mtokc, char *mptr)
|
|
{
|
|
int errmsg = 0;
|
|
int tp = type[mtokc];
|
|
if (tp & (OP_A2|OP_A2IR|OP_A2IO|OP_E2|OP_E2IO|OP_OP)) {
|
|
if (unary)
|
|
errmsg = 1;
|
|
} else {
|
|
if (!unary)
|
|
errmsg = 2;
|
|
}
|
|
if (errmsg) {
|
|
int len = 0, over = 0;
|
|
char *errtype = errmsg == 2 ? "operator" : "operand";
|
|
while (inblank(*mptr))
|
|
mptr++;
|
|
if (isset(MULTIBYTE))
|
|
MB_CHARINIT();
|
|
while (over < 10 && mptr[len]) {
|
|
if (isset(MULTIBYTE))
|
|
len += MB_METACHARLEN(mptr+len);
|
|
else
|
|
len += (mptr[len] == Meta ? 2 : 1);
|
|
++over;
|
|
}
|
|
if ((over = mptr[len])) {
|
|
mptr = dupstring(mptr);
|
|
if (mptr[len] == Meta)
|
|
mptr[len+1] = 0;
|
|
else
|
|
mptr[len] = 0;
|
|
}
|
|
if (!*mptr)
|
|
zerr("bad math expression: %s expected at end of string",
|
|
errtype);
|
|
else
|
|
zerr("bad math expression: %s expected at `%s%s'",
|
|
errtype, mptr, over ? "..." : "");
|
|
}
|
|
unary = !(tp & OP_OPF);
|
|
}
|
|
|
|
/* operator-precedence parse the string and execute */
|
|
|
|
/**/
|
|
static void
|
|
mathparse(int pc)
|
|
{
|
|
zlong q;
|
|
int otok, onoeval;
|
|
char *optr = ptr;
|
|
|
|
if (errflag)
|
|
return;
|
|
queue_signals();
|
|
mtok = zzlex();
|
|
/* Handle empty input */
|
|
if (pc == TOPPREC && mtok == EOI) {
|
|
unqueue_signals();
|
|
return;
|
|
}
|
|
checkunary(mtok, optr);
|
|
while (prec[mtok] <= pc) {
|
|
if (errflag) {
|
|
unqueue_signals();
|
|
return;
|
|
}
|
|
switch (mtok) {
|
|
case NUM:
|
|
push(yyval, NULL, 0);
|
|
break;
|
|
case ID:
|
|
push(zero_mnumber, yylval, !noeval);
|
|
break;
|
|
case CID:
|
|
push((noeval ? zero_mnumber : getcvar(yylval)), yylval, 0);
|
|
break;
|
|
case FUNC:
|
|
push((noeval ? zero_mnumber : callmathfunc(yylval)), yylval, 0);
|
|
break;
|
|
case M_INPAR:
|
|
mathparse(TOPPREC);
|
|
if (mtok != M_OUTPAR) {
|
|
if (!errflag)
|
|
zerr("bad math expression: ')' expected");
|
|
unqueue_signals();
|
|
return;
|
|
}
|
|
break;
|
|
case QUEST:
|
|
if (stack[sp].val.type == MN_UNSET)
|
|
stack[sp].val = getmathparam(stack + sp);
|
|
q = (stack[sp].val.type == MN_FLOAT) ?
|
|
(stack[sp].val.u.d == 0 ? 0 : 1) :
|
|
stack[sp].val.u.l;
|
|
|
|
if (!q)
|
|
noeval++;
|
|
mathparse(prec[COLON] - 1);
|
|
if (!q)
|
|
noeval--;
|
|
if (mtok != COLON) {
|
|
if (!errflag)
|
|
zerr("bad math expression: ':' expected");
|
|
unqueue_signals();
|
|
return;
|
|
}
|
|
if (q)
|
|
noeval++;
|
|
mathparse(prec[QUEST]);
|
|
if (q)
|
|
noeval--;
|
|
op(QUEST);
|
|
continue;
|
|
default:
|
|
otok = mtok;
|
|
onoeval = noeval;
|
|
if (MTYPE(type[otok]) == BOOL)
|
|
bop(otok);
|
|
mathparse(prec[otok] - (MTYPE(type[otok]) != RL));
|
|
noeval = onoeval;
|
|
op(otok);
|
|
continue;
|
|
}
|
|
optr = ptr;
|
|
mtok = zzlex();
|
|
checkunary(mtok, optr);
|
|
}
|
|
unqueue_signals();
|
|
}
|