#include #include #include #include #include #include #include static bool rtosc_match_number(const char **pattern, const char **msg) { //Verify both hold digits if(!isdigit(**pattern) || !isdigit(**msg)) return false; //Read in both numeric values unsigned max = atoi(*pattern); unsigned val = atoi(*msg); ////Advance pointers while(isdigit(**pattern))++*pattern; while(isdigit(**msg))++*msg; //Match iff msg number is strictly less than pattern return val < max; } // pattern = /previous/{A,B,C,D,E}/after // ^ // message = /previous/C/after // ^ const char *rtosc_match_options(const char *pattern, const char **msg) { const char *preserve = *msg; assert(*pattern == '{'); pattern++; retry: while(1) { //Check for special characters if(*pattern == ',' || *pattern == '}') { goto advance_until_end; } else if((*pattern == **msg)) { //verbatim compare if(**msg) ++pattern, ++*msg; else goto try_next; } else goto try_next; } advance_until_end: while(*pattern && *pattern != '}') pattern++; if(*pattern == '}') pattern++; return pattern; try_next: *msg = preserve; while(*pattern && *pattern != '}' && *pattern != ',') pattern++; if(*pattern == ',') { pattern++; goto retry; } return NULL; } const char *rtosc_match_path(const char *pattern, const char *msg, const char** path_end) { if(!path_end) path_end = &msg; // writing *path_end = msg later will have no effect while(1) { //Check for special characters if(*pattern == ':' && !*msg) return *path_end = msg, pattern; else if(*pattern == '{') { pattern = rtosc_match_options(pattern, &msg); if(!pattern) return NULL; } else if(*pattern == '*') { //advance message and pattern to '/' or ':' and '\0' while(*pattern && *pattern != '/' && *pattern != ':') pattern++; if(*pattern == '/' || *pattern == ':') while(*msg && *msg != '/') msg++; } else if(*pattern == '/' && *msg == '/') { ++pattern; ++msg; if(*pattern == '\0' || *pattern == ':') return *path_end = msg, pattern; } else if(*pattern == '#') { ++pattern; if(!rtosc_match_number(&pattern, &msg)) return NULL; } else if((*pattern == *msg)) { //verbatim compare if(*msg) ++pattern, ++msg; else return *path_end = msg, pattern; } else return NULL; } } //Match the arg string or fail static bool rtosc_match_args(const char *pattern, const char *msg) { //match anything if now arg restriction is present (ie the ':') if(*pattern++ != ':') return true; const char *arg_str = rtosc_argument_string(msg); bool arg_match = *pattern || *pattern == *arg_str; while(*pattern && *pattern != ':') arg_match &= (*pattern++==*arg_str++); if(*pattern==':') { if(arg_match && !*arg_str) return true; else return rtosc_match_args(pattern, msg); //retry } return arg_match; } bool rtosc_match(const char *pattern, const char *msg, const char** path_end) { const char *arg_pattern = rtosc_match_path(pattern, msg, path_end); if(!arg_pattern) return false; else if(*arg_pattern == ':') return rtosc_match_args(arg_pattern, msg); return true; } /* * Special characters from the specification: * ' ' space 32 * # number sign 35 * * asterisk 42 * , comma 44 * / forward slash 47 * ? question mark 63 * [ open bracket 91 * ] close bracket 93 * { open curly brace 123 * } close curly brace 125 */ #if 0 QUOTE FROM OSC 1.0 SPEC '?' in the OSC Address Pattern matches any single character '*' in the OSC Address Pattern matches any sequence of zero or more characters A string of characters in square brackets (e.g., "[string]") in the OSC Address Pattern matches any character in the string. Inside square brackets, the minus sign (-) and exclamation point (!) have special meanings: two characters separated by a minus sign indicate the range of characters between the given two in ASCII collating sequence. (A minus sign at the end of the string has no special meaning.) An exclamation point at the beginning of a bracketed string negates the sense of the list, meaning that the list matches any character not in the list. (An exclamation point anywhere besides the first character after the open bracket has no special meaning.) A comma-separated list of strings enclosed in curly braces (e.g., "{foo,bar}") in the OSC Address Pattern matches any of the strings in the list. #endif //for literal string X //for X+?+[] Y //for Y+single{} Z //for numeric string N //assume a is of the form X //assume b is a pattern of the form: //* (1) //Y (2) //Y* (3) //*X* (4) //Z (5) //Z* (6) //Y#N (7) #define RTOSC_MATCH_ALL 1 #define RTOSC_MATCH_CHAR 2 #define RTOSC_MATCH_PARTIAL_CHAR 3 #define RTOSC_MATCH_SUBSTRING 4 #define RTOSC_MATCH_OPTIONS 5 #define RTOSC_MATCH_PARTIAL_OPTIONS 6 #define RTOSC_MATCH_ENUMERATED 7 static bool is_charwise(uint8_t c) { return c<=0x7f && c != ' ' && c != '#' && c != '/' && c != '{' && c != '}'; } int rtosc_subpath_pat_type(const char *pattern) { int charwise_only = 1; const char *last_star = strrchr(pattern, '*'); const char *pound = strchr(pattern, '#'); if(!strcmp("*", pattern)) return RTOSC_MATCH_ALL; for(const char *p = pattern;*p;++p) charwise_only &= is_charwise(*p); if(charwise_only && !last_star) return RTOSC_MATCH_CHAR; if(pound) return RTOSC_MATCH_ENUMERATED; return 2; } static bool rtosc_match_char(const char **path, const char **pattern) { //printf("rtosc_match_char('%s','%s')\n", *path, *pattern); if(**path == **pattern && **path) { ++*path; ++*pattern; return true; } else if(**pattern == '?' && *path) { ++*path; ++*pattern; return true; } else if(**pattern == '[') { bool matched = false; bool negation = false; char last_range = '\0'; char to_match = **path; ++*pattern; if(**pattern == '!') { negation = true; ++*pattern; } while(**pattern && **pattern != ']') { last_range = **pattern; if(**pattern == to_match) { matched = true; } else if(**pattern == '-') {//range ++*pattern; char range_high = **pattern; if(range_high == ']' || !range_high) break; if(to_match <= range_high && to_match >= last_range) matched = true; } ++*pattern; } if(**pattern == ']') ++*pattern; ++*path; return negation ^ matched; } return false; } bool rtosc_match_partial(const char *a, const char *b) { //assume a is of the form X //assume b is a pattern of the form: (1..6) //This is done to avoid backtracking of any kind //This is an OSC serialization library, not a regex //implementation char patternbuf[256]; (void) patternbuf; int type = rtosc_subpath_pat_type(b); if(type == RTOSC_MATCH_ALL) return true; else if(type == RTOSC_MATCH_CHAR || type == RTOSC_MATCH_PARTIAL_CHAR) { while(rtosc_match_char(&a,&b)); if(!*a && !*b) return true; else if(*a && *b=='*' && b[1] == '\0') return true; else return false; } else if(type == 4) { //extract substring const char *sub=NULL; return strstr(a,sub); } else if(type == RTOSC_MATCH_OPTIONS || type == 6) { return false; } else if(type == RTOSC_MATCH_ENUMERATED) { while(rtosc_match_char(&a,&b)); if(*a && *b=='#' && b[1] != '\0') return atoi(a) < atoi(b+1); return false; } else return 0; assert(false); } int rtosc_matchable_path(const char *pattern) { (void) pattern; return 0; } int chunk_path(const char *a, int b, const char *c) { (void) a; (void) b; (void) c; return 0; } void advance_path(const char **a) { (void) a; } bool rtosc_match_full_path(const char *pattern, const char *message) { assert(false && "This API is a WIP"); char subpattern[256]; char submessage[256]; const char *p = pattern; const char *m = message; step: if(*p != *m) return 0; if(chunk_path(subpattern, sizeof(subpattern), p)) return 0; if(chunk_path(submessage, sizeof(submessage), m)) return 0; advance_path(&p); advance_path(&m); if(*p == 0 && *m == 0) return 1; else goto step; }