]> git.alsa-project.org Git - alsa-lib.git/commitdiff
ucm: implement MacroDefine and Macro subtree evaluation
authorJaroslav Kysela <perex@perex.cz>
Fri, 13 May 2022 14:07:38 +0000 (16:07 +0200)
committerJaroslav Kysela <perex@perex.cz>
Fri, 13 May 2022 15:25:29 +0000 (17:25 +0200)
The arguments are set as temporary variables as /MACRO_NAME/_/ARGUMENT_NAME/.

Example:

  # define new macro MyMacro with arguments ctl_name and ctl_value
  DefineMacro.MyMacro {
    BootSequence [
      cset "name='${var:MyMacro_ctl_name}' ${var:MyMacro_ctl_value}"
    ]
  }

  # instantiate macro for Speaker control (short version)
  Macro.headphone.MyMacro "ctl_name='Speaker Switch',ctl_value=off"

  # instantiate macro for Mic control (second version)
  Macro.mic.MyMacro {
ctl_name "Mic Switch"
ctl_value "off"
  }

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
src/ucm/main.c
src/ucm/parser.c
src/ucm/ucm_confdoc.h
src/ucm/ucm_local.h
src/ucm/ucm_subs.c
src/ucm/utils.c

index 078cfd641fe76027d49ac55d1380259e0e9e4699..8ca766309cc8602732c2b3c2537aefdf7ff9008c 100644 (file)
@@ -1398,6 +1398,10 @@ int snd_use_case_mgr_open(snd_use_case_mgr_t **uc_mgr,
        if (err < 0)
                goto _err;
 
+       err = snd_config_top(&mgr->macros);
+       if (err < 0)
+               goto _err;
+
        mgr->card_name = strdup(card_name);
        if (mgr->card_name == NULL) {
                err = -ENOMEM;
index 7bdaa8fe7b1bde36292fd8b563f9cfba9fa000c5..830c37d2a360c7289c7d73312a165205cf2804cd 100644 (file)
@@ -327,8 +327,10 @@ static int evaluate_define(snd_use_case_mgr_t *uc_mgr,
                free(var);
                if (err < 0)
                        return err;
-               uc_mgr_set_variable(uc_mgr, id, s);
+               err = uc_mgr_set_variable(uc_mgr, id, s);
                free(s);
+               if (err < 0)
+                       return err;
        }
 
        snd_config_delete(d);
@@ -336,6 +338,159 @@ static int evaluate_define(snd_use_case_mgr_t *uc_mgr,
        return evaluate_regex(uc_mgr, cfg);
 }
 
+/*
+ * Evaluate macro definitions (in-place delete)
+ */
+static int evaluate_define_macro(snd_use_case_mgr_t *uc_mgr,
+                                snd_config_t *cfg)
+{
+       snd_config_t *d;
+       int err;
+
+       err = snd_config_search(cfg, "DefineMacro", &d);
+       if (err == -ENOENT)
+               return 1;
+       if (err < 0)
+               return err;
+
+       if (snd_config_get_type(d) != SND_CONFIG_TYPE_COMPOUND) {
+               uc_error("compound type expected for DefineMacro");
+               return -EINVAL;
+       }
+
+       if (uc_mgr->conf_format < 5) {
+               uc_error("DefineMacro is supported in v5+ syntax");
+               return -EINVAL;
+       }
+
+       err = snd_config_merge(uc_mgr->macros, d, 0);
+       if (err < 0)
+               return err;
+       return 0;
+}
+
+static int evaluate_macro1(snd_use_case_mgr_t *uc_mgr,
+                          snd_config_t *dst,
+                          snd_config_t *args)
+{
+       snd_config_iterator_t i, next;
+       snd_config_t *m, *mc, *a, *n;
+       const char *mid, *id;
+       char name[128], *var;
+       const char *s;
+       int err;
+
+       err = snd_config_get_id(args, &mid);
+       if (err < 0)
+               return err;
+       err = snd_config_search(uc_mgr->macros, mid, &m);
+       if (err < 0) {
+               uc_error("Macro '%s' is not defined", mid);
+               return err;
+       }
+
+       a = args;
+       if (snd_config_get_type(args) == SND_CONFIG_TYPE_STRING) {
+               err = snd_config_get_string(args, &s);
+               if (err < 0)
+                       return err;
+               err = snd_config_load_string(&a, s, 0);
+               if (err < 0)
+                       return err;
+       } else if (snd_config_get_type(args) != SND_CONFIG_TYPE_COMPOUND) {
+               return -EINVAL;
+       }
+
+       /* set arguments */
+       snd_config_for_each(i, next, a) {
+               n = snd_config_iterator_entry(i);
+               err = snd_config_get_id(n, &id);
+               if (err < 0)
+                       return err;
+               err = snd_config_get_ascii(n, &var);
+               if (err < 0)
+                       return err;
+               snprintf(name, sizeof(name), "%s_%s", mid, id);
+               err = uc_mgr_set_variable(uc_mgr, name, var);
+               free(var);
+               if (err < 0)
+                       return err;
+       }
+
+       /* merge + substitute variables */
+       err = snd_config_copy(&mc, m);
+       if (err < 0)
+               goto __err_path;
+       err = uc_mgr_config_tree_merge(uc_mgr, dst, mc, NULL, NULL);
+       snd_config_delete(mc);
+
+       /* delete arguments */
+       snd_config_for_each(i, next, a) {
+               n = snd_config_iterator_entry(i);
+               err = snd_config_get_id(n, &id);
+               if (err < 0)
+                       return err;
+               snprintf(name, sizeof(name), "%s_%s", mid, id);
+               err = uc_mgr_delete_variable(uc_mgr, name);
+               if (err < 0)
+                       return err;
+       }
+
+__err_path:
+       if (a != args)
+               snd_config_delete(a);
+       return err;
+}
+
+/*
+ * Evaluate macro definitions and instances (in-place delete)
+ */
+static int evaluate_macro(snd_use_case_mgr_t *uc_mgr,
+                         snd_config_t *cfg)
+{
+       snd_config_iterator_t i, i2, next, next2;
+       snd_config_t *d, *n, *n2;
+       int err, ret;
+
+       ret = evaluate_define_macro(uc_mgr, cfg);
+       if (ret < 0)
+               return ret;
+
+       err = snd_config_search(cfg, "Macro", &d);
+       if (err == -ENOENT)
+               return ret;
+       if (err < 0)
+               return err;
+
+       if (snd_config_get_type(d) != SND_CONFIG_TYPE_COMPOUND) {
+               uc_error("compound type expected for DefineMacro");
+               return -EINVAL;
+       }
+
+       if (uc_mgr->conf_format < 5) {
+               uc_error("Macro is supported in v5+ syntax");
+               return -EINVAL;
+       }
+
+       snd_config_for_each(i, next, d) {
+               n = snd_config_iterator_entry(i);
+               if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
+                       uc_error("compound type expected for DefineMacro");
+                       return -EINVAL;
+               }
+               snd_config_for_each(i2, next2, n) {
+                       n2 = snd_config_iterator_entry(i2);
+                       err = evaluate_macro1(uc_mgr, cfg, n2);
+                       if (err < 0)
+                               return err;
+               }
+       }
+
+       snd_config_delete(d);
+
+       return 0;
+}
+
 /*
  * Evaluate include (in-place)
  */
@@ -382,9 +537,15 @@ static int evaluate_condition(snd_use_case_mgr_t *uc_mgr,
 int uc_mgr_evaluate_inplace(snd_use_case_mgr_t *uc_mgr,
                            snd_config_t *cfg)
 {
-       int err1 = 0, err2 = 0, err3 = 0;
+       long iterations = 10000;
+       int err1 = 0, err2 = 0, err3 = 0, err4 = 0;
 
-       while (err1 == 0 || err2 == 0 || err3 == 0) {
+       while (err1 == 0 || err2 == 0 || err3 == 0 || err4 == 0) {
+               if (iterations == 0) {
+                       uc_error("Maximal inplace evaluation iterations number reached (recursive references?)");
+                       return -EINVAL;
+               }
+               iterations--;
                /* variables at first */
                err1 = evaluate_define(uc_mgr, cfg);
                if (err1 < 0)
@@ -393,13 +554,18 @@ int uc_mgr_evaluate_inplace(snd_use_case_mgr_t *uc_mgr,
                err2 = evaluate_include(uc_mgr, cfg);
                if (err2 < 0)
                        return err2;
-               /* include may define another variables */
+               /* include or macro may define another variables */
                /* conditions may depend on them */
                if (err2 == 0)
                        continue;
-               err3 = evaluate_condition(uc_mgr, cfg);
+               err3 = evaluate_macro(uc_mgr, cfg);
                if (err3 < 0)
                        return err3;
+               if (err3 == 0)
+                       continue;
+               err4 = evaluate_condition(uc_mgr, cfg);
+               if (err4 < 0)
+                       return err3;
        }
        return 0;
 }
@@ -2458,6 +2624,10 @@ int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr)
                goto __error;
 
        err = parse_master_file(uc_mgr, cfg);
+       if (uc_mgr->macros) {
+               snd_config_delete(uc_mgr->macros);
+               uc_mgr->macros = NULL;
+       }
        snd_config_delete(cfg);
        if (err < 0) {
                uc_mgr_free_ctl_list(uc_mgr);
index 430afa271976400add751aa1c6664346ee889ff1..090bd86c72cd99102e69e433475e52159f1b1293 100644 (file)
@@ -452,6 +452,41 @@ substrings are stored to a separate variable with the sequence number postfix.
 
 Variables can be substituted using the `${var:rval1}` reference for example.
 
+### Macros
+
+Macros were added for *Syntax* version *5*. The *DefineMacro* defines new
+macro like:
+
+~~~{.html}
+DefineMacro.macro1 {
+  Define.a "${var:macro1_arg1}"
+  Define.b "${var:macro1_other}"
+  # Device or any other block may be defined here...
+}
+~~~
+
+The arguments in the macro are refered as the variables with the macro
+name prefix and underscore (*'_'*) delimiter. The configuration block
+in the DefineMacro subtree is always evaluated (including arguments
+and variables) at the time of the instantiation.
+
+The macros can be instantiated (expanded) using:
+
+~~~{.html}
+# short version
+Macro.id1.macro1 "arg1='something 1',other='other x'"
+
+# long version
+Macro.id1.macro1 {
+  arg1 'something 1'
+  other 'other x'
+}
+~~~
+
+The second identifier (in example as *id1*) must be unique, but the contents
+is ignored. It just differentiate the items in the subtree (allowing multiple
+instances for one macro).
+
 ### Conditions
 
 The configuration tree evaluation supports the conditions - *If* blocks. Each *If* blocks
index e6ebe0f314c2bab291d26136a2a48a2234bb5061..3f59bb2c74b0ed00e770e357f8dcc2bbf8e84d7d 100644 (file)
@@ -262,6 +262,9 @@ struct snd_use_case_mgr {
        /* list of opened control devices */
        struct list_head ctl_list;
 
+       /* tree with macros */
+       snd_config_t *macros;
+
        /* local library configuration */
        snd_config_t *local_config;
 
@@ -334,6 +337,8 @@ int uc_mgr_set_variable(snd_use_case_mgr_t *uc_mgr,
                        const char *name,
                        const char *val);
 
+int uc_mgr_delete_variable(snd_use_case_mgr_t *uc_mgr, const char *name);
+
 int uc_mgr_get_substituted_value(snd_use_case_mgr_t *uc_mgr,
                                 char **_rvalue,
                                 const char *value);
index fd3dcc6d628905cbdebb57aab231e76b3d488440..8b42b889944e4f7951341e0f8e065c6b3723f73c 100644 (file)
@@ -866,6 +866,12 @@ int uc_mgr_substitute_tree(snd_use_case_mgr_t *uc_mgr, snd_config_t *node)
                }
                return 0;
        }
+       /* exception - macros are evaluated when instantied */
+       err = snd_config_get_id(node, &id);
+       if (err < 0)
+               return err;
+       if (id && strcmp(id, "DefineMacro") == 0)
+               return 0;
        snd_config_for_each(i, next, node) {
                n = snd_config_iterator_entry(i);
                err = uc_mgr_substitute_tree(uc_mgr, n);
index 185170f981d7c97567afb1df57e164a1be4e3c9e..13e084f2e4045d3a02b9c16401da66195a3ddac6 100644 (file)
@@ -400,6 +400,14 @@ int uc_mgr_config_load(int format, const char *file, snd_config_t **cfg)
        return 0;
 }
 
+static void uc_mgr_free_value1(struct ucm_value *val)
+{
+       free(val->name);
+       free(val->data);
+       list_del(&val->list);
+       free(val);
+}
+
 void uc_mgr_free_value(struct list_head *base)
 {
        struct list_head *pos, *npos;
@@ -407,10 +415,7 @@ void uc_mgr_free_value(struct list_head *base)
        
        list_for_each_safe(pos, npos, base) {
                val = list_entry(pos, struct ucm_value, list);
-               free(val->name);
-               free(val->data);
-               list_del(&val->list);
-               free(val);
+               uc_mgr_free_value1(val);
        }
 }
 
@@ -704,6 +709,22 @@ int uc_mgr_set_variable(snd_use_case_mgr_t *uc_mgr, const char *name,
        return 0;
 }
 
+int uc_mgr_delete_variable(snd_use_case_mgr_t *uc_mgr, const char *name)
+{
+       struct list_head *pos;
+       struct ucm_value *curr;
+
+       list_for_each(pos, &uc_mgr->variable_list) {
+               curr = list_entry(pos, struct ucm_value, list);
+               if (strcmp(curr->name, name) == 0) {
+                       uc_mgr_free_value1(curr);
+                       return 0;
+               }
+       }
+
+       return -ENOENT;
+}
+
 void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr)
 {
        struct list_head *pos, *npos;
@@ -745,6 +766,8 @@ void uc_mgr_free(snd_use_case_mgr_t *uc_mgr)
 {
        if (uc_mgr->local_config)
                snd_config_delete(uc_mgr->local_config);
+       if (uc_mgr->macros)
+               snd_config_delete(uc_mgr->macros);
        uc_mgr_free_verb(uc_mgr);
        uc_mgr_free_ctl_list(uc_mgr);
        free(uc_mgr->card_name);