END_TEST START_TEST(Whitespace_terminates_decimal_number) { Streader* sr = init_with_cstr("- 1"); double num = NAN; fail_if(Streader_read_float(sr, &num), "Streader accepted \"- 1\" as a float"); sr = init_with_cstr("-1 .5"); num = NAN; fail_if(!Streader_read_float(sr, &num), "Could not read float from \"-1 .5\": %s", Streader_get_error_desc(sr)); fail_if(num != -1, "Streader read %f instead of -1 from \"-1 .5\"", num); sr = init_with_cstr("-1. 5"); num = NAN; fail_if(Streader_read_float(sr, &num), "Streader accepted \"-1.\" as a float"); sr = init_with_cstr("-1 e5"); num = NAN; fail_if(!Streader_read_float(sr, &num), "Could not read float from \"-1 e5\": %s", Streader_get_error_desc(sr)); fail_if(num != -1, "Streader read %f instead of -1 from \"-1 e5\"", num); sr = init_with_cstr("-1e 5"); num = NAN; fail_if(Streader_read_float(sr, &num), "Streader accepted \"-1e\" as a float"); }
END_TEST START_TEST(Read_zero_float) { const char* zeros[] = { "0 x", "0.0 x", "0e0 x", "0.0e0 x", "0.0e+0 x", "0.0e-0 x", }; for (size_t i = 0; i < arr_size(zeros); ++i) { Streader* sr = init_with_cstr(zeros[i]); double num = NAN; fail_if(!Streader_read_float(sr, &num), "Could not read 0 from \"%s\": %s", zeros[i], Streader_get_error_desc(sr)); fail_if(num != 0, "Streader stored %f instead of 0 from \"%s\"", num, zeros[i]); fail_if(!Streader_match_char(sr, 'x'), "Streader did not consume 0 from \"%s\" correctly: %s", zeros[i], Streader_get_error_desc(sr)); } // TODO: The code below does not test the sign of negative zero // as C99 doesn't guarantee it. // Revisit when migrating to emulated floats. const char* neg_zeros[] = { "-0 x", "-0.0 x", "-0e0 x", "-0.0e0 x", "-0.0e+0 x", "-0.0e-0 x", }; for (size_t i = 0; i < arr_size(neg_zeros); ++i) { Streader* sr = init_with_cstr(neg_zeros[i]); double num = NAN; fail_if(!Streader_read_float(sr, &num), "Could not read -0 from \"%s\": %s", neg_zeros[i], Streader_get_error_desc(sr)); fail_if(num != 0, "Streader stored %f instead of -0 from \"%s\"", num, neg_zeros[i]); fail_if(!Streader_match_char(sr, 'x'), "Streader did not consume -0 from \"%s\" correctly", neg_zeros[i]); } }
bool Module_read_force_shift(Module* module, Streader* sr) { rassert(module != NULL); rassert(sr != NULL); if (Streader_is_error_set(sr)) return false; double shift = 0; if (Streader_has_data(sr)) { if (!Streader_read_float(sr, &shift)) return false; if (!isfinite(shift) || (shift < -60) || (shift > 0)) { Streader_set_error(sr, "Invalid force shift: %.4f", shift); return false; } } module->force_shift = shift; return true; }
bool Module_read_mixing_volume(Module* module, Streader* sr) { rassert(module != NULL); rassert(sr != NULL); if (Streader_is_error_set(sr)) return false; double mix_vol = COMP_DEFAULT_MIX_VOL; if (Streader_has_data(sr)) { if (!Streader_read_float(sr, &mix_vol)) return false; if (!isfinite(mix_vol) && mix_vol != -INFINITY) { Streader_set_error(sr, "Invalid mixing volume: %.4f", mix_vol); return false; } } module->mix_vol_dB = mix_vol; module->mix_vol = exp2(module->mix_vol_dB / 6); return true; }
bool Streader_read_finite_rt(Streader* sr, Value* dest) { rassert(sr != NULL); if (Streader_is_error_set(sr)) return false; Streader_skip_whitespace(sr); const int64_t start_pos = sr->pos; Value* value = VALUE_AUTO; if (Streader_read_bool(sr, &value->value.bool_type)) value->type = VALUE_TYPE_BOOL; else if ((recover(sr, start_pos), Streader_read_int(sr, &value->value.int_type)) && (CUR_CH != '.') && (CUR_CH != 'e') && (CUR_CH != 'E')) value->type = VALUE_TYPE_INT; else if (recover(sr, start_pos), (Streader_read_float(sr, &value->value.float_type) && isfinite(value->value.float_type))) value->type = VALUE_TYPE_FLOAT; else if (recover(sr, start_pos), Streader_read_tstamp(sr, &value->value.Tstamp_type)) value->type = VALUE_TYPE_TSTAMP; if (value->type == VALUE_TYPE_NONE) return false; if (dest != NULL) Value_copy(dest, value); return true; }
END_TEST START_TEST(Read_nonzero_float) { const double nums[] = { 0.5, 1.0, 1.5, -0.5, -1.0, -1.5, 0.0625, 10.0, }; const char* formats[] = { "%.4f x", "%f x", "%e x", }; for (size_t i = 0; i < arr_size(nums); ++i) { for (size_t k = 0; k < arr_size(formats); ++k) { char data[128] = ""; sprintf(data, formats[k], nums[i]); Streader* sr = init_with_cstr(data); double num = NAN; fail_if(!Streader_read_float(sr, &num), "Could not read float from \"%s\": %s", data, Streader_get_error_desc(sr)); fail_if(num != nums[i], "Streader stored %f instead of %.6f from \"%s\"", num, nums[i]); fail_if(!Streader_match_char(sr, 'x'), "Streader did not consume float from \"%s\" correctly", data); } } }
static bool Streader_read_tuning(Streader* sr, double* cents) { rassert(sr != NULL); rassert(cents != NULL); if (Streader_is_error_set(sr)) return false; if (Streader_try_match_char(sr, '[')) { int64_t num = 0; int64_t den = 0; if (!Streader_readf(sr, "%i,%i]", &num, &den)) return false; if (num <= 0) { Streader_set_error(sr, "Numerator must be positive"); return false; } if (den <= 0) { Streader_set_error(sr, "Denominator must be positive"); return false; } *cents = log2((double)num / (double)den) * 1200; } else { if (!Streader_read_float(sr, cents)) return false; if (!isfinite(*cents)) { Streader_set_error(sr, "Cents value must be finite"); return false; } } return true; }
END_TEST static bool fail_at_key_2(Streader* sr, const char* key, void* userdata) { (void)sr; (void)userdata; fail_if(strcmp(key, "2") > 0, "Dictionary processing continued after failure"); if (strcmp(key, "2") == 0) return false; fail_if(!Streader_read_float(sr, NULL), "Could not read a float from dictionary: %s", Streader_get_error_desc(sr)); return true; }
static bool read_num(Streader* sr, int32_t index, void* userdata) { rassert(sr != NULL); ignore(index); rassert(userdata != NULL); Num_list* nl = userdata; double num = NAN; if (!Streader_read_float(sr, &num)) return false; if (!Num_list_append(nl, num)) { del_Num_list(nl); Streader_set_memory_error( sr, "Could not allocate memory for number list"); return false; } return true; }
static bool read_tuning_table_item(Streader* sr, const char* key, void* userdata) { rassert(sr != NULL); rassert(key != NULL); rassert(userdata != NULL); Tuning_table* tt = userdata; if (string_eq(key, "ref_note")) { int64_t num = 0; if (!Streader_read_int(sr, &num)) return false; if (num < 0 || num >= KQT_TUNING_TABLE_NOTES_MAX) { Streader_set_error( sr, "Invalid reference note number: %" PRId64, num); return false; } tt->ref_note = (int)num; } else if (string_eq(key, "ref_pitch")) { double num = NAN; if (!Streader_read_float(sr, &num)) return false; if (!isfinite(num)) { Streader_set_error(sr, "Invalid reference pitch: %f", num); return false; } tt->ref_pitch = num; } else if (string_eq(key, "pitch_offset")) { double cents = NAN; if (!Streader_read_float(sr, ¢s)) return false; if (!isfinite(cents)) { Streader_set_error(sr, "Pitch offset is not finite"); return false; } tt->global_offset = cents; } else if (string_eq(key, "octave_width")) { double cents = NAN; if (!Streader_read_tuning(sr, ¢s)) return false; if (cents <= 0) { Streader_set_error(sr, "Octave width must be positive"); return false; } Tuning_table_set_octave_width(tt, cents); } else if (string_eq(key, "centre_octave")) { int64_t octave = -1; if (!Streader_read_int(sr, &octave)) return false; if (octave < 0 || octave >= KQT_TUNING_TABLE_OCTAVES) { Streader_set_error(sr, "Invalid centre octave: %" PRId64, octave); return false; } tt->centre_octave = (int)octave; Tuning_table_set_octave_width(tt, tt->octave_width); } else if (string_eq(key, "notes")) { for (int i = 0; i < KQT_TUNING_TABLE_NOTES_MAX; ++i) tt->note_offsets[i] = NAN; if (!Streader_read_list(sr, read_note, tt)) return false; } else { Streader_set_error(sr, "Unrecognised key in tuning table: %s", key); return false; } return true; }
bool Streader_readf(Streader* sr, const char* format, ...) { rassert(sr != NULL); rassert(format != NULL); va_list args; va_start(args, format); while (!Streader_is_error_set(sr) && *format != '\0') { if (*format == '%') { // Conversion characters ++format; switch (*format) { case 'n': { Streader_read_null(sr); } break; case 'b': { bool* dest = va_arg(args, bool*); Streader_read_bool(sr, dest); } break; case 'i': { int64_t* dest = va_arg(args, int64_t*); Streader_read_int(sr, dest); } break; case 'f': { double* dest = va_arg(args, double*); Streader_read_float(sr, dest); } break; case 's': { Streader_readf_str_info info = va_arg(args, Streader_readf_str_info); rassert(info.guard == Streader_readf_str_guard); const int64_t max_bytes = info.max_bytes; char* dest = info.dest; Streader_read_string(sr, max_bytes, dest); } break; case 't': { Tstamp* dest = va_arg(args, Tstamp*); Streader_read_tstamp(sr, dest); } break; case 'p': { Pat_inst_ref* dest = va_arg(args, Pat_inst_ref*); Streader_read_piref(sr, dest); } break; case 'l': { List_item_reader* ir = va_arg(args, List_item_reader*); void* userdata = va_arg(args, void*); Streader_read_list(sr, ir, userdata); } break; case 'd': { Dict_item_reader* ir = va_arg(args, Dict_item_reader*); void* userdata = va_arg(args, void*); Streader_read_dict(sr, ir, userdata); } break; case '%': { Streader_match_char(sr, '%'); } break; default: rassert(false); } } else { // Characters to be matched if (!isspace(*format))
Env_var* new_Env_var_from_string(Streader* sr) { rassert(sr != NULL); if (Streader_is_error_set(sr)) return NULL; char type_name[16] = ""; char name[KQT_VAR_NAME_MAX] = ""; if (!Streader_readf( sr, "[%s,%s,", READF_STR(16, type_name), READF_STR(KQT_VAR_NAME_MAX, name))) return NULL; if (!is_valid_var_name(name)) { Streader_set_error( sr, "Illegal variable name %s" " (Variable names may only contain" " lower-case letters and underscores" " (and digits as other than first characters))", name); return NULL; } Value* value = VALUE_AUTO; if (string_eq(type_name, "bool")) { value->type = VALUE_TYPE_BOOL; Streader_read_bool(sr, &value->value.bool_type); } else if (string_eq(type_name, "int")) { value->type = VALUE_TYPE_INT; Streader_read_int(sr, &value->value.int_type); } else if (string_eq(type_name, "float")) { value->type = VALUE_TYPE_FLOAT; Streader_read_float(sr, &value->value.float_type); } else if (string_eq(type_name, "timestamp")) { value->type = VALUE_TYPE_TSTAMP; Streader_read_tstamp(sr, &value->value.Tstamp_type); } else { Streader_set_error( sr, "Invalid type of environment variable %s: %s", name, type_name); return NULL; } if (!Streader_match_char(sr, ']')) return NULL; Env_var* var = new_Env_var(value->type, name); if (var == NULL) { Streader_set_memory_error( sr, "Could not allocate memory for environment variable"); return NULL; } Env_var_set_value(var, value); return var; }