]> git.alsa-project.org Git - alsa-lib.git/commitdiff
conf: add possibility to evaluate simple integer math expressions
authorJaroslav Kysela <perex@perex.cz>
Mon, 29 Nov 2021 13:57:29 +0000 (14:57 +0100)
committerJaroslav Kysela <perex@perex.cz>
Tue, 30 Nov 2021 10:33:35 +0000 (11:33 +0100)
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>
include/conf.h
src/Makefile.am
src/conf.c
src/confeval.c [new file with mode: 0644]
test/lsb/config.c

index 9cf39dc0e1c2dc4823f688bd52133a0235d21581..c649be3bcb16e5eba0e6b85edda6f7023f984dfa 100644 (file)
@@ -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);
index 43fd3330ac76a784bbfddecc471bcb327a78d802..df46dbc4ea6482030b2a1228ba38a3e4819bbaa1 100644 (file)
@@ -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
index ef421151cc7d6aefaf761c08a24448d55ba1e02e..09d74b5331b38f731ed7b47c7ed39b3c1ccdc8e2 100644 (file)
@@ -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 (file)
index 0000000..62d8869
--- /dev/null
@@ -0,0 +1,260 @@
+/**
+ * \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;
+}
index 3503798f8af4573c44e9c53b3d756d22d936be30..c57f441d33b26bf6bc958ac6f7d715dfcc0325f5 100644 (file)
@@ -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();
 }