Cleanup: rename BLI_simple_expr -> BLI_expr_pylike_eval

Simple isn't a good prefix for library names since
lots of unrelated modules could be called 'simple'.

Include 'py' in module name since this is a subset of Python,
one of the main motivations for this is to be Python like/compatible.
This commit is contained in:
Campbell Barton
2018-09-19 10:40:35 +10:00
parent 345c348262
commit 3aea5695bb
8 changed files with 219 additions and 217 deletions

View File

@@ -49,7 +49,7 @@
#include "BLI_threads.h" #include "BLI_threads.h"
#include "BLI_string_utils.h" #include "BLI_string_utils.h"
#include "BLI_utildefines.h" #include "BLI_utildefines.h"
#include "BLI_simple_expr.h" #include "BLI_expr_pylike_eval.h"
#include "BLI_alloca.h" #include "BLI_alloca.h"
#include "BLT_translation.h" #include "BLT_translation.h"
@@ -1866,7 +1866,7 @@ void fcurve_free_driver(FCurve *fcu)
BPY_DECREF(driver->expr_comp); BPY_DECREF(driver->expr_comp);
#endif #endif
BLI_simple_expr_free(driver->expr_simple); BLI_expr_pylike_free(driver->expr_simple);
/* free driver itself, then set F-Curve's point to this to NULL (as the curve may still be used) */ /* free driver itself, then set F-Curve's point to this to NULL (as the curve may still be used) */
MEM_freeN(driver); MEM_freeN(driver);
@@ -1897,7 +1897,7 @@ ChannelDriver *fcurve_copy_driver(const ChannelDriver *driver)
/* Driver Expression Evaluation --------------- */ /* Driver Expression Evaluation --------------- */
static ParsedSimpleExpr *driver_compile_simple_expr_impl(ChannelDriver *driver) static ExprPyLike_Parsed *driver_compile_simple_expr_impl(ChannelDriver *driver)
{ {
/* Prepare parameter names. */ /* Prepare parameter names. */
int num_vars = BLI_listbase_count(&driver->variables); int num_vars = BLI_listbase_count(&driver->variables);
@@ -1910,10 +1910,10 @@ static ParsedSimpleExpr *driver_compile_simple_expr_impl(ChannelDriver *driver)
names[i++] = dvar->name; names[i++] = dvar->name;
} }
return BLI_simple_expr_parse(driver->expression, num_vars + 1, names); return BLI_expr_pylike_parse(driver->expression, num_vars + 1, names);
} }
static bool driver_evaluate_simple_expr(ChannelDriver *driver, ParsedSimpleExpr *expr, float *result, float time) static bool driver_evaluate_simple_expr(ChannelDriver *driver, ExprPyLike_Parsed *expr, float *result, float time)
{ {
/* Prepare parameter values. */ /* Prepare parameter values. */
int num_vars = BLI_listbase_count(&driver->variables); int num_vars = BLI_listbase_count(&driver->variables);
@@ -1928,19 +1928,19 @@ static bool driver_evaluate_simple_expr(ChannelDriver *driver, ParsedSimpleExpr
/* Evaluate expression. */ /* Evaluate expression. */
double result_val; double result_val;
eSimpleExpr_EvalStatus status = BLI_simple_expr_evaluate(expr, &result_val, num_vars + 1, vars); eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, &result_val, num_vars + 1, vars);
const char *message; const char *message;
switch (status) { switch (status) {
case SIMPLE_EXPR_SUCCESS: case EXPR_PYLIKE_SUCCESS:
if (isfinite(result_val)) { if (isfinite(result_val)) {
*result = (float)result_val; *result = (float)result_val;
} }
return true; return true;
case SIMPLE_EXPR_DIV_BY_ZERO: case EXPR_PYLIKE_DIV_BY_ZERO:
case SIMPLE_EXPR_MATH_ERROR: case EXPR_PYLIKE_MATH_ERROR:
message = (status == SIMPLE_EXPR_DIV_BY_ZERO) ? "Division by Zero" : "Math Domain Error"; message = (status == EXPR_PYLIKE_DIV_BY_ZERO) ? "Division by Zero" : "Math Domain Error";
fprintf(stderr, "\n%s in Driver: '%s'\n", message, driver->expression); fprintf(stderr, "\n%s in Driver: '%s'\n", message, driver->expression);
driver->flag |= DRIVER_FLAG_INVALID; driver->flag |= DRIVER_FLAG_INVALID;
@@ -1966,12 +1966,12 @@ static bool driver_compile_simple_expr(ChannelDriver *driver)
/* It's safe to parse in multiple threads; at worst it'll /* It's safe to parse in multiple threads; at worst it'll
* waste some effort, but in return avoids mutex contention. */ * waste some effort, but in return avoids mutex contention. */
ParsedSimpleExpr *expr = driver_compile_simple_expr_impl(driver); ExprPyLike_Parsed *expr = driver_compile_simple_expr_impl(driver);
/* Store the result if the field is still NULL, or discard /* Store the result if the field is still NULL, or discard
* it if another thread got here first. */ * it if another thread got here first. */
if (atomic_cas_ptr((void **)&driver->expr_simple, NULL, expr) != NULL) { if (atomic_cas_ptr((void **)&driver->expr_simple, NULL, expr) != NULL) {
BLI_simple_expr_free(expr); BLI_expr_pylike_free(expr);
} }
return true; return true;
@@ -1984,21 +1984,21 @@ static bool driver_try_evaluate_simple_expr(ChannelDriver *driver, ChannelDriver
*result = 0.0f; *result = 0.0f;
return driver_compile_simple_expr(driver_orig) && return driver_compile_simple_expr(driver_orig) &&
BLI_simple_expr_is_valid(driver_orig->expr_simple) && BLI_expr_pylike_is_valid(driver_orig->expr_simple) &&
driver_evaluate_simple_expr(driver, driver_orig->expr_simple, result, time); driver_evaluate_simple_expr(driver, driver_orig->expr_simple, result, time);
} }
/* Check if the expression in the driver conforms to the simple subset. */ /* Check if the expression in the driver conforms to the simple subset. */
bool BKE_driver_has_simple_expression(ChannelDriver *driver) bool BKE_driver_has_simple_expression(ChannelDriver *driver)
{ {
return driver_compile_simple_expr(driver) && BLI_simple_expr_is_valid(driver->expr_simple); return driver_compile_simple_expr(driver) && BLI_expr_pylike_is_valid(driver->expr_simple);
} }
/* Reset cached compiled expression data */ /* Reset cached compiled expression data */
void BKE_driver_invalidate_expression(ChannelDriver *driver, bool expr_changed, bool varname_changed) void BKE_driver_invalidate_expression(ChannelDriver *driver, bool expr_changed, bool varname_changed)
{ {
if (expr_changed || varname_changed) { if (expr_changed || varname_changed) {
BLI_simple_expr_free(driver->expr_simple); BLI_expr_pylike_free(driver->expr_simple);
driver->expr_simple = NULL; driver->expr_simple = NULL;
} }

View File

@@ -0,0 +1,63 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2018 Blender Foundation, Alexander Gavrilov
* All rights reserved.
*
* Contributor(s): Alexander Gavrilov
*
* ***** END GPL LICENSE BLOCK *****
*/
#ifndef __BLI_EXPR_PYLIKE_EVAL_H__
#define __BLI_EXPR_PYLIKE_EVAL_H__
/** \file BLI_expr_pylike_eval.h
* \ingroup bli
*/
#ifdef __cplusplus
extern "C" {
#endif
/** Opaque structure containing pre-parsed data for evaluation. */
typedef struct ExprPyLike_Parsed ExprPyLike_Parsed;
/** Expression evaluation return code. */
typedef enum eExprPyLike_EvalStatus {
EXPR_PYLIKE_SUCCESS = 0,
/* Computation errors; result is still set, but may be NaN */
EXPR_PYLIKE_DIV_BY_ZERO,
EXPR_PYLIKE_MATH_ERROR,
/* Expression dependent errors or bugs; result is 0 */
EXPR_PYLIKE_INVALID,
EXPR_PYLIKE_FATAL_ERROR,
} eExprPyLike_EvalStatus;
void BLI_expr_pylike_free(struct ExprPyLike_Parsed *expr);
bool BLI_expr_pylike_is_valid(struct ExprPyLike_Parsed *expr);
bool BLI_expr_pylike_is_constant(struct ExprPyLike_Parsed *expr);
ExprPyLike_Parsed *BLI_expr_pylike_parse(
const char *expression, int num_params, const char **param_names);
eExprPyLike_EvalStatus BLI_expr_pylike_eval(
struct ExprPyLike_Parsed *expr, double *result, int num_params, const double *params);
#ifdef __cplusplus
}
#endif
#endif /* __BLI_EXPR_PYLIKE_EVALUATE_H__ */

View File

@@ -1,96 +0,0 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2018 Blender Foundation, Alexander Gavrilov
* All rights reserved.
*
* Contributor(s): Alexander Gavrilov
*
* ***** END GPL LICENSE BLOCK *****
*/
#ifndef __BLI_SIMPLE_EXPR_H__
#define __BLI_SIMPLE_EXPR_H__
/** \file BLI_simple_expr.h
* \ingroup bli
* \author Alexander Gavrilov
* \since 2018
*
* Simple evaluator for a subset of Python expressions that can be
* computed using purely double precision floating point values.
*
* Supported subset:
*
* - Identifiers use only ASCII characters.
* - Literals:
* floating point and decimal integer.
* - Constants:
* pi, True, False
* - Operators:
* +, -, *, /, ==, !=, <, <=, >, >=, and, or, not, ternary if
* - Functions:
* radians, degrees,
* abs, fabs, floor, ceil, trunc, int,
* sin, cos, tan, asin, acos, atan, atan2,
* exp, log, sqrt, pow, fmod
*
* The implementation has no global state and can be used multithreaded.
*/
#ifdef __cplusplus
extern "C" {
#endif
/** Opaque structure containing pre-parsed data for evaluation. */
typedef struct ParsedSimpleExpr ParsedSimpleExpr;
/** Simple expression evaluation return code. */
typedef enum eSimpleExpr_EvalStatus {
SIMPLE_EXPR_SUCCESS = 0,
/* Computation errors; result is still set, but may be NaN */
SIMPLE_EXPR_DIV_BY_ZERO,
SIMPLE_EXPR_MATH_ERROR,
/* Expression dependent errors or bugs; result is 0 */
SIMPLE_EXPR_INVALID,
SIMPLE_EXPR_FATAL_ERROR,
} eSimpleExpr_EvalStatus;
/** Free the parsed data; NULL argument is ok. */
void BLI_simple_expr_free(struct ParsedSimpleExpr *expr);
/** Check if the parsing result is valid for evaluation. */
bool BLI_simple_expr_is_valid(struct ParsedSimpleExpr *expr);
/** Check if the parsed expression always evaluates to the same value. */
bool BLI_simple_expr_is_constant(struct ParsedSimpleExpr *expr);
/** Parse the expression for evaluation later.
* Returns non-NULL even on failure; use is_valid to check.
*/
ParsedSimpleExpr *BLI_simple_expr_parse(const char *expression, int num_params, const char **param_names);
/** Evaluate the expression with the given parameters.
* The order and number of parameters must match the names given to parse.
*/
eSimpleExpr_EvalStatus BLI_simple_expr_evaluate(struct ParsedSimpleExpr *expr, double *result, int num_params, const double *params);
#ifdef __cplusplus
}
#endif
#endif /* __BLI_SIMPLE_EXPR_H__*/

View File

@@ -68,6 +68,7 @@ set(SRC
intern/easing.c intern/easing.c
intern/edgehash.c intern/edgehash.c
intern/endian_switch.c intern/endian_switch.c
intern/expr_pylike_eval.c
intern/fileops.c intern/fileops.c
intern/fnmatch.c intern/fnmatch.c
intern/freetypefont.c intern/freetypefont.c
@@ -104,7 +105,6 @@ set(SRC
intern/rct.c intern/rct.c
intern/scanfill.c intern/scanfill.c
intern/scanfill_utils.c intern/scanfill_utils.c
intern/simple_expr.c
intern/smallhash.c intern/smallhash.c
intern/sort.c intern/sort.c
intern/sort_utils.c intern/sort_utils.c
@@ -152,6 +152,7 @@ set(SRC
BLI_edgehash.h BLI_edgehash.h
BLI_endian_switch.h BLI_endian_switch.h
BLI_endian_switch_inline.h BLI_endian_switch_inline.h
BLI_expr_pylike_eval.h
BLI_fileops.h BLI_fileops.h
BLI_fileops_types.h BLI_fileops_types.h
BLI_fnmatch.h BLI_fnmatch.h

View File

@@ -25,8 +25,30 @@
* ***** END GPL LICENSE BLOCK ***** * ***** END GPL LICENSE BLOCK *****
*/ */
/** \file blender/blenlib/intern/simple_expr.c /** \file blender/blenlib/intern/expr_pylike_eval.c
* \ingroup bli * \ingroup bli
* \author Alexander Gavrilov
* \since 2018
*
* Simple evaluator for a subset of Python expressions that can be
* computed using purely double precision floating point values.
*
* Supported subset:
*
* - Identifiers use only ASCII characters.
* - Literals:
* floating point and decimal integer.
* - Constants:
* pi, True, False
* - Operators:
* +, -, *, /, ==, !=, <, <=, >, >=, and, or, not, ternary if
* - Functions:
* radians, degrees,
* abs, fabs, floor, ceil, trunc, int,
* sin, cos, tan, asin, acos, atan, atan2,
* exp, log, sqrt, pow, fmod
*
* The implementation has no global state and can be used multithreaded.
*/ */
#include <math.h> #include <math.h>
@@ -40,7 +62,7 @@
#include "MEM_guardedalloc.h" #include "MEM_guardedalloc.h"
#include "BLI_simple_expr.h" #include "BLI_expr_pylike_eval.h"
#include "BLI_blenlib.h" #include "BLI_blenlib.h"
#include "BLI_math.h" #include "BLI_math.h"
#include "BLI_string_utils.h" #include "BLI_string_utils.h"
@@ -53,7 +75,7 @@
/* Simple Expression Stack Machine ------------------------- */ /* Simple Expression Stack Machine ------------------------- */
typedef enum eSimpleExpr_Opcode { typedef enum eOpCode {
/* Double constant: (-> dval) */ /* Double constant: (-> dval) */
OPCODE_CONST, OPCODE_CONST,
/* 1 argument function call: (a -> func1(a)) */ /* 1 argument function call: (a -> func1(a)) */
@@ -76,13 +98,13 @@ typedef enum eSimpleExpr_Opcode {
OPCODE_JMP_AND, OPCODE_JMP_AND,
/* For comparison chaining: (a b -> 0 JUMP) IF NOT func2(a,b) ELSE (a b -> b) */ /* For comparison chaining: (a b -> 0 JUMP) IF NOT func2(a,b) ELSE (a b -> b) */
OPCODE_CMP_CHAIN, OPCODE_CMP_CHAIN,
} eSimpleExpr_Opcode; } eOpCode;
typedef double (*UnaryOpFunc)(double); typedef double (*UnaryOpFunc)(double);
typedef double (*BinaryOpFunc)(double, double); typedef double (*BinaryOpFunc)(double, double);
typedef struct SimpleExprOp { typedef struct ExprOp {
eSimpleExpr_Opcode opcode; eOpCode opcode;
int jmp_offset; int jmp_offset;
@@ -93,43 +115,50 @@ typedef struct SimpleExprOp {
UnaryOpFunc func1; UnaryOpFunc func1;
BinaryOpFunc func2; BinaryOpFunc func2;
} arg; } arg;
} SimpleExprOp; } ExprOp;
struct ParsedSimpleExpr { struct ExprPyLike_Parsed {
int ops_count; int ops_count;
int max_stack; int max_stack;
SimpleExprOp ops[]; ExprOp ops[];
}; };
void BLI_simple_expr_free(ParsedSimpleExpr *expr) /** Free the parsed data; NULL argument is ok. */
void BLI_expr_pylike_free(ExprPyLike_Parsed *expr)
{ {
if (expr != NULL) { if (expr != NULL) {
MEM_freeN(expr); MEM_freeN(expr);
} }
} }
bool BLI_simple_expr_is_valid(ParsedSimpleExpr *expr) /** Check if the parsing result is valid for evaluation. */
bool BLI_expr_pylike_is_valid(ExprPyLike_Parsed *expr)
{ {
return expr != NULL && expr->ops_count > 0; return expr != NULL && expr->ops_count > 0;
} }
bool BLI_simple_expr_is_constant(ParsedSimpleExpr *expr) /** Check if the parsed expression always evaluates to the same value. */
bool BLI_expr_pylike_is_constant(ExprPyLike_Parsed *expr)
{ {
return expr != NULL && expr->ops_count == 1 && expr->ops[0].opcode == OPCODE_CONST; return expr != NULL && expr->ops_count == 1 && expr->ops[0].opcode == OPCODE_CONST;
} }
/* Stack Machine Evaluation -------------------------------- */ /* Stack Machine Evaluation -------------------------------- */
eSimpleExpr_EvalStatus BLI_simple_expr_evaluate(ParsedSimpleExpr *expr, double *result, int num_params, const double *params) /**
* Evaluate the expression with the given parameters.
* The order and number of parameters must match the names given to parse.
*/
eExprPyLike_EvalStatus BLI_expr_pylike_eval(ExprPyLike_Parsed *expr, double *result, int num_params, const double *params)
{ {
*result = 0.0; *result = 0.0;
if (!BLI_simple_expr_is_valid(expr)) { if (!BLI_expr_pylike_is_valid(expr)) {
return SIMPLE_EXPR_INVALID; return EXPR_PYLIKE_INVALID;
} }
#define FAIL_IF(condition) if (condition) { return SIMPLE_EXPR_FATAL_ERROR; } #define FAIL_IF(condition) if (condition) { return EXPR_PYLIKE_FATAL_ERROR; }
/* Check the stack requirement is at least remotely sane and allocate on the actual stack. */ /* Check the stack requirement is at least remotely sane and allocate on the actual stack. */
FAIL_IF(expr->max_stack <= 0 || expr->max_stack > 1000); FAIL_IF(expr->max_stack <= 0 || expr->max_stack > 1000);
@@ -137,7 +166,7 @@ eSimpleExpr_EvalStatus BLI_simple_expr_evaluate(ParsedSimpleExpr *expr, double *
double *stack = BLI_array_alloca(stack, expr->max_stack); double *stack = BLI_array_alloca(stack, expr->max_stack);
/* Evaluate expression. */ /* Evaluate expression. */
SimpleExprOp *ops = expr->ops; ExprOp *ops = expr->ops;
int sp = 0, pc; int sp = 0, pc;
feclearexcept(FE_ALL_EXCEPT); feclearexcept(FE_ALL_EXCEPT);
@@ -212,7 +241,7 @@ eSimpleExpr_EvalStatus BLI_simple_expr_evaluate(ParsedSimpleExpr *expr, double *
break; break;
default: default:
return SIMPLE_EXPR_FATAL_ERROR; return EXPR_PYLIKE_FATAL_ERROR;
} }
} }
@@ -225,10 +254,10 @@ eSimpleExpr_EvalStatus BLI_simple_expr_evaluate(ParsedSimpleExpr *expr, double *
/* Detect floating point evaluation errors. */ /* Detect floating point evaluation errors. */
int flags = fetestexcept(FE_DIVBYZERO | FE_INVALID); int flags = fetestexcept(FE_DIVBYZERO | FE_INVALID);
if (flags) { if (flags) {
return (flags & FE_INVALID) ? SIMPLE_EXPR_MATH_ERROR : SIMPLE_EXPR_DIV_BY_ZERO; return (flags & FE_INVALID) ? EXPR_PYLIKE_MATH_ERROR : EXPR_PYLIKE_DIV_BY_ZERO;
} }
return SIMPLE_EXPR_SUCCESS; return EXPR_PYLIKE_SUCCESS;
} }
/* Simple Expression Built-In Operations ------------------- */ /* Simple Expression Built-In Operations ------------------- */
@@ -317,7 +346,7 @@ static BuiltinConstDef builtin_consts[] = {
typedef struct BuiltinOpDef { typedef struct BuiltinOpDef {
const char *name; const char *name;
eSimpleExpr_Opcode op; eOpCode op;
void *funcptr; void *funcptr;
} BuiltinOpDef; } BuiltinOpDef;
@@ -397,27 +426,27 @@ typedef struct SimpleExprParseState {
/* Opcode buffer */ /* Opcode buffer */
int ops_count, max_ops, last_jmp; int ops_count, max_ops, last_jmp;
SimpleExprOp *ops; ExprOp *ops;
/* Stack space requirement tracking */ /* Stack space requirement tracking */
int stack_ptr, max_stack; int stack_ptr, max_stack;
} SimpleExprParseState; } SimpleExprParseState;
/* Reserve space for the specified number of operations in the buffer. */ /* Reserve space for the specified number of operations in the buffer. */
static SimpleExprOp *parse_alloc_ops(SimpleExprParseState *state, int count) static ExprOp *parse_alloc_ops(SimpleExprParseState *state, int count)
{ {
if (state->ops_count + count > state->max_ops) { if (state->ops_count + count > state->max_ops) {
state->max_ops = power_of_2_max_i(state->ops_count + count); state->max_ops = power_of_2_max_i(state->ops_count + count);
state->ops = MEM_reallocN(state->ops, state->max_ops * sizeof(SimpleExprOp)); state->ops = MEM_reallocN(state->ops, state->max_ops * sizeof(ExprOp));
} }
SimpleExprOp *op = &state->ops[state->ops_count]; ExprOp *op = &state->ops[state->ops_count];
state->ops_count += count; state->ops_count += count;
return op; return op;
} }
/* Add one operation and track stack usage. */ /* Add one operation and track stack usage. */
static SimpleExprOp *parse_add_op(SimpleExprParseState *state, eSimpleExpr_Opcode code, int stack_delta) static ExprOp *parse_add_op(SimpleExprParseState *state, eOpCode code, int stack_delta)
{ {
/* track evaluation stack depth */ /* track evaluation stack depth */
state->stack_ptr += stack_delta; state->stack_ptr += stack_delta;
@@ -425,14 +454,14 @@ static SimpleExprOp *parse_add_op(SimpleExprParseState *state, eSimpleExpr_Opcod
CLAMP_MIN(state->max_stack, state->stack_ptr); CLAMP_MIN(state->max_stack, state->stack_ptr);
/* allocate the new instruction */ /* allocate the new instruction */
SimpleExprOp *op = parse_alloc_ops(state, 1); ExprOp *op = parse_alloc_ops(state, 1);
memset(op, 0, sizeof(SimpleExprOp)); memset(op, 0, sizeof(ExprOp));
op->opcode = code; op->opcode = code;
return op; return op;
} }
/* Add one jump operation and return an index for parse_set_jump. */ /* Add one jump operation and return an index for parse_set_jump. */
static int parse_add_jump(SimpleExprParseState *state, eSimpleExpr_Opcode code) static int parse_add_jump(SimpleExprParseState *state, eOpCode code)
{ {
parse_add_op(state, code, -1); parse_add_op(state, code, -1);
return state->last_jmp = state->ops_count; return state->last_jmp = state->ops_count;
@@ -446,9 +475,9 @@ static void parse_set_jump(SimpleExprParseState *state, int jump)
} }
/* Add a function call operation, applying constant folding when possible. */ /* Add a function call operation, applying constant folding when possible. */
static bool parse_add_func(SimpleExprParseState *state, eSimpleExpr_Opcode code, int args, void *funcptr) static bool parse_add_func(SimpleExprParseState *state, eOpCode code, int args, void *funcptr)
{ {
SimpleExprOp *prev_ops = &state->ops[state->ops_count]; ExprOp *prev_ops = &state->ops[state->ops_count];
int jmp_gap = state->ops_count - state->last_jmp; int jmp_gap = state->ops_count - state->last_jmp;
feclearexcept(FE_ALL_EXCEPT); feclearexcept(FE_ALL_EXCEPT);
@@ -853,9 +882,9 @@ static bool parse_expr(SimpleExprParseState *state)
/* Ternary IF expression in python requires swapping the /* Ternary IF expression in python requires swapping the
* main body with condition, so stash the body opcodes. */ * main body with condition, so stash the body opcodes. */
int size = state->ops_count - start; int size = state->ops_count - start;
int bytes = size * sizeof(SimpleExprOp); int bytes = size * sizeof(ExprOp);
SimpleExprOp *body = MEM_mallocN(bytes, "driver if body"); ExprOp *body = MEM_mallocN(bytes, "driver if body");
memcpy(body, state->ops + start, bytes); memcpy(body, state->ops + start, bytes);
state->last_jmp = state->ops_count = start; state->last_jmp = state->ops_count = start;
@@ -896,8 +925,13 @@ static bool parse_expr(SimpleExprParseState *state)
/* Main Parsing Function ----------------------------------- */ /* Main Parsing Function ----------------------------------- */
/* Compile the expression and return the result. */ /**
ParsedSimpleExpr *BLI_simple_expr_parse(const char *expression, int num_params, const char **param_names) * Compile the expression and return the result.
*
* Parse the expression for evaluation later.
* Returns non-NULL even on failure; use is_valid to check.
*/
ExprPyLike_Parsed *BLI_expr_pylike_parse(const char *expression, int num_params, const char **param_names)
{ {
/* Prepare the parser state. */ /* Prepare the parser state. */
SimpleExprParseState state; SimpleExprParseState state;
@@ -911,25 +945,25 @@ ParsedSimpleExpr *BLI_simple_expr_parse(const char *expression, int num_params,
state.tokenbuf = MEM_mallocN(strlen(expression) + 1, __func__); state.tokenbuf = MEM_mallocN(strlen(expression) + 1, __func__);
state.max_ops = 16; state.max_ops = 16;
state.ops = MEM_mallocN(state.max_ops * sizeof(SimpleExprOp), __func__); state.ops = MEM_mallocN(state.max_ops * sizeof(ExprOp), __func__);
/* Parse the expression. */ /* Parse the expression. */
ParsedSimpleExpr *expr; ExprPyLike_Parsed *expr;
if (parse_next_token(&state) && parse_expr(&state) && state.token == 0) { if (parse_next_token(&state) && parse_expr(&state) && state.token == 0) {
BLI_assert(state.stack_ptr == 1); BLI_assert(state.stack_ptr == 1);
int bytesize = sizeof(ParsedSimpleExpr) + state.ops_count * sizeof(SimpleExprOp); int bytesize = sizeof(ExprPyLike_Parsed) + state.ops_count * sizeof(ExprOp);
expr = MEM_mallocN(bytesize, "ParsedSimpleExpr"); expr = MEM_mallocN(bytesize, "ExprPyLike_Parsed");
expr->ops_count = state.ops_count; expr->ops_count = state.ops_count;
expr->max_stack = state.max_stack; expr->max_stack = state.max_stack;
memcpy(expr->ops, state.ops, state.ops_count * sizeof(SimpleExprOp)); memcpy(expr->ops, state.ops, state.ops_count * sizeof(ExprOp));
} }
else { else {
/* Always return a non-NULL object so that parse failure can be cached. */ /* Always return a non-NULL object so that parse failure can be cached. */
expr = MEM_callocN(sizeof(ParsedSimpleExpr), "ParsedSimpleExpr(empty)"); expr = MEM_callocN(sizeof(ExprPyLike_Parsed), "ExprPyLike_Parsed(empty)");
} }
MEM_freeN(state.tokenbuf); MEM_freeN(state.tokenbuf);

View File

@@ -415,7 +415,7 @@ typedef struct ChannelDriver {
char expression[256]; /* expression to compile for evaluation */ char expression[256]; /* expression to compile for evaluation */
void *expr_comp; /* PyObject - compiled expression, don't save this */ void *expr_comp; /* PyObject - compiled expression, don't save this */
struct ParsedSimpleExpr *expr_simple; /* compiled simple arithmetic expression */ struct ExprPyLike_Parsed *expr_simple; /* compiled simple arithmetic expression */
float curval; /* result of previous evaluation */ float curval; /* result of previous evaluation */
float influence; /* influence of driver on result */ // XXX to be implemented... this is like the constraint influence setting float influence; /* influence of driver on result */ // XXX to be implemented... this is like the constraint influence setting

View File

@@ -5,87 +5,87 @@
#include <string.h> #include <string.h>
extern "C" { extern "C" {
#include "BLI_simple_expr.h" #include "BLI_expr_pylike_eval.h"
#include "BLI_math.h" #include "BLI_math.h"
}; };
#define TRUE_VAL 1.0 #define TRUE_VAL 1.0
#define FALSE_VAL 0.0 #define FALSE_VAL 0.0
static void simple_expr_parse_fail_test(const char *str) static void expr_pylike_parse_fail_test(const char *str)
{ {
ParsedSimpleExpr *expr = BLI_simple_expr_parse(str, 0, NULL); ExprPyLike_Parsed *expr = BLI_expr_pylike_parse(str, 0, NULL);
EXPECT_FALSE(BLI_simple_expr_is_valid(expr)); EXPECT_FALSE(BLI_expr_pylike_is_valid(expr));
BLI_simple_expr_free(expr); BLI_expr_pylike_free(expr);
} }
static void simple_expr_const_test(const char *str, double value, bool force_const) static void expr_pylike_const_test(const char *str, double value, bool force_const)
{ {
ParsedSimpleExpr *expr = BLI_simple_expr_parse(str, 0, NULL); ExprPyLike_Parsed *expr = BLI_expr_pylike_parse(str, 0, NULL);
if (force_const) { if (force_const) {
EXPECT_TRUE(BLI_simple_expr_is_constant(expr)); EXPECT_TRUE(BLI_expr_pylike_is_constant(expr));
} }
else { else {
EXPECT_TRUE(BLI_simple_expr_is_valid(expr)); EXPECT_TRUE(BLI_expr_pylike_is_valid(expr));
EXPECT_FALSE(BLI_simple_expr_is_constant(expr)); EXPECT_FALSE(BLI_expr_pylike_is_constant(expr));
} }
double result; double result;
eSimpleExpr_EvalStatus status = BLI_simple_expr_evaluate(expr, &result, 0, NULL); eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, &result, 0, NULL);
EXPECT_EQ(status, SIMPLE_EXPR_SUCCESS); EXPECT_EQ(status, EXPR_PYLIKE_SUCCESS);
EXPECT_EQ(result, value); EXPECT_EQ(result, value);
BLI_simple_expr_free(expr); BLI_expr_pylike_free(expr);
} }
static ParsedSimpleExpr *parse_for_eval(const char *str, bool nonconst) static ExprPyLike_Parsed *parse_for_eval(const char *str, bool nonconst)
{ {
const char *names[1] = { "x" }; const char *names[1] = { "x" };
ParsedSimpleExpr *expr = BLI_simple_expr_parse(str, 1, names); ExprPyLike_Parsed *expr = BLI_expr_pylike_parse(str, 1, names);
EXPECT_TRUE(BLI_simple_expr_is_valid(expr)); EXPECT_TRUE(BLI_expr_pylike_is_valid(expr));
if (nonconst) { if (nonconst) {
EXPECT_FALSE(BLI_simple_expr_is_constant(expr)); EXPECT_FALSE(BLI_expr_pylike_is_constant(expr));
} }
return expr; return expr;
} }
static void verify_eval_result(ParsedSimpleExpr *expr, double x, double value) static void verify_eval_result(ExprPyLike_Parsed *expr, double x, double value)
{ {
double result; double result;
eSimpleExpr_EvalStatus status = BLI_simple_expr_evaluate(expr, &result, 1, &x); eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, &result, 1, &x);
EXPECT_EQ(status, SIMPLE_EXPR_SUCCESS); EXPECT_EQ(status, EXPR_PYLIKE_SUCCESS);
EXPECT_EQ(result, value); EXPECT_EQ(result, value);
} }
static void simple_expr_eval_test(const char *str, double x, double value) static void expr_pylike_eval_test(const char *str, double x, double value)
{ {
ParsedSimpleExpr *expr = parse_for_eval(str, true); ExprPyLike_Parsed *expr = parse_for_eval(str, true);
verify_eval_result(expr, x, value); verify_eval_result(expr, x, value);
BLI_simple_expr_free(expr); BLI_expr_pylike_free(expr);
} }
static void simple_expr_error_test(const char *str, double x, eSimpleExpr_EvalStatus error) static void expr_pylike_error_test(const char *str, double x, eExprPyLike_EvalStatus error)
{ {
ParsedSimpleExpr *expr = parse_for_eval(str, false); ExprPyLike_Parsed *expr = parse_for_eval(str, false);
double result; double result;
eSimpleExpr_EvalStatus status = BLI_simple_expr_evaluate(expr, &result, 1, &x); eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, &result, 1, &x);
EXPECT_EQ(status, error); EXPECT_EQ(status, error);
BLI_simple_expr_free(expr); BLI_expr_pylike_free(expr);
} }
#define TEST_PARSE_FAIL(name, str) \ #define TEST_PARSE_FAIL(name, str) \
TEST(simple_expr, ParseFail_##name) { simple_expr_parse_fail_test(str); } TEST(expr_pylike, ParseFail_##name) { expr_pylike_parse_fail_test(str); }
TEST_PARSE_FAIL(Empty, "") TEST_PARSE_FAIL(Empty, "")
TEST_PARSE_FAIL(ConstHex, "0x0") TEST_PARSE_FAIL(ConstHex, "0x0")
@@ -113,15 +113,15 @@ TEST_PARSE_FAIL(Truncated10, "fmod(1,")
/* Constant expression with working constant folding */ /* Constant expression with working constant folding */
#define TEST_CONST(name, str, value) \ #define TEST_CONST(name, str, value) \
TEST(simple_expr, Const_##name) { simple_expr_const_test(str, value, true); } TEST(expr_pylike, Const_##name) { expr_pylike_const_test(str, value, true); }
/* Constant expression but constant folding is not supported */ /* Constant expression but constant folding is not supported */
#define TEST_RESULT(name, str, value) \ #define TEST_RESULT(name, str, value) \
TEST(simple_expr, Result_##name) { simple_expr_const_test(str, value, false); } TEST(expr_pylike, Result_##name) { expr_pylike_const_test(str, value, false); }
/* Expression with an argument */ /* Expression with an argument */
#define TEST_EVAL(name, str, x, value) \ #define TEST_EVAL(name, str, x, value) \
TEST(simple_expr, Eval_##name) { simple_expr_eval_test(str, x, value); } TEST(expr_pylike, Eval_##name) { expr_pylike_eval_test(str, x, value); }
TEST_CONST(Zero, "0", 0.0) TEST_CONST(Zero, "0", 0.0)
TEST_CONST(Zero2, "00", 0.0) TEST_CONST(Zero2, "00", 0.0)
@@ -237,9 +237,9 @@ TEST_RESULT(Or2, "0 or 3", 3.0)
TEST_RESULT(Bool1, "2 or 3 and 4", 2.0) TEST_RESULT(Bool1, "2 or 3 and 4", 2.0)
TEST_RESULT(Bool2, "not 2 or 3 and 4", 4.0) TEST_RESULT(Bool2, "not 2 or 3 and 4", 4.0)
TEST(simple_expr, Eval_Ternary1) TEST(expr_pylike, Eval_Ternary1)
{ {
ParsedSimpleExpr *expr = parse_for_eval("x / 2 if x < 4 else x - 2 if x < 8 else x*2 - 12", true); ExprPyLike_Parsed *expr = parse_for_eval("x / 2 if x < 4 else x - 2 if x < 8 else x*2 - 12", true);
for (int i = 0; i <= 10; i++) { for (int i = 0; i <= 10; i++) {
double x = i; double x = i;
@@ -248,63 +248,63 @@ TEST(simple_expr, Eval_Ternary1)
verify_eval_result(expr, x, v); verify_eval_result(expr, x, v);
} }
BLI_simple_expr_free(expr); BLI_expr_pylike_free(expr);
} }
TEST(simple_expr, MultipleArgs) TEST(expr_pylike, MultipleArgs)
{ {
const char* names[3] = { "x", "y", "x" }; const char* names[3] = { "x", "y", "x" };
double values[3] = { 1.0, 2.0, 3.0 }; double values[3] = { 1.0, 2.0, 3.0 };
ParsedSimpleExpr *expr = BLI_simple_expr_parse("x*10 + y", 3, names); ExprPyLike_Parsed *expr = BLI_expr_pylike_parse("x*10 + y", 3, names);
EXPECT_TRUE(BLI_simple_expr_is_valid(expr)); EXPECT_TRUE(BLI_expr_pylike_is_valid(expr));
double result; double result;
eSimpleExpr_EvalStatus status = BLI_simple_expr_evaluate(expr, &result, 3, values); eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, &result, 3, values);
EXPECT_EQ(status, SIMPLE_EXPR_SUCCESS); EXPECT_EQ(status, EXPR_PYLIKE_SUCCESS);
EXPECT_EQ(result, 32.0); EXPECT_EQ(result, 32.0);
BLI_simple_expr_free(expr); BLI_expr_pylike_free(expr);
} }
#define TEST_ERROR(name, str, x, code) \ #define TEST_ERROR(name, str, x, code) \
TEST(simple_expr, Error_##name) { simple_expr_error_test(str, x, code); } TEST(expr_pylike, Error_##name) { expr_pylike_error_test(str, x, code); }
TEST_ERROR(DivZero1, "0 / 0", 0.0, SIMPLE_EXPR_MATH_ERROR) TEST_ERROR(DivZero1, "0 / 0", 0.0, EXPR_PYLIKE_MATH_ERROR)
TEST_ERROR(DivZero2, "1 / 0", 0.0, SIMPLE_EXPR_DIV_BY_ZERO) TEST_ERROR(DivZero2, "1 / 0", 0.0, EXPR_PYLIKE_DIV_BY_ZERO)
TEST_ERROR(DivZero3, "1 / x", 0.0, SIMPLE_EXPR_DIV_BY_ZERO) TEST_ERROR(DivZero3, "1 / x", 0.0, EXPR_PYLIKE_DIV_BY_ZERO)
TEST_ERROR(DivZero4, "1 / x", 1.0, SIMPLE_EXPR_SUCCESS) TEST_ERROR(DivZero4, "1 / x", 1.0, EXPR_PYLIKE_SUCCESS)
TEST_ERROR(SqrtDomain1, "sqrt(-1)", 0.0, SIMPLE_EXPR_MATH_ERROR) TEST_ERROR(SqrtDomain1, "sqrt(-1)", 0.0, EXPR_PYLIKE_MATH_ERROR)
TEST_ERROR(SqrtDomain2, "sqrt(x)", -1.0, SIMPLE_EXPR_MATH_ERROR) TEST_ERROR(SqrtDomain2, "sqrt(x)", -1.0, EXPR_PYLIKE_MATH_ERROR)
TEST_ERROR(SqrtDomain3, "sqrt(x)", 0.0, SIMPLE_EXPR_SUCCESS) TEST_ERROR(SqrtDomain3, "sqrt(x)", 0.0, EXPR_PYLIKE_SUCCESS)
TEST_ERROR(PowDomain1, "pow(-1, 0.5)", 0.0, SIMPLE_EXPR_MATH_ERROR) TEST_ERROR(PowDomain1, "pow(-1, 0.5)", 0.0, EXPR_PYLIKE_MATH_ERROR)
TEST_ERROR(PowDomain2, "pow(-1, x)", 0.5, SIMPLE_EXPR_MATH_ERROR) TEST_ERROR(PowDomain2, "pow(-1, x)", 0.5, EXPR_PYLIKE_MATH_ERROR)
TEST_ERROR(PowDomain3, "pow(-1, x)", 2.0, SIMPLE_EXPR_SUCCESS) TEST_ERROR(PowDomain3, "pow(-1, x)", 2.0, EXPR_PYLIKE_SUCCESS)
TEST_ERROR(Mixed1, "sqrt(x) + 1 / max(0, x)", -1.0, SIMPLE_EXPR_MATH_ERROR) TEST_ERROR(Mixed1, "sqrt(x) + 1 / max(0, x)", -1.0, EXPR_PYLIKE_MATH_ERROR)
TEST_ERROR(Mixed2, "sqrt(x) + 1 / max(0, x)", 0.0, SIMPLE_EXPR_DIV_BY_ZERO) TEST_ERROR(Mixed2, "sqrt(x) + 1 / max(0, x)", 0.0, EXPR_PYLIKE_DIV_BY_ZERO)
TEST_ERROR(Mixed3, "sqrt(x) + 1 / max(0, x)", 1.0, SIMPLE_EXPR_SUCCESS) TEST_ERROR(Mixed3, "sqrt(x) + 1 / max(0, x)", 1.0, EXPR_PYLIKE_SUCCESS)
TEST(simple_expr, Error_Invalid) TEST(expr_pylike, Error_Invalid)
{ {
ParsedSimpleExpr *expr = BLI_simple_expr_parse("", 0, NULL); ExprPyLike_Parsed *expr = BLI_expr_pylike_parse("", 0, NULL);
double result; double result;
EXPECT_EQ(BLI_simple_expr_evaluate(expr, &result, 0, NULL), SIMPLE_EXPR_INVALID); EXPECT_EQ(BLI_expr_pylike_eval(expr, &result, 0, NULL), EXPR_PYLIKE_INVALID);
BLI_simple_expr_free(expr); BLI_expr_pylike_free(expr);
} }
TEST(simple_expr, Error_ArgumentCount) TEST(expr_pylike, Error_ArgumentCount)
{ {
ParsedSimpleExpr *expr = parse_for_eval("x", false); ExprPyLike_Parsed *expr = parse_for_eval("x", false);
double result; double result;
EXPECT_EQ(BLI_simple_expr_evaluate(expr, &result, 0, NULL), SIMPLE_EXPR_FATAL_ERROR); EXPECT_EQ(BLI_expr_pylike_eval(expr, &result, 0, NULL), EXPR_PYLIKE_FATAL_ERROR);
BLI_simple_expr_free(expr); BLI_expr_pylike_free(expr);
} }

View File

@@ -43,6 +43,7 @@ endif()
BLENDER_TEST(BLI_array_store "bf_blenlib") BLENDER_TEST(BLI_array_store "bf_blenlib")
BLENDER_TEST(BLI_array_utils "bf_blenlib") BLENDER_TEST(BLI_array_utils "bf_blenlib")
BLENDER_TEST(BLI_expr_pylike_eval "bf_blenlib")
BLENDER_TEST(BLI_ghash "bf_blenlib") BLENDER_TEST(BLI_ghash "bf_blenlib")
BLENDER_TEST(BLI_hash_mm2a "bf_blenlib") BLENDER_TEST(BLI_hash_mm2a "bf_blenlib")
BLENDER_TEST(BLI_heap "bf_blenlib") BLENDER_TEST(BLI_heap "bf_blenlib")
@@ -55,7 +56,6 @@ BLENDER_TEST(BLI_math_geom "bf_blenlib")
BLENDER_TEST(BLI_memiter "bf_blenlib") BLENDER_TEST(BLI_memiter "bf_blenlib")
BLENDER_TEST(BLI_path_util "${BLI_path_util_extra_libs}") BLENDER_TEST(BLI_path_util "${BLI_path_util_extra_libs}")
BLENDER_TEST(BLI_polyfill_2d "bf_blenlib") BLENDER_TEST(BLI_polyfill_2d "bf_blenlib")
BLENDER_TEST(BLI_simple_expr "bf_blenlib")
BLENDER_TEST(BLI_stack "bf_blenlib") BLENDER_TEST(BLI_stack "bf_blenlib")
BLENDER_TEST(BLI_string "bf_blenlib") BLENDER_TEST(BLI_string "bf_blenlib")
BLENDER_TEST(BLI_string_utf8 "bf_blenlib") BLENDER_TEST(BLI_string_utf8 "bf_blenlib")