1 #include "../util.h"
2 #include "../../include/rtosc/ports.h"
3 #include "../../include/rtosc/ports-runtime.h"
4 #include "../../include/rtosc/bundle-foreach.h"
5 
6 #include <ostream>
7 #include <cassert>
8 #include <limits>
9 #include <cstring>
10 #include <set>
11 #include <string>
12 #include <algorithm>
13 
14 /* Compatibility with non-clang compilers */
15 #ifndef __has_feature
16 # define __has_feature(x) 0
17 #endif
18 #ifndef __has_extension
19 # define __has_extension __has_feature
20 #endif
21 
22 /* Check for C++11 support */
23 #if defined(HAVE_CPP11_SUPPORT)
24 # if HAVE_CPP11_SUPPORT
25 #  define DISTRHO_PROPER_CPP11_SUPPORT
26 # endif
27 #elif __cplusplus >= 201103L || (defined(__GNUC__) && defined(__GXX_EXPERIMENTAL_CXX0X__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 405) || __has_extension(cxx_noexcept)
28 # define DISTRHO_PROPER_CPP11_SUPPORT
29 # if (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) < 407 && ! defined(__clang__)) || (defined(__clang__) && ! __has_extension(cxx_override_control))
30 #  define override // gcc4.7+ only
31 #  define final    // gcc4.7+ only
32 # endif
33 #endif
34 
35 using namespace rtosc;
36 
scat(char * dest,const char * src)37 static inline void scat(char *dest, const char *src)
38 {
39     while(*dest) dest++;
40     while(*src && *src!=':') *dest++ = *src++;
41     *dest = 0;
42 }
43 
RtData(void)44 RtData::RtData(void)
45     :loc(NULL), loc_size(0), obj(NULL), matches(0), message(NULL)
46 {
47     for(size_t i=0; i<sizeof(idx)/sizeof(int); ++i)
48         idx[i] = 0;
49 }
50 
push_index(int ind)51 void RtData::push_index(int ind)
52 {
53     for(size_t i=1; i<sizeof(idx)/sizeof(int); ++i)
54         idx[i] = idx[i-1];
55     idx[0] = ind;
56 }
57 
pop_index(void)58 void RtData::pop_index(void)
59 {
60     int n = sizeof(idx)/sizeof(int);
61     for(int i=n-2; i >= 0; --i)
62         idx[i] = idx[i+1];
63     idx[n-1] = 0;
64 }
65 
replyArray(const char * path,const char * args,rtosc_arg_t * vals)66 void RtData::replyArray(const char *path, const char *args,
67         rtosc_arg_t *vals)
68 {
69     (void) path;
70     (void) args;
71     (void) vals;
72 }
reply(const char * path,const char * args,...)73 void RtData::reply(const char *path, const char *args, ...)
74 {
75     va_list va;
76     va_start(va,args);
77     char buffer[1024];
78     rtosc_vmessage(buffer,1024,path,args,va);
79     reply(buffer);
80     va_end(va);
81 }
reply(const char * msg)82 void RtData::reply(const char *msg)
83 {(void)msg;}
chain(const char * path,const char * args,...)84 void RtData::chain(const char *path, const char *args, ...)
85 {
86     (void) path;
87     (void) args;
88 }
89 
chain(const char * msg)90 void RtData::chain(const char *msg)
91 {
92     (void) msg;
93 }
chainArray(const char * path,const char * args,rtosc_arg_t * vals)94 void RtData::chainArray(const char *path, const char *args,
95         rtosc_arg_t *vals)
96 {
97     (void) path;
98     (void) args;
99     (void) vals;
100 };
broadcast(const char * path,const char * args,...)101 void RtData::broadcast(const char *path, const char *args, ...)
102 {
103     va_list va;
104     va_start(va,args);
105     char buffer[1024];
106     rtosc_vmessage(buffer,1024,path,args,va);
107     broadcast(buffer);
108     va_end(va);
109 }
broadcast(const char * msg)110 void RtData::broadcast(const char *msg)
111 {reply(msg);};
broadcastArray(const char * path,const char * args,rtosc_arg_t * vals)112 void RtData::broadcastArray(const char *path, const char *args,
113         rtosc_arg_t *vals)
114 {
115     (void) path;
116     (void) args;
117     (void) vals;
118 }
119 
forward(const char * rational)120 void RtData::forward(const char *rational)
121 {
122     (void) rational;
123 }
124 
metaiterator_advance(const char * & title,const char * & value)125 void metaiterator_advance(const char *&title, const char *&value)
126 {
127     if(!title || !*title) {
128         value = NULL;
129         return;
130     }
131 
132     //Try to find "\0=" after title string
133     value = title;
134     while(*value)
135         ++value;
136     if(*++value != '=')
137         value = NULL;
138     else
139         value++;
140 }
141 
MetaIterator(const char * str)142 Port::MetaIterator::MetaIterator(const char *str)
143     :title(str), value(NULL)
144 {
145     metaiterator_advance(title, value);
146 }
147 
operator ++(void)148 Port::MetaIterator& Port::MetaIterator::operator++(void)
149 {
150     if(!title || !*title) {
151         title = NULL;
152         return *this;
153     }
154     //search for next parameter start
155     //aka "\0:" unless "\0\0" is seen
156     char prev = 0;
157     while(prev || (*title && *title != ':'))
158         prev = *title++;
159 
160     if(!*title)
161         title = NULL;
162     else
163         ++title;
164 
165     metaiterator_advance(title, value);
166     return *this;
167 }
168 
operator bool(void) const169 Port::MetaIterator::operator bool(void) const
170 {
171     return title;
172 }
173 
MetaContainer(const char * str_)174 Port::MetaContainer::MetaContainer(const char *str_)
175 :str_ptr(str_)
176 {}
177 
begin(void) const178 Port::MetaIterator Port::MetaContainer::begin(void) const
179 {
180     if(str_ptr && *str_ptr == ':')
181         return Port::MetaIterator(str_ptr+1);
182     else
183         return Port::MetaIterator(str_ptr);
184 }
185 
end(void) const186 Port::MetaIterator Port::MetaContainer::end(void) const
187 {
188     return MetaIterator(NULL);
189 }
190 
find(const char * str) const191 Port::MetaIterator Port::MetaContainer::find(const char *str) const
192 {
193     for(const auto x : *this)
194         if(!strcmp(x.title, str))
195             return x;
196     return NULL;
197 }
198 
length(void) const199 size_t Port::MetaContainer::length(void) const
200 {
201         if(!str_ptr || !*str_ptr)
202             return 0;
203         char prev = 0;
204         const char *itr = str_ptr;
205         while(prev || *itr)
206             prev = *itr++;
207         return 2+(itr-str_ptr);
208 }
209 
operator [](const char * str) const210 const char *Port::MetaContainer::operator[](const char *str) const
211 {
212     for(const auto x : *this)
213         if(!strcmp(x.title, str))
214             return x.value;
215     return NULL;
216 }
217 //Match the arg string or fail
arg_matcher(const char * pattern,const char * args)218 inline bool arg_matcher(const char *pattern, const char *args)
219 {
220     //match anything if now arg restriction is present (ie the ':')
221     if(*pattern++ != ':')
222         return true;
223 
224     const char *arg_str = args;
225     bool      arg_match = *pattern || *pattern == *arg_str;
226 
227     while(*pattern && *pattern != ':')
228         arg_match &= (*pattern++==*arg_str++);
229 
230     if(*pattern==':') {
231         if(arg_match && !*arg_str)
232             return true;
233         else
234             return arg_matcher(pattern, args); //retry
235     }
236 
237     return arg_match;
238 }
239 
scmp(const char * a,const char * b)240 inline bool scmp(const char *a, const char *b)
241 {
242     while(*a && *a == *b) a++, b++;
243     return a[0] == b[0];
244 }
245 
246 typedef std::vector<std::string>  words_t;
247 typedef std::vector<std::string>  svec_t;
248 typedef std::vector<const char *> cvec_t;
249 typedef std::vector<int> ivec_t;
250 typedef std::vector<int> tuple_t;
251 typedef std::vector<tuple_t> tvec_t;
252 
253 namespace rtosc{
254 class Port_Matcher
255 {
256         bool *m_enump = nullptr; //!< @invariant nullptr or new[]'ed memory
257     public:
Port_Matcher(int ports_size)258         Port_Matcher(int ports_size) : m_enump(new bool[ports_size]) {}
~Port_Matcher()259         ~Port_Matcher() { delete[] m_enump; }
260 
enump() const261         const bool* enump() const { return m_enump; }
set_enump(int i,bool val) const262         void set_enump(int i, bool val) const { m_enump[i] = val; }
263 
264         svec_t fixed;
265         cvec_t arg_spec;
266         ivec_t pos;
267         ivec_t assoc;
268         ivec_t remap;
269 
rtosc_match_args(const char * pattern,const char * msg)270         bool rtosc_match_args(const char *pattern, const char *msg)
271         {
272             //match anything if no arg restriction is present
273             //(ie the ':')
274             if(*pattern++ != ':')
275                 return true;
276 
277             const char *arg_str = rtosc_argument_string(msg);
278             bool      arg_match = *pattern || *pattern == *arg_str;
279 
280             while(*pattern && *pattern != ':')
281                 arg_match &= (*pattern++==*arg_str++);
282 
283             if(*pattern==':') {
284                 if(arg_match && !*arg_str)
285                     return true;
286                 else
287                     return rtosc_match_args(pattern, msg); //retry
288             }
289 
290             return arg_match;
291         }
292 
hard_match(int i,const char * msg)293         bool hard_match(int i, const char *msg)
294         {
295             if(strncmp(msg, fixed[i].c_str(), fixed[i].length()))
296                 return false;
297             if(arg_spec[i])
298                 return rtosc_match_args(arg_spec[i], msg);
299             else
300                 return true;
301         }
302 };
303 
304 }
305 
306 
do_hash(const words_t & strs,const ivec_t & pos)307 tvec_t do_hash(const words_t &strs, const ivec_t &pos)
308 {
309     tvec_t  tvec;
310     for(auto &s:strs) {
311         tuple_t tuple;
312         tuple.push_back(s.length());
313         for(const auto &p:pos)
314             if(p < (int)s.size())
315                 tuple.push_back(s[p]);
316         tvec.push_back(std::move(tuple));
317     }
318     return tvec;
319 }
320 
321 template<class T>
count_dups(std::vector<T> & t)322 int count_dups(std::vector<T> &t)
323 {
324     int dups = 0;
325     int N = t.size();
326     STACKALLOC(bool, mark, t.size());
327     memset(mark, 0, N);
328     for(int i=0; i<N; ++i) {
329         if(mark[i])
330             continue;
331         for(int j=i+1; j<N; ++j) {
332             if(t[i] == t[j]) {
333                 dups++;
334                 mark[j] = true;
335             }
336         }
337     }
338     return dups;
339 }
340 
341 template<class T, class Z>
has(T & t,Z & z)342 bool has(T &t, Z&z)
343 {
344     for(auto tt:t)
345         if(tt==z)
346             return true;
347     return false;
348 }
349 
int_max(int a,int b)350 static int int_max(int a, int b) { return a<b?b:a;}
351 
find_pos(words_t & strs)352 static ivec_t find_pos(words_t &strs)
353 {
354     ivec_t pos;
355     int current_dups = strs.size();
356     int N = 0;
357     for(auto w:strs)
358         N = int_max(N,w.length());
359 
360     int pos_best = -1;
361     int pos_best_val = std::numeric_limits<int>::max();
362     while(true)
363     {
364         for(int i=0; i<N; ++i) {
365             ivec_t npos = pos;
366             if(has(pos, i))
367                 continue;
368             npos.push_back(i);
369             auto hashed = do_hash(strs, npos);
370             int d = count_dups(hashed);
371             if(d < pos_best_val) {
372                 pos_best_val = d;
373                 pos_best = i;
374             }
375         }
376         if(pos_best_val >= current_dups)
377             break;
378         current_dups = pos_best_val;
379         pos.push_back(pos_best);
380     }
381     auto hashed = do_hash(strs, pos);
382     int d = count_dups(hashed);
383     //printf("Total Dups: %d\n", d);
384     if(d != 0)
385         pos.clear();
386     return pos;
387 }
388 
do_hash(const words_t & strs,const ivec_t & pos,const ivec_t & assoc)389 static ivec_t do_hash(const words_t &strs, const ivec_t &pos, const ivec_t &assoc)
390 {
391     ivec_t ivec;
392     ivec.reserve(strs.size());
393     for(auto &s:strs) {
394         int t = s.length();
395         for(auto p:pos)
396             if(p < (int)s.size())
397                 t += assoc[s[p]];
398         ivec.push_back(t);
399     }
400     return ivec;
401 }
402 
find_assoc(const words_t & strs,const ivec_t & pos)403 static ivec_t find_assoc(const words_t &strs, const ivec_t &pos)
404 {
405     ivec_t assoc;
406     int current_dups = strs.size();
407     int N = 127;
408     std::vector<char> useful_chars;
409     for(auto w:strs)
410         for(auto c:w)
411             if(!has(useful_chars, c))
412                 useful_chars.push_back(c);
413 
414     for(int i=0; i<N; ++i)
415         assoc.push_back(0);
416 
417     int assoc_best = -1;
418     int assoc_best_val = std::numeric_limits<int>::max();;
419     for(int k=0; k<4; ++k)
420     {
421         for(int i:useful_chars) {
422             assoc_best_val = std::numeric_limits<int>::max();
423             for(int j=0; j<100; ++j) {
424                 //printf(".");
425                 assoc[i] = j;
426                 auto hashed = do_hash(strs, pos, assoc);
427                 //for(int i=0; i<hashed.size(); ++i)
428                 //    printf("%d ", hashed[i]);
429                 //printf("\n");
430                 int d = count_dups(hashed);
431                 //printf("dup %d\n",d);
432                 if(d < assoc_best_val) {
433                     assoc_best_val = d;
434                     assoc_best = j;
435                 }
436             }
437             assoc[i] = assoc_best;
438         }
439         if(assoc_best_val >= current_dups)
440             break;
441         current_dups = assoc_best_val;
442     }
443     auto hashed = do_hash(strs, pos, assoc);
444     //int d = count_dups(hashed);
445     //printf("Total Dups Assoc: %d\n", d);
446     return assoc;
447 }
448 
find_remap(words_t & strs,ivec_t & pos,ivec_t & assoc)449 static ivec_t find_remap(words_t &strs, ivec_t &pos, ivec_t &assoc)
450 {
451     ivec_t remap;
452     auto hashed = do_hash(strs, pos, assoc);
453     //for(int i=0; i<strs.size(); ++i)
454     //    printf("%d) '%s'\n", hashed[i], strs[i].c_str());
455     int N = 0;
456     for(auto h:hashed)
457         N = int_max(N,h+1);
458     for(int i=0; i<N; ++i)
459         remap.push_back(0);
460     for(int i=0; i<(int)hashed.size(); ++i)
461         remap[hashed[i]] = i;
462 
463     return remap;
464 }
465 
generate_minimal_hash(std::vector<std::string> str,Port_Matcher & pm)466 static void generate_minimal_hash(std::vector<std::string> str, Port_Matcher &pm)
467 {
468     if(str.empty())
469         return;
470     pm.pos   = find_pos(str);
471     if(pm.pos.empty()) {
472         fprintf(stderr, "rtosc: Failed to generate minimal hash\n");
473         return;
474     }
475     pm.assoc = find_assoc(str, pm.pos);
476     pm.remap = find_remap(str, pm.pos, pm.assoc);
477 }
478 
generate_minimal_hash(Ports & p,Port_Matcher & pm)479 static void generate_minimal_hash(Ports &p, Port_Matcher &pm)
480 {
481     svec_t keys;
482     cvec_t args;
483 
484     bool enump = false;
485     for(unsigned i=0; i<p.ports.size(); ++i)
486         if(strchr(p.ports[i].name, '#'))
487             enump = true;
488     if(enump)
489         return;
490     for(unsigned i=0; i<p.ports.size(); ++i)
491     {
492         std::string tmp = p.ports[i].name;
493         const char *arg = NULL;
494         int idx = tmp.find(':');
495         if(idx > 0) {
496             arg = p.ports[i].name+idx;
497             tmp = tmp.substr(0,idx);
498         }
499         keys.push_back(tmp);
500         args.push_back(arg);
501 
502     }
503     pm.fixed    = keys;
504     pm.arg_spec = args;
505 
506     generate_minimal_hash(keys, pm);
507 }
508 
Ports(std::initializer_list<Port> l)509 Ports::Ports(std::initializer_list<Port> l)
510     :ports(l), impl(NULL)
511 {
512     refreshMagic();
513 }
514 
~Ports()515 Ports::~Ports()
516 {
517     delete impl;
518 }
519 
520 #if !defined(__GNUC__)
521 #define __builtin_expect(a,b) a
522 #endif
523 
dispatch(const char * m,rtosc::RtData & d,bool base_dispatch) const524 void Ports::dispatch(const char *m, rtosc::RtData &d, bool base_dispatch) const
525 {
526     // rRecur*Cb have already set d.loc to the required pointer
527     // in case no port will match, d.loc will not be touched
528     // this enables returning the address of a runtime object
529 
530     void *obj = d.obj;
531 
532     //handle the first dispatch layer
533     if(base_dispatch) {
534         d.matches = 0;
535         d.message = m;
536         if(m && *m == '/')
537             m++;
538         if(d.loc)
539             d.loc[0] = 0;
540     }
541 
542     //simple case
543     if(!d.loc || !d.loc_size) {
544         for(const Port &port: ports) {
545             if(rtosc_match(port.name,m, NULL))
546                 d.port = &port, port.cb(m,d), d.obj = obj;
547         }
548     } else {
549 
550         //TODO this function is certainly buggy at the moment, some tests
551         //are needed to make it clean
552         //XXX buffer_size is not properly handled yet
553         if(__builtin_expect(d.loc[0] == 0, 0)) {
554             memset(d.loc, 0, d.loc_size);
555             d.loc[0] = '/';
556         }
557 
558         char *old_end = d.loc;
559         while(*old_end) ++old_end;
560 
561         if(impl->pos.empty()) { //No perfect minimal hash function
562             for(unsigned i=0; i<elms; ++i) {
563                 const Port &port = ports[i];
564                 const char* m_end;
565                 if(!rtosc_match(port.name, m, &m_end))
566                     continue;
567                 if(!port.ports)
568                     d.matches++;
569 
570                 //Append the path
571                 if(strchr(port.name,'#')) {
572                     const char *msg = m;
573                     char       *pos = old_end;
574                     while(*msg && msg != m_end)
575                         *pos++ = *msg++;
576                     *pos = '\0';
577                 } else
578                     scat(d.loc, port.name);
579 
580                 d.port = &port;
581 
582                 //Apply callback
583                 port.cb(m,d), d.obj = obj;
584 
585                 //Remove the rest of the path
586                 char *tmp = old_end;
587                 while(*tmp) *tmp++=0;
588             }
589         } else {
590 
591             //Define string to be hashed
592             unsigned len=0;
593             const char *tmp = m;
594 
595             while(*tmp && *tmp != '/')
596                 tmp++;
597             if(*tmp == '/')
598                 tmp++;
599             len = tmp-m;
600 
601             //Compute the hash
602             int t = len;
603             for(auto p:impl->pos)
604                 if(p < (int)len)
605                     t += impl->assoc[m[p]];
606             if(t >= (int)impl->remap.size() && !default_handler)
607                 return;
608             else if(t >= (int)impl->remap.size() && default_handler) {
609                 d.matches++;
610                 default_handler(m,d), d.obj = obj;
611                 return;
612             }
613 
614             int port_num = impl->remap[t];
615 
616             //Verify the chosen port is correct
617             if(__builtin_expect(impl->hard_match(port_num, m), 1)) {
618                 const Port &port = ports[impl->remap[t]];
619                 if(!port.ports)
620                     d.matches++;
621 
622                 //Append the path
623                 if(impl->enump()[port_num]) {
624                     const char *msg = m;
625                     char       *pos = old_end;
626                     while(*msg && *msg != '/')
627                         *pos++ = *msg++;
628                     if(strchr(port.name, '/'))
629                         *pos++ = '/';
630                     *pos = '\0';
631                 } else
632                     memcpy(old_end, impl->fixed[port_num].c_str(),
633                             impl->fixed[port_num].length()+1);
634 
635                 d.port = &port;
636 
637                 //Apply callback
638                 port.cb(m,d), d.obj = obj;
639 
640                 //Remove the rest of the path
641                 old_end[0] = '\0';
642             } else if(default_handler) {
643                 d.matches++;
644                 default_handler(m,d), d.obj = obj;
645             }
646         }
647     }
648 }
649 
canonicalize_arg_vals(rtosc_arg_val_t * av,size_t n,const char * port_args,Port::MetaContainer meta)650 int rtosc::canonicalize_arg_vals(rtosc_arg_val_t* av, size_t n,
651                                  const char* port_args,
652                                  Port::MetaContainer meta)
653 {
654     const char* first0 = port_args;
655     int errors_found = 0;
656 
657     // skip "[]:"
658     for( ; *first0 && (*first0 == ':' || *first0 == '[' || *first0 == ']');
659            ++first0) ;
660 
661     size_t arr_size;
662     size_t max;
663     bool is_array;
664     rtosc_arg_val_t* start = av;
665     if(av->type == 'a') {
666         arr_size = av->val.a.len;
667         ++av;
668         max = 1; // only one element per bundle element
669                  // TODO: multiple may be possible
670         is_array = true;
671     }
672     else {
673         arr_size = 1;
674         max = n;
675         is_array = false;
676     }
677 
678     for(size_t a = 0; a < arr_size; ++a)
679     {
680         const char* first = first0;
681         for(size_t i = 0; i < max; ++i, ++first, ++av)
682         {
683             // skip "[]"
684             for( ; *first && (*first == '[' || *first == ']'); ++first) ;
685 
686             assert(!strchr(first0, '#'));
687 
688 //            if(is_array) // TODO: currently, only one element per bundle element
689 //                assert(first[1] == 0);
690 
691             if(!*first || *first == ':')
692             {
693                 // (n-i) arguments left, but we have no recipe to convert them
694                 return n-i;
695             }
696 
697             if(av->type == 'S' && *first == 'i')
698             {
699                 int val = enum_key(meta, av->val.s);
700                 if(val == std::numeric_limits<int>::min())
701                     ++errors_found;
702                 else
703                 {
704                     av->type = 'i';
705                     av->val.i = val;
706                 }
707             }
708         }
709     }
710     if(is_array && arr_size)
711         start->val.a.type = av[-1].type;
712 
713     return errors_found;
714 }
715 
map_arg_vals(rtosc_arg_val_t * av,size_t n,Port::MetaContainer meta)716 void rtosc::map_arg_vals(rtosc_arg_val_t* av, size_t n,
717                          Port::MetaContainer meta)
718 {
719     char mapbuf[20] = "map ";
720 
721     for(size_t i = 0; i < n; ++i, ++av)
722     {
723         if(av->type == 'i')
724         {
725             snprintf(mapbuf + 4, 16, "%d", av->val.i);
726             const char* val = meta[mapbuf];
727             if(val)
728             {
729                 av->type = 'S';
730                 av->val.s = val;
731             }
732         }
733     }
734 }
735 
736 /*
737  * Miscellaneous
738  */
739 
operator [](const char * name) const740 const Port *Ports::operator[](const char *name) const
741 {
742     for(const Port &port:ports) {
743         const char *_needle = name,
744               *_haystack = port.name;
745         while(*_needle && *_needle==*_haystack)_needle++,_haystack++;
746 
747         if(*_needle == 0 && (*_haystack == ':' || *_haystack == '\0')) {
748             return &port;
749         }
750     }
751     return NULL;
752 }
753 
apropos(const char * path) const754 const Port *Ports::apropos(const char *path) const
755 {
756     if(path && path[0] == '/')
757         ++path;
758 
759     const char* path_end;
760     for(const Port &port: ports)
761         if(strchr(port.name,'/') && rtosc_match_path(port.name,path, &path_end))
762             return (port.ports && strchr(path,'/')[1])
763                 ? port.ports->apropos(path_end)
764                 : &port;
765 
766     //This is the lowest level, now find the best port
767     for(const Port &port: ports)
768         if(*path && (strstr(port.name, path)==port.name ||
769                     rtosc_match_path(port.name, path, NULL)))
770             return &port;
771 
772     return NULL;
773 }
774 
parent_path_p(char * read,char * start)775 static bool parent_path_p(char *read, char *start)
776 {
777     if(read-start<2)
778         return false;
779     return read[0]=='.' && read[-1]=='.' && read[-2]=='/';
780 }
781 
read_path(char * & r,char * start)782 static void read_path(char *&r, char *start)
783 {
784     while(1)
785     {
786         if(r<start)
787             break;
788         bool doBreak = *r=='/';
789         r--;
790         if(doBreak)
791             break;
792     }
793 }
794 
move_path(char * & r,char * & w,char * start)795 static void move_path(char *&r, char *&w, char *start)
796 {
797     while(1)
798     {
799         if(r<start)
800             break;
801         bool doBreak = *r=='/';
802         *w-- = *r--;
803         if(doBreak)
804             break;
805     }
806 }
807 
808 
collapsePath(char * p)809 char *Ports::collapsePath(char *p)
810 {
811     //obtain the pointer to the last non-null char
812     char *p_end = p;
813     while(*p_end) p_end++;
814     p_end--;
815 
816     //number of subpaths to consume
817     int consuming = 0;
818 
819     char *write_pos = p_end;
820     char *read_pos = p_end;
821     while(read_pos >= p) {
822         //per path chunk either
823         //(1) find a parent ref and inc consuming
824         //(2) find a normal ref and consume
825         //(3) find a normal ref and write through
826         bool ppath = parent_path_p(read_pos, p);
827         if(ppath) {
828             read_path(read_pos, p);
829             consuming++;
830         } else if(consuming) {
831             read_path(read_pos, p);
832             consuming--;
833         } else
834             move_path(read_pos, write_pos, p);
835     }
836     //return last written location, not next to write
837     return write_pos+1;
838 };
839 
refreshMagic()840 void Ports::refreshMagic()
841 {
842     delete impl;
843     impl = new Port_Matcher(ports.size());
844     generate_minimal_hash(*this, *impl);
845     for(int i=0; i<(int)ports.size(); ++i)
846         impl->set_enump(i, !!strchr(ports[i].name, '#'));
847 
848     elms = ports.size();
849 }
850 
ClonePorts(const Ports & ports_,std::initializer_list<ClonePort> c)851 ClonePorts::ClonePorts(const Ports &ports_,
852         std::initializer_list<ClonePort> c)
853     :Ports({})
854 {
855     for(auto &to_clone:c) {
856         const Port *clone_port = NULL;
857         for(auto &p:ports_.ports)
858             if(!strcmp(p.name, to_clone.name))
859                 clone_port = &p;
860         if(!clone_port && strcmp("*", to_clone.name)) {
861             fprintf(stderr, "Cannot find a clone port for '%s'\n",to_clone.name);
862             assert(false);
863         }
864 
865         if(clone_port) {
866             ports.push_back({clone_port->name, clone_port->metadata,
867                     clone_port->ports, to_clone.cb});
868         } else {
869             default_handler = to_clone.cb;
870         }
871     }
872 
873     refreshMagic();
874 }
MergePorts(std::initializer_list<const rtosc::Ports * > c)875 MergePorts::MergePorts(std::initializer_list<const rtosc::Ports*> c)
876     :Ports({})
877 {
878     //XXX TODO remove duplicates in some sane and documented way
879     //e.g. repeated ports override and remove older ones
880     for(auto *to_clone:c) {
881         assert(to_clone);
882         for(auto &p:to_clone->ports) {
883             bool already_there = false;
884             for(auto &pp:ports)
885                 if(!strcmp(pp.name, p.name))
886                     already_there = true;
887 
888             if(!already_there)
889                 ports.push_back(p);
890         }
891     }
892 
893     refreshMagic();
894 }
895 
896 /**
897  * @brief Check if the port @p port is enabled
898  * @param port The port to be checked. Usually of type rRecur* or rSelf.
899  * @param loc The absolute path of @p port
900  * @param loc_size The maximum usable size of @p loc
901  * @param ports The Ports object containing @p port
902  * @param runtime The runtime object (optional)
903  * @return True if no runtime is provided or @p port has no enabled property.
904  *         Otherwise, the state of the "enabled by" toggle
905  */
port_is_enabled(const Port * port,char * loc,size_t loc_size,const Ports & base,void * runtime)906 bool port_is_enabled(const Port* port, char* loc, size_t loc_size,
907                      const Ports& base, void *runtime)
908 {
909     // TODO: this code should be improved
910     if(port && runtime)
911     {
912         const char* enable_port = port->meta()["enabled by"];
913         if(enable_port)
914         {
915             /*
916                 find out which Ports object to dispatch at
917                 (the current one or its child?)
918              */
919             const char* n = port->name;
920             const char* e = enable_port;
921             for( ; *n && (*n == *e) && *n != '/' && *e != '/'; ++n, ++e) ;
922 
923             bool subport = (*e == '/' && *n == '/');
924 
925             const char* ask_port_str = subport
926                                        ? e+1
927                                        : enable_port;
928 
929             const Ports& ask_ports = subport ? *base[port->name]->ports
930                                              : base;
931 
932             assert(!strchr(ask_port_str, '/'));
933             const Port* ask_port = ask_ports[ask_port_str];
934             assert(ask_port);
935 
936             rtosc_arg_val_t rval;
937 
938             /*
939                 concatenate the location string
940              */
941             int loclen = strlen(loc);
942             STACKALLOC(char, loc_copy, loc_size);
943             strcpy(loc_copy, loc);
944             if(subport)
945                 strncat(loc_copy, "/../", loc_size - loclen - 1);
946             strncat(loc_copy, enable_port, loc_size - loclen - 4 - 1);
947 
948             char* collapsed_loc = Ports::collapsePath(loc_copy);
949             loc_size -= (collapsed_loc - loc_copy);
950 
951             /*
952                 receive the "enabled" property
953              */
954             STACKALLOC(char, buf, loc_size);
955             // TODO: pass a parameter portname_from_base, since Ports might
956             //       also be of type a#N/b
957             const char* last_slash = strrchr(collapsed_loc, '/');
958             fast_strcpy(buf, last_slash ? last_slash + 1 : collapsed_loc,
959                         loc_size);
960 
961             helpers::get_value_from_runtime(runtime,
962                 *ask_port, loc_size, collapsed_loc, ask_port_str,
963                 buf, 0, 1, &rval);
964             assert(rval.type == 'T' || rval.type == 'F');
965             return rval.type == 'T';
966         }
967         else // Port has no "enabled" property, so it is always enabled
968             return true;
969     }
970     else // no runtime provided, so run statically through all subports
971         return true;
972 }
973 
974 /**
975     This recursing function is called if walk_ports hits one port @p p which has
976     sub-ports again. The @p runtime object still is the one belonging to the
977     base port which contains @p p as a subport.
978 */
979 // this is doing nothing else than checking if a port is enabled (using the runtime),
980 // and if yes, call walk_ports on its subports again
981 // in case of no runtime, this only calls walk_ports
walk_ports_recurse(const Port & p,char * name_buffer,size_t buffer_size,const Ports & base,void * data,port_walker_t walker,void * runtime,const char * old_end,bool expand_bundles,bool ranges)982 static void walk_ports_recurse(const Port& p, char* name_buffer,
983                                size_t buffer_size, const Ports& base,
984                                void* data, port_walker_t walker,
985                                void* runtime, const char* old_end,
986                                bool expand_bundles, bool ranges)
987 {
988     // TODO: all/most of these checks must also be done for the
989     // first, non-recursive call
990     bool enabled = true;
991     if(runtime)
992     {
993         // get child runtime and check if it's NULL
994 
995         assert(old_end >= name_buffer);
996         assert(old_end - name_buffer <= 255);
997 
998         const char* buf_ptr;
999         char buf[1024] = "";
1000         fast_strcpy(buf, name_buffer, sizeof(buf));
1001         // there is no "pointer" callback. thus, there will be nothing
1002         // dispatched, but the rRecur*Cb already have set r.obj
1003         // that way, we get our pointer
1004         strncat(buf, "pointer", sizeof(buf) - strlen(buf) - 1);
1005         assert(1024 - strlen(buf) >= 8);
1006         fast_strcpy(buf + strlen(buf) + 1, ",", 2);
1007 
1008         buf_ptr = buf + (old_end - name_buffer);
1009 
1010         char locbuf[1024];
1011         fast_strcpy(locbuf, name_buffer, sizeof(locbuf));
1012         RtData r;
1013         r.obj = runtime; // runtime object of the port that contains p
1014         r.port = &p;
1015         r.message = buf;
1016         r.loc = locbuf;
1017         r.loc_size = sizeof(locbuf);
1018 
1019         p.cb(buf_ptr, r); // call "pointer" callback (see above)
1020         // if there is runtime information (see above), but this pointer
1021         // is NULL, the port is not enabled
1022         enabled = (bool) r.obj; // r.obj = the next runtime object
1023         if(enabled)
1024         {
1025             // check if the port is disabled by a switch
1026             enabled = port_is_enabled(&p, name_buffer, buffer_size,
1027                                       base, runtime);
1028             runtime = r.obj; // callback has stored the pointer of p here
1029         }
1030     }
1031     if(enabled)
1032         rtosc::walk_ports(p.ports, name_buffer, buffer_size,
1033                           data, walker, expand_bundles, runtime, ranges);
1034 };
1035 
1036 /**
1037     This recursing function is called if walk_ports hits one port @p p which has
1038     sub-ports again. The @p runtime object still is the one belonging to the
1039     base port which contains @p p as a subport.
1040 */
1041 /*
1042 char pointer example:
1043 
1044           (Ports& base)         p.name       e.g. read_head
1045                 v               v            v
1046                "/path/from/root/new#2/path#2/to#2/"
1047 
1048                "/path/from/root/new1/path1/"
1049                 ^               ^          ^
1050    name_buffer[buffer_size]     old_end    e.g. write_head
1051 
1052 */
walk_ports_recurse0(const Port & p,char * name_buffer,size_t buffer_size,const Ports * base,void * data,port_walker_t walker,void * runtime,char * const old_end,char * write_head,bool expand_bundles,const char * read_head,bool ranges)1053 static void walk_ports_recurse0(const Port& p, char* name_buffer,
1054                                 size_t buffer_size, const Ports* base,
1055                                 void* data, port_walker_t walker,
1056                                 void* runtime, char* const old_end, char* write_head,
1057                                 bool expand_bundles, const char* read_head,
1058                                 bool ranges)
1059 {
1060     const char* hash_ptr = strchr(read_head + 1,'#');
1061     std::size_t to_copy = hash_ptr ? hash_ptr - read_head : strlen(read_head);
1062 
1063     //Append the path, until possible '#'
1064     //TODO: buffer size checking
1065     //yes, there are subports with ':', e.g. ".../::i"
1066     while(to_copy-->0 && *read_head != ':')
1067     {
1068         *write_head++ = *read_head++;
1069     }
1070 
1071     if(hash_ptr)
1072     {
1073         //Overwrite the "#.../" by "0/", "1/" ...
1074         assert(*read_head == '#');
1075         const unsigned max = atoi(++read_head);
1076         assert(isdigit(*read_head));
1077         for(;isdigit(*read_head); ++read_head) {}
1078 
1079         if(*read_head == '/') { ++read_head; }
1080         if(ranges)
1081         {
1082             int written = sprintf(write_head,"[0,%d]/", max-1);
1083             //Recurse
1084             walk_ports_recurse0(p, name_buffer, buffer_size, base, data, walker,
1085                                 runtime, old_end, write_head + written,
1086                                 expand_bundles, read_head, ranges);
1087         }
1088         else for(unsigned i=0; i<max; ++i)
1089         {
1090             int written = sprintf(write_head,"%d/",i);
1091             //Recurse
1092             walk_ports_recurse0(p, name_buffer, buffer_size, base, data, walker,
1093                                 runtime, old_end, write_head + written,
1094                                 expand_bundles, read_head, ranges);
1095         }
1096     }
1097     else
1098     {
1099         //Ensure last to trail with "/" before recursion
1100         if(write_head[-1] != '/')
1101             *write_head++ = '/';
1102         *write_head = 0;
1103         //Recurse
1104         walk_ports_recurse(p, name_buffer, buffer_size,
1105                            *base, data, walker, runtime, old_end,
1106                            expand_bundles, ranges);
1107     }
1108 };
1109 
walk_ports(const Ports * base,char * name_buffer,size_t buffer_size,void * data,port_walker_t walker,bool expand_bundles,void * runtime,bool ranges)1110 void rtosc::walk_ports(const Ports  *base,
1111                        char         *name_buffer,
1112                        size_t        buffer_size,
1113                        void         *data,
1114                        port_walker_t walker,
1115                        bool          expand_bundles,
1116                        void*         runtime,
1117                        bool          ranges)
1118 {
1119     //only walk valid ports
1120     if(!base)
1121         return;
1122 
1123     assert(name_buffer);
1124     //XXX buffer_size is not properly handled yet
1125     if(name_buffer[0] == 0)
1126         name_buffer[0] = '/';
1127 
1128     char * const old_end = name_buffer + strlen(name_buffer);
1129 
1130     if(port_is_enabled((*base)["self:"], name_buffer, buffer_size, *base,
1131                        runtime))
1132     for(const Port &p: *base) {
1133         //if(strchr(p.name, '/')) {//it is another tree
1134         if(p.ports) {//it is another tree
1135 
1136             walk_ports_recurse0(p, name_buffer, buffer_size,
1137                                 base, data, walker, runtime, old_end, old_end,
1138                                 expand_bundles, p.name, ranges);
1139 
1140         } else {
1141             if(strchr(p.name,'#')) {
1142                 bundle_foreach(p, p.name, old_end, name_buffer, *base,
1143                                data, runtime, walker, expand_bundles, true, ranges);
1144             } else {
1145                 //Append the path
1146                 scat(name_buffer, p.name);
1147 
1148                 //Apply walker function
1149                 walker(&p, name_buffer, old_end, *base, data, runtime);
1150             }
1151         }
1152 
1153         //Remove the rest of the path
1154         char *tmp = old_end;
1155         while(*tmp) *tmp++=0;
1156     }
1157 }
1158 
1159 // this is just an std::array replacement for path_search
1160 template <typename T, size_t N>
1161 struct my_array
1162 {
1163     T data[N];
swapmy_array1164     inline void swap(my_array &other)
1165     {
1166         std::swap_ranges(&data[0], &data[0] + N, other.data);
1167     }
operator []my_array1168     inline T& operator[](const size_t idx)
1169     {
1170         return data[idx];
1171     }
operator []my_array1172     inline const T& operator[](const size_t idx) const
1173     {
1174         return data[idx];
1175     }
1176 };
1177 
path_search(const rtosc::Ports & root,const char * str,const char * needle,char * types,std::size_t max_types,rtosc_arg_t * args,std::size_t max_args,path_search_opts opts,bool reply_with_query)1178 void rtosc::path_search(const rtosc::Ports& root,
1179                         const char *str, const char* needle,
1180                         char *types, std::size_t max_types,
1181                         rtosc_arg_t* args, std::size_t max_args,
1182                         path_search_opts opts, bool reply_with_query)
1183 {
1184     using rtosc::Ports;
1185     using rtosc::Port;
1186 
1187     if(!needle)
1188         needle = "";
1189 
1190     // the last char of "types" is being used for a terminating 0
1191     std::size_t  max         = std::min(max_types - 1, max_args);
1192     size_t       pos         = 0;
1193     const Ports *ports       = nullptr;
1194     const Port  *single_port = nullptr;
1195 
1196     //zero out data
1197     memset(types, 0, max + 1);
1198     memset(args,  0, max);
1199 
1200     if(reply_with_query) {
1201         assert(max >= 2);
1202         types[pos]    = 's';
1203         args[pos++].s = str;
1204         types[pos]    = 's';
1205         args[pos++].s = needle;
1206     }
1207 
1208     if(!*str || !strcmp(str, "/")) {
1209         ports = &root;
1210     } else {
1211         const Port *port = root.apropos(str);
1212         //fprintf(stderr, "apropos %s -> %s\n",str,port?port->name:"<no result>");
1213         if(port) {
1214             if(port->ports) {
1215                 ports = port->ports;
1216             } else {
1217                 single_port = port;
1218             }
1219         }
1220     }
1221 
1222     const auto fn = [&pos,&needle,&types,&args,&max](const Port& p)
1223     {
1224         assert(pos < max);
1225         //fprintf(stderr, "path search iterating port: %s (needle %s) (pos %d)\n", p.name, needle, (int)pos);
1226         if(p.name && strstr(p.name, needle) == p.name)
1227         {
1228             types[pos]    = 's';
1229             args[pos++].s = p.name;
1230             types[pos]    = 'b';
1231             if(p.metadata && *p.metadata) {
1232                 args[pos].b.data = (unsigned char*) p.metadata;
1233                 auto tmp = rtosc::Port::MetaContainer(p.metadata);
1234                 args[pos++].b.len  = tmp.length();
1235             } else {
1236                 args[pos].b.data = (unsigned char*) NULL;
1237                 args[pos++].b.len  = 0;
1238             }
1239         }
1240     };
1241 
1242     if(ports)
1243         for(const Port &p:*ports) fn(p);
1244     else if(single_port)
1245         fn(*single_port);
1246 
1247     if (opts == path_search_opts::sorted ||
1248         opts == path_search_opts::sorted_and_unique_prefix)
1249     {
1250         // we could use std::array, but its internal array does not necessarily
1251         // have offset 0
1252         using val_on_2 = my_array<rtosc_arg_t, 2>;
1253         using ptr_on_2 = val_on_2*;
1254         auto is_less = [](const val_on_2 &p1, const val_on_2 &p2) -> bool {
1255             return strcmp(p1[0].s, p2[0].s) < 0;
1256         };
1257         std::size_t n_paths_found = pos >> 1;
1258         std::sort((ptr_on_2)args, ((ptr_on_2)(args))+n_paths_found, is_less);
1259 
1260         if (opts == path_search_opts::sorted_and_unique_prefix)
1261         {
1262             std::size_t prev_pos = 0;
1263             std::size_t strlen_prev = n_paths_found > 1 ? strlen(args[prev_pos].s) : 0;
1264             std::size_t unused_paths = 0;
1265             for(pos = 2; pos < (n_paths_found<<1); ++++pos)
1266             {
1267                 assert(args[prev_pos].s); // invariant
1268 
1269                 // is the prev path a (real) sub-path of this path?
1270                 // i.e. the current can be accessed by recursing into the prev?
1271                 if(strlen_prev < strlen(args[pos].s) &&
1272                    0 == strncmp(args[pos].s, args[prev_pos].s, strlen_prev) &&
1273                    args[prev_pos].s[strlen_prev-1] == '/')
1274                 {
1275                     // then mark this as unused
1276                     args[pos].s = nullptr;
1277                     ++unused_paths;
1278                 }
1279                 else
1280                 {
1281                     prev_pos = pos;
1282                     strlen_prev = strlen(args[prev_pos].s);
1283                 }
1284             }
1285 
1286             // another sort, only to move unused paths to the end
1287             auto is_less_2 = [](const val_on_2 &p1, const val_on_2 &p2) -> bool {
1288                 return (!(p1[0].s)) ? false // move p1 to the end
1289                                     : (!(p2[0].s)) ? true // move p2 to the end
1290                                                    // is actually already sorted:
1291                                                    : (strcmp(p1[0].s, p2[0].s) < 0);
1292             };
1293             std::sort((ptr_on_2)args, ((ptr_on_2)(args))+n_paths_found, is_less_2);
1294 
1295             // cut off unused paths
1296             types[(n_paths_found - unused_paths)<<1] = 0;
1297         }
1298     }
1299 }
1300 
path_search(const Ports & root,const char * m,std::size_t max_ports,char * msgbuf,std::size_t bufsize,path_search_opts opts,bool reply_with_query)1301 std::size_t rtosc::path_search(const Ports &root, const char *m,
1302                                std::size_t max_ports,
1303                                char *msgbuf, std::size_t bufsize,
1304                                path_search_opts opts, bool reply_with_query)
1305 {
1306     const char *str    = rtosc_argument(m,0).s;
1307     const char *needle = rtosc_argument(m,1).s;
1308     size_t max_args    = max_ports << 1;
1309     size_t max_types   = max_args + 1;
1310     STACKALLOC(char, types, max_types);
1311     STACKALLOC(rtosc_arg_t, args, max_args);
1312 
1313     path_search(root, str, needle, types, max_types, args, max_args, opts, reply_with_query);
1314     size_t length = rtosc_amessage(msgbuf, bufsize,
1315                                    "/paths", types, args);
1316     return length;
1317 }
1318 
units(std::ostream & o,const char * u)1319 static void units(std::ostream &o, const char *u)
1320 {
1321     if(!u)
1322         return;
1323     o << " units=\"" << u << "\"";
1324 }
1325 
1326 using std::ostream;
1327 using std::string;
enum_min(Port::MetaContainer meta)1328 static int enum_min(Port::MetaContainer meta)
1329 {
1330     int min = 0;
1331     for(auto m:meta)
1332         if(strstr(m.title, "map "))
1333             min = atoi(m.title+4);
1334 
1335     for(auto m:meta)
1336         if(strstr(m.title, "map "))
1337             min = min>atoi(m.title+4) ? atoi(m.title+4) : min;
1338 
1339     return min;
1340 }
1341 
enum_max(Port::MetaContainer meta)1342 static int enum_max(Port::MetaContainer meta)
1343 {
1344     int max = 0;
1345     for(auto m:meta)
1346         if(strstr(m.title, "map "))
1347             max = atoi(m.title+4);
1348 
1349     for(auto m:meta)
1350         if(strstr(m.title, "map "))
1351             max = max<atoi(m.title+4) ? atoi(m.title+4) : max;
1352 
1353     return max;
1354 }
1355 
enum_key(Port::MetaContainer meta,const char * value)1356 int rtosc::enum_key(Port::MetaContainer meta, const char* value)
1357 {
1358     int result = std::numeric_limits<int>::min();
1359 
1360     for(auto m:meta)
1361     if(strstr(m.title, "map "))
1362     if(!strcmp(m.value, value))
1363     {
1364         result = atoi(m.title+4);
1365         break;
1366     }
1367 
1368     return result;
1369 }
1370 
add_options(ostream & o,Port::MetaContainer meta)1371 static ostream &add_options(ostream &o, Port::MetaContainer meta)
1372 {
1373     string sym_names = "xyzabcdefghijklmnopqrstuvw";
1374     int sym_idx = 0;
1375     bool has_options = false;
1376     for(auto m:meta)
1377         if(strstr(m.title, "map "))
1378             has_options = true;
1379     for(auto m:meta)
1380         if(strcmp(m.title, "documentation") &&
1381                 strcmp(m.title, "parameter") &&
1382                 strcmp(m.title, "max") &&
1383                 strcmp(m.title, "min"))
1384         printf("m.title = <%s>\n", m.title);
1385 
1386     if(!has_options)
1387         return o;
1388 
1389     o << "    <hints>\n";
1390     for(auto m:meta) {
1391         if(strstr(m.title, "map ")) {
1392             o << "      <point symbol=\"" << sym_names[sym_idx++] << "\" value=\"";
1393             o << m.title+4 << "\">" << m.value << "</point>\n";
1394         }
1395     }
1396     o << "    </hints>\n";
1397 
1398     return o;
1399 }
dump_t_f_port(ostream & o,string name,string doc)1400 static ostream &dump_t_f_port(ostream &o, string name, string doc)
1401 {
1402     o << " <message_in pattern=\"" << name << "\" typetag=\"T\">\n";
1403     o << "  <desc>Enable " << doc << "</desc>\n";
1404     o << "  <param_T symbol=\"x\"/>\n";
1405     o << " </message_in>\n";
1406     o << " <message_in pattern=\"" << name << "\" typetag=\"F\">\n";
1407     o << "  <desc>Disable "  << doc << "</desc>\n";
1408     o << "  <param_F symbol=\"x\"/>\n";
1409     o << " </message_in>\n";
1410     o << " <message_in pattern=\"" << name << "\" typetag=\"\">\n";
1411     o << "  <desc>Get state of " << doc << "</desc>\n";
1412     o << " </message_in>\n";
1413     o << " <message_out pattern=\"" << name << "\" typetag=\"T\">\n";
1414     o << "  <desc>Value of " << doc << "</desc>\n";
1415     o << "  <param_T symbol=\"x\"/>";
1416     o << " </message_out>\n";
1417     o << " <message_out pattern=\"" << name << "\" typetag=\"F\">\n";
1418     o << "  <desc>Value of " <<  doc << "</desc>\n";
1419     o << "  <param_F symbol=\"x\"/>";
1420     o << " </message_out>\n";
1421     return o;
1422 }
dump_any_port(ostream & o,string name,string doc)1423 static ostream &dump_any_port(ostream &o, string name, string doc)
1424 {
1425     o << " <message_in pattern=\"" << name << "\" typetag=\"*\">\n";
1426     o << "  <desc>" << doc << "</desc>\n";
1427     o << " </message_in>\n";
1428     return o;
1429 }
1430 
dump_generic_port(ostream & o,string name,string doc,string type)1431 static ostream &dump_generic_port(ostream &o, string name, string doc, string type)
1432 {
1433     const char *t = type.c_str();
1434     string arg_names = "xyzabcdefghijklmnopqrstuvw";
1435 
1436     //start out with argument separator
1437     if(*t++ != ':')
1438         return o;
1439     //now real arguments (assume [] don't exist)
1440     string args;
1441     while(*t && *t != ':')
1442         args += *t++;
1443 
1444     o << " <message_in pattern=\"" << name << "\" typetag=\"" << args << "\">\n";
1445     o << "  <desc>" << doc << "</desc>\n";
1446 
1447     assert(args.length()<arg_names.length());
1448     for(unsigned i=0; i<args.length(); ++i)
1449         o << "  <param_" << args[i] << " symbol=\"" << arg_names[i] << "\"/>\n";
1450     o << " </message_in>\n";
1451 
1452     if(*t == ':')
1453         return dump_generic_port(o, name, doc, t);
1454     else
1455         return o;
1456 }
1457 
do_dump_ports(const rtosc::Port * p,const char * name,void * v)1458 static bool do_dump_ports(const rtosc::Port *p, const char *name, void *v)
1459 {
1460     std::ostream &o  = *(std::ostream*)v;
1461     auto meta        = p->meta();
1462     const char *args = strchr(p->name, ':');
1463     auto mparameter  = meta.find("parameter");
1464     auto mdoc        = meta.find("documentation");
1465     string doc;
1466 
1467     if(mdoc != p->meta().end())
1468         doc = mdoc.value;
1469     if(meta.find("internal") != meta.end()) {
1470         doc += "[INTERNAL]";
1471     }
1472 
1473     if(mparameter != p->meta().end()) {
1474         char type = 0;
1475         if(args) {
1476             if(strchr(args, 'f'))
1477                 type = 'f';
1478             else if(strchr(args, 'i'))
1479                 type = 'i';
1480             else if(strchr(args, 'c'))
1481                 type = 'c';
1482             else if(strchr(args, 'T'))
1483                 type = 't';
1484             else if(strchr(args, 's'))
1485                 type = 's';
1486         }
1487 
1488         if(!type) {
1489             fprintf(stderr, "rtosc port dumper: Cannot handle '%s'\n", name);
1490             fprintf(stderr, "    args = <%s>\n", args);
1491             return false;
1492         }
1493 
1494         if(type == 't') {
1495             dump_t_f_port(o, name, doc);
1496             return true;
1497         }
1498 
1499         o << " <message_in pattern=\"" << name << "\" typetag=\"" << type << "\">\n";
1500         o << "  <desc>Set Value of " << doc << "</desc>\n";
1501         if(meta.find("min") != meta.end() && meta.find("max") != meta.end() && type != 'c') {
1502             o << "  <param_" << type << " symbol=\"x\"";
1503             units(o, meta["unit"]);
1504             o << ">\n";
1505             o << "   <range_min_max " << (type == 'f' ? "lmin=\"[\" lmax=\"]\"" : "");
1506             o << " min=\"" << meta["min"] << "\"  max=\"" << meta["max"] << "\"/>\n";
1507             o << "  </param_" << type << ">";
1508         } else if(meta.find("enumerated") != meta.end()) {
1509             o << "  <param_" << type << " symbol=\"x\">\n";
1510             o << "    <range_min_max min=\"" << enum_min(meta) << "\" max=\"";
1511             o << enum_max(meta) << "\">\n";
1512             add_options(o, meta);
1513             o << "    </range_min_max>\n";
1514             o << "  </param_" << type << ">\n";
1515         } else {
1516             o << "  <param_" << type << " symbol=\"x\"";
1517             units(o, meta["unit"]);
1518             o << "/>\n";
1519         }
1520         o << " </message_in>\n";
1521         o << " <message_in pattern=\"" << name << "\" typetag=\"\">\n";
1522         o << "  <desc>Get Value of " << doc << "</desc>\n";
1523         o << " </message_in>\n";
1524         o << " <message_out pattern=\"" << name << "\" typetag=\"" << type << "\">\n";
1525         o << "  <desc>Value of " << doc << "</desc>\n";
1526         if(meta.find("min") != meta.end() && meta.find("max") != meta.end() && type != 'c') {
1527             o << "  <param_" << type << " symbol=\"x\"";
1528             units(o, meta["unit"]);
1529             o << ">\n";
1530             o << "   <range_min_max " << (type == 'f' ? "lmin=\"[\" lmax=\"]\"" : "");
1531             o << " min=\"" << meta["min"] << "\"  max=\"" << meta["max"] << "\"/>\n";
1532             o << "  </param_" << type << ">\n";
1533         } else if(meta.find("enumerated") != meta.end()) {
1534             o << "  <param_" << type << " symbol=\"x\">\n";
1535             o << "    <range_min_max min=\"" << enum_min(meta) << "\" max=\"";
1536             o << enum_max(meta) << "\">\n";
1537             add_options(o, meta);
1538             o << "    </range_min_max>\n";
1539             o << "  </param_" << type << ">\n";
1540         } else {
1541             o << "  <param_" << type << " symbol=\"x\"";
1542             units(o, meta["unit"]);
1543             o << "/>\n";
1544         }
1545         o << " </message_out>\n";
1546     } else if(mdoc != meta.end() && (!args || args == std::string(""))) {
1547         dump_any_port(o, name, doc);
1548     } else if(mdoc != meta.end() && args) {
1549         dump_generic_port(o, name, doc, args);
1550     } else if(mdoc != meta.end()) {
1551         fprintf(stderr, "Skipping \"%s\"\n", name);
1552         if(args) {
1553             fprintf(stderr, "    type = %s\n", args);
1554         }
1555         return false;
1556     } else {
1557         fprintf(stderr, "Skipping [UNDOCUMENTED] \"%s\"\n", name);
1558         return false;
1559     }
1560     return true;
1561 }
1562 
dump_ports_cb(const rtosc::Port * p,const char * name,const char *,const Ports &,void * v,void *)1563 static void dump_ports_cb(const rtosc::Port *p, const char *name,const char*,
1564                           const Ports&,void *v, void*)
1565 {
1566     static std::set<std::pair<std::string, std::string>> already_dumped;
1567     if(already_dumped.find(std::make_pair(name, p->name)) == already_dumped.end())
1568     {
1569         bool dumped = do_dump_ports(p, name, v);
1570         if(dumped)
1571             already_dumped.emplace(name, p->name);
1572     }
1573 }
1574 
operator <<(std::ostream & o,rtosc::OscDocFormatter & formatter)1575 std::ostream &rtosc::operator<<(std::ostream &o, rtosc::OscDocFormatter &formatter)
1576 {
1577     o << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1578     o << "<osc_unit format_version=\"1.0\">\n";
1579     o << " <meta>\n";
1580     o << "  <name>" << formatter.prog_name << "</name>\n";
1581     o << "  <uri>" << formatter.uri << "</uri>\n";
1582     o << "  <doc_origin>" << formatter.doc_origin << "</doc_origin>\n";
1583     o << "  <author><firstname>" << formatter.author_first;
1584     o << "</firstname><lastname>" << formatter.author_last << "</lastname></author>\n";
1585     o << " </meta>\n";
1586     char buffer[1024];
1587     memset(buffer, 0, sizeof(buffer));
1588     walk_ports(formatter.p, buffer, 1024, &o, dump_ports_cb, false, nullptr, true);
1589     o << "</osc_unit>\n";
1590     return o;
1591 }
1592 
1593