Abbreviated multi-part command recognition C library
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.
prefix_match/prefix_match.c

170 lines
5.5 KiB

5 years ago
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include "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 *word, const char *delims) {
char d = 0;
const char *dp = delims;
size_t word_len = 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 *sentence, const char *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;
}
int pm_multipart_test(const char *a, const char* b, const char *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(a, b)) || (!case_sensitive && 0 == strcasecmp(a, b))) {
return 1; // full match
}
const char *word_a = a;
const char *word_b = b;
size_t word_a_len = 0;
size_t word_b_len = 0;
while (1) {
word_a += word_a_len;
word_b += word_b_len;
// advance past leading delims, if any
while (*word_a != '\0' && NULL != strchr(delims, *word_a)) word_a++;
while (*word_b != '\0' && NULL != strchr(delims, *word_b)) word_b++;
// test for terminator
if (*word_a == '\0' && *word_b == '\0') {
// both ended at the same number of words
return 1; // full match
}
if (*word_a == '\0' || *word_b == '\0') {
// sentences ended at different length
if (0 != (flags & PREFIXMATCH_MULTI_PARTIAL) && *word_b != '\0') { // word prefix match (a is a prefix of b)
return 2; // partial word match
} else {
return 0; // no match
}
}
// find end of the words
word_a_len = pm_word_len(word_a, delims);
word_b_len = pm_word_len(word_b, delims);
if (word_a_len > word_b_len || (!can_abbrev && word_a_len != word_b_len)) {
return 0; // no match
}
int cmp = ncmpfn(word_a, word_b, word_a_len);
if (0 != cmp) { // words differ
return 0; // no match
}
}
}
int prefix_multipart_match(const char *value, const char **options, const char* 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;
int result_partial_nwords = 0;
while (NULL != (option = options[counter])) {
if (pm_multipart_test(value, option, delims, flags | PREFIXMATCH_NOABBREV)) {
return counter; // full exact match
} else if (can_abbrev) {
// Test for partial match
if (pm_multipart_test(value, option, delims, flags)) {
if (result == -1) {
result = counter; // first partial match in all words
} else {
return -1;
}
} else if (multi_partial && 2 == pm_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;
}