From bf528b90660543b0f3b75d1a9c91319d79be680f Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 29 Nov 2021 14:57:29 +0100 Subject: [PATCH] conf: add possibility to evaluate simple integer math expressions It is useful to use the math expressions for the values in configuration. This patch adds a simple expression evaluation routines (integer only). The syntax is simplified unix shell (bash) style. Examples: $[1 + 1] $[$[2 + 2] / $var1] $[0xa0 | 0x05] As a bonus, the variable substitutions were more abstracted. The function snd_config_expand_custom() was introduced to be used for example in the topology pre-precessor. Signed-off-by: Jaroslav Kysela --- include/conf.h | 7 ++ src/Makefile.am | 2 +- src/conf.c | 88 +++++++++++++--- src/confeval.c | 260 ++++++++++++++++++++++++++++++++++++++++++++++ test/lsb/config.c | 50 +++++++++ 5 files changed, 389 insertions(+), 18 deletions(-) create mode 100644 src/confeval.c diff --git a/include/conf.h b/include/conf.h index 9cf39dc0..c649be3b 100644 --- a/include/conf.h +++ b/include/conf.h @@ -108,11 +108,18 @@ int snd_config_search_definition(snd_config_t *config, const char *base, const char *key, snd_config_t **result); +typedef int (*snd_config_expand_fcn_t)(snd_config_t **dst, const char *s, void *private_data); + +int snd_config_expand_custom(snd_config_t *config, snd_config_t *root, + snd_config_expand_fcn_t fcn, void *private_data, + snd_config_t **result); int snd_config_expand(snd_config_t *config, snd_config_t *root, const char *args, snd_config_t *private_data, snd_config_t **result); int snd_config_evaluate(snd_config_t *config, snd_config_t *root, snd_config_t *private_data, snd_config_t **result); +int snd_config_evaluate_string(snd_config_t **dst, const char *s, + snd_config_expand_fcn_t fcn, void *private_data); int snd_config_add(snd_config_t *config, snd_config_t *child); int snd_config_add_before(snd_config_t *before, snd_config_t *child); diff --git a/src/Makefile.am b/src/Makefile.am index 43fd3330..df46dbc4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,7 +14,7 @@ SYMFUNCS = endif lib_LTLIBRARIES = libasound.la -libasound_la_SOURCES = conf.c confmisc.c input.c output.c async.c error.c dlmisc.c socket.c shmarea.c userfile.c names.c +libasound_la_SOURCES = conf.c confeval.c confmisc.c input.c output.c async.c error.c dlmisc.c socket.c shmarea.c userfile.c names.c SUBDIRS=control libasound_la_LIBADD = control/libcontrol.la diff --git a/src/conf.c b/src/conf.c index ef421151..09d74b53 100644 --- a/src/conf.c +++ b/src/conf.c @@ -318,6 +318,15 @@ Arguments are referred to with a dollar-sign ($) and the name of the argument: card $CARD \endcode +\section confarg_math simple math expressions + +The simple math expressions are identified using a unix shell like expression syntax +with a dollar-sign ($) and bracket ([): + +\code + card "$[$CARD + 1]" +\endcode + \section confarg_usage Usage To use a block with arguments, write the argument values after the key, @@ -355,6 +364,7 @@ pcm.demo { } \endcode + */ /*! \page conffunc Runtime functions in configuration files @@ -4802,21 +4812,23 @@ typedef int (*snd_config_walk_callback_t)(snd_config_t *src, snd_config_t *root, snd_config_t **dst, snd_config_walk_pass_t pass, - snd_config_t *private_data); + snd_config_expand_fcn_t fcn, + void *private_data); #endif static int snd_config_walk(snd_config_t *src, snd_config_t *root, snd_config_t **dst, snd_config_walk_callback_t callback, - snd_config_t *private_data) + snd_config_expand_fcn_t fcn, + void *private_data) { int err; snd_config_iterator_t i, next; switch (snd_config_get_type(src)) { case SND_CONFIG_TYPE_COMPOUND: - err = callback(src, root, dst, SND_CONFIG_WALK_PASS_PRE, private_data); + err = callback(src, root, dst, SND_CONFIG_WALK_PASS_PRE, fcn, private_data); if (err <= 0) return err; snd_config_for_each(i, next, src) { @@ -4824,7 +4836,7 @@ static int snd_config_walk(snd_config_t *src, snd_config_t *d = NULL; err = snd_config_walk(s, root, (dst && *dst) ? &d : NULL, - callback, private_data); + callback, fcn, private_data); if (err < 0) goto _error; if (err && d) { @@ -4833,7 +4845,7 @@ static int snd_config_walk(snd_config_t *src, goto _error; } } - err = callback(src, root, dst, SND_CONFIG_WALK_PASS_POST, private_data); + err = callback(src, root, dst, SND_CONFIG_WALK_PASS_POST, fcn, private_data); if (err <= 0) { _error: if (dst && *dst) @@ -4841,7 +4853,7 @@ static int snd_config_walk(snd_config_t *src, } break; default: - err = callback(src, root, dst, SND_CONFIG_WALK_PASS_LEAF, private_data); + err = callback(src, root, dst, SND_CONFIG_WALK_PASS_LEAF, fcn, private_data); break; } return err; @@ -4851,7 +4863,8 @@ static int _snd_config_copy(snd_config_t *src, snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t **dst, snd_config_walk_pass_t pass, - snd_config_t *private_data ATTRIBUTE_UNUSED) + snd_config_expand_fcn_t fcn ATTRIBUTE_UNUSED, + void *private_data ATTRIBUTE_UNUSED) { int err; const char *id = src->id; @@ -4932,14 +4945,23 @@ static int _snd_config_copy(snd_config_t *src, int snd_config_copy(snd_config_t **dst, snd_config_t *src) { - return snd_config_walk(src, NULL, dst, _snd_config_copy, NULL); + return snd_config_walk(src, NULL, dst, _snd_config_copy, NULL, NULL); +} + +static int _snd_config_expand_vars(snd_config_t **dst, const char *s, void *private_data) +{ + snd_config_t *val, *vars = private_data; + if (snd_config_search(vars, s, &val) < 0) + return snd_config_make_string(dst, ""); + return snd_config_copy(dst, val); } static int _snd_config_expand(snd_config_t *src, snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t **dst, snd_config_walk_pass_t pass, - snd_config_t *private_data) + snd_config_expand_fcn_t fcn, + void *private_data) { int err; const char *id = src->id; @@ -4989,14 +5011,10 @@ static int _snd_config_expand(snd_config_t *src, case SND_CONFIG_TYPE_STRING: { const char *s; - snd_config_t *val; snd_config_t *vars = private_data; snd_config_get_string(src, &s); if (s && *s == '$') { - s++; - if (snd_config_search(vars, s, &val) < 0) - return 0; - err = snd_config_copy(dst, val); + err = snd_config_evaluate_string(dst, s, fcn, vars); if (err < 0) return err; err = snd_config_set_id(*dst, id); @@ -5025,7 +5043,8 @@ static int _snd_config_evaluate(snd_config_t *src, snd_config_t *root, snd_config_t **dst ATTRIBUTE_UNUSED, snd_config_walk_pass_t pass, - snd_config_t *private_data) + snd_config_expand_fcn_t fcn ATTRIBUTE_UNUSED, + void *private_data) { int err; if (pass == SND_CONFIG_WALK_PASS_PRE) { @@ -5139,7 +5158,7 @@ int snd_config_evaluate(snd_config_t *config, snd_config_t *root, { /* FIXME: Only in place evaluation is currently implemented */ assert(result == NULL); - return snd_config_walk(config, root, result, _snd_config_evaluate, private_data); + return snd_config_walk(config, root, result, _snd_config_evaluate, NULL, private_data); } static int load_defaults(snd_config_t *subs, snd_config_t *defs) @@ -5539,6 +5558,41 @@ static int parse_args(snd_config_t *subs, const char *str, snd_config_t *defs) return 0; } +/** + * \brief Expands a configuration node, applying arguments and functions. + * \param[in] config Handle to the configuration node. + * \param[in] root Handle to the root configuration node. + * \param[in] fcn Custom function to obtain the referred variable name + * \param[in] private_data Private data node for the custom function + * \param[out] result The function puts the handle to the result + * configuration node at the address specified by + * \a result. + * \return A non-negative value if successful, otherwise a negative error code. + * + * If \a config has arguments (defined by a child with id \c \@args), + * this function replaces any string node beginning with $ with the + * respective argument value, or the default argument value, or nothing. + * Furthermore, any functions are evaluated (see #snd_config_evaluate). + * The resulting copy of \a config is returned in \a result. + * + * The new tree is not evaluated (\ref snd_config_evaluate). + */ +int snd_config_expand_custom(snd_config_t *config, snd_config_t *root, + snd_config_expand_fcn_t fcn, void *private_data, + snd_config_t **result) +{ + snd_config_t *res; + int err; + + err = snd_config_walk(config, root, &res, _snd_config_expand, fcn, private_data); + if (err < 0) { + SNDERR("Expand error (walk): %s", snd_strerror(err)); + return err; + } + *result = res; + return 1; +} + /** * \brief Expands a configuration node, applying arguments and functions. * \param[in] config Handle to the configuration node. @@ -5589,7 +5643,7 @@ int snd_config_expand(snd_config_t *config, snd_config_t *root, const char *args SNDERR("Args evaluate error: %s", snd_strerror(err)); goto _end; } - err = snd_config_walk(config, root, &res, _snd_config_expand, subs); + err = snd_config_walk(config, root, &res, _snd_config_expand, _snd_config_expand_vars, subs); if (err < 0) { SNDERR("Expand error (walk): %s", snd_strerror(err)); goto _end; diff --git a/src/confeval.c b/src/confeval.c new file mode 100644 index 00000000..62d88697 --- /dev/null +++ b/src/confeval.c @@ -0,0 +1,260 @@ +/** + * \file confeval.c + * \ingroup Configuration + * \brief Configuration helper functions + * \author Jaroslav Kysela + * \date 2021 + * + * Configuration string evaluation. + * + * See the \ref confarg_math page for more details. + */ +/* + * Configuration string evaluation + * Copyright (c) 2000 by Abramo Bagnara , + * Jaroslav Kysela + * + * + * This library 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 version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include "local.h" + +typedef long long value_type_t; + +static const char *_find_end_of_expression(const char *s) +{ + int count = 1; + while (*s) { + if (*s == '[') { + count++; + } else if (*s == ']') { + count--; + if (count == 0) + return s + 1; + } + s++; + } + return NULL; +} + +static int _parse_integer(value_type_t *val, const char **s) +{ + long long v; + char *end; + + errno = 0; + v = strtoll(*s, &end, 0); + if (errno) + return -errno; + *val = v; + if (((long long)*val) != v) + return -ERANGE; + *s = end; + return 0; +} + +static int _to_integer(value_type_t *val, snd_config_t *c) +{ + int err; + + switch(snd_config_get_type(c)) { + case SND_CONFIG_TYPE_INTEGER: + { + long v; + err = snd_config_get_integer(c, &v); + if (err >= 0) + *val = v; + } + break; + case SND_CONFIG_TYPE_INTEGER64: + { + long long v; + err = snd_config_get_integer64(c, &v); + if (err >= 0) { + *val = v; + if (((long long)*val) != v) + return -ERANGE; + return 0; + } + } + break; + case SND_CONFIG_TYPE_STRING: + { + const char *s; + long long v; + err = snd_config_get_string(c, &s); + if (err >= 0) { + err = safe_strtoll(s, &v); + if (err >= 0) { + *val = v; + if (((long long)*val) != v) + return -ERANGE; + return 0; + } + } + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int _eval_string(snd_config_t **dst, const char *s, + snd_config_expand_fcn_t fcn, void *private_data) +{ + snd_config_t *tmp; + const char *save, *e; + char *m; + value_type_t left, right; + int err, c, op; + enum { + LEFT, + OP, + RIGHT, + END + } pos; + + while (*s && *s <= ' ') s++; + save = s; + pos = LEFT; + op = 0; + while (*s) { + while (*s && *s <= ' ') s++; + c = *s; + if (c == '\0') + break; + if (pos == END) { + SNDERR("unexpected expression tail '%s'", s); + return -EINVAL; + } + if (pos == OP) { + switch (c) { + case '+': + case '-': + case '*': + case '/': + case '%': + case '|': + case '&': op = c; break; + default: + SNDERR("unknown operation '%c'", c); + return -EINVAL; + } + pos = RIGHT; + s++; + continue; + } + if (c == '$') { + if (s[1] == '[') { + e = _find_end_of_expression(s + 2); + if (e == NULL) + return -EINVAL; + m = malloc(e - s - 1); + if (m == NULL) + return -ENOMEM; + memcpy(m, s + 2, e - s - 2); + m[e - s - 3] = '\0'; + err = _eval_string(&tmp, m, fcn, private_data); + free(m); + if (err < 0) + return err; + s = e; + if (*s) + s++; + } else { + e = s + 1; + while (*e) { + if (!isalnum(*e)) + break; + e++; + } + m = malloc(e - s); + if (m == NULL) + return -ENOMEM; + memcpy(m, s + 1, e - s - 1); + m[e - s - 1] = '\0'; + err = fcn(&tmp, m, private_data); + free(m); + if (err < 0) + return err; + s = e; + } + err = _to_integer(op == LEFT ? &left : &right, tmp); + snd_config_delete(tmp); + } else if (c == '-' || (c >= '0' && c <= '9')) { + err = _parse_integer(op == LEFT ? &left : &right, &s); + } + if (err < 0) + return err; + pos = op == LEFT ? OP : END; + } + if (pos != OP && pos != END) { + SNDERR("incomplete expression '%s'", save); + return -EINVAL; + } + + if (pos == END) { + switch (op) { + case '+': left = left + right; break; + case '-': left = left - right; break; + case '*': left = left * right; break; + case '/': left = left / right; break; + case '%': left = left % right; break; + case '|': left = left | right; break; + case '&': left = left & right; break; + default: return -EINVAL; + } + } + + if (left > INT_MAX || left < INT_MIN) + return snd_config_imake_integer64(dst, NULL, left); + else + return snd_config_imake_integer(dst, NULL, left); +} + +/** + * \brief Evaluate an math expression in the string + * \param[out] dst The function puts the handle to the new configuration + * node at the address specified by \a dst. + * \param[in] s A string to evaluate + * \param[in] fcn A function to get the variable contents + * \param[in] private_value A private value for the variable contents function + * \return 0 if successful, otherwise a negative error code. + */ +int snd_config_evaluate_string(snd_config_t **dst, const char *s, + snd_config_expand_fcn_t fcn, void *private_data) +{ + assert(dst && s); + int err; + + if (*s != '$') + return -EINVAL; + if (s[1] == '[') { + err = _eval_string(dst, s, fcn, private_data); + if (err < 0) + SNDERR("wrong expression '%s'", s); + } else { + err = fcn(dst, s + 1, private_data); + } + return err; +} diff --git a/test/lsb/config.c b/test/lsb/config.c index 3503798f..c57f441d 100644 --- a/test/lsb/config.c +++ b/test/lsb/config.c @@ -548,6 +548,55 @@ static void test_for_each(void) ALSA_CHECK(snd_config_delete(c)); } +static int _expand_fcn(snd_config_t **dst, const char *s, void *private_data ATTRIBUTE_UNUSED) +{ + if (strcmp(s, "var10") == 0) + return snd_config_imake_integer(dst, NULL, 10); + if (strcmp(s, "var50") == 0) + return snd_config_imake_integer(dst, NULL, 50); + return snd_config_imake_string(dst, NULL, ""); +} + +static void test_evaluate_string(void) +{ + struct { + const char *expr; + long long result; + } *p, e[] = { + { .expr = "$var10", .result = 10 }, + { .expr = "$var50", .result = 50 }, + { .expr = "$[1+1]", .result = 2 }, + { .expr = "$[10-5]", .result = 5 }, + { .expr = "$[10*5]", .result = 50 }, + { .expr = "$[15/5]", .result = 3 }, + { .expr = "$[12%5]", .result = 2 }, + { .expr = "$[0xaa|0x55]", .result = 0xff }, + { .expr = "$[0xff&0xfc]", .result = 0xfc }, + { .expr = "$[4294967296+10]", .result = 4294967306LL }, + { .expr = "$[$var10+1]", .result = 11 }, + { .expr = "$[$var10 + $var50]", .result = 60 }, + { .expr = "$[ $var10 + $[ $var50 + 10 ] ]", .result = 70 }, + { .expr = NULL, .result = 0 }, + }; + snd_config_t *dst; + long l; + long long ll; + + for (p = e; p->expr; p++) { + ALSA_CHECK(snd_config_evaluate_string(&dst, p->expr, _expand_fcn, NULL)); + if (snd_config_get_type(dst) == SND_CONFIG_TYPE_INTEGER) { + ALSA_CHECK(snd_config_get_integer(dst, &l)); + TEST_CHECK(l == p->result); + } else if (snd_config_get_type(dst) == SND_CONFIG_TYPE_INTEGER64) { + ALSA_CHECK(snd_config_get_integer64(dst, &ll)); + TEST_CHECK(ll == p->result); + } else { + ALSA_CHECK(0); + } + ALSA_CHECK(snd_config_delete(dst)); + } +} + int main(void) { test_top(); @@ -578,5 +627,6 @@ int main(void) test_get_ascii(); test_iterators(); test_for_each(); + test_evaluate_string(); return TEST_EXIT_CODE(); } -- 2.47.1