#include #include #include #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; }