Newer
Older
* Copyright (c) 2002-2006 Michael Niedermayer <michaelni@gmx.at>
* Copyright (c) 2006 Oded Shimon <ods15@ods15.dyndns.org>
Diego Biurrun
committed
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
Diego Biurrun
committed
* version 2.1 of the License, or (at your option) any later version.
Diego Biurrun
committed
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
Diego Biurrun
committed
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* see http://joe.hotchkiss.com/programming/eval/eval.html
*/
#include "libavutil/avutil.h"
const AVClass *class;
Stefano Sabatini
committed
const double *const_values;
const char * const *const_names; // NULL terminated
double (* const *funcs1)(void *, double a); // NULL terminated
const char * const *func1_names; // NULL terminated
double (* const *funcs2)(void *, double a, double b); // NULL terminated
const char * const *func2_names; // NULL terminated
int log_offset;
void *log_ctx;
#define VARS 10
double var[VARS];
static const AVClass class = { "Eval", av_default_item_name, NULL, LIBAVUTIL_VERSION_INT, offsetof(Parser,log_offset), offsetof(Parser,log_ctx) };
static const int8_t si_prefixes['z' - 'E' + 1] = {
['y'-'E']= -24,
['z'-'E']= -21,
['a'-'E']= -18,
['f'-'E']= -15,
['p'-'E']= -12,
['n'-'E']= - 9,
['u'-'E']= - 6,
['m'-'E']= - 3,
['c'-'E']= - 2,
['d'-'E']= - 1,
['h'-'E']= 2,
['k'-'E']= 3,
['K'-'E']= 3,
['M'-'E']= 6,
['G'-'E']= 9,
['T'-'E']= 12,
['P'-'E']= 15,
['E'-'E']= 18,
['Z'-'E']= 21,
['Y'-'E']= 24,
};
double av_strtod(const char *numstr, char **tail)
{
d = strtod(numstr, &next);
/* if parsing succeeded, check for and interpret postfixes */
if (next!=numstr) {
if (*next >= 'E' && *next <= 'z') {
int e= si_prefixes[*next - 'E'];
if (e) {
if (next[1] == 'i') {
d*= pow( 2, e/0.3);
next+=2;
d*= pow(10, e);
next++;
}
}
}
}
}
/* if requested, fill in tail with the position after the last parsed
character */
if (tail)
*tail = next;
return d;
}
static int strmatch(const char *s, const char *prefix)
{
for (i=0; prefix[i]; i++) {
if (prefix[i] != s[i]) return 0;
struct AVExpr {
enum {
e_value, e_const, e_func0, e_func1, e_func2,
e_mod, e_max, e_min, e_eq, e_gt, e_gte,
e_pow, e_mul, e_div, e_add,
Michael Niedermayer
committed
e_last, e_st, e_while,
} type;
double value; // is sign in other types
union {
int const_index;
double (*func0)(double);
double (*func1)(void *, double);
double (*func2)(void *, double, double);
} a;
struct AVExpr *param[2];
static double eval_expr(Parser *p, AVExpr *e)
{
switch (e->type) {
case e_value: return e->value;
Stefano Sabatini
committed
case e_const: return e->value * p->const_values[e->a.const_index];
case e_func0: return e->value * e->a.func0(eval_expr(p, e->param[0]));
case e_func1: return e->value * e->a.func1(p->opaque, eval_expr(p, e->param[0]));
case e_func2: return e->value * e->a.func2(p->opaque, eval_expr(p, e->param[0]), eval_expr(p, e->param[1]));
case e_squish: return 1/(1+exp(4*eval_expr(p, e->param[0])));
case e_gauss: { double d = eval_expr(p, e->param[0]); return exp(-d*d/2)/sqrt(2*M_PI); }
case e_ld: return e->value * p->var[av_clip(eval_expr(p, e->param[0]), 0, VARS-1)];
Michael Niedermayer
committed
case e_while: {
Oded Shimon
committed
double d = NAN;
while (eval_expr(p, e->param[0]))
Michael Niedermayer
committed
d=eval_expr(p, e->param[1]);
return d;
}
default: {
double d = eval_expr(p, e->param[0]);
double d2 = eval_expr(p, e->param[1]);
switch (e->type) {
case e_mod: return e->value * (d - floor(d/d2)*d2);
case e_max: return e->value * (d > d2 ? d : d2);
case e_min: return e->value * (d < d2 ? d : d2);
case e_eq: return e->value * (d == d2 ? 1.0 : 0.0);
case e_gt: return e->value * (d > d2 ? 1.0 : 0.0);
case e_gte: return e->value * (d >= d2 ? 1.0 : 0.0);
case e_pow: return e->value * pow(d, d2);
case e_mul: return e->value * (d * d2);
case e_div: return e->value * (d / d2);
case e_add: return e->value * (d + d2);
case e_st : return e->value * (p->var[av_clip(d, 0, VARS-1)]= d2);
}
}
}
return NAN;
}
static int parse_expr(AVExpr **e, Parser *p);
void av_free_expr(AVExpr *e)
if (!e) return;
av_free_expr(e->param[0]);
av_free_expr(e->param[1]);
av_freep(&e);
}
static int parse_primary(AVExpr **e, Parser *p)
{
AVExpr *d = av_mallocz(sizeof(AVExpr));
char *next = p->s, *s0 = p->s;
int ret, i;
return AVERROR(ENOMEM);
d->value = av_strtod(p->s, &next);
d->type = e_value;
*e = d;
return 0;
d->value = 1;
for (i=0; p->const_names && p->const_names[i]; i++) {
if (strmatch(p->s, p->const_names[i])) {
Stefano Sabatini
committed
p->s+= strlen(p->const_names[i]);
d->type = e_const;
d->a.const_index = i;
*e = d;
return 0;
av_log(p, AV_LOG_ERROR, "Undefined constant or missing '(' in '%s'\n", s0);
av_free_expr(d);
return AVERROR(EINVAL);
if (*next == '(') { // special case do-nothing
av_freep(&d);
if ((ret = parse_expr(&d, p)) < 0)
return ret;
av_log(p, AV_LOG_ERROR, "Missing ')' in '%s'\n", s0);
av_free_expr(d);
return AVERROR(EINVAL);
}
p->s++; // ")"
*e = d;
return 0;
}
if ((ret = parse_expr(&(d->param[0]), p)) < 0) {
av_free_expr(d);
return ret;
parse_expr(&d->param[1], p);
av_log(p, AV_LOG_ERROR, "Missing ')' or too many args in '%s'\n", s0);
av_free_expr(d);
return AVERROR(EINVAL);
d->type = e_func0;
if (strmatch(next, "sinh" )) d->a.func0 = sinh;
else if (strmatch(next, "cosh" )) d->a.func0 = cosh;
else if (strmatch(next, "tanh" )) d->a.func0 = tanh;
else if (strmatch(next, "sin" )) d->a.func0 = sin;
else if (strmatch(next, "cos" )) d->a.func0 = cos;
else if (strmatch(next, "tan" )) d->a.func0 = tan;
else if (strmatch(next, "atan" )) d->a.func0 = atan;
else if (strmatch(next, "asin" )) d->a.func0 = asin;
else if (strmatch(next, "acos" )) d->a.func0 = acos;
else if (strmatch(next, "exp" )) d->a.func0 = exp;
else if (strmatch(next, "log" )) d->a.func0 = log;
else if (strmatch(next, "abs" )) d->a.func0 = fabs;
else if (strmatch(next, "squish")) d->type = e_squish;
else if (strmatch(next, "gauss" )) d->type = e_gauss;
else if (strmatch(next, "mod" )) d->type = e_mod;
else if (strmatch(next, "max" )) d->type = e_max;
else if (strmatch(next, "min" )) d->type = e_min;
else if (strmatch(next, "eq" )) d->type = e_eq;
else if (strmatch(next, "gte" )) d->type = e_gte;
else if (strmatch(next, "gt" )) d->type = e_gt;
else if (strmatch(next, "lte" )) { AVExpr *tmp = d->param[1]; d->param[1] = d->param[0]; d->param[0] = tmp; d->type = e_gt; }
else if (strmatch(next, "lt" )) { AVExpr *tmp = d->param[1]; d->param[1] = d->param[0]; d->param[0] = tmp; d->type = e_gte; }
else if (strmatch(next, "ld" )) d->type = e_ld;
else if (strmatch(next, "st" )) d->type = e_st;
else if (strmatch(next, "while" )) d->type = e_while;
for (i=0; p->func1_names && p->func1_names[i]; i++) {
if (strmatch(next, p->func1_names[i])) {
Stefano Sabatini
committed
d->a.func1 = p->funcs1[i];
d->type = e_func1;
*e = d;
return 0;
for (i=0; p->func2_names && p->func2_names[i]; i++) {
if (strmatch(next, p->func2_names[i])) {
Stefano Sabatini
committed
d->a.func2 = p->funcs2[i];
d->type = e_func2;
*e = d;
return 0;
av_log(p, AV_LOG_ERROR, "Unknown function in '%s'\n", s0);
av_free_expr(d);
return AVERROR(EINVAL);
*e = d;
return 0;
static AVExpr *new_eval_expr(int type, int value, AVExpr *p0, AVExpr *p1)
{
AVExpr *e = av_mallocz(sizeof(AVExpr));
if (!e)
return NULL;
e->type =type ;
e->value =value ;
e->param[0] =p0 ;
e->param[1] =p1 ;
return e;
}
static int parse_pow(AVExpr **e, Parser *p, int *sign)
{
*sign= (*p->s == '+') - (*p->s == '-');
p->s += *sign&1;
return parse_primary(e, p);
static int parse_factor(AVExpr **e, Parser *p)
{
int sign, sign2, ret;
AVExpr *e0, *e1, *e2;
if ((ret = parse_pow(&e0, p, &sign)) < 0)
return ret;
e1 = e0;
if ((ret = parse_pow(&e2, p, &sign2)) < 0) {
av_free_expr(e1);
return ret;
}
e0 = new_eval_expr(e_pow, 1, e1, e2);
if (!e0) {
av_free_expr(e1);
av_free_expr(e2);
return AVERROR(ENOMEM);
}
if (e0->param[1]) e0->param[1]->value *= (sign2|1);
if (e0) e0->value *= (sign|1);
*e = e0;
return 0;
static int parse_term(AVExpr **e, Parser *p)
{
int ret;
AVExpr *e0, *e1, *e2;
if ((ret = parse_factor(&e0, p)) < 0)
return ret;
while (p->s[0]=='*' || p->s[0]=='/') {
e1 = e0;
if ((ret = parse_factor(&e2, p)) < 0) {
av_free_expr(e1);
return ret;
}
e0 = new_eval_expr(c == '*' ? e_mul : e_div, 1, e1, e2);
if (!e0) {
av_free_expr(e1);
av_free_expr(e2);
return AVERROR(ENOMEM);
}
*e = e0;
return 0;
static int parse_subexpr(AVExpr **e, Parser *p)
{
int ret;
AVExpr *e0, *e1, *e2;
if ((ret = parse_term(&e0, p)) < 0)
return ret;
while (*p->s == '+' || *p->s == '-') {
e1 = e0;
if ((ret = parse_term(&e2, p)) < 0) {
av_free_expr(e1);
return ret;
}
e0 = new_eval_expr(e_add, 1, e1, e2);
if (!e0) {
av_free_expr(e1);
av_free_expr(e2);
return AVERROR(ENOMEM);
}
*e = e0;
return 0;
static int parse_expr(AVExpr **e, Parser *p)
{
int ret;
AVExpr *e0, *e1, *e2;
if (p->stack_index <= 0) //protect against stack overflows
return AVERROR(EINVAL);
if ((ret = parse_subexpr(&e0, p)) < 0)
return ret;
e1 = e0;
if ((ret = parse_subexpr(&e2, p)) < 0) {
av_free_expr(e1);
return ret;
}
e0 = new_eval_expr(e_last, 1, e1, e2);
if (!e0) {
av_free_expr(e1);
av_free_expr(e2);
return AVERROR(ENOMEM);
}
*e = e0;
return 0;
static int verify_expr(AVExpr *e)
{
if (!e) return 0;
switch (e->type) {
case e_value:
case e_const: return 1;
case e_func0:
case e_func1:
case e_squish:
case e_gauss: return verify_expr(e->param[0]);
default: return verify_expr(e->param[0]) && verify_expr(e->param[1]);
}
}
int av_parse_expr(AVExpr **expr, const char *s,
Stefano Sabatini
committed
const char * const *const_names,
const char * const *func1_names, double (* const *funcs1)(void *, double),
const char * const *func2_names, double (* const *funcs2)(void *, double, double),
int log_offset, void *log_ctx)
{
AVExpr *e = NULL;
char *w = av_malloc(strlen(s) + 1);
char *wp = w;
const char *s0 = s;
int ret = 0;
return AVERROR(ENOMEM);
while (*s)
if (!isspace(*s++)) *wp++ = s[-1];
*wp++ = 0;
p.class = &class;
Stefano Sabatini
committed
p.const_names = const_names;
p.funcs1 = funcs1;
p.func1_names = func1_names;
p.funcs2 = funcs2;
p.func2_names = func2_names;
p.log_offset = log_offset;
p.log_ctx = log_ctx;
if ((ret = parse_expr(&e, &p)) < 0)
goto end;
if (*p.s) {
av_log(&p, AV_LOG_ERROR, "Invalid chars '%s' at the end of expression '%s'\n", p.s, s0);
ret = AVERROR(EINVAL);
goto end;
}
if (!verify_expr(e)) {
av_free_expr(e);
ret = AVERROR(EINVAL);
goto end;
*expr = e;
end:
av_free(w);
return ret;
double av_eval_expr(AVExpr *e, const double *const_values, void *opaque)
Parser p;
Stefano Sabatini
committed
p.const_values = const_values;
p.opaque = opaque;
return eval_expr(&p, e);
}
int av_parse_and_eval_expr(double *d, const char *s,
Stefano Sabatini
committed
const char * const *const_names, const double *const_values,
const char * const *func1_names, double (* const *funcs1)(void *, double),
const char * const *func2_names, double (* const *funcs2)(void *, double, double),
void *opaque, int log_offset, void *log_ctx)
{
AVExpr *e = NULL;
int ret = av_parse_expr(&e, s, const_names, func1_names, funcs1, func2_names, funcs2, log_offset, log_ctx);
if (ret < 0) {
*d = NAN;
return ret;
}
*d = av_eval_expr(e, const_values, opaque);
av_free_expr(e);
return isnan(*d) ? AVERROR(EINVAL) : 0;
static double const_values[] = {
static const char *const_names[] = {
double d;
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
const char **expr, *exprs[] = {
"",
"1+(5-2)^(3-1)+1/2+sin(PI)-max(-2.2,-3.1)",
"80G/80Gi"
"1k",
"1Gi",
"1gi",
"1GiFoo",
"1k+1k",
"1Gi*3foo",
"foo",
"foo(",
"foo()",
"foo)",
"sin",
"sin(",
"sin()",
"sin)",
"sin 10",
"sin(1,2,3)",
"sin(1 )",
"1",
"1foo",
"bar + PI + E + 100f*2 + foo",
"13k + 12f - foo(1, 2)",
"1gi",
"1Gi",
NULL
};
for (expr = exprs; *expr; expr++) {
printf("Evaluating '%s'\n", *expr);
av_parse_and_eval_expr(&d, *expr,
const_names, const_values,
NULL, NULL, NULL, NULL, NULL, 0, NULL);
printf("'%s' -> %f\n\n", *expr, d);
}
av_parse_and_eval_expr(&d, "1+(5-2)^(3-1)+1/2+sin(PI)-max(-2.2,-3.1)",
const_names, const_values,
NULL, NULL, NULL, NULL, NULL, 0, NULL);
printf("%f == 12.7\n", d);
av_parse_and_eval_expr(&d, "80G/80Gi",
const_names, const_values,
printf("%f == 0.931322575\n", d);
av_parse_and_eval_expr(&d, "1+(5-2)^(3-1)+1/2+sin(PI)-max(-2.2,-3.1)",
const_names, const_values,
NULL, NULL, NULL, NULL, NULL, 0, NULL);
STOP_TIMER("av_parse_and_eval_expr")