aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2020-08-09 14:16:03 +0200
committercbdev <cb@cbcdn.com>2020-08-09 14:16:03 +0200
commitee055791d1430187ec175c3f065398460a5acf6b (patch)
tree29796c9de60e440e3d29333d68e4f7370a1c6e5a
parent7a00b8fda337ad38cfba4689dd5fc07686783158 (diff)
downloadmidimonster-ee055791d1430187ec175c3f065398460a5acf6b.tar.gz
midimonster-ee055791d1430187ec175c3f065398460a5acf6b.tar.bz2
midimonster-ee055791d1430187ec175c3f065398460a5acf6b.zip
Implement list-type multichannel specification (Fixes #67)
-rw-r--r--README.md19
-rw-r--r--config.c114
-rw-r--r--config.h9
3 files changed, 116 insertions, 26 deletions
diff --git a/README.md b/README.md
index 161fcaa..ea079bf 100644
--- a/README.md
+++ b/README.md
@@ -122,18 +122,25 @@ The last line is a shorter way to create a bi-directional mapping.
### Multi-channel mapping
-To make mapping large contiguous sets of channels easier, channel names may contain
-expressions of the form `{<start>..<end>}`, with *start* and *end* being positive integers
-delimiting a range of channels. Multiple such expressions may be used in one channel
-specification, with the rightmost expression being incremented (or decremented) first for
-evaluation.
+To make mapping large contiguous sets of channels easier, channel names may contain certain
+types of expressions specifying multiple channels at once.
+
+Expressions of the form `{<start>..<end>}`, with *start* and *end* being positive integers,
+expand to a range of channels, with the expression replaced by the incrementing or decrementing
+value.
+
+Expressions of the form `{value1,value2,value3}` (with any number of values separated by commas)
+are replaced with each of the specified values in sequence.
+
+Multiple such expressions may be used in one channel specification, with the rightmost expression
+being evaluated first.
Both sides of a multi-channel assignment need to have the same number of channels, or one
side must have exactly one channel.
Example multi-channel mapping:
```
-instance-a.channel{1..10} > instance-b.{10..1}
+instance-a.channel{1..5} > instance-b.{1,2,3,4,5}
```
## Backend documentation
diff --git a/config.c b/config.c
index b10740c..b939f4e 100644
--- a/config.c
+++ b/config.c
@@ -7,6 +7,7 @@
#endif
#define BACKEND_NAME "core/cfg"
+#define DEBUG
#include "midimonster.h"
#include "config.h"
#include "backend.h"
@@ -99,9 +100,10 @@ static char* config_trim_line(char* in){
return in;
}
-static int config_glob_parse(channel_glob* glob, char* spec, size_t length){
- char* parse_offset = NULL;
+static int config_glob_parse_range(channel_glob* glob, char* spec, size_t length){
//FIXME might want to allow negative delimiters at some point
+ char* parse_offset = NULL;
+ glob->type = glob_range;
//first interval member
glob->limits.u64[0] = strtoul(spec, &parse_offset, 10);
@@ -130,6 +132,39 @@ static int config_glob_parse(channel_glob* glob, char* spec, size_t length){
return 0;
}
+static int config_glob_parse_list(channel_glob* glob, char* spec, size_t length){
+ size_t u = 0;
+ glob->type = glob_list;
+ glob->values = 1;
+
+ //count number of values in list
+ for(u = 0; u < length; u++){
+ if(spec[u] == ','){
+ glob->values++;
+ }
+ }
+ return 0;
+}
+
+static int config_glob_parse(channel_glob* glob, char* spec, size_t length){
+ size_t u = 0;
+
+ //detect glob type
+ for(u = 0; u < length; u++){
+ if(length - u > 2 && !strncmp(spec + u, "..", 2)){
+ DBGPF("Detected glob %.*s as range type", (int) length, spec);
+ return config_glob_parse_range(glob, spec, length);
+ }
+ else if(spec[u] == ','){
+ DBGPF("Detected glob %.*s as list type", (int) length, spec);
+ return config_glob_parse_list(glob, spec, length);
+ }
+ }
+
+ LOGPF("Failed to detect glob type for spec %.*s", (int) length, spec);
+ return 1;
+}
+
static int config_glob_scan(instance* inst, channel_spec* spec){
char* glob_start = spec->spec, *glob_end = NULL;
size_t u;
@@ -182,50 +217,89 @@ static int config_glob_scan(instance* inst, channel_spec* spec){
return 0;
}
+static ssize_t config_glob_resolve_range(char* spec, size_t length, channel_glob* glob, uint64_t n){
+ uint64_t current_value = glob->limits.u64[0] + (n % glob->values);
+ //if counting down
+ if(glob->limits.u64[0] > glob->limits.u64[1]){
+ current_value = glob->limits.u64[0] - (n % glob->values);
+ }
+
+ //write out value
+ return snprintf(spec, length, "%" PRIu64, current_value);
+}
+
+static ssize_t config_glob_resolve_list(char* spec, size_t length, channel_glob* glob, uint64_t n){
+ uint64_t current_replacement = 0;
+ size_t replacement_length = 0;
+ char* source = spec + 1;
+ n %= glob->values;
+
+ //find start of replacement value
+ DBGPF("Searching instance %" PRIu64 " of spec %.*s", n, (int) length, spec);
+ for(current_replacement = 0; current_replacement < n; current_replacement++){
+ for(; source[0] != ','; source++){
+ }
+ source++;
+ }
+
+ //calculate replacement length
+ for(; source[replacement_length] != ',' && source[replacement_length] != '}'; replacement_length++){
+ }
+
+ //write out new value
+ memmove(spec, source, replacement_length);
+ return replacement_length;
+}
+
static channel* config_glob_resolve(instance* inst, channel_spec* spec, uint64_t n, uint8_t map_direction){
size_t glob = 0, glob_length;
ssize_t bytes = 0;
- uint64_t current_value = 0;
channel* result = NULL;
char* resolved_spec = strdup(spec->spec);
if(!resolved_spec){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
return NULL;
}
//TODO if not internal, try to resolve externally
-
//iterate and resolve globs
for(glob = spec->globs; glob > 0; glob--){
- current_value = spec->glob[glob - 1].limits.u64[0] + (n % spec->glob[glob - 1].values);
- if(spec->glob[glob - 1].limits.u64[0] > spec->glob[glob - 1].limits.u64[1]){
- current_value = spec->glob[glob - 1].limits.u64[0] - (n % spec->glob[glob - 1].values);
- }
glob_length = spec->glob[glob - 1].offset[1] - spec->glob[glob - 1].offset[0];
- n /= spec->glob[glob - 1].values;
- //write out value
- bytes = snprintf(resolved_spec + spec->glob[glob - 1].offset[0],
- glob_length,
- "%" PRIu64,
- current_value);
- if(bytes > glob_length){
- fprintf(stderr, "Internal error resolving glob %s\n", spec->spec);
- goto bail;
+ switch(spec->glob[glob - 1].type){
+ case glob_range:
+ bytes = config_glob_resolve_range(resolved_spec + spec->glob[glob - 1].offset[0],
+ glob_length,
+ spec->glob + (glob - 1),
+ n);
+ break;
+ case glob_list:
+ bytes = config_glob_resolve_list(resolved_spec + spec->glob[glob - 1].offset[0],
+ glob_length,
+ spec->glob + (glob - 1),
+ n);
+ break;
}
+ n /= spec->glob[glob - 1].values;
+
//move trailing data
- if(bytes < glob_length){
+ if(bytes > 0 && bytes < glob_length){
memmove(resolved_spec + spec->glob[glob - 1].offset[0] + bytes,
resolved_spec + spec->glob[glob - 1].offset[1] + 1,
strlen(spec->spec) - spec->glob[glob - 1].offset[1]);
}
+ else{
+ LOGPF("Failure parsing glob spec %s", resolved_spec);
+ goto bail;
+ }
}
+ DBGPF("Resolved spec %s to %s", spec->spec, resolved_spec);
result = inst->backend->channel(inst, resolved_spec, map_direction);
if(spec->globs && !result){
- fprintf(stderr, "Failed to match multichannel evaluation %s to a channel\n", resolved_spec);
+ LOGPF("Failed to match multichannel evaluation %s to a channel", resolved_spec);
}
bail:
diff --git a/config.h b/config.h
index d15aed2..b96a866 100644
--- a/config.h
+++ b/config.h
@@ -1,4 +1,12 @@
/*
+ * Channel glob type
+ */
+enum /*_mm_channel_glob_type */ {
+ glob_range,
+ glob_list
+};
+
+/*
* Channel specification glob
*/
typedef struct /*_mm_channel_glob*/ {
@@ -7,6 +15,7 @@ typedef struct /*_mm_channel_glob*/ {
void* impl;
uint64_t u64[2];
} limits;
+ uint8_t type;
uint64_t values;
} channel_glob;