From ee055791d1430187ec175c3f065398460a5acf6b Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 9 Aug 2020 14:16:03 +0200 Subject: Implement list-type multichannel specification (Fixes #67) --- README.md | 19 +++++++---- config.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++----------- config.h | 9 +++++ 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 `{..}`, 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 `{..}`, 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,3 +1,11 @@ +/* + * Channel glob type + */ +enum /*_mm_channel_glob_type */ { + glob_range, + glob_list +}; + /* * Channel specification 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; -- cgit v1.2.3