#include "../ports.h" #include #include #include #include #include using namespace rtosc; static inline void scat(char *dest, const char *src) { while(*dest) dest++; if(*dest) dest++; while(*src && *src!=':') *dest++ = *src++; *dest = 0; } RtData::RtData(void) :loc(NULL), loc_size(0), obj(NULL), matches(0), message(NULL) {} void RtData::reply(const char *path, const char *args, ...) { va_list va; va_start(va,args); char buffer[1024]; rtosc_vmessage(buffer,1024,path,args,va); reply(buffer); va_end(va); }; void RtData::reply(const char *msg) {(void)msg;}; void RtData::broadcast(const char *path, const char *args, ...) { va_list va; va_start(va,args); char buffer[1024]; rtosc_vmessage(buffer,1024,path,args,va); broadcast(buffer); va_end(va); } void RtData::broadcast(const char *msg) {reply(msg);}; void RtData::forward(const char *rational) {} void metaiterator_advance(const char *&title, const char *&value) { if(!title || !*title) { value = NULL; return; } //Try to find "\0=" after title string value = title; while(*value) ++value; if(*++value != '=') value = NULL; else value++; } Port::MetaIterator::MetaIterator(const char *str) :title(str), value(NULL) { metaiterator_advance(title, value); } Port::MetaIterator& Port::MetaIterator::operator++(void) { if(!title || !*title) { title = NULL; return *this; } //search for next parameter start //aka "\0:" unless "\0\0" is seen char prev = 0; while(prev || (*title && *title != ':')) prev = *title++; if(!*title) title = NULL; else ++title; metaiterator_advance(title, value); return *this; } Port::MetaContainer::MetaContainer(const char *str_) :str_ptr(str_) {} Port::MetaIterator Port::MetaContainer::begin(void) const { if(str_ptr && *str_ptr == ':') return Port::MetaIterator(str_ptr+1); else return Port::MetaIterator(str_ptr); } Port::MetaIterator Port::MetaContainer::end(void) const { return MetaIterator(NULL); } Port::MetaIterator Port::MetaContainer::find(const char *str) const { for(const auto x : *this) if(!strcmp(x.title, str)) return x; return NULL; } size_t Port::MetaContainer::length(void) const { if(!str_ptr || !*str_ptr) return 0; char prev = 0; const char *itr = str_ptr; while(prev || *itr) prev = *itr++; return 2+(itr-str_ptr); } const char *Port::MetaContainer::operator[](const char *str) const { for(const auto x : *this) if(!strcmp(x.title, str)) return x.value; return NULL; } //Match the arg string or fail inline bool arg_matcher(const char *pattern, const char *args) { //match anything if now arg restriction is present (ie the ':') if(*pattern++ != ':') return true; const char *arg_str = args; 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 arg_matcher(pattern, args); //retry } return arg_match; } inline bool scmp(const char *a, const char *b) { while(*a && *a == *b) a++, b++; return a[0] == b[0]; } typedef std::vector words_t; typedef std::vector svec_t; typedef std::vector cvec_t; typedef std::vector ivec_t; typedef std::vector tuple_t; typedef std::vector tvec_t; namespace rtosc{ class Port_Matcher { public: bool *enump; svec_t fixed; cvec_t arg_spec; ivec_t pos; ivec_t assoc; ivec_t remap; 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 hard_match(int i, const char *msg) { if(strncmp(msg, fixed[i].c_str(), fixed[i].length())) return false; if(arg_spec[i]) return rtosc_match_args(arg_spec[i], msg); else return true; } }; } tvec_t do_hash(const words_t &strs, const ivec_t &pos) { tvec_t tvec; for(auto &s:strs) { tuple_t tuple; tuple.push_back(s.length()); for(const auto &p:pos) if(p < (int)s.size()) tuple.push_back(s[p]); tvec.push_back(std::move(tuple)); } return tvec; } template int count_dups(std::vector &t) { int dups = 0; int N = t.size(); bool mark[t.size()]; memset(mark, 0, N); for(int i=0; i bool has(T &t, Z&z) { for(auto tt:t) if(tt==z) return true; return false; } static int int_max(int a, int b) { return a= current_dups) break; current_dups = pos_best_val; pos.push_back(pos_best); } auto hashed = do_hash(strs, pos); int d = count_dups(hashed); //printf("Total Dups: %d\n", d); if(d != 0) pos.clear(); return pos; } static ivec_t do_hash(const words_t &strs, const ivec_t &pos, const ivec_t &assoc) { ivec_t ivec; ivec.reserve(strs.size()); for(auto &s:strs) { int t = s.length(); for(auto p:pos) if(p < (int)s.size()) t += assoc[s[p]]; ivec.push_back(t); } return ivec; } static ivec_t find_assoc(const words_t &strs, const ivec_t &pos) { ivec_t assoc; int current_dups = strs.size(); int N = 127; std::vector useful_chars; for(auto w:strs) for(auto c:w) if(!has(useful_chars, c)) useful_chars.push_back(c); for(int i=0; i= current_dups) break; current_dups = assoc_best_val; } auto hashed = do_hash(strs, pos, assoc); //int d = count_dups(hashed); //printf("Total Dups Assoc: %d\n", d); return assoc; } static ivec_t find_remap(words_t &strs, ivec_t &pos, ivec_t &assoc) { ivec_t remap; auto hashed = do_hash(strs, pos, assoc); //for(int i=0; i str, Port_Matcher &pm) { if(str.empty()) return; pm.pos = find_pos(str); if(pm.pos.empty()) { fprintf(stderr, "rtosc: Failed to generate minimal hash\n"); return; } pm.assoc = find_assoc(str, pm.pos); pm.remap = find_remap(str, pm.pos, pm.assoc); } static void generate_minimal_hash(Ports &p, Port_Matcher &pm) { svec_t keys; cvec_t args; bool enump = false; for(unsigned i=0; i 0) { arg = p.ports[i].name+idx; tmp = tmp.substr(0,idx); } keys.push_back(tmp); args.push_back(arg); } pm.fixed = keys; pm.arg_spec = args; generate_minimal_hash(keys, pm); } Ports::Ports(std::initializer_list l) :ports(l), impl(NULL) { refreshMagic(); } Ports::~Ports() { delete []impl->enump; delete impl; } #if !defined(__GNUC__) #define __builtin_expect(a,b) a #endif void Ports::dispatch(const char *m, rtosc::RtData &d, bool base_dispatch) const { void *obj = d.obj; //handle the first dispatch layer if(base_dispatch) { d.matches = 0; d.message = m; if(m && *m == '/') m++; if(d.loc) d.loc[0] = 0; } //simple case if(!d.loc || !d.loc_size) { for(const Port &port: ports) { if(rtosc_match(port.name,m)) d.port = &port, port.cb(m,d), d.obj = obj; } } else { //TODO this function is certainly buggy at the moment, some tests //are needed to make it clean //XXX buffer_size is not properly handled yet if(__builtin_expect(d.loc[0] == 0, 0)) { memset(d.loc, 0, d.loc_size); d.loc[0] = '/'; } char *old_end = d.loc; while(*old_end) ++old_end; if(impl->pos.empty()) { //No perfect minimal hash function for(unsigned i=0; ipos) if(p < (int)len) t += impl->assoc[m[p]]; if(t >= (int)impl->remap.size() && !default_handler) return; else if(t >= (int)impl->remap.size() && default_handler) { d.matches++; default_handler(m,d), d.obj = obj; return; } int port_num = impl->remap[t]; //Verify the chosen port is correct if(__builtin_expect(impl->hard_match(port_num, m), 1)) { const Port &port = ports[impl->remap[t]]; if(!port.ports) d.matches++; //Append the path if(impl->enump[port_num]) { const char *msg = m; char *pos = old_end; while(*msg && *msg != '/') *pos++ = *msg++; if(strchr(port.name, '/')) *pos++ = '/'; *pos = '\0'; } else memcpy(old_end, impl->fixed[port_num].c_str(), impl->fixed[port_num].length()+1); d.port = &port; //Apply callback port.cb(m,d), d.obj = obj; //Remove the rest of the path old_end[0] = '\0'; } else if(default_handler) { d.matches++; default_handler(m,d), d.obj = obj; } } } } const Port *Ports::operator[](const char *name) const { for(const Port &port:ports) { const char *_needle = name, *_haystack = port.name; while(*_needle && *_needle==*_haystack)_needle++,_haystack++; if(*_needle == 0 && (*_haystack == ':' || *_haystack == '\0')) { return &port; } } return NULL; } static msg_t snip(msg_t m) { while(*m && *m != '/') ++m; return m+1; } const Port *Ports::apropos(const char *path) const { if(path && path[0] == '/') ++path; for(const Port &port: ports) if(strchr(port.name,'/') && rtosc_match_path(port.name,path)) return (strchr(path,'/')[1]==0) ? &port : port.ports->apropos(snip(path)); //This is the lowest level, now find the best port for(const Port &port: ports) if(*path && (strstr(port.name, path)==port.name || rtosc_match_path(port.name, path))) return &port; return NULL; } static bool parent_path_p(char *read, char *start) { if(read-start<2) return false; return read[0]=='.' && read[-1]=='.' && read[-2]=='/'; } static void read_path(char *&r, char *start) { while(1) { if(r= p) { //per path chunk either //(1) find a parent ref and inc consuming //(2) find a normal ref and consume //(3) find a normal ref and write through bool ppath = parent_path_p(read_pos, p); if(ppath) { read_path(read_pos, p); consuming++; } else if(consuming) { read_path(read_pos, p); consuming--; } else move_path(read_pos, write_pos, p); } //return last written location, not next to write return write_pos+1; }; void Ports::refreshMagic() { delete impl; impl = new Port_Matcher; generate_minimal_hash(*this, *impl); impl->enump = new bool[ports.size()]; for(int i=0; i<(int)ports.size(); ++i) impl->enump[i] = strchr(ports[i].name, '#'); elms = ports.size(); } ClonePorts::ClonePorts(const Ports &ports_, std::initializer_list c) :Ports({}) { for(auto &to_clone:c) { const Port *clone_port = NULL; for(auto &p:ports_.ports) if(!strcmp(p.name, to_clone.name)) clone_port = &p; if(!clone_port && strcmp("*", to_clone.name)) { fprintf(stderr, "Cannot find a clone port for '%s'\n",to_clone.name); assert(false); } if(clone_port) { ports.push_back({clone_port->name, clone_port->metadata, clone_port->ports, to_clone.cb}); } else { default_handler = to_clone.cb; } } refreshMagic(); } MergePorts::MergePorts(std::initializer_list c) :Ports({}) { //XXX TODO remove duplicates in some sane and documented way //e.g. repeated ports override and remove older ones for(auto *to_clone:c) { assert(to_clone); for(auto &p:to_clone->ports) { bool already_there = false; for(auto &pp:ports) if(!strcmp(pp.name, p.name)) already_there = true; if(!already_there) ports.push_back(p); } } refreshMagic(); } void rtosc::walk_ports(const Ports *base, char *name_buffer, size_t buffer_size, void *data, port_walker_t walker) { assert(name_buffer); //XXX buffer_size is not properly handled yet if(name_buffer[0] == 0) name_buffer[0] = '/'; char *old_end = name_buffer; while(*old_end) ++old_end; for(const Port &p: *base) { if(strchr(p.name, '/')) {//it is another tree if(strchr(p.name,'#')) { const char *name = p.name; char *pos = old_end; while(*name != '#') *pos++ = *name++; const unsigned max = atoi(name+1); for(unsigned i=0; iatoi(m.title+4) ? atoi(m.title+4) : min; return min; } static int enum_max(Port::MetaContainer meta) { int max = 0; for(auto m:meta) if(strstr(m.title, "map ")) max = atoi(m.title+4); for(auto m:meta) if(strstr(m.title, "map ")) max = max\n", m.title); if(!has_options) return o; o << " \n"; for(auto m:meta) { if(strstr(m.title, "map ")) { o << " " << m.value << "\n"; } } o << " \n"; return o; } static ostream &dump_t_f_port(ostream &o, string name, string doc) { o << " \n"; o << " Enable " << doc << "\n"; o << " \n"; o << " \n"; o << " \n"; o << " Disable " << doc << "\n"; o << " \n"; o << " \n"; o << " \n"; o << " Get state of " << doc << "\n"; o << " \n"; o << " \n"; o << " Value of " << doc << "\n"; o << " "; o << " \n"; o << " \n"; o << " Value of " << doc << "\n"; o << " "; o << " \n"; return o; } static ostream &dump_any_port(ostream &o, string name, string doc) { o << " \n"; o << " " << doc << "\n"; o << " \n"; return o; } static ostream &dump_generic_port(ostream &o, string name, string doc, string type) { const char *t = type.c_str(); string arg_names = "xyzabcdefghijklmnopqrstuvw"; //start out with argument separator if(*t++ != ':') return o; //now real arguments (assume [] don't exist) string args; while(*t && *t != ':') args += *t++; o << " \n"; o << " " << doc << "\n"; assert(args.length()\n"; o << " \n"; if(*t == ':') return dump_generic_port(o, name, doc, t); else return o; } void dump_ports_cb(const rtosc::Port *p, const char *name, void *v) { std::ostream &o = *(std::ostream*)v; auto meta = p->meta(); const char *args = strchr(p->name, ':'); auto mparameter = meta.find("parameter"); auto mdoc = meta.find("documentation"); string doc; if(mdoc != p->meta().end()) doc = mdoc.value; if(meta.find("internal") != meta.end()) { doc += "[INTERNAL]"; } if(mparameter != p->meta().end()) { char type = 0; if(args) { if(strchr(args, 'f')) type = 'f'; else if(strchr(args, 'i')) type = 'i'; else if(strchr(args, 'c')) type = 'c'; else if(strchr(args, 'T')) type = 't'; else if(strchr(args, 's')) type = 's'; } if(!type) { fprintf(stderr, "rtosc port dumper: Cannot handle '%s'\n", name); fprintf(stderr, " args = <%s>\n", args); return; } if(type == 't') { dump_t_f_port(o, name, doc); return; } o << " \n"; o << " Set Value of " << doc << "\n"; if(meta.find("min") != meta.end() && meta.find("max") != meta.end() && type != 'c') { o << " \n"; o << " \n"; o << " "; } else if(meta.find("enumerated") != meta.end()) { o << " \n"; o << " \n"; add_options(o, meta); o << " \n"; o << " \n"; } else { o << " \n"; } o << " \n"; o << " \n"; o << " Get Value of " << doc << "\n"; o << " \n"; o << " \n"; o << " Value of " << doc << "\n"; if(meta.find("min") != meta.end() && meta.find("max") != meta.end() && type != 'c') { o << " \n"; o << " \n"; o << " \n"; } else if(meta.find("enumerated") != meta.end()) { o << " \n"; o << " \n"; add_options(o, meta); o << " \n"; o << " \n"; } else { o << " \n"; } o << " \n"; } else if(mdoc != meta.end() && (!args || args == std::string(""))) { dump_any_port(o, name, doc); } else if(mdoc != meta.end() && args) { dump_generic_port(o, name, doc, args); } else if(mdoc != meta.end()) { fprintf(stderr, "Skipping \"%s\"\n", name); if(args) { fprintf(stderr, " type = %s\n", args); } } else fprintf(stderr, "Skipping [UNDOCUMENTED] \"%s\"\n", name); } std::ostream &rtosc::operator<<(std::ostream &o, rtosc::OscDocFormatter &formatter) { o << "\n"; o << "\n"; o << " \n"; o << " " << formatter.prog_name << "\n"; o << " " << formatter.uri << "\n"; o << " " << formatter.doc_origin << "\n"; o << " " << formatter.author_first; o << "" << formatter.author_last << "\n"; o << " \n"; char buffer[1024]; memset(buffer, 0, sizeof(buffer)); walk_ports2(formatter.p, buffer, 1024, &o, dump_ports_cb); o << "\n"; return o; }