forked from electro/esp-irblaster
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
6.4 KiB
197 lines
6.4 KiB
3 years ago
|
#include <stddef.h>
|
||
|
#include <stdbool.h>
|
||
|
#include <string.h>
|
||
|
#include "console/prefix_match.h"
|
||
|
|
||
|
int prefix_match(const char *value, const char **options, int flags) {
|
||
|
flags &= ~PREFIXMATCH_MULTI_PARTIAL; // this doesn't make sense here
|
||
|
bool case_sensitive = PREFIXMATCH_CASE_SENSITIVE == (flags & PREFIXMATCH_CASE_SENSITIVE);
|
||
|
bool can_abbrev = 0 == (flags & PREFIXMATCH_NOABBREV);
|
||
|
|
||
|
int (*cmpfn) (const char *, const char *) = case_sensitive ? strcmp : strcasecmp;
|
||
|
int (*ncmpfn) (const char *, const char *, size_t) = case_sensitive ? strncmp : strncasecmp;
|
||
|
|
||
|
if (!value || !options) return -1;
|
||
|
size_t input_len = strlen(value);
|
||
|
const char *option = NULL;
|
||
|
int counter = 0;
|
||
|
int result = -1;
|
||
|
while (NULL != (option = options[counter])) {
|
||
|
if (cmpfn(option, value) == 0) {
|
||
|
return counter; // full exact match
|
||
|
} else {
|
||
|
// Test for partial match
|
||
|
if (can_abbrev && ncmpfn(value, option, input_len) == 0) {
|
||
|
if (result == -1) {
|
||
|
result = counter; // first partial match
|
||
|
} else {
|
||
|
// ambiguous match
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
counter++;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
size_t pm_word_len(const char * restrict word, const char * restrict delims) {
|
||
|
char d;
|
||
|
const char *dp = delims;
|
||
|
size_t word_len = 0;
|
||
|
if (!word || !delims) return 0;
|
||
|
while ('\0' != (d = *dp++)) {
|
||
|
char *end = strchr(word, d);
|
||
|
if (NULL == end) continue;
|
||
|
size_t len = end - word;
|
||
|
if (!word_len || len < word_len) {
|
||
|
word_len = len;
|
||
|
}
|
||
|
}
|
||
|
if (!word_len) {
|
||
|
word_len = strlen(word);
|
||
|
}
|
||
|
return word_len;
|
||
|
}
|
||
|
|
||
|
size_t pm_count_words(const char * restrict sentence, const char * restrict delims) {
|
||
|
char c;
|
||
|
size_t n = 0;
|
||
|
bool in_word = false;
|
||
|
if (!sentence || !delims) return 0;
|
||
|
while (0 != (c = *sentence++)) {
|
||
|
bool is_delim = NULL != strchr(delims, c);
|
||
|
if (is_delim && in_word) {
|
||
|
in_word = false;
|
||
|
} else if (!in_word && !is_delim) {
|
||
|
n++;
|
||
|
in_word = true;
|
||
|
}
|
||
|
}
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
const char *pm_skip_words(const char * restrict sentence, const char * restrict delims, size_t skip) {
|
||
|
char c;
|
||
|
size_t n = 0;
|
||
|
bool in_word = false;
|
||
|
if (!sentence || !delims) return NULL;
|
||
|
while (0 != (c = *sentence++)) {
|
||
|
bool is_delim = NULL != strchr(delims, c);
|
||
|
if (is_delim && in_word) {
|
||
|
in_word = false;
|
||
|
skip--;
|
||
|
if (skip == 0) {
|
||
|
return sentence - 1;
|
||
|
}
|
||
|
} else if (!in_word && !is_delim) {
|
||
|
n++;
|
||
|
in_word = true;
|
||
|
}
|
||
|
}
|
||
|
return sentence - 1;
|
||
|
}
|
||
|
|
||
|
enum pm_test_result prefix_multipart_test(
|
||
|
const char * restrict tested,
|
||
|
const char* restrict full,
|
||
|
const char * restrict delims,
|
||
|
int flags
|
||
|
) {
|
||
|
bool case_sensitive = PREFIXMATCH_CASE_SENSITIVE == (flags & PREFIXMATCH_CASE_SENSITIVE);
|
||
|
bool can_abbrev = 0 == (flags & PREFIXMATCH_NOABBREV);
|
||
|
|
||
|
int (*ncmpfn) (const char *, const char *, size_t) = case_sensitive ? strncmp : strncasecmp;
|
||
|
// lazy shortcut first...
|
||
|
if ((case_sensitive && 0 == strcmp(tested, full)) || (!case_sensitive && 0 == strcasecmp(tested, full))) {
|
||
|
return PM_TEST_MATCH; // full match
|
||
|
}
|
||
|
|
||
|
const char *word_t = tested;
|
||
|
const char *word_f = full;
|
||
|
size_t word_t_len = 0;
|
||
|
size_t word_f_len = 0;
|
||
|
while (1) {
|
||
|
word_t += word_t_len;
|
||
|
word_f += word_f_len;
|
||
|
|
||
|
// advance past leading delims, if any
|
||
|
while (*word_t != '\0' && NULL != strchr(delims, *word_t)) word_t++;
|
||
|
while (*word_f != '\0' && NULL != strchr(delims, *word_f)) word_f++;
|
||
|
|
||
|
// test for terminator
|
||
|
if (*word_t == '\0' && *word_f == '\0') {
|
||
|
// both ended at the same number of words
|
||
|
return PM_TEST_MATCH; // full match
|
||
|
}
|
||
|
|
||
|
if (*word_t == '\0' || *word_f == '\0') {
|
||
|
// sentences ended at different length
|
||
|
if (0 != (flags & PREFIXMATCH_MULTI_PARTIAL) && *word_f != '\0') { // word prefix match (a is a prefix of b)
|
||
|
return PM_TEST_MATCH_MULTI_PARTIAL;
|
||
|
} else {
|
||
|
return PM_TEST_NO_MATCH;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// find end of the words
|
||
|
word_t_len = pm_word_len(word_t, delims);
|
||
|
word_f_len = pm_word_len(word_f, delims);
|
||
|
|
||
|
if (word_t_len > word_f_len || (!can_abbrev && word_t_len != word_f_len)) {
|
||
|
return PM_TEST_NO_MATCH;
|
||
|
}
|
||
|
|
||
|
int cmp = ncmpfn(word_t, word_f, word_t_len);
|
||
|
if (0 != cmp) { // words differ
|
||
|
return PM_TEST_NO_MATCH;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int prefix_multipart_match(const char * restrict value, const char **options, const char * restrict delims, int flags) {
|
||
|
bool multi_partial = 0 != (flags & PREFIXMATCH_MULTI_PARTIAL);
|
||
|
flags &= ~PREFIXMATCH_MULTI_PARTIAL; // turn it off for passing the to test fn
|
||
|
bool can_abbrev = 0 == (flags & PREFIXMATCH_NOABBREV);
|
||
|
|
||
|
if (!value || !options) return -1;
|
||
|
const char *option = NULL;
|
||
|
int counter = 0;
|
||
|
int result = -1;
|
||
|
int result_partial = -1; // -1=none yet, -2=ambiguous multi-word partial, >=0=index
|
||
|
int result_partial_nwords = 0;
|
||
|
while (NULL != (option = options[counter])) {
|
||
|
if (PM_TEST_MATCH == prefix_multipart_test(value, option, delims, flags | PREFIXMATCH_NOABBREV)) {
|
||
|
return counter; // full exact match
|
||
|
} else if (can_abbrev) {
|
||
|
// Test for partial match
|
||
|
if (PM_TEST_MATCH == prefix_multipart_test(value, option, delims, flags)) {
|
||
|
if (result == -1) {
|
||
|
result = counter; // first partial match in all words
|
||
|
} else {
|
||
|
return -1;
|
||
|
}
|
||
|
} else if (multi_partial && PM_TEST_MATCH_MULTI_PARTIAL == prefix_multipart_test(value, option, delims, flags | PREFIXMATCH_MULTI_PARTIAL)) {
|
||
|
int nwords = pm_count_words(option, delims);
|
||
|
if (result_partial == -1 || result_partial_nwords < nwords) {
|
||
|
result_partial = counter; // first partial match
|
||
|
result_partial_nwords = nwords;
|
||
|
} else {
|
||
|
result_partial = -2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
counter++;
|
||
|
}
|
||
|
|
||
|
if (result != -1) {
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
if (result_partial >= 0) {
|
||
|
return result_partial;
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|