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