/*************************************************************************
* TinyFugue - programmable mud client
* Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
*
* TinyFugue (aka "tf") is protected under the terms of the GNU
* General Public License. See the file "COPYING" for details.
************************************************************************/
static const char RCSid[] = "$Id: expr.c,v 35004.179 2007/01/13 23:12:39 kkeys Exp $";
/********************************************************************
* Fugue expression interpreter
*
* Written by Ken Keys
* Parses and evaluates expressions.
********************************************************************/
#include "tfconfig.h"
#include <math.h>
#include <limits.h>
#include "port.h"
#include "tf.h"
#include "util.h"
#include "pattern.h"
#include "search.h"
#include "tfio.h"
#include "macro.h"
#include "signals.h" /* interrupted() */
#include "socket.h" /* socktime() */
#include "output.h" /* igoto() */
#include "attr.h"
#include "keyboard.h" /* do_kb*() */
#include "parse.h"
#include "expand.h"
#include "expr.h"
#include "cmdlist.h"
#include "command.h"
#include "variable.h"
#include "tty.h" /* no_tty */
#include "history.h" /* log_count */
#include "world.h" /* new_world() */
#define STACKSIZE 512
int stacktop = 0;
Value *stack[STACKSIZE];
const int feature_float = !(NO_FLOAT-0);
static Value *valpool = NULL; /* freelist */
/* dup string operand */
#define opdstrdup(N) Stringdup(opdstr(N))
typedef struct ExprFunc {
const char *name; /* name invoked by user */
unsigned min, max; /* allowable argument counts */
} ExprFunc;
static ExprFunc functab[] = {
#define funccode(name, pure, min, max) { #name, min, max }
#include "funclist.h"
#undef funccode
};
enum func_id {
#define funccode(name, pure, min, max) FN_##name
#include "funclist.h"
#undef funccode
};
static int comma_expr(Program *prog);
static int assignment_expr(Program *prog);
static int conditional_expr(Program *prog);
static int or_expr(Program *prog);
static int and_expr(Program *prog);
static int relational_expr(Program *prog);
static int additive_expr(Program *prog);
static int multiplicative_expr(Program *prog);
static int unary_expr(Program *prog, int could_be_div_or_macro);
static int primary_expr(Program *prog, int could_be_div_or_macro);
static int reduce_arithmetic(opcode_t op, const Value *val0, int n, Value *res);
static Value *function_switch(const ExprFunc *func, int n, const char *parent);
static Value *do_function(int n);
int expr(Program *prog)
{
return comma_expr(prog);
}
/* Returns the value of expression. Caller must freeval() the value. */
Value *expr_value(const char *expression)
{
Program *prog;
Value *result;
conString *str;
if (!expression) return shareval(val_blank);
str = CS(Stringnew(expression, -1, 0)); /* XXX String should be a param */
if (!(prog = compile_tf(str, 0, -1, 1, 0))) return NULL;
result = prog_interpret(prog, 1);
prog_free(prog);
return result;
}
Value *expr_value_safe(Program *prog)
{
return prog_interpret(prog, 1);
}
#if !NO_FLOAT
Value *newfloat_fl(double f, const char *file, int line)
{
Value *val;
palloc(val, Value, valpool, u.next, file, line);
val->count = 1;
val->type = TYPE_FLOAT;
val->u.fval = f;
val->name = NULL;
val->sval = NULL;
return val;
}
#endif /* NO_FLOAT */
inline Value *newval_fl(const char *file, int line)
{
Value *val;
palloc(val, Value, valpool, u.next, file, line);
val->count = 1;
val->name = NULL;
val->sval = NULL;
return val;
}
Value *newint_fl(long i, const char *file, int line)
{
Value *val;
val = newval_fl(file, line);
val->type = TYPE_INT;
val->u.ival = i;
return val;
}
Value *newtime_fl(long s, long u, type_t type, const char *file, int line)
{
Value *val;
val = newval_fl(file, line);
val->type = type;
val->u.tval.tv_sec = s;
val->u.tval.tv_usec = u;
return val;
}
Value *newstr_fl(const char *data, int len, const char *file, int line)
{
Value *val;
val = newval_fl(file, line);
val->type = TYPE_STR;
(val->sval = CS(Stringnew(data, len, 0)))->links++;
val->u.p = NULL;
return val;
}
/* newSstr shares argument; caller must Stringdup() if needed. */
Value *newSstr_fl(conString *str, const char *file, int line)
{
Value *val;
val = newval_fl(file, line);
val->type = TYPE_STR;
(val->sval = str)->links++;
val->u.p = NULL;
return val;
}
Value *newid_fl(const char *id, int len, const char *file, int line)
{
Value *val;
char *new;
val = newval_fl(file, line);
val->type = TYPE_ID;
new = strncpy((char *)xmalloc(NULL, len + 1, file, line), id, len);
new[len] = '\0';
val->name = new;
val->u.hash = hash_string(new); /* cache hashkey to speed lookup */
return val;
}
/* Push a void pointer. */
Value *newptr_fl(void *ptr, const char *file, int line)
{
Value *val;
val = newval_fl(file, line);
val->type = TYPE_FILE;
val->u.p = ptr;
return val;
}
/* If val is an ID, create a copy of its value, free the ID, return the copy;
* otherwise, just return val.
*/
Value *valval_fl(Value *val, const char *file, int line)
{
Value *result;
struct timeval tv;
if (val->type != TYPE_ID)
return val;
if (!(result = hgetnearestvarval(val))) {
result = newSstr_fl(blankline, file, line);
} else {
switch (result->type & TYPES_BASIC) {
case TYPE_ENUM:
case TYPE_POS:
case TYPE_INT:
result = newint_fl(result->u.ival, file, line); break;
case TYPE_DECIMAL:
case TYPE_DTIME:
case TYPE_ATIME:
if (valtime(&tv, result))
result = newtime_fl(tv.tv_sec, tv.tv_usec, result->type,
file, line);
else
result = NULL;
break;
case TYPE_FLOAT:
result = newfloat_fl(valfloat(result), file, line); break;
default:
result = newSstr_fl(valstr(result), file, line); break;
}
}
freeval(val);
return result;
}
/* free value of val (but not its name) */
void clearval_fl(Value *val, const char *file, int line)
{
if (val->sval) {
conStringfree_fl(val->sval, file, line);
val->sval = NULL;
}
if (val->type & TYPE_REGEX) {
tf_reg_free(val->u.ri);
}
if (val->type & TYPE_EXPR) {
prog_free(val->u.prog);
}
val->type &= TYPES_BASIC;
val->u.ival = 0;
}
void freeval_fl(Value *val, const char *file, int line)
{
if (!val) return;
if (--val->count > 0) return;
assert(val->count == 0);
clearval_fl(val, file, line);
if (val->name) {
xfree(NULL, (void*)val->name, file, line);
val->name = NULL;
}
pfree_fl(val, valpool, u.next, file, line);
}
/* Return numeric value of val (converting ID or STR if needed) */
static const Value *valnum(const Value *val)
{
static Value parsed[1]; /* NB: may return pointer to this */
if (!val) return NULL;
if (val->type == TYPE_ID)
if (!(val = hgetnearestvarval(val)))
return NULL;
if (val->type & TYPE_STR) {
if (!val->sval) return NULL;
if (!parsenumber(val->sval->data, NULL, TYPE_NUM, parsed)) {
#if 0
if (pedantic)
wprintf("%s",
"non-numeric string value used in numeric context");
#endif
return NULL;
}
val = parsed;
}
return val;
}
/* return boolean value of item */
int valbool(const Value *val)
{
if (!(val = valnum(val)))
return 0;
if (val->type & (TYPE_INT | TYPE_POS | TYPE_ENUM))
return !!val->u.ival;
if (val->type & (TYPE_DECIMAL|TYPE_DTIME|TYPE_ATIME))
return (val->u.tval.tv_sec || val->u.tval.tv_usec);
#if !NO_FLOAT
else if (val->type & TYPE_FLOAT)
return !!val->u.fval;
#endif
return 0;
}
/* return integer value of item */
long valint(const Value *val)
{
if (!(val = valnum(val)))
return 0;
if (val->type & (TYPE_INT | TYPE_POS | TYPE_ENUM))
return val->u.ival;
if (val->type & (TYPE_DECIMAL|TYPE_DTIME|TYPE_ATIME))
return (long)val->u.tval.tv_sec;
#if !NO_FLOAT
else if (val->type & TYPE_FLOAT) {
double fival = val->u.fval < 0 ? ceil(val->u.fval) : floor(val->u.fval);
if (fival != (long)val->u.fval) {
eprintf("real value too large to convert to integer");
}
return (long)val->u.fval;
}
#endif
return 0;
}
/* copy timeval of item */
int valtime(struct timeval *tvp, const Value *val)
{
if (!(val = valnum(val)))
goto valtime_error;
/* *tvp and val->u might be aliases, so we can't write to *tvp if we still
* need to read from val->u. */
if (val->type & (TYPE_INT | TYPE_POS | TYPE_ENUM)) {
tvp->tv_sec = val->u.ival;
tvp->tv_usec = 0;
} else if (val->type & (TYPE_DECIMAL|TYPE_DTIME|TYPE_ATIME)) {
*tvp = val->u.tval;
#if !NO_FLOAT
} else if (val->type & TYPE_FLOAT) {
long ival = (long)val->u.fval;
double fival = val->u.fval < 0 ? ceil(val->u.fval) : floor(val->u.fval);
if (fival != ival) {
eprintf("real value too large to convert to time");
goto valtime_error;
}
tvp->tv_usec = (long)((val->u.fval - ival) * 1000000);
tvp->tv_sec = ival;
#endif
}
return 1;
valtime_error:
*tvp = tvzero;
return 0;
}
#if !NO_FLOAT
/* return floating value of item */
double valfloat(const Value *val)
{
if (!val) return 0.0;
if (val->type == TYPE_ID) {
if (!(val = hgetnearestvarval(val)))
return 0.0;
}
errno = 0;
if (val->type & TYPE_FLOAT)
return val->u.fval;
if (val->type & (TYPE_INT | TYPE_POS | TYPE_ENUM))
return (double)val->u.ival;
if (val->type & (TYPE_DECIMAL|TYPE_DTIME|TYPE_ATIME))
return (double)val->u.tval.tv_sec + val->u.tval.tv_usec / 1000000.0;
return val->sval ? strtod(val->sval->data, NULL) : 0.0;
}
#endif /* NO_FLOAT */
/* return String value of item (only valid for lifetime of val!) */
/* If C had "mutable", sval could be mutable, and val could be const */
conString *valstr(Value *val)
{
const char *p;
String *sval;
long sec, usec;
if (!val) return NULL;
if (val->type == TYPE_ID) {
if (!(val = hgetnearestvarval(val)))
return blankline;
}
/* use existing sval (but floats may be affected by %sigfigs) */
if (val->sval && val->type != TYPE_FLOAT)
return val->sval;
/* generate and cache new sval */
switch (val->type & TYPES_BASIC) {
case TYPE_STR:
case TYPE_ENUM:
val->sval = blankline;
break;
case TYPE_INT:
case TYPE_POS:
sval = Stringnew(NULL, 0, 0);
Sprintf(sval, "%ld", val->u.ival);
val->sval = CS(sval);
break;
case TYPE_DECIMAL:
case TYPE_ATIME:
/* s.u format */
sval = Stringnew(NULL, 0, 0);
tftime(sval, NULL, &val->u.tval);
val->sval = CS(sval);
break;
case TYPE_DTIME:
sval = Stringnew(NULL, 0, 0);
sec = val->u.tval.tv_sec;
if (sec >= 60 || sec <= -60) {
/* h:mm:ss.u format */
usec = val->u.tval.tv_usec;
if (sec < 0) {
Stringadd(sval, '-');
sec = -sec;
usec = -usec;
}
Sappendf(sval, "%ld:%02ld", sec/3600, (sec/60)%60);
if (sec % 60 || usec)
Sappendf(sval, ":%02ld", sec % 60);
if (usec)
append_usec(sval, usec, 1);
} else {
/* s.u format */
tftime(sval, NULL, &val->u.tval);
}
val->sval = CS(sval);
break;
#if !NO_FLOAT
case TYPE_FLOAT:
if (val->sval) conStringfree(val->sval);
sval = Stringnew(NULL, 0, 0);
Sprintf(sval, "%.*g", sigfigs, val->u.fval);
/* note: appending ".0" could imply more precision than is proper */
for (p = sval->data; is_digit(*p); p++) ;
if (!*p)
Stringadd(sval, '.');
val->sval = CS(sval);
break;
#endif
default:
internal_error(__FILE__, __LINE__, "valstr: impossible type %d",
val->type);
return NULL;
}
val->sval->links++;
return val->sval;
}
/* return String data (char*) of item (only valid for lifetime of val!) */
const char *valstd(Value *val)
{
return val ? valstr(val)->data : NULL;
}
/* Return void pointer value of item. Internal, conversion not needed. */
void *valptr(Value *val)
{
return val ? val->u.p : NULL;
}
int pushval(Value *val)
{
if (stacktop == STACKSIZE) {
eprintf("expression stack overflow");
freeval(val);
return 0;
}
stack[stacktop++] = val;
return 1;
}
/* variable's name has already been looked up, so if var is NULL, it does
* not exist, and assign() creates a new variable with name */
static Value *assign(Var *var, const Value *idval, Value *val)
{
Value tmpvalue, *result;
if (!var)
var = newglobalvar(idval->name);
if (!val) {
tmpvalue.type = TYPE_STR;
tmpvalue.sval = blankline;
val = &tmpvalue;
} else if (val->type & TYPE_STR) { /* must copy the String, not share it */
tmpvalue.type = TYPE_STR;
tmpvalue.sval = CS(Stringdup(val->sval));
val = &tmpvalue;
}
setvar(var, val, 0);
palloc(result, Value, valpool, u.next, __FILE__, __LINE__);
result->name = NULL;
result->count = 1;
if (var) {
if (val->type & TYPE_REGMATCH)
var->val.type |= TYPE_REGMATCH;
result->type = var->val.type;
if ((result->sval = var->val.sval))
result->sval->links++;
result->u = var->val.u;
} else {
result->type = TYPE_STR;
(result->sval = blankline)->links++;
result->u.p = NULL;
}
return result;
}
/* Pop n operands, apply op to them, and push result */
int reduce(opcode_t op, int n)
{
Value *val = NULL;
const Value *val0;
Value res; /* not a proper Value, just result space for reduce_arithmetic */
Var *var;
long i; /* scratch */
if (stacktop < n) {
if (oplabel(op))
internal_error(__FILE__, __LINE__,
"stack underflow, op=%s, n=%d", oplabel(op), n);
else
internal_error(__FILE__, __LINE__,
"stack underflow, op=0x%04X, n=%d", op, n);
return 0;
}
switch (op) {
case '>':
case '<':
case OP_EQUAL:
case OP_NOTEQ:
case OP_GTE:
case OP_LTE:
case '+':
case '-':
case '*':
case '/':
if (!reduce_arithmetic(op, opd(n), n, &res))
break;
switch (res.type) {
case TYPE_FLOAT:
if (res.u.fval == HUGE_VAL || res.u.fval == -HUGE_VAL) {
eprintf("%s operator: arithmetic overflow", oplabel(op));
} else {
val = newfloat(res.u.fval);
}
break;
case TYPE_DECIMAL:
case TYPE_ATIME:
case TYPE_DTIME:
val = newtime(res.u.tval.tv_sec, res.u.tval.tv_usec, res.type);
break;
case TYPE_INT:
val = newint(res.u.ival); break;
default:
eprintf("impossible result type %d", res.type);
}
break;
case OP_ADDA:
case OP_SUBA:
case OP_MULA:
case OP_DIVA:
if (opd(n)->type != TYPE_ID) goto reduce_bad_assignment;
var = hfindnearestvar(opd(n));
val0 = var ? getvarval(var) : val_zero;
if (!reduce_arithmetic(op, val0, n, &res))
break;
if (res.type == TYPE_FLOAT &&
(res.u.fval == HUGE_VAL || res.u.fval == -HUGE_VAL))
{
eprintf("%s operator: arithmetic overflow", oplabel(op));
} else {
val = assign(var, opd(2), &res);
}
break;
case OP_ASSIGN:
if (opd(n)->type != TYPE_ID) goto reduce_bad_assignment;
var = hfindnearestvar(opd(2));
val = opd(1);
if (val && val->type == TYPE_ID)
val = hgetnearestvarval(val);
if (!(val = assign(var, opd(2), val)))
return 0;
break;
case OP_PREDEC: if (opd(n)->type != TYPE_ID) goto reduce_bad_assignment;
i = valint(getvarval(var = hfindnearestvar(opd(1)))) - 1;
if (i == LONG_MAX)
eprintf("integer overflow in --%s", opd(1)->name);
var = setintvar(var ? var : newglobalvar(opd(1)->name),
i, 0);
val = newint(var ? i : 0);
break;
case OP_PREINC: if (opd(n)->type != TYPE_ID) goto reduce_bad_assignment;
i = valint(getvarval(var = hfindnearestvar(opd(1)))) + 1;
if (i == LONG_MIN)
eprintf("integer overflow in ++%s", opd(1)->name);
var = setintvar(var ? var : newglobalvar(opd(1)->name),
i, 0);
val = newint(var ? i : 0);
break;
case OP_STREQ: val = newint(strcmp(opdstd(2), opdstd(1)) == 0); break;
case OP_STRNEQ: val = newint(strcmp(opdstd(2), opdstd(1)) != 0); break;
case OP_MATCH: val = newint(smatch_check(opdstd(1)) &&
smatch(opdstd(1),opdstd(2))==0);
break;
case OP_NMATCH: val = newint(smatch_check(opdstd(1)) &&
smatch(opdstd(1),opdstd(2))!=0);
break;
#if 0 /* doesn't work right */
case '.': if (opd(n-0)->count == 1 && (opd(n-0)->type & TYPE_STR))
(val = opd(n-0))->count++;
else
val = newSstr(opdstr(n-0));
SStringcat(val->sval, opdstr(n-1));
break;
#endif
case OP_FUNC: val = do_function(n); break;
case '!': val = newint(!opdbool(1)); break;
default: internal_error(__FILE__, __LINE__,
"internal error: reduce: bad op 0x%04X", op);
break;
}
goto reduce_exit;
reduce_bad_assignment:
eprintf("illegal object of assignment");
reduce_exit:
while (n--) freeval(popval());
return val ? pushval(val) : 0;
}
/* Fills in *res with a TYPE_INT, TYPE_DECIMAL, TYPE_DTIME, TYPE_ATIME,
* or TYPE_FLOAT value */
static int reduce_arithmetic(opcode_t op, const Value *val0, int n, Value *res)
{
int i;
int int0, int1, neg0, neg1, sum;
double f;
struct timeval t, t1;
type_t type, promoted_type = 0;
const Value *val[2];
Value localval[2];
#define resint(i) \
((res->type = TYPE_INT), (res->u.ival = (i)), 1)
#define resfloat(f) \
((res->type = TYPE_FLOAT), (res->u.fval = (f)), 1)
#define restime(sec, usec, restype) \
((res->type = restype), (res->u.tval.tv_sec = (sec)), \
(res->u.tval.tv_usec = (usec)), 1)
for (i = 0; i < n; i++) {
val[i] = (i == 0) ? val0 : opd(n-i);
if (val[i]->type == TYPE_ID)
if (!(val[i] = hgetnearestvarval(val[i])))
val[i] = val_zero;
if (val[i]->type & TYPE_STR) {
parsenumber(val[i]->sval->data, NULL, TYPE_NUM, &localval[i]);
val[i] = &localval[i];
}
type = val[i]->type & TYPES_BASIC;
if (type & (TYPE_ENUM | TYPE_POS))
type = TYPE_INT;
if (type > promoted_type)
promoted_type = type;
}
if (n == 2 && val[0]->type & TYPE_ATIME && val[1]->type & TYPE_ATIME) {
switch (op & ~OPF_SIDE) {
case '+':
case '*':
case '/':
wprintf("invalid operation %s on absolute time values.",
oplabel(op));
default: break;
}
if (op == '-') /* atime - atime => dtime */
promoted_type = TYPE_DTIME;
} else if (n == 1 && val[0]->type == TYPE_ATIME && op == '-') {
wprintf("invalid operation %s on absolute time value.", oplabel(op));
promoted_type = TYPE_ATIME;
}
if ((op == OP_EQUAL || op == OP_NOTEQ) &&
((val[0]->type & TYPE_REGMATCH) || (val[1]->type & TYPE_REGMATCH)))
{
if ((val[0]->type & TYPE_REGMATCH && valint(val[1]) == 1) ||
(val[1]->type & TYPE_REGMATCH && valint(val[0]) == 1) ||
((val[0]->type & TYPE_REGMATCH) && (val[1]->type & TYPE_REGMATCH)))
{
wprintf("regmatch() may return >= 1 for success.");
}
}
if ((promoted_type & (TYPE_INT | TYPE_DECIMAL | TYPE_ATIME | TYPE_DTIME)) &&
is_additive(op & ~OPF_SIDE))
{
/* additive overflow causes promotion to float */
int0 = (n > 1) ? valint(val[0]) : 0;
int1 = valint(val[n-1]);
neg0 = int0 < 0;
if ((op & ~OPF_SIDE) == '-') {
neg1 = int1 >= 0;
sum = (int0 - int1);
} else {
neg1 = int1 < 0;
sum = (int0 + int1);
}
if (neg0 == neg1 && sum<0 != neg0) {
/* operands have same sign, but sum has different sign: overflow */
promoted_type = TYPE_FLOAT;
}
}
switch (promoted_type) {
case TYPE_INT:
switch (op & ~OPF_SIDE) {
case '>': return resint(valint(val[0]) > valint(val[1]));
case '<': return resint(valint(val[0]) < valint(val[1]));
case OP_EQUAL: return resint(valint(val[0]) == valint(val[1]));
case OP_NOTEQ: return resint(valint(val[0]) != valint(val[1]));
case OP_GTE: return resint(valint(val[0]) >= valint(val[1]));
case OP_LTE: return resint(valint(val[0]) <= valint(val[1]));
case '+': /* fall thru to '-' */
case '-': return resint(sum);
case '*': i = valint(val[0]) * valint(val[1]);
f = valfloat(val[0]) * valfloat(val[1]);
return (i == f) ? resint(i) : resfloat(f);
case '/': if ((i = valint(val[1])) != 0)
return resint(valint(val[0]) / i);
eprintf("division by zero");
return 0;
default: return 0;
}
case TYPE_DECIMAL:
case TYPE_ATIME:
case TYPE_DTIME:
if (!valtime(&t1, val[n-1])) return 0;
if (n == 1)
t = tvzero;
else
if (!valtime(&t, val[0])) return 0;
switch (op & ~OPF_SIDE) {
case '+': tvadd(&t, &t, &t1);
return restime(t.tv_sec, t.tv_usec, promoted_type);
case '*': return resfloat(valfloat(val[0]) * valfloat(val[1]));
case '/': return resfloat(valfloat(val[0]) / valfloat(val[1]));
default: break;
}
tvsub(&t, &t, &t1);
switch (op & ~OPF_SIDE) {
case '>': return resint(t.tv_sec ? t.tv_sec > 0 : t.tv_usec > 0);
case '<': return resint(t.tv_sec ? t.tv_sec < 0 : t.tv_usec < 0);
case OP_EQUAL: return resint(!t.tv_sec && !t.tv_usec);
case OP_NOTEQ: return resint(t.tv_sec || t.tv_usec);
case OP_GTE: return resint(t.tv_sec ? t.tv_sec >=0 : t.tv_usec >=0);
case OP_LTE: return resint(t.tv_sec ? t.tv_sec <=0 : t.tv_usec <=0);
case '-': return restime(t.tv_sec, t.tv_usec, promoted_type);
default: return 0;
}
case TYPE_FLOAT:
f = valfloat(val[0]);
switch (op & ~OPF_SIDE) {
case '>': return resint(f > valfloat(val[1]));
case '<': return resint(f < valfloat(val[1]));
case OP_EQUAL: return resint(f == valfloat(val[1]));
case OP_NOTEQ: return resint(f != valfloat(val[1]));
case OP_GTE: return resint(f >= valfloat(val[1]));
case OP_LTE: return resint(f <= valfloat(val[1]));
case '+': return resfloat((n>1) ? f + valfloat(val[1]) : +f);
case '-': return resfloat((n>1) ? f - valfloat(val[1]) : -f);
case '*': return resfloat(f * valfloat(val[1]));
case '/': return resfloat(f / valfloat(val[1]));
default: return 0;
}
default:
internal_error(__FILE__, __LINE__,
"impossible type %d in reduce_arithmetic", promoted_type);
return 0; /* impossible */
}
#undef resint
#undef resfloat
#undef restime
}
static Value *function_switch(const ExprFunc *func, int n, const char *parent)
{
int oldblock;
long i, j;
char c;
const char *str, *ptr;
String *Sstr, *Sstr2;
conString *constr;
FILE *file;
TFILE *tfile;
struct timeval tv;
const struct timeval *then;
ValueUnion uval;
int type;
int symbol = func - functab;
#ifdef __CYGWIN32__
STATIC_STRING(systype, "cygwin32", 0);
#else
# ifdef PLATFORM_UNIX
STATIC_STRING(systype, "unix", 0);
# else
# ifdef PLATFORM_OS2
STATIC_STRING(systype, "os/2", 0);
# else
STATIC_STRING(systype, "unknown", 0);
# endif
# endif
#endif
switch (symbol) {
case FN_test:
return expr_value(opdstd(1));
case FN_addworld:
/* addworld(name, type, host, port, char, pass, file, flags, srchost) */
{
int flags = 0;
if (restriction >= RESTRICT_WORLD) {
eprintf("restricted");
return shareval(val_zero);
}
if (n > 7) {
for (str = opdstd(n-7); *str; str++) {
switch (*str) {
case 'x': flags |= WORLD_SSL; break;
case 'p': flags |= WORLD_NOPROXY; break;
case 'e': flags |= WORLD_ECHO; break;
/* compatibility with old use_proxy */
case 'f':
case '0': flags |= WORLD_NOPROXY; break;
case 'o':
case 'n':
case '1': /* ignore */; break;
default:
eprintf("invalid flag %c", *str);
return shareval(val_zero);
}
}
}
return newint(!!new_world(
opdstd(n-0), /* name */
opdstd(n-1), /* type */
n>2 ? opdstd(n-2) : "", /* host */
n>3 ? opdstd(n-3) : "", /* port */
n>4 ? opdstd(n-4) : "", /* char */
n>5 ? opdstd(n-5) : "", /* pass */
n>6 ? opdstd(n-6) : "", /* mfile */
flags, /* flags */
n>8 ? opdstd(n-8) : "")); /* srchost */
}
case FN_columns:
return newint(columns);
case FN_lines:
return newint(lines);
case FN_winlines:
return newint(winlines());
case FN_encode_ansi:
return newSstr(CS(encode_ansi(opdstr(n-0), 0)));
case FN_decode_ansi:
constr = CS(decode_ansi(opdstd(n), 0, EMUL_ANSI_ATTR, NULL));
return constr ? newSstr(constr) : shareval(val_blank);
case FN_encode_attr:
return newSstr(CS(encode_attr(opdstr(n-0), 0)));
case FN_decode_attr:
{
attr_t attr = 0;
if (n > 1) {
if (!parse_attrs(opdstd(n-1), &attr, 0))
return shareval(val_blank);
}
i = (n>2) ?
enum2int(opdstd(n-2), 0, enum_flag, "arg 3 (inline)") : 1;
constr = CS(i ? decode_attr(opdstr(n), 0, 0) :
Stringdup(opdstr(n)));
constr->attrs = adj_attr(constr->attrs, attr);
return constr ? newSstr(constr) : shareval(val_blank);
}
case FN_strip_attr:
constr = opdstr(n-0);
return newstr(constr->data, constr->len);
case FN_status_width:
return newint(handle_status_width_func(opdstd(n-0)));
case FN_status_fields:
return newSstr(status_field_text(n>0 ? opdint(n-0) : 0));
case FN_echo:
i = (n>2) ?
enum2int(opdstd(n-2), 0, enum_flag, "arg 3 (inline)") : 0;
if (i < 0) return shareval(val_zero);
return newint(handle_echo_func(opdstr(n),
(n >= 2) ? opdstd(n-1) : "",
i, (n >= 4) ? opdstd(n-3) : "o"));
case FN_substitute:
i = (n>2) ?
enum2int(opdstd(n-2), 0, enum_flag, "arg 3 (inline)") : 0;
if (i < 0) return shareval(val_zero);
return newint(handle_substitute_func(opdstr(n),
(n >= 2) ? opdstd(n-1) : "", i));
case FN_prompt:
return newint(handle_prompt_func(opdstr(n)));
case FN_eval:
i = SUB_MACRO;
if (n>1)
if ((i = enum2int(opdstd(n-1), 0, enum_sub, "arg 2 (sub)")) < 0)
return shareval(val_zero);
if (!macro_run(opdstr(n-0), 0, NULL, 0, i, "\bEVAL"))
return shareval(val_zero);
return_user_result();
case FN_send:
i = handle_send_function(opdstr(n), (n>1 ? opdstd(n-1) : NULL),
(n>2 ? opdstd(n-2) : ""));
return newint(i);
case FN_fake_recv:
i = handle_fake_recv_function(opdstr(n),
(n>1 ? opdstd(n-1) : NULL), (n>2 ? opdstd(n-2) : ""));
return newint(i);
case FN_fwrite:
ptr = opdstd(2);
file = fopen(expand_filename(ptr), "a");
if (!file) {
eprintf("%S: %s", opdstr(2), strerror(errno));
return shareval(val_zero);
}
fputs(opdstd(1), file);
fputc('\n', file);
fclose(file);
return shareval(val_one);
case FN_tfopen:
return newint(handle_tfopen_func(
n<2 ? "" : opdstd(2), n<1 ? "q" : opdstd(1)));
case FN_tfclose:
str = opdstd(1);
if (!str[1]) {
switch(lcase(str[0])) {
case 'i': tfin = NULL; return shareval(val_zero);
case 'o': tfout = NULL; return shareval(val_zero);
case 'e': eprintf("tferr can not be closed.");
return newint(-1);
default: break;
}
}
tfile = find_tfile(str);
return newint(tfile ? tfclose(tfile) : -1);
case FN_tfwrite:
tfile = (n > 1) ? find_usable_tfile(opdstd(2), S_IWUSR) : tfout;
if (!tfile) return newint(-1);
Sstr = opdstrdup(1); /* XXX optimize */
tfputline(CS(Sstr), tfile);
return shareval(val_one);
case FN_tfreadable:
tfile = find_usable_tfile(opdstd(1), S_IRUSR);
return newint(tfreadable(tfile));
case FN_tfread:
tfile = (n > 1) ? find_usable_tfile(opdstd(2), S_IRUSR) : tfin;
if (!tfile) return newint(-1);
if (opd(1)->type != TYPE_ID) {
eprintf("arg %d: illegal object of assignment", n);
return newint(-1);
}
oldblock = block; /* condition and evalflag are already correct */
block = 0;
j = -1;
(Sstr = Stringnew(NULL, -1, 0))->links++;
if (tfgetS(Sstr, tfile)) {
if (hsetnearestvar(opd(1), CS(Sstr)))
j = Sstr->len;
}
Stringfree(Sstr);
block = oldblock;
return newint(j);
case FN_tfflush:
tfile = find_usable_tfile(opdstd(n), S_IWUSR);
if (!tfile) return newint(-1);
if (n > 1) {
if ((i = enum2int(opdstd(1), 0, enum_flag, "argument 2")) < 0)
return shareval(val_zero);
tfile->autoflush = i;
} else {
tfflush(tfile);
}
return shareval(val_one);
case FN_ascii:
return newint((0x100 + unmapchar(*opdstd(1))) & 0xFF);
case FN_char:
c = mapchar(localize(opdint(1)));
return newstr(&c, 1);
case FN_keycode:
constr = opdstr(1);
ptr = get_keycode(constr->data);
if (ptr) return newstr(ptr, -1);
eprintf("unknown key name \"%S\"", constr);
return shareval(val_blank);
case FN_mod:
if ((i = opdint(1)) == 0) {
eprintf("division by zero");
return NULL;
}
return newint(opdint(2) % i);
case FN_morepaused:
{
World *world;
Screen *screen;
world = (n>=1 && *opdstd(1)) ? find_world(opdstd(1)) : xworld();
screen = (virtscreen && world) ? world->screen : display_screen;
return newint(screen->paused);
}
case FN_moresize:
{
int lim_only = 0, new_only = 0, include_A = 0;
int nnew, nback;
World *world;
Screen *screen;
if (n > 0 && (str = opdstd(n-0))) {
for ( ; *str; str++) {
if (lcase(*str) == 'l') lim_only++;
else if (lcase(*str) == 'n') new_only++;
else if (lcase(*str) == 'a') include_A++;
else {
eprintf("illegal flag '%c'", *str);
return newint(0);
}
}
}
world = (n>=2 && *opdstd(n-1)) ? find_world(opdstd(n-1)) : xworld();
screen = (virtscreen && world) ? world->screen : display_screen;
if (!screen) /* screen hasn't been created yet */
return newint(0);
nnew = lim_only ? screen->nnew_filtered : screen->nnew;
nback = lim_only ? screen->nback_filtered : screen->nback;
if (screen == display_screen || screen->active || include_A ||
(!new_only && nback > nnew))
return newint(new_only ? nnew : nback);
else
return newint(0);
}
case FN_morescroll:
return newint(clear_more(opdint(1)));
#if !NO_FLOAT
case FN_sqrt:
return newfloat(sqrt(opdfloat(1)));
case FN_sin:
return newfloat(sin(opdfloat(1)));
case FN_cos:
return newfloat(cos(opdfloat(1)));
case FN_tan:
return newfloat(tan(opdfloat(1)));
case FN_asin:
return newfloat(asin(opdfloat(1)));
case FN_acos:
return newfloat(acos(opdfloat(1)));
case FN_atan:
return newfloat(atan(opdfloat(1)));
case FN_exp:
return newfloat(exp(opdfloat(1)));
case FN_ln:
return newfloat(log(opdfloat(1)));
case FN_log10:
return newfloat(log10(opdfloat(1)));
case FN_pow:
return newfloat(pow(opdfloat(2), opdfloat(1)));
case FN_trunc:
return newint(opdint(1));
case FN_abs:
{
const Value *val = valnum(opd(1));
return (!val) ? shareval(val_zero) : (val->type & TYPE_INT) ?
newint(labs(valint(val))) : newfloat(fabs(valfloat(val)));
}
#else
case FN_abs:
return newint(abs(opdint(1)));
#endif /* NO_FLOAT */
case FN_rand:
if (n == 0) return newint(RAND());
i = (n==1) ? 0 : opdint(2);
if (i < 0) i = 0;
j = opdint(1) - (n==1);
return newint((j > i) ? RRAND(i, j) : i);
case FN_isatty:
return newint(!no_tty);
case FN_ftime:
Sstr = Stringnew(NULL, 0, 0);
if (n < 2)
gettime(&tv);
else if (!opdtime(&tv, n-1))
return shareval(val_blank);
tftime(Sstr, n>0 ? opdstr(n) : blankline, &tv);
return newSstr(CS(Sstr));
case FN_time:
gettime(&tv);
return newatime(tv.tv_sec, tv.tv_usec);
case FN_cputime:
{
clock_t t;
t = clock();
return t == -1 ? newint(-1) :
newfloat(t / (double)CLOCKS_PER_SEC);
}
case FN_idle:
case FN_sidle:
if (symbol == FN_sidle)
then = socktime(n > 0 ? opdstd(1) : "", SOCK_SEND);
else if (n > 0)
then = socktime(opdstd(1), SOCK_RECV);
else
then = &keyboard_time;
if (!then) return newdtime(0, 0);
gettime(&tv);
tvsub(&tv, &tv, then);
return newdtime(tv.tv_sec, tv.tv_usec);
case FN_mktime:
{
struct tm tm;
time_t t;
unsigned int usec = 0;
tm.tm_sec = 0;
tm.tm_min = 0;
tm.tm_hour = 0;
tm.tm_mday = 1;
tm.tm_mon = 0;
tm.tm_year = 0;
tm.tm_isdst = -1;
switch (n) {
case 7: usec = opdint(n-6);
case 6: tm.tm_sec = opdint(n-5);
case 5: tm.tm_min = opdint(n-4);
case 4: tm.tm_hour = opdint(n-3);
case 3: tm.tm_mday = opdint(n-2);
case 2: tm.tm_mon = opdint(n-1) - 1;
case 1: tm.tm_year = opdint(n-0) - 1900;
}
t = mktime(&tm);
if (t == -1 || usec < 0 || usec > 999999)
return newatime(-1, 0);
if (t < 0 && usec > 0) {
t += 1;
usec -= 1000000;
}
return newatime(t, usec);
}
case FN_filename:
str = expand_filename(opdstd(1));
return newstr(str, -1);
case FN_fg_world:
return ((str=fgname())) ? newstr(str, -1) : shareval(val_blank);
case FN_world_info:
ptr = n>=1 ? opdstd(1) : NULL;
str = world_info(n>=2 ? opdstd(2) : NULL, ptr);
if (!str) {
str = "";
eprintf("illegal field name '%s'", ptr);
}
return newstr(str, -1);
case FN_is_connected:
return newint(is_connected(n>0 ? opdstd(1) : ""));
case FN_is_open:
return newint(is_open(n>0 ? opdstd(1) : ""));
case FN_getpid:
return newint((long)getpid());
case FN_regmatch:
{
Value *val;
(Sstr = opdstrdup(1))->links++; /* XXX not needed if no match */
/* test for type ==, not &; it must not have other extentions */
i = regmatch_in_scope(opd(2)->type == TYPE_STR ? opd(2) : NULL,
opdstd(2), Sstr);
Stringfree(Sstr);
val = newint(i);
val->type |= TYPE_REGMATCH;
return val;
}
case FN_strcat:
Sstr = opdstrdup(n);
for (n--; n; n--)
SStringcat(Sstr, opdstr(n));
return newSstr(CS(Sstr));
case FN_strrep:
constr = opdstr(2);
i = opdint(1);
if (i < 0) i = 0;
for (Sstr2 = Stringnew(NULL, constr->len * i, 0); i > 0; i--)
SStringcat(Sstr2, constr);
return newSstr(CS(Sstr2));
case FN_pad:
for (Sstr2 = Stringnew(NULL, 0, 0); n > 0; n -= 2) {
constr = opdstr(n);
i = (n > 1) ? opdint(n-1) : 0;
if (i > constr->len) Stringnadd(Sstr2, ' ', i - constr->len);
SStringcat(Sstr2, constr);
if (-i > constr->len) Stringnadd(Sstr2, ' ', -i - constr->len);
}
return newSstr(CS(Sstr2));
case FN_strcmp:
return newint(strcmp(opdstd(2), opdstd(1)));
case FN_strcmpattr:
return newint(Stringcmp(opdstr(2), opdstr(1)));
case FN_strncmp:
return newint(strncmp(opdstd(3), opdstd(2), opdint(1)));
case FN_strlen:
constr = opdstr(1);
return newint(constr->len);
#define bound_check(var, maxval) \
do { \
if ((var) > (maxval)) { \
(var) = (maxval); \
} else if ((var) < 0) { \
(var) = (maxval) + (var); \
if ((var) < 0) (var) = 0; \
} \
} while (0)
#define optional_int_arg(var, argc, argnum, maxval, defaultval) \
do { \
if ((argc) >= argnum) { \
(var) = opdint((argc) - (argnum-1)); \
bound_check((var), (maxval)); \
} else { \
(var) = (defaultval); \
} \
} while (0)
case FN_substr:
constr = opdstr(n);
i = opdint(n - 1);
bound_check(i, constr->len);
optional_int_arg(j, n, 3, constr->len - i, constr->len - i);
Sstr2 = Stringnew(NULL, j, 0);
return newSstr(CS(SStringoncat(Sstr2, constr, i, j)));
case FN_strstr:
constr = opdstr(n);
optional_int_arg(j, n, 3, constr->len, 0);
ptr = strstr(constr->data + j, opdstd(n-1));
return newint(ptr ? (ptr - constr->data) : -1);
case FN_strchr:
constr = opdstr(n);
optional_int_arg(j, n, 3, constr->len, 0);
i = strcspn(constr->data + j, opdstd(n-1));
return newint(constr->data[i+j] ? i+j : -1);
case FN_strrchr:
constr = opdstr(n);
ptr = opdstd(n-1);
optional_int_arg(i, n, 3, constr->len - 1, constr->len - 1);
for ( ; i >= 0; i--)
if (strchr(ptr, constr->data[i]))
return newint(i);
return newint(-1);
case FN_replace: {
conString *old, *new;
const char *start, *next;
old = opdstr(3);
new = opdstr(2);
constr = opdstr(1);
Sstr2 = Stringnew(NULL, -1, constr->attrs);
start = constr->data;
while (old->len > 0 && (next = strstr(start, old->data))) {
SStringoncat(Sstr2, constr, start - constr->data, next - start);
SStringcat(Sstr2, new);
start = next + old->len;
}
SStringocat(Sstr2, constr, start - constr->data);
return newSstr(CS(Sstr2));
}
case FN_tolower:
Sstr2 = opdstrdup(n);
optional_int_arg(j, n, 2, Sstr2->len, Sstr2->len);
for (i = 0; i < j; i++)
Sstr2->data[i] = lcase(Sstr2->data[i]);
return newSstr(CS(Sstr2));
case FN_toupper:
Sstr2 = opdstrdup(n);
optional_int_arg(j, n, 2, Sstr2->len, Sstr2->len);
for (i = 0; i < j; i++)
Sstr2->data[i] = ucase(Sstr2->data[i]);
return newSstr(CS(Sstr2));
case FN_kbhead:
return newstr(keybuf->data, keyboard_pos);
case FN_kbtail:
return newstr(keybuf->data + keyboard_pos, keybuf->len - keyboard_pos);
case FN_kbpoint:
return newint(keyboard_pos);
case FN_kbgoto:
return newint(igoto(opdint(1)));
case FN_kbdel:
return (newint(do_kbdel(opdint(1))));
case FN_kbmatch:
return newint(do_kbmatch(n>0 ? opdint(1) : keyboard_pos));
case FN_kbwordleft:
return newint(do_kbword(n>0 ? opdint(1) : keyboard_pos, -1));
case FN_kbwordright:
return newint(do_kbword(n>0 ? opdint(1) : keyboard_pos, 1));
case FN_kblen:
return newint(keybuf->len);
case FN_gethostname:
{
char buf[1024] = "";
#ifdef HAVE_GETHOSTNAME
gethostname(buf, sizeof(buf) - 1);
#endif
buf[sizeof(buf)-1] = '\0'; /* gethostname() might not terminate */
return newstr(buf, -1);
}
case FN_getopts: {
char name[] = "opt_?";
int offset;
current_command = parent;
if (!tf_argv) {
eprintf("getopts may only be called from a command.");
return shareval(val_zero);
}
str = opdstd(n);
if (n>1) {
conString *init = opdstr(n-1);
for (ptr = str; *ptr; ptr++) {
if (!is_alpha(*ptr)) {
eprintf("%s: invalid option specifier: %c",
func->name, *ptr);
return shareval(val_zero);
}
name[4] = *ptr;
setlocalstrvar(name, CS(Stringdup(init)));
if (ptr[1] == ':' || ptr[1] == '#' || ptr[1] == '@')
ptr++;
}
}
if (!tf_argc) return shareval(val_one);
offset = tf_argv[0].start;
startopt(argstring, str);
while ((c = nextopt(&ptr, &uval, &type, &offset))) {
if (!is_alpha(c))
return shareval(val_zero);
name[4] = c;
switch (type) {
case TYPE_STR:
setlocalstrvar(name, CS(Stringnew(ptr, -1, 0)));
break;
case TYPE_INT:
setlocalintvar(name, uval.ival);
break;
case TYPE_DTIME:
setlocaldtimevar(name, &uval.tval);
break;
default:
setlocalintvar(name, 1);
break;
}
}
while (tf_argc > 0 && offset >= tf_argv[0].end) {
tf_argv++;
tf_argc--;
}
if (tf_argc) {
tf_argv[0].start = offset;
}
return shareval(val_one);
}
case FN_read:
wprintf("read() is deprecated. Use tfread() instead.");
oldblock = block; /* condition and evalflag are already correct */
block = 0;
Sstr = Stringnew(NULL, -1, 0);
if (!tfgetS(Sstr, tfin))
Stringtrunc(Sstr, 0);
block = oldblock;
return newSstr(CS(Sstr));
case FN_nread:
return newint(read_depth);
case FN_nactive:
return newint(nactive(n ? opdstd(1) : NULL));
case FN_nlog:
return newint(log_count);
case FN_nmail:
return newint(mail_count);
case FN_systype:
return newSstr(systype);
case FN_whatis: {
Value *val = opd(1);
Sstr = Stringnew(NULL, 0, 0);
if (val->type == TYPE_ID) {
Stringcat(Sstr, "id:");
val = hgetnearestvarval(val);
}
if (val) {
switch (val->type & TYPES_BASIC) {
case TYPE_STR: Stringcat(Sstr, "string"); break;
case TYPE_ENUM: Stringcat(Sstr, "enum"); break;
case TYPE_INT: Stringcat(Sstr, "int"); break;
case TYPE_POS: Stringcat(Sstr, "pos"); break;
case TYPE_DECIMAL: Stringcat(Sstr, "decimal"); break;
case TYPE_ATIME: Stringcat(Sstr, "atime"); break;
case TYPE_DTIME: Stringcat(Sstr, "dtime"); break;
case TYPE_FLOAT: Stringcat(Sstr, "float"); break;
default: Sappendf(Sstr, "%d", opd(1)->type);
}
}
return newSstr(CS(Sstr));
}
default:
eprintf("not supported");
return NULL;
}
}
static inline ExprFunc *find_builtin_func(const char *name) {
return (ExprFunc *)bsearch((void*)name, (void*)functab,
sizeof(functab)/sizeof(ExprFunc), sizeof(ExprFunc), strstructcmp);
}
static Value *do_function(int n /* number of operands (including func id) */)
{
Value *val, *func_result;
const ExprFunc *funcrec = NULL;
Macro *macro = NULL;
BuiltinCmd *cmd = NULL;
const char *old_command;
STATIC_BUFFER(scratch);
int i;
val = opd(n);
n--;
if (val->type == TYPE_FUNC) {
funcrec = valptr(val);
old_command = current_command;
current_command = val->name;
errno = 0;
val = function_switch(funcrec, n, old_command);
#if !NO_FLOAT
/* BUG: setting val=NULL aborts the macro. Perhaps instead we should
* set val to a value with TYPE_ERROR.
*/
if (errno == EDOM) {
eprintf("argument outside of domain");
freeval(val);
val = NULL;
} else if (errno == ERANGE) {
eprintf("result outside of range");
freeval(val);
val = NULL;
}
#endif
current_command = old_command;
return val;
}
if (val->type == TYPE_CMD) {
cmd = valptr(val);
if (cmd->macro)
macro = (cmd->macro);
} else if (!(macro = find_hashed_macro(val->name, val->u.hash))) {
eprintf("%s: no such function", val->name);
return NULL;
}
if (macro) {
Value *saved_user_result;
int saved_argtop, saved_argc;
Arg *saved_argv;
/* pass parameters by value, not by [pseudo]reference */
for (i = 1; i <= n; i++) {
val = opd(i);
if (val->type == TYPE_ID) {
opd(i) = newSstr(CS(Stringdup(valstr(val))));
freeval(val);
}
}
saved_argtop = argtop;
saved_argc = tf_argc;
saved_argv = tf_argv;
saved_user_result = user_result;
argtop = stacktop;
tf_argc = n;
tf_argv = NULL;
user_result = NULL; /* prevent macro from freeing it */
func_result = do_macro(macro, NULL, 0, USED_NAME, 0) ?
user_result : NULL;
argtop = saved_argtop;
tf_argc = saved_argc;
tf_argv = saved_argv;
user_result = saved_user_result;
} else /* if (cmd) */ {
SStringcpy(scratch, n ? opdstr(1) : blankline);
func_result = (*cmd->func)(scratch, 0);
}
return func_result;
}
static int comma_expr(Program *prog)
{
if (!assignment_expr(prog)) return 0;
while (*ip == ',') {
ip++;
code_add(prog, OP_POP, 1); /* throw it away */
if (!assignment_expr(prog)) return 0;
}
return 1;
}
static int assignment_expr(Program *prog)
{
if (!conditional_expr(prog)) return 0;
if (is_assignpfx(ip[0]) && ip[1] == '=') {
opcode_t op = ip[0] | OPF_SIDE;
ip += 2;
if (!assignment_expr(prog)) return 0;
code_add(prog, op, 2);
}
return 1;
}
static int conditional_expr(Program *prog)
{
int jump_point;
if (!or_expr(prog)) return 0;
if (*ip == '?') {
while (is_space(*++ip));
if (*ip == ':') {
code_add(prog, OP_DUP, 1); /* reuse condition val as true val */
code_add(prog, OP_JNZ, -1); /* place holder */
jump_point = prog->len - 1;
code_add(prog, OP_POP, 1); /* discard dup'd value */
} else {
code_add(prog, OP_JZ, -1); /* place holder */
jump_point = prog->len - 1;
if (!comma_expr(prog)) return 0;
if (*ip != ':') {
parse_error(prog, "expression", "':' after '?...'");
return 0;
}
code_add(prog, OP_JUMP, -1); /* place holder */
comefrom(prog, jump_point, prog->len);
jump_point = prog->len - 1;
}
ip++;
if (!conditional_expr(prog)) return 0;
comefrom(prog, jump_point, prog->len);
}
return 1;
}
static int or_expr(Program *prog)
{
int jump_point;
if (!and_expr(prog)) return 0;
while (*ip == '|') {
ip++;
code_add(prog, OP_DUP, 1);
code_add(prog, OP_JNZ, -1); /* place holder */
jump_point = prog->len - 1;
code_add(prog, OP_POP, 1); /* discard dup'd value */
if (!and_expr(prog)) return 0;
comefrom(prog, jump_point, prog->len);
}
return 1;
}
static int and_expr(Program *prog)
{
int jump_point;
if (!relational_expr(prog)) return 0;
while (*ip == '&') {
ip++;
code_add(prog, OP_DUP, 1);
code_add(prog, OP_JZ, -1); /* place holder */
jump_point = prog->len - 1;
code_add(prog, OP_POP, 1); /* discard dup'd value */
if (!relational_expr(prog)) return 0;
comefrom(prog, jump_point, prog->len);
}
/* XXX optimize: in a series of &'s, a zero should jump all the way to
* the end, not to another { DUP; JZ }.
*/
return 1;
}
static int relational_expr(Program *prog)
{
opcode_t op;
if (!additive_expr(prog)) return 0;
while (1) {
if (ip[0] == '=') {
ip++;
if (*ip == '~') op = OP_STREQ;
else if (*ip == '/') op = OP_MATCH;
else if (*ip == '=') op = OP_EQUAL;
else {
if (pedantic) eprintf("suggestion: use == instead of =");
op = OP_EQUAL;
ip--;
}
} else if (ip[0] == '!') {
if (ip[1] == '~') op = OP_STRNEQ;
else if (ip[1] == '/') op = OP_NMATCH;
else if (ip[1] == '=') op = OP_NOTEQ;
else break;
ip++;
}
else if (ip[0] == '>') op = (ip[1] == '=') ? ++ip, OP_GTE : *ip;
else if (ip[0] == '<') op = (ip[1] == '=') ? ++ip, OP_LTE : *ip;
else break;
ip++;
if (!additive_expr(prog)) return 0;
code_add(prog, op, 2);
}
return 1;
}
static int additive_expr(Program *prog)
{
opcode_t op;
if (!multiplicative_expr(prog)) return 0;
while (is_additive(*ip) && ip[1] != '=') {
op = *ip++;
if (!multiplicative_expr(prog)) return 0;
code_add(prog, op, 2);
}
return 1;
}
static int multiplicative_expr(Program *prog)
{
opcode_t op;
if (!unary_expr(prog, 0)) return 0;
while (is_mult(*ip) && ip[1] != '=') {
op = *ip++;
if (!unary_expr(prog, op == '/')) return 0;
code_add(prog, op, 2);
}
return 1;
}
static int unary_expr(Program *prog, int could_be_div_or_macro)
{
opcode_t op;
if (is_space(*ip)) could_be_div_or_macro = 0;
while (is_space(*ip)) ip++;
if (is_unary(*ip)) {
if (ip[0] == '!') {
op = '!';
} else if (ip[0] == '+') {
op = (ip[1] == '+') ? ++ip, OP_PREINC : '+';
} else /* if (ip[0] == '-') */ {
op = (ip[1] == '-') ? ++ip, OP_PREDEC : '-';
}
ip++;
if (!unary_expr(prog, 0)) return 0;
code_add(prog, op, 1);
return 1;
} else {
if (!primary_expr(prog, could_be_div_or_macro)) return 0;
if (*ip == '(') {
/* function call expression */
int n = 1;
InstructionArg *arg;
ExprFunc *funcrec = NULL;
BuiltinCmd *cmd = NULL;
arg = &prog->code[prog->len-1].arg;
if (prog->code[prog->len-1].op != OP_PUSH ||
arg->val->type != TYPE_ID)
{
eprintf("function name must be an identifier.");
return 0;
}
if ((funcrec = find_builtin_func(arg->val->name))) {
arg->val->type = TYPE_FUNC;
arg->val->u.p = funcrec;
} else if ((cmd = find_builtin_cmd(arg->val->name))) {
arg->val->type = TYPE_CMD;
arg->val->u.p = cmd;
if (cmd->func == handle_exit_command) {
eprintf("%s: not a function", cmd->name);
return 0;
}
}
++ip;
eat_space(prog);
if (*ip != ')') {
while (1) {
if (!assignment_expr(prog)) return 0;
n++;
if (*ip == ')') break;
if (*ip != ',') {
parse_error(prog, "expression",
"',' or ')' after function argument");
return 0;
}
++ip;
}
}
if (funcrec && (n-1 < funcrec->min || n-1 > funcrec->max)) {
eprintf((funcrec->min == funcrec->max) ?
"%s: found %d arguments, expected %d" :
"%s: found %d arguments, expected between %d and %d",
funcrec->name, n-1, funcrec->min, funcrec->max);
return 0;
}
if (cmd && n > 2) {
eprintf("%s: command called as function must have 0 or 1 "
"argument", cmd->name);
return 0;
}
++ip;
eat_space(prog);
code_add(prog, OP_FUNC, n);
}
return 1;
}
}
static int primary_expr(Program *prog, int could_be_div_or_macro)
{
const char *end;
Value *val;
String *str;
eat_space(prog);
if (is_digit(*ip) || (ip[0] == '.' && is_digit(ip[1]))) {
if (!(val = parsenumber(ip, &ip, TYPE_NUM, NULL)))
return 0;
code_add(prog, OP_PUSH, val);
} else if (is_quote(*ip)) {
int error = 0;
(str = Stringnew(NULL, -1, 0))->links++;
if (!stringliteral(str, &ip)) {
eprintf("%S in string literal", str);
error++;
} else {
val = newSstr(CS(str));
}
Stringfree(str);
if (error) return 0;
code_add(prog, OP_PUSH, val);
} else if (is_alpha(*ip) || *ip == '_') {
for (end = ip + 1; is_alnum(*end) || *end == '_'; end++);
val = newid(ip, end - ip);
ip = end;
code_add(prog, OP_PUSH, val);
if (could_be_div_or_macro &&
(keyword(val->name) || find_builtin_cmd(val->name) ||
find_macro(val->name)))
{
wprintf("possibly missing '%%;' or ')' before /%s", val->name);
}
} else if (*ip == '$') {
static int warned = 0;
++ip;
if ((!warned || pedantic) && *ip == '[') {
wprintf("$[...] substitution in expression is legal, but redundant. Try using (...) instead.");
warned = 1;
}
dollarsub(prog, NULL);
} else if (*ip == '{') {
if (!varsub(prog, 0, 1)) return 0;
} else if (*ip == '%') {
++ip;
if (!varsub(prog, 1, 1)) return 0;
} else if (*ip == '(') {
++ip;
if (!comma_expr(prog)) return 0;
if (*ip != ')') {
parse_error(prog, "expression", "')' after '(...'");
return 0;
}
++ip;
} else {
parse_error(prog, "expression", "operand");
return 0;
}
eat_space(prog);
return 1;
}
#if USE_DMALLOC
void free_expr(void)
{
pfreepool(Value, valpool, u.next);
}
#endif