#include #include #include #include #include #include #include #include #include /** Call snprintf and assert() that it did fit into the buffer */ static int asnprintf(char* str, size_t size, const char* format, ...) { va_list args; va_start(args, format); int written = vsnprintf(str, size, format, args); assert(written >= 0); // no error // snprintf(3) says that a return value of size or more bytes means // that the output has been truncated assert((size_t)written < size); va_end(args); return written; } static const rtosc_print_options* default_print_options = &((rtosc_print_options) { true, 2, " ", 80}); /** * Return the char that represents the escape sequence * * @param c The escape sequence, e.g. '\n' * @param chr True if the character appears in a single character * (vs in a string) * @return The character describing the escape sequence, e.g. 'n'; * -1 if none exists */ static int as_escaped_char(int c, int chr) { switch(c) { case '\a': return 'a'; case '\b': return 'b'; case '\t': return 't'; case '\n': return 'n'; case '\v': return 'v'; case '\f': return 'f'; case '\r': return 'r'; case '\\': return '\\'; default: if(chr && c == '\'') return '\''; else if(!chr && c == '"') return '"'; else return -1; } } // internal function for rtosc_print_arg_val static void break_string(char** buffer, size_t bs, int wrt, int* cols_used) { // unquoted, this means: "\ " int tmp = asnprintf(*buffer, bs, "\"\\\n \""); wrt += tmp; *buffer += tmp; *cols_used = 5; } size_t rtosc_print_arg_val(const rtosc_arg_val_t *arg, char *buffer, size_t bs, const rtosc_print_options* opt, int *cols_used) { size_t wrt = 0; if(!opt) opt = default_print_options; assert(arg); const rtosc_arg_t* val = &arg->val; switch(arg->type) { case 'T': assert(bs>4); strncpy(buffer, "true", bs); wrt = 4; break; case 'F': assert(bs>5); strncpy(buffer, "false", bs); wrt = 5; break; case 'N': assert(bs>3); strncpy(buffer, "nil", bs); wrt = 3; break; case 'I': assert(bs>3); strncpy(buffer, "inf", bs); wrt = 3; break; case 'h': wrt = asnprintf(buffer, bs, "%"PRId64"h", val->h); break; case 't': // write to ISO 8601 date { if(val->t == 1) wrt = asnprintf(buffer, bs, "immediately"); else { time_t t = (time_t)(val->t >> 32); int32_t secfracs = val->t & (0xffffffff); struct tm* m_tm = localtime(&t); const char* strtimefmt = (secfracs || m_tm->tm_sec) ? "%Y-%m-%d %H:%M:%S" : (m_tm->tm_hour || m_tm->tm_min) ? "%Y-%m-%d %H:%M" : "%Y-%m-%d"; wrt = strftime(buffer, bs, strtimefmt, m_tm); assert(wrt); if(secfracs) { int rd = 0; int prec = opt->floating_point_precision; assert(prec>=0); assert(prec<100); // convert fractions -> float char lossless[16]; asnprintf(lossless, 16, "0x%xp-32", secfracs); float flt; sscanf(lossless, "%f%n", &flt, &rd); assert(rd); // append float char fmtstr[8]; asnprintf(fmtstr, 5, "%%.%df", prec); int lastwrt = wrt; wrt += asnprintf(buffer + wrt, bs - wrt, fmtstr, flt); // snip part before separator const char* sep = strchr(buffer + lastwrt, '.'); assert(sep); memmove(buffer + lastwrt, sep, strlen(sep)+1); wrt -= (sep - (buffer + lastwrt)); if(opt->lossless) wrt += asnprintf(buffer + wrt, bs - wrt, " (...+%as)", flt); } // if secfracs } // else break; } case 'r': wrt = asnprintf(buffer, bs, "#%02x%02x%02x%02x", (val->i >> 24) & 0xff, (val->i >> 16) & 0xff, (val->i >> 8) & 0xff, val->i & 0xff ); break; case 'd': case 'f': { int prec = opt->floating_point_precision; assert(prec>=0); assert(prec<100); char fmtstr[9]; if(arg->type == 'f') { // e.g. "%.42f" or "%.0f.": asnprintf(fmtstr, 6, "%%#.%df", prec); wrt = asnprintf(buffer, bs, fmtstr, val->f); if(opt->lossless) wrt += asnprintf(buffer + wrt, bs - wrt, " (%a)", val->f); } else { // e.g. "%.42lfd" or "%.0lf.d" asnprintf(fmtstr, 8, "%%#.%dlfd", prec); wrt = asnprintf(buffer, bs, fmtstr, val->d); if(opt->lossless) wrt += asnprintf(buffer + wrt, bs - wrt, " (%la)", val->d); } break; } case 'c': { int is_esc = (as_escaped_char(val->i, true) != -1); wrt = asnprintf(buffer, bs, "'%s%c'", is_esc ? "\\" : "", is_esc ? as_escaped_char(val->i, true) : val->i); break; } case 'i': wrt = asnprintf(buffer, bs, "%"PRId32, val->i); break; case 'm': wrt = asnprintf(buffer, bs, "MIDI [0x%02x 0x%02x 0x%02x 0x%02x]", val->m[0], val->m[1], val->m[2], val->m[3]); break; case 's': case 'S': { bool plain; // e.g. without quotes if(arg->type == 'S') { plain = true; // "Symbol": are quotes required? if(*val->s != '_' && !isalpha(*val->s)) plain = false; else for(const char* s = val->s + 1; *s && plain; ++s) plain = (*s == '_' || (isalnum(*s))); } else plain = false; if(plain) { wrt = asnprintf(buffer, bs, "%s", val->s); break; } else { char* b = buffer; *b++ = '"'; for(const char* s = val->s; *s; ++s) { if(*cols_used >= opt->linelength - 2) break_string(&b, bs, wrt, cols_used); assert(bs); int as_esc = as_escaped_char(*s, false); if(as_esc != -1) { assert(bs-1); *b++ = '\\'; *b++ = as_esc; *cols_used += 2; if(as_esc == 'n') break_string(&b, bs, wrt, cols_used); } else { *b++ = *s; ++*cols_used; } } assert(bs >= 2); *b++ = '"'; if(arg->type == 'S') { assert(bs >= 2); *b++ = 'S'; } *b = 0; wrt += (b-buffer); break; } } case 'b': wrt = asnprintf(buffer, bs, "[%d ", val->b.len); *cols_used += wrt; buffer += wrt; for(int32_t i = 0; i < val->b.len; ++i) { if(*cols_used >= opt->linelength - 6) { int tmp = asnprintf(buffer-1, bs+1, "\n "); wrt += (tmp-1); buffer += (tmp-1); *cols_used = 4; } wrt += asnprintf(buffer, bs, "0x%02x ", val->b.data[i]); bs -= 5; buffer += 5; *cols_used += 5; } buffer[-1] = ']'; break; default: ; } switch(arg->type) { case 's': case 'b': // these can break the line, so they compute *cols_used themselves break; default: *cols_used += wrt; } return wrt; } size_t rtosc_print_arg_vals(const rtosc_arg_val_t *args, size_t n, char *buffer, size_t bs, const rtosc_print_options *opt, int cols_used) { size_t wrt=0; int args_written_this_line = (cols_used) ? 1 : 0; if(!opt) opt = default_print_options; size_t sep_len = strlen(opt->sep); char* last_sep = buffer - 1; for(size_t i = 0; i < n; ++i) { size_t tmp = rtosc_print_arg_val(args++, buffer, bs, opt, &cols_used); wrt += tmp; buffer += tmp; bs -= tmp; ++args_written_this_line; // did we break the line length, // and this is not the first arg written in this line? if(cols_used > opt->linelength && (args_written_this_line > 1)) { // insert "\n " *last_sep = '\n'; assert(bs >= 4); memmove(last_sep+5, last_sep+1, tmp); last_sep[1] = last_sep[2] = last_sep[3] = last_sep[4] = ' '; cols_used = 4 + wrt; wrt += 4; buffer += 4; bs -= 4; args_written_this_line = 0; } if(isep, bs); cols_used += sep_len; wrt += sep_len; buffer += sep_len; bs -= sep_len; } } return wrt; } size_t rtosc_print_message(const char* address, const rtosc_arg_val_t *args, size_t n, char *buffer, size_t bs, const rtosc_print_options* opt, int cols_used) { size_t wrt = asnprintf(buffer, bs, "%s ", address); cols_used += wrt; buffer += wrt; bs -= wrt; wrt += rtosc_print_arg_vals(args, n, buffer, bs, opt, cols_used); return wrt; } /** * Increase @p s while property @property is true and the end has not yet * been reached */ static void skip_while(const char** s, int (*property)(int)) { for(;**s && (*property)(**s);++*s); } /** * Parse the string pointed to by @p src conforming to the format string @p * @param src Pointer to the input string * @param fmt Format string for sscanf(). Must suppress all assignments, * except the last one, which must be "%n" and be at the string's end. * @return The number of bytes skipped from the string pointed to by @p src */ static int skip_fmt(const char** src, const char* fmt) { assert(!strncmp(fmt + strlen(fmt) - 2, "%n", 2)); int rd = 0; sscanf(*src, fmt, &rd); *src += rd; return rd; } /** * Behave like skip_fmt() , but set *src to NULL if the string didn't match * @see skip_fmt */ static int skip_fmt_null(const char** src, const char* fmt) { int result = skip_fmt(src, fmt); if(!result) *src = NULL; return result; } /** Helper function for scanf_fmtstr() */ static const char* try_fmt(const char* src, int exp, const char* fmt, char* typesrc, char type) { int rd = 0; sscanf(src, fmt, &rd); if(rd == exp) { *typesrc = type; return fmt; } else return NULL; } /** * Return the right format string for skipping the next numeric value * * This can be used to find out how to sscanf() ints, floats and doubles. * If the string needs to be read, too, see scanf_fmtstr_scan() below. * @param src The beginning of the numeric value to scan * @param type If non-NULL, the corresponding rtosc argument character * (h,i,f,d) will be written here * @return The format string to use or NULL */ static const char* scanf_fmtstr(const char* src, char* type) { const char* end = src; // skip to string end, word end or a closing paranthesis for(;*end && !isspace(*end) && (*end != ')');++end); int exp = end - src; // store type byte in the parameter, or in a temporary variable? char tmp; char* _type = type ? type : &tmp; const char i32[] = "%*"PRIi32"%n"; const char* r; // result int ok = (r = try_fmt(src, exp, "%*"PRIi64"h%n", _type, 'h')) || (r = try_fmt(src, exp, "%*d%n", _type, 'i')) || (r = try_fmt(src, exp, "%*"PRIi32"i%n", _type, 'i')) || (r = try_fmt(src, exp, i32, _type, 'i')) || (r = try_fmt(src, exp, "%*lfd%n", _type, 'd')) || (r = try_fmt(src, exp, "%*ff%n", _type, 'f')) || (r = try_fmt(src, exp, "%*f%n", _type, 'f')); (void)ok; if(r == i32) r = "%*x%n"; return r; } /** Return the right format string for reading the next numeric value */ static const char* scanf_fmtstr_scan(const char* src, char* bytes8, char* type) { const char *buf = scanf_fmtstr(src, type); assert(buf); assert(bytes8); strncpy(bytes8, buf, 8); *++bytes8 = '%'; // transform "%*" to "%" return bytes8; } /** Skip the next numeric at @p src */ static size_t skip_numeric(const char** src, char* type) { const char* scan_str = scanf_fmtstr(*src, type); if(!scan_str) return 0; else return skip_fmt(src, scan_str); } /** * Return the escape sequence that's generated with the given character * @param c The character, e.g. 'n' * @param chr True if the character appears in a single character * (vs in a string) * @return The escape sequence generated by the character, e.g. '\n'; * 0 if none exists */ static char get_escaped_char(char c, int chr) { switch(c) { case 'a': return '\a'; case 'b': return '\b'; case 't': return '\t'; case 'n': return '\n'; case 'v': return '\v'; case 'f': return '\f'; case 'r': return '\r'; case '\\': return '\\'; default: if(chr && c == '\'') return '\''; else if(!chr && c == '"') return '"'; else return 0; } } /** Called inside a string at @p src, skips until after the closing quote */ static const char* end_of_printed_string(const char* src) { bool escaped = false; ++src; bool cont; do { for(; *src && (escaped || *src != '"') ; ++src) { if(escaped) // last char introduced an escape sequence? { if(!get_escaped_char(*src, false)) return NULL; // bad escape sequence } escaped = (*src == '\\') ? (!escaped) : false; } if(*src == '"' && src[1] == '\\') { skip_fmt_null(&src, "\"\\ \"%n"); cont = true; } else cont = false; } while(cont); if(!src || !*src) return NULL; return ++src; } /** * Skips string @p exp at the current string pointed to by @p str, * but only if it's a separated word, i.e. if there's a char after the string, * it mus be a slash or whitespace. * * @return The position after the word, or NULL if the word was not present * at @p str . */ static const char* skip_word(const char* exp, const char** str) { size_t explen = strlen(exp); const char* cur = *str; int match = (!strncmp(exp, cur, explen) && (!cur[explen] || cur[explen] == '/' || isspace(cur[explen]))); if(match) { *str += explen; return *str; } else return NULL; } /** * Tries to skip the next identifier beginning at @p str * * @return The position after the identifier, or NULL if there's no identifier * at @p str . */ static const char* skip_identifier(const char* str) { if(!isalpha(*str) && *str != '_') return NULL; else { ++str; for(; isalnum(*str) || *str == '_'; ++str) ; return str; } } const char* rtosc_skip_next_printed_arg(const char* src) { switch(*src) { case 't': if(!skip_word("true", &src)) src = skip_identifier(src); break; case 'f': if(!skip_word("false", &src)) src = skip_identifier(src); break; case 'n': if(!skip_word("nil", &src)) if(!skip_word("now", &src)) src = skip_identifier(src); break; case 'i': if(!skip_word("inf", &src)) if(!skip_word("immediately", &src)) src = skip_identifier(src); break; case '#': for(size_t i = 0; i<8; ++i) { ++src; if(!isxdigit(*src)) { src = NULL; i = 8; } } if(src) ++src; break; case '\'': { int esc = -1; if(strlen(src) < 3) return NULL; // type 1: '' => normal char // type 2: '\' => escaped char // type 3: '\'' => escaped quote // type 4: '\' => mistyped backslash if(src[1] == '\\') { if(src[2] == '\'' && (!src[3] || isspace(src[3]))) { // type 4 // the user inputs '\', which is wrong, but // we accept it as a backslash anyways } else { ++src; // type 2 or 3 esc = get_escaped_char(src[1], 1); } } // if the last char was no single quote, // or we had an invalid escape sequence, return NULL src = (!esc || src[2] != '\'') ? NULL : (src + 3); break; } case '"': src = end_of_printed_string(src); if(src && *src == 'S') ++src; break; case 'M': if(!strncmp("MIDI", src, 4) && (isspace(src[4]) || src[4] == '[')) skip_fmt_null(&src, "MIDI [ 0x%*x 0x%*x 0x%*x 0x%*x ]%n"); else src = skip_identifier(src); break; case '[': { int rd = 0, blobsize = 0; sscanf(src, "[ %i %n", &blobsize, &rd); src = rd ? (src + rd) : NULL; for(;src && *src == '0';) // i.e. 0x... { skip_fmt_null(&src, "0x%*x %n"); blobsize--; } if(blobsize) src = NULL; if(src) src = (*src == ']') ? (src + 1) : NULL; break; } default: { // is it an identifier? if(*src == '_' || isalpha(*src)) { for(; *src == '_' || isalnum(*src); ++src) ; } // is it a date? (vs a numeric) else if(skip_fmt(&src, "%*4d-%*1d%*1d-%*1d%*1d%n")) { if(skip_fmt(&src, " %*2d:%*1d%*1d%n")) if(skip_fmt(&src, ":%*1d%*1d%n")) if(skip_fmt(&src, ".%*d%n")) { if(skip_fmt(&src, " ( ... + 0x%n")) { skip_fmt(&src, "%*x.%n"); if(skip_fmt(&src, "%*xp%n")) { int rd = 0, expm; sscanf(src, "-%d s )%n", &expm, &rd); if(rd && expm > 0 && expm <= 32) { // ok src += rd; } else src = NULL; } else src = NULL; } } } else { char type; int rd = skip_numeric(&src, &type); if(!rd) { src = NULL; break; } const char* after_num = src; skip_while(&after_num, isspace); if(*after_num == '(') { if (type == 'f' || type =='d') { // skip lossless representation src = ++after_num; skip_while(&src, isspace); rd = skip_numeric(&src, NULL); if(!rd) { src = NULL; break; } skip_fmt_null(&src, " )%n"); } else src = NULL; } } } } return src; } int rtosc_count_printed_arg_vals(const char* src) { int num = 0; skip_while(&src, isspace); while (*src == '%') skip_fmt(&src, "%*[^\n] %n"); for(; src && *src && *src != '/'; ++num) { src = rtosc_skip_next_printed_arg(src); if(src) // parse error { skip_while(&src, isspace); if(*src && !isspace(*src)) { while (*src == '%') skip_fmt(&src, "%*[^\n] %n"); } } } return src ? num : -num; } int rtosc_count_printed_arg_vals_of_msg(const char* msg) { skip_while(&msg, isspace); while (*msg == '%') skip_fmt(&msg, "%*[^\n] %n"); if (*msg == '/') { for(; *msg && !isspace(*msg); ++msg); return rtosc_count_printed_arg_vals(msg); } else if(!*msg) return INT_MIN; else return -1; } //! Tries to parse an identifier at @p src and stores it in @p arg const char* parse_identifier(const char* src, rtosc_arg_val_t *arg, char* buffer_for_strings, size_t* bufsize) { if(*src == '_' || isalpha(*src)) { arg->type = 'S'; arg->val.s = buffer_for_strings; for(; *src == '_' || isalnum(*src); ++src) { --*bufsize; assert(*bufsize); *buffer_for_strings = *src; ++buffer_for_strings; } --*bufsize; assert(*bufsize); *buffer_for_strings = 0; ++buffer_for_strings; } return src; } size_t rtosc_scan_arg_val(const char* src, rtosc_arg_val_t *arg, char* buffer_for_strings, size_t* bufsize) { int rd = 0; const char* start = src; switch(*src) { case 't': case 'f': case 'n': case 'i': { const char* src_backup = src; // timestamps "immediately" or "now"? if(skip_word("immediately", &src) || skip_word("now", &src)) { arg->type = 't'; arg->val.t = 1; } else if(skip_word("nil", &src) || skip_word("inf", &src) || skip_word("true", &src) || skip_word("false", &src) ) { arg->type = arg->val.T = toupper(*src_backup); } else { // no reserved keyword => identifier src = parse_identifier(src, arg, buffer_for_strings, bufsize); } break; } case '#': { arg->type = 'r'; sscanf(++src, "%x", &arg->val.i); src+=8; break; } case '\'': // type 1: '' => normal char // type 2: '\' => escaped char // type 3: '\'' => escaped quote // type 4: '\' => mistyped backslash arg->type = 'c'; if(*++src == '\\') { if(src[2] && !isspace(src[2])) // escaped and 4 chars arg->val.i = get_escaped_char(*++src, true); else // escaped, but only 3 chars: type 4 arg->val.i = '\\'; } else // non-escaped arg->val.i = *src; src+=2; break; case '"': { ++src; // skip obligatory '"' char* dest = buffer_for_strings; bool cont; do { while(*src != '"') { (*bufsize)--; assert(*bufsize); if(*src == '\\') { *dest++ = get_escaped_char(*++src, false); ++src; } else *dest++ = *src++; } if(src[1] == '\\') { skip_fmt(&src, "\"\\ \"%n"); cont = true; } else cont = false; } while (cont); *dest = 0; ++src; // skip final '"' (*bufsize)--; arg->val.s = buffer_for_strings; if(*src == 'S') { ++src; (*bufsize)--; arg->type = 'S'; } else arg->type = 's'; break; } case 'M': { if(!strncmp("MIDI", src, 4) && (isspace(src[4]) || src[4] == '[')) { arg->type = 'm'; int32_t tmp[4]; sscanf(src, "MIDI [ 0x%"PRIx32" 0x%"PRIx32 " 0x%"PRIx32" 0x%"PRIx32" ]%n", tmp, tmp + 1, tmp + 2, tmp + 3, &rd); src+=rd; for(size_t i = 0; i < 4; ++i) arg->val.m[i] = tmp[i]; // copy to 8 bit array } else src = parse_identifier(src, arg, buffer_for_strings, bufsize); break; } case '[': // blob { arg->type = 'b'; while( isspace(*++src) ) ; sscanf(src, "%"PRIi32" %n", &arg->val.b.len, &rd); src +=rd; assert(*bufsize >= (size_t)arg->val.b.len); *bufsize -= (size_t)arg->val.b.len; arg->val.b.data = (uint8_t*)buffer_for_strings; for(int32_t i = 0; i < arg->val.b.len; ++i) { int32_t tmp; int rd; sscanf(src, "0x%x %n", &tmp, &rd); arg->val.b.data[i] = tmp; src+=rd; } ++src; // skip ']' break; } default: // is it an identifier? if(*src == '_' || isalpha(*src)) { arg->type = 'S'; arg->val.s = buffer_for_strings; for(; *src == '_' || isalnum(*src); ++src) { --*bufsize; assert(*bufsize); *buffer_for_strings = *src; ++buffer_for_strings; } --*bufsize; assert(*bufsize); *buffer_for_strings = 0; ++buffer_for_strings; } // "YYYY-" => it's a date else if(src[0] && src[1] && src[2] && src[3] && src[4] == '-') { arg->val.t = 0; struct tm m_tm; m_tm.tm_hour = 0; m_tm.tm_min = 0; m_tm.tm_sec = 0; sscanf(src, "%4d-%2d-%2d%n", &m_tm.tm_year, &m_tm.tm_mon, &m_tm.tm_mday, &rd); src+=rd; float secfracsf; rd = 0; sscanf(src, " %2d:%2d%n", &m_tm.tm_hour, &m_tm.tm_min, &rd); if(rd) src+=rd; rd = 0; sscanf(src, ":%2d%n", &m_tm.tm_sec, &rd); if(rd) src+=rd; // lossless format is appended in parantheses? // => take it directly from there if(skip_fmt(&src, "%*f (%n")) { sscanf(src, " ... + 0x%8"PRIx64"p-32 s )%n", &arg->val.t, &rd); src += rd; } // float number, but not lossless? // => convert it to fractions of seconds else if(*src == '.') { sscanf(src, "%f%n", &secfracsf, &rd); src += rd; // convert float -> secfracs char secfracs_as_hex[16]; asnprintf(secfracs_as_hex, 16, "%a", secfracsf); assert(secfracs_as_hex[3]=='.'); // 0x?. secfracs_as_hex[3] = secfracs_as_hex[2]; // remove '.' uint64_t secfracs; int exp; sscanf(secfracs_as_hex + 3, "%"PRIx64"p-%i", &secfracs, &exp); const char* p = strchr(secfracs_as_hex, 'p'); assert(p); int lshift = 32-exp-((int)(p-(secfracs_as_hex+4))<<2); assert(lshift > 0); secfracs <<= lshift; assert((secfracs & 0xFFFFFFFF) == secfracs); arg->val.t = secfracs; } else { // no fractional / floating seconds part } // adjust ranges to be POSIX conform m_tm.tm_year -= 1900; --m_tm.tm_mon; // don't mess around with Daylight Saving Time m_tm.tm_isdst = -1; arg->val.t |= (((uint64_t)mktime(&m_tm)) << ((uint64_t)32)); arg->type = 't'; } else { char bytes8[8]; char type = arg->type = 0; bool repeat_once = false; do { rd = 0; const char *fmtstr = scanf_fmtstr_scan(src, bytes8, &type); if(!arg->type) // the first occurence determins the type arg->type = type; switch(type) { case 'h': sscanf(src, fmtstr, &arg->val.h, &rd); break; case 'i': sscanf(src, fmtstr, &arg->val.i, &rd); break; case 'f': sscanf(src, fmtstr, &arg->val.f, &rd); break; case 'd': sscanf(src, fmtstr, &arg->val.d, &rd); break; } src += rd; if(repeat_once) { // we have read the lossless part. skip spaces and ')' skip_fmt(&src, " )%n"); repeat_once = false; } else { // is a lossless part appended in parantheses? const char* after_num = src; skip_while(&after_num, isspace); if(*after_num == '(') { ++after_num; skip_while(&after_num, isspace); src = after_num; repeat_once = true; } } } while(repeat_once); } // date vs integer // case ident } // switch return (size_t)(src-start); } size_t rtosc_scan_arg_vals(const char* src, rtosc_arg_val_t *args, size_t n, char* buffer_for_strings, size_t bufsize) { size_t last_bufsize; size_t rd=0; for(size_t i = 0; i < n; ++i) { last_bufsize = bufsize; size_t tmp = rtosc_scan_arg_val(src, args + i, buffer_for_strings, &bufsize); src += tmp; rd += tmp; size_t written = last_bufsize - bufsize; buffer_for_strings += written; do { rd += skip_fmt(&src, " %n"); while(*src == '%') rd += skip_fmt(&src, "%*[^\n]%n"); } while(isspace(*src)); } return rd; } size_t rtosc_scan_message(const char* src, char* address, size_t adrsize, rtosc_arg_val_t *args, size_t n, char* buffer_for_strings, size_t bufsize) { size_t rd = 0; for(;*src && isspace(*src); ++src) ++rd; while (*src == '%') rd += skip_fmt(&src, "%*[^\n] %n"); assert(*src == '/'); for(; *src && !isspace(*src) && rd < adrsize; ++rd) *address++ = *src++; assert(rd < adrsize); // otherwise, the address was too long *address = 0; for(;*src && isspace(*src); ++src) ++rd; rd += rtosc_scan_arg_vals(src, args, n, buffer_for_strings, bufsize); return rd; }