Commit fd29cd74 authored by Jonathan Wilkes's avatar Jonathan Wilkes
Browse files

Vanilla 0.47 backport 16: add expr objects to Pd source

parent ff27764b
/* Copyright (c) IRCAM.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */
/* "expr" was written by Shahrokh Yadegari c. 1989. -msp */
/* "expr~" and "fexpr~" conversion by Shahrokh Yadegari c. 1999,2000 */
/*
* Feb 2002 - added access to variables
* multiple expression support
* new short hand forms for fexpr~
* now $y or $y1 = $y1[-1] and $y2 = $y2[-1]
* --sdy
*
* July 2002
* fixed bugs introduced in last changes in store and ET_EQ
* --sdy
*
* Oct 2015
* $x[-1] was not equal $x1[-1], not accessing the previous block
* (bug fix by Dan Ellis)
*/
/*
* vexp.c -- a variable expression evaluator
*
* This modules implements an expression evaluator using the
* operator-precedence parsing. It transforms an infix expression
* to a prefix stack ready to be evaluated. The expression sysntax
* is close to that of C. There are a few operators that are not
* supported and functions are also recognized. Strings can be
* passed to functions when they are quoted in '"'s. "[]" are implememted
* as an easy way of accessing the content of tables, and the syntax
* table_name[index].
* Variables (inlets) are specified with the following syntax: $x#,
* where x is either i(integers), f(floats), and s(strings); and #
* is a digit that coresponds to the inlet number. The string variables
* can be used as strings when they are quoted and can also be used as
* table names when they are followed by "[]".
*
* signal vectors have been added to this implementation:
* $v# denotes a signal vector
* $x#[index] is the value of a sample at the index of a the signal vector
* $x# is the shorthand for $x#[0]
* $y[index] is the value of the sample output at the index of a the
* signal output
* "index" for $x#[index] has to have this range (0 <= index < vectorsize)
* "index" for $y[index] has to have this range (0 < index < vectorsize)
*/
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "x_vexp.h"
#include <errno.h>
#ifdef MSP
#undef isdigit
#define isdigit(x) (x >= '0' && x <= '9')
#endif
#ifdef _MSC_VER
#define strtof _atoldbl
#endif
char *atoif(char *s, long int *value, long int *type);
static struct ex_ex *ex_lex(struct expr *expr, long int *n);
struct ex_ex *ex_match(struct ex_ex *eptr, long int op);
struct ex_ex *ex_parse(struct expr *expr, struct ex_ex *iptr,
struct ex_ex *optr, long int *argc);
struct ex_ex *ex_eval(struct expr *expr, struct ex_ex *eptr,
struct ex_ex *optr, int i);
int expr_donew(struct expr *exprr, int ac, t_atom *av);
struct ex_ex *eval_func(struct expr *expr,struct ex_ex *eptr,
struct ex_ex *optr, int i);
struct ex_ex *eval_tab(struct expr *expr, struct ex_ex *eptr,
struct ex_ex *optr, int i);
struct ex_ex *eval_var(struct expr *expr, struct ex_ex *eptr,
struct ex_ex *optr, int i);
struct ex_ex *eval_store(struct expr *expr, struct ex_ex *eptr,
struct ex_ex *optr, int i);
struct ex_ex *eval_sigidx(struct expr *expr, struct ex_ex *eptr,
struct ex_ex *optr, int i);
static int cal_sigidx(struct ex_ex *optr, /* The output value */
int i, t_float rem_i, /* integer and fractinal part of index */
int idx, /* index of current fexpr~ processing */
int vsize, /* vector size */
t_float *curvec, t_float *prevec); /* current and previous table */
t_ex_func *find_func(char *s);
void ex_dzdetect(struct expr *expr);
#define MAX_ARGS 10
extern t_ex_func ex_funcs[];
struct ex_ex nullex;
void set_tokens (char *s);
int getoken (struct expr *expr, struct ex_ex *eptr);
void ex_print (struct ex_ex *eptr);
#ifdef MSP
void atom_string(t_atom *a, char *buf, unsigned int bufsize);
void atom_string(t_atom *a, char *buf, unsigned int bufsize)
{
char tbuf[30];
switch(a->a_type)
{
case A_SEMI: strcpy(buf, ";"); break;
case A_COMMA: strcpy(buf, ","); break;
#ifdef PD
case A_POINTER:
strcpy(buf, "(pointer)");
break;
#endif
case A_FLOAT:
sprintf(tbuf, "%g", a->a_w.w_float);
if (strlen(tbuf) < bufsize-1) strcpy(buf, tbuf);
else if (a->a_w.w_float < 0) strcpy(buf, "-");
else strcat(buf, "+");
break;
case A_LONG:
sprintf(tbuf, "%d", a->a_w.w_long);
if (strlen(tbuf) < bufsize-1) strcpy(buf, tbuf);
else if (a->a_w.w_float < 0) strcpy(buf, "-");
else strcat(buf, "+");
break;
case A_SYMBOL:
{
char *sp;
unsigned int len;
int quote;
for (sp = a->a_w.w_symbol->s_name, len = 0, quote = 0; *sp; sp++, len++)
if (*sp == ';' || *sp == ',' || *sp == '\\' ||
(*sp == '$' && sp == a->a_w.w_symbol->s_name && sp[1] >= '0'
&& sp[1] <= '9'))
quote = 1;
if (quote)
{
char *bp = buf, *ep = buf + (bufsize-2);
sp = a->a_w.w_symbol->s_name;
while (bp < ep && *sp)
{
if (*sp == ';' || *sp == ',' || *sp == '\\' ||
(*sp == '$' && bp == buf && sp[1] >= '0' && sp[1] <= '9'))
*bp++ = '\\';
*bp++ = *sp++;
}
if (*sp) *bp++ = '*';
*bp = 0;
/* post("quote %s -> %s", a->a_w.w_symbol->s_name, buf); */
}
else
{
if (len < bufsize-1) strcpy(buf, a->a_w.w_symbol->s_name);
else
{
strncpy(buf, a->a_w.w_symbol->s_name, bufsize - 2);
strcpy(buf + (bufsize - 2), "*");
}
}
}
break;
#ifdef PD
case A_DOLLAR:
sprintf(buf, "$%d", a->a_w.w_index);
break;
case A_DOLLSYM:
sprintf(buf, "$%s", a->a_w.w_symbol->s_name);
break;
#else /* MAX */
case A_DOLLAR:
sprintf(buf, "$%s", a->a_w.w_symbol->s_name);
break;
#endif
default:
post("atom_string bug");
}
}
#endif /* MSP */
/*
* expr_donew -- create a new "expr" object.
* returns 1 on failure, 0 on success.
*/
int
expr_donew(struct expr *expr, int ac, t_atom *av)
{
struct ex_ex *list;
struct ex_ex *ret;
long max_node = 0; /* maximum number of nodes needed */
char *exp_string;
int exp_strlen;
t_binbuf *b;
int i;
memset(expr->exp_var, 0, MAX_VARS * sizeof (*expr->exp_var));
#ifdef PD
b = binbuf_new();
binbuf_add(b, ac, av);
binbuf_gettext(b, &exp_string, &exp_strlen);
binbuf_free(b);
#else /* MSP */
{
char *buf = getbytes(0), *newbuf;
int length = 0;
char string[250];
t_atom *ap;
int indx;
for (ap = av, indx = 0; indx < ac; indx++, ap = ++av) {
int newlength;
if ((ap->a_type == A_SEMI || ap->a_type == A_COMMA) &&
length && buf[length-1] == ' ')
length--;
atom_string(ap, string, 250);
newlength = length + strlen(string) + 1;
if (!(newbuf = t_resizebytes(buf, length, newlength)))
break;
buf = newbuf;
strcpy(buf + length, string);
length = newlength;
if (ap->a_type == A_SEMI)
buf[length-1] = '\n';
else
buf[length-1] = ' ';
}
if (length && buf[length-1] == ' ') {
if (newbuf = t_resizebytes(buf, length, length-1))
{
buf = newbuf;
length--;
}
}
exp_string = buf;
exp_strlen = length;
}
#endif
exp_string = (char *)t_resizebytes(exp_string, exp_strlen,exp_strlen+1);
exp_string[exp_strlen] = 0;
expr->exp_string = exp_string;
expr->exp_str = exp_string;
expr->exp_nexpr = 0;
ret = (struct ex_ex *) 0;
/*
* if ret == 0 it means that we have no expression
* so we let the pass go through to build a single null stack
*/
while (*expr->exp_str || !ret) {
list = ex_lex(expr, &max_node);
if (!list) { /* syntax error */
goto error;
}
expr->exp_stack[expr->exp_nexpr] =
(struct ex_ex *)fts_malloc(max_node * sizeof (struct ex_ex));
expr->exp_nexpr++;
ret = ex_match(list, (long)0);
if (expr->exp_nexpr > MAX_VARS)
/* we cannot exceed MAX_VARS '$' variables */
{
post_error((fts_object_t *) expr,
"expr: too many variables (maximum %d allowed)",
MAX_VARS);
goto error;
}
if (!ret) /* syntax error */
goto error;
ret = ex_parse(expr,
list, expr->exp_stack[expr->exp_nexpr - 1], (long *)0);
if (!ret)
goto error;
}
*ret = nullex;
t_freebytes(exp_string, exp_strlen+1);
return (0);
error:
for (i = 0; i < expr->exp_nexpr; i++) {
fts_free(expr->exp_stack[i]);
expr->exp_stack[i] = 0;
}
expr->exp_nexpr = 0;
if (list)
fts_free(list);
t_freebytes(exp_string, exp_strlen+1);
return (1);
}
/*
* ex_lex -- This routine is a bit more than a lexical parser since it will
* also do some syntax checking. It reads the string s and will
* return a linked list of struct ex_ex.
* It will also put the number of the nodes in *n.
*/
struct ex_ex *
ex_lex(struct expr *expr, long int *n)
{
struct ex_ex *list_arr;
struct ex_ex *exptr;
long non = 0; /* number of nodes */
long maxnode = 0;
list_arr = (struct ex_ex *)fts_malloc(sizeof (struct ex_ex) * MINODES);
if (! list_arr) {
post("ex_lex: no mem\n");
return ((struct ex_ex *)0);
}
exptr = list_arr;
maxnode = MINODES;
while (8)
{
if (non >= maxnode) {
maxnode += MINODES;
list_arr = fts_realloc((void *)list_arr,
sizeof (struct ex_ex) * maxnode);
if (!list_arr) {
post("ex_lex: no mem\n");
return ((struct ex_ex *)0);
}
exptr = &(list_arr)[non];
}
if (getoken(expr, exptr)) {
fts_free(list_arr);
return ((struct ex_ex *)0);
}
non++;
if (!exptr->ex_type)
break;
exptr++;
}
*n = non;
return list_arr;
}
/*
* ex_match -- this routine walks through the eptr and matches the
* perentheses and brackets, it also converts the function
* names to a pointer to the describing structure of the
* specified function
*/
/* operator to match */
struct ex_ex *
ex_match(struct ex_ex *eptr, long int op)
{
int firstone = 1;
struct ex_ex *ret;
t_ex_func *fun;
for (; 8; eptr++, firstone = 0) {
switch (eptr->ex_type) {
case 0:
if (!op)
return (eptr);
post("expr syntax error: an open %s not matched\n",
op == OP_RP ? "parenthesis" : "bracket");
return (exNULL);
case ET_INT:
case ET_FLT:
case ET_II:
case ET_FI:
case ET_SI:
case ET_VI:
case ET_SYM:
case ET_VSYM:
continue;
case ET_YO:
if (eptr[1].ex_type != ET_OP || eptr[1].ex_op != OP_LB)
eptr->ex_type = ET_YOM1;
continue;
case ET_XI:
if (eptr[1].ex_type != ET_OP || eptr[1].ex_op != OP_LB)
eptr->ex_type = ET_XI0;
continue;
/*
* tables, functions, parenthesis, and brackets, are marked as
* operations, and they are assigned their proper operation
* in this function. Thus, if we arrive to any of these in this
* type tokens at this location, we must have had some error
*/
case ET_TBL:
case ET_FUNC:
case ET_LP:
case ET_LB:
post("ex_match: unexpected type, %ld\n", eptr->ex_type);
return (exNULL);
case ET_OP:
if (op == eptr->ex_op)
return (eptr);
/*
* if we are looking for a right peranthesis
* or a right bracket and find the other kind,
* it has to be a syntax error
*/
if ((eptr->ex_op == OP_RP && op == OP_RB) ||
(eptr->ex_op == OP_RB && op == OP_RP)) {
post("expr syntax error: prenthesis or brackets not matched\n");
return (exNULL);
}
/*
* Up to now we have marked the unary minuses as
* subrtacts. Any minus that is the first one in
* chain or is preceeded by anything except ')' and
* ']' is a unary minus.
*/
if (eptr->ex_op == OP_SUB) {
ret = eptr - 1;
if (firstone || (ret->ex_type == ET_OP &&
ret->ex_op != OP_RB && ret->ex_op != OP_RP))
eptr->ex_op = OP_UMINUS;
} else if (eptr->ex_op == OP_LP) {
ret = ex_match(eptr + 1, OP_RP);
if (!ret)
return (ret);
eptr->ex_type = ET_LP;
eptr->ex_ptr = (char *) ret;
eptr = ret;
} else if (eptr->ex_op == OP_LB) {
ret = ex_match(eptr + 1, OP_RB);
if (!ret)
return (ret);
/*
* this is a special case handling
* for $1, $2 processing in Pd
*
* Pure data translates $#[x] (e.g. $1[x]) to 0[x]
* for abstracting patches so that later
* in the instantiation of the abstraction
* the $# is replaced with the proper argument
* of the abstraction
* so we change 0[x] to a special table pointing to null
* and catch errors in execution time
*/
if (!firstone && (eptr - 1)->ex_type == ET_INT &&
((eptr - 1)->ex_int == 0)) {
(eptr - 1)->ex_type = ET_TBL;
(eptr - 1)->ex_ptr = (char *)0;
}
eptr->ex_type = ET_LB;
eptr->ex_ptr = (char *) ret;
eptr = ret;
}
continue;
case ET_STR:
if (eptr[1].ex_op == OP_LB) {
char *tmp;
eptr->ex_type = ET_TBL;
tmp = eptr->ex_ptr;
if (ex_getsym(tmp, (t_symbol **)&(eptr->ex_ptr))) {
post("expr: syntax error: problms with ex_getsym\n");
return (exNULL);
}
fts_free((void *)tmp);
} else if (eptr[1].ex_op == OP_LP) {
fun = find_func(eptr->ex_ptr);
if (!fun) {
post(
"expr: error: function %s not found\n",
eptr->ex_ptr);
return (exNULL);
}
eptr->ex_type = ET_FUNC;
eptr->ex_ptr = (char *) fun;
} else {
char *tmp;
if (eptr[1].ex_type && eptr[1].ex_type!=ET_OP){
post("expr: syntax error: bad string '%s'\n", eptr->ex_ptr);
return (exNULL);
}
/* it is a variable */
eptr->ex_type = ET_VAR;
tmp = eptr->ex_ptr;
if (ex_getsym(tmp,
(t_symbol **)&(eptr->ex_ptr))) {
post("expr: variable '%s' not found",tmp);
return (exNULL);
}
}
continue;
default:
post("ex_match: bad type\n");
return (exNULL);
}
}
/* NOTREACHED */
}
/*
* ex_parse -- This function if called when we have already done some
* parsing on the expression, and we have already matched
* our brackets and parenthesis. The main job of this
* function is to convert the infix expression to the
* prefix form.
* First we find the operator with the lowest precedence and
* put it on the stack ('optr', it is really just an array), then
* we call ourself (ex_parse()), on its arguments (unary operators
* only have one operator.)
* When "argc" is set it means that we are parsing the arguments
* of a function and we will increment *argc anytime we find
* a segment that can qualify as an argument (counting commas).
*
* returns 0 on syntax error
*/
/* number of argument separated by comma */
struct ex_ex *
ex_parse(struct expr *x, struct ex_ex *iptr, struct ex_ex *optr, long int *argc)
{
struct ex_ex *eptr;
struct ex_ex *lowpre = 0; /* pointer to the lowest precedence */
struct ex_ex savex;
long pre = HI_PRE;
long count;
if (!iptr) {
post("ex_parse: input is null, iptr = 0x%lx\n", iptr);
return (exNULL);
}
if (!iptr->ex_type)
return (exNULL);
/*
* the following loop finds the lowest precedence operator in the
* the input token list, comma is explicitly checked here since
* that is a special operator and is only legal in functions
*/
for (eptr = iptr, count = 0; eptr->ex_type; eptr++, count++)
switch (eptr->ex_type) {
case ET_SYM:
case ET_VSYM:
if (!argc) {
post("expr: syntax error: symbols allowed for functions only\n");
ex_print(eptr);
return (exNULL);
}
case ET_INT:
case ET_FLT:
case ET_II:
case ET_FI:
case ET_XI0:
case ET_YOM1:
case ET_VI:
case ET_VAR:
if (!count && !eptr[1].ex_type) {
*optr++ = *eptr;
return (optr);
}
break;
case ET_XI:
case ET_YO:
case ET_SI:
case ET_TBL:
if (eptr[1].ex_type != ET_LB) {
post("expr: syntax error: brackets missing\n");
ex_print(eptr);
return (exNULL);
}
/* if this table is the only token, parse the table */
if (!count &&
!((struct ex_ex *) eptr[1].ex_ptr)[1].ex_type) {
savex = *((struct ex_ex *) eptr[1].ex_ptr);
*((struct ex_ex *) eptr[1].ex_ptr) = nullex;
*optr++ = *eptr;
lowpre = ex_parse(x, &eptr[2], optr, (long *)0);
*((struct ex_ex *) eptr[1].ex_ptr) = savex;
return(lowpre);
}
eptr = (struct ex_ex *) eptr[1].ex_ptr;
break;
case ET_OP:
if (eptr->ex_op == OP_COMMA) {
if (!argc || !count || !eptr[1].ex_type) {
post("expr: syntax error: illegal comma\n");
ex_print(eptr[1].ex_type ? eptr : iptr);
return (exNULL);
}
}
if (!eptr[1].ex_type) {
post("expr: syntax error: missing operand\n");
ex_print(iptr);
return (exNULL);
}
if ((eptr->ex_op & PRE_MASK) <= pre) {
pre = eptr->ex_op & PRE_MASK;
lowpre = eptr;
}
break;
case ET_FUNC:
if (eptr[1].ex_type != ET_LP) {
post("expr: ex_parse: no parenthesis\n");
return (exNULL);
}
/* if this function is the only token, parse it */
if (!count &&
!((struct ex_ex *) eptr[1].ex_ptr)[1].ex_type) {
long ac;
if (eptr[1].ex_ptr == (char *) &eptr[2]) {
post("expr: syntax error: missing argument\n");
ex_print(eptr);
return (exNULL);
}
ac = 0;
savex = *((struct ex_ex *) eptr[1].ex_ptr);
*((struct ex_ex *) eptr[1].ex_ptr) = nullex;
*optr++ = *eptr;
lowpre = ex_parse(x, &eptr[2], optr, &ac);