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 <perex@perex.cz>
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);
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
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,
}
\endcode
+
*/
/*! \page conffunc Runtime functions in configuration files
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) {
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) {
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)
}
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;
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;
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;
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);
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) {
{
/* 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)
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.
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;
--- /dev/null
+/**
+ * \file confeval.c
+ * \ingroup Configuration
+ * \brief Configuration helper functions
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2021
+ *
+ * Configuration string evaluation.
+ *
+ * See the \ref confarg_math page for more details.
+ */
+/*
+ * Configuration string evaluation
+ * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>,
+ * Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#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;
+}
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();
test_get_ascii();
test_iterators();
test_for_each();
+ test_evaluate_string();
return TEST_EXIT_CODE();
}