/*************************************************************************** options.c Core options code code Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. Visit http://mamedev.org for licensing and usage restrictions. ***************************************************************************/ #include #include #include "options.h" #include "astring.h" /*************************************************************************** CONSTANTS ***************************************************************************/ #define MAX_ENTRY_NAMES 4 /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ /* forward references */ typedef struct _options_data options_data; /* range parameter for minimum/maximum values */ typedef union _options_range_parameter options_range_parameter; union _options_range_parameter { float f; int i; }; /* hash table entries */ typedef struct _options_hash_entry options_hash_entry; struct _options_hash_entry { options_hash_entry * next; /* link to the next entry */ astring * name; /* name under the current link */ options_data * data; /* link to associated options_data */ }; /* information about a single entry in the options */ struct _options_data { options_hash_entry links[MAX_ENTRY_NAMES]; /* array of hash table entries (one per name) */ options_data * next; /* link to the next data */ UINT32 flags; /* flags from the entry */ UINT32 seqid; /* sequence ID; bumped on each change */ int error_reported; /* have we reported an error on this option yet? */ int priority; /* priority of the data set */ astring * data; /* data for this item */ astring * defdata; /* default data for this item */ const char * description; /* description for this item */ options_range_type range_type; /* the type of range to apply to this item */ options_range_parameter range_minimum; /* the minimum of the range */ options_range_parameter range_maximum; /* the maximum of the range */ void (*callback)(core_options *opts, const char *arg); /* callback to be invoked when parsing */ }; /* structure holding information about a collection of options */ struct _core_options { void (*output[OPTMSG_COUNT])(const char *s);/* output callbacks */ /* linked list, for sequential iteration */ options_data * datalist; /* linked list for sequential iteration */ options_data ** datalist_nextptr; /* pointer to pointer to tail entry */ /* hashtable, for fast lookup */ options_hash_entry * hashtable[101]; /* hash table for fast lookup */ }; /* information about an in-progress options enumeration */ struct _options_enumerator { options_data * current; }; /*************************************************************************** FUNCTION PROTOTYPES ***************************************************************************/ static options_data *find_entry_data(core_options *opts, const char *string, int is_command_line); static void update_data(core_options *opts, options_data *data, const char *newdata, int priority); static int parse_option_name(core_options *opts, const char *srcstring, options_data *data); static void message(core_options *opts, options_message msgtype, const char *format, ...); static UINT32 hash_value(core_options *opts, const char *str); static void output_printf(void (*output)(const char *s), const char *format, ...); /*************************************************************************** GLOBAL VARIABLES ***************************************************************************/ const char *option_unadorned[MAX_UNADORNED_OPTIONS] = { "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }; /*************************************************************************** OPTIONS COLLECTION MANAGEMENT ***************************************************************************/ /*------------------------------------------------- options_create - creates a new instance of core options -------------------------------------------------*/ core_options *options_create(void (*fail)(const char *message)) { /* allocate memory for the option block */ core_options *opts = (core_options *)malloc(sizeof(*opts)); if (opts == NULL) goto error; /* and set up the structure */ memset(opts, 0, sizeof(*opts)); opts->datalist_nextptr = &opts->datalist; return opts; error: return NULL; } /*------------------------------------------------- options_free - frees an options object -------------------------------------------------*/ void options_free(core_options *opts) { options_data *data, *next; /* loop over data items and free them */ for (data = opts->datalist; data != NULL; data = next) { int linknum; next = data->next; /* free names */ for (linknum = 0; linknum < ARRAY_LENGTH(data->links); linknum++) if (data->links[linknum].name != NULL) astring_free(data->links[linknum].name); /* free strings */ astring_free(data->data); astring_free(data->defdata); /* free the data itself */ free(data); } /* free the options itself */ free(opts); } /*------------------------------------------------- options_set_output_callback - installs a callback to be invoked when data is outputted in the process of outputting options -------------------------------------------------*/ void options_set_output_callback(core_options *opts, options_message msgtype, void (*callback)(const char *s)) { opts->output[msgtype] = callback; } /*------------------------------------------------- options_revert - revert options at or below a certain priority back to their defaults -------------------------------------------------*/ void options_revert(core_options *opts, int priority) { options_data *data; /* iterate over options and revert to defaults if below the given priority */ for (data = opts->datalist; data != NULL; data = data->next) if (data->priority <= priority) { astring_cpy(data->data, data->defdata); data->priority = OPTION_PRIORITY_DEFAULT; } } /*------------------------------------------------- options_copy - copy options from one core_options to another -------------------------------------------------*/ int options_copy(core_options *dest_opts, core_options *src_opts) { options_data *data; /* iterate over options in the destination */ for (data = dest_opts->datalist; data != NULL; data = data->next) if (!(data->flags & OPTION_HEADER)) { options_data *srcdata = find_entry_data(src_opts, astring_c(data->links[0].name), FALSE); /* if the option exists in the source, set it in the destination */ if (srcdata != NULL) options_set_string(dest_opts, astring_c(srcdata->links[0].name), astring_c(srcdata->data), srcdata->priority); } return TRUE; } /*------------------------------------------------- options_equal - compare two sets of options -------------------------------------------------*/ int options_equal(core_options *opts1, core_options *opts2) { options_data *data; /* iterate over options in the first list */ for (data = opts1->datalist; data != NULL; data = data->next) if (!(data->flags & OPTION_HEADER)) { const char *value1 = options_get_string(opts1, astring_c(data->links[0].name)); const char *value2 = options_get_string(opts2, astring_c(data->links[0].name)); /* if the values differ, return false */ if (strcmp(value1, value2) != 0) return FALSE; } return TRUE; } /*************************************************************************** OPTION DEFINITIONS ***************************************************************************/ /*------------------------------------------------- options_add_entries - add entries to the current options sets -------------------------------------------------*/ int options_add_entries(core_options *opts, const options_entry *entrylist) { /* loop over entries until we hit a NULL name */ for ( ; entrylist->name != NULL || (entrylist->flags & OPTION_HEADER); entrylist++) { options_data *match = NULL; int i; /* allocate a new item */ options_data *data = malloc(sizeof(*data)); if (data == NULL) return FALSE; memset(data, 0, sizeof(*data)); /* parse the option name */ if (entrylist->name != NULL) parse_option_name(opts, entrylist->name, data); /* do we match an existing entry? */ for (i = 0; i < ARRAY_LENGTH(data->links) && match == NULL; i++) if (data->links[i].name != NULL) match = find_entry_data(opts, astring_c(data->links[i].name), FALSE); /* if so, throw away this entry and replace the data */ if (match != NULL) { /* free what we've allocated so far */ for (i = 0; i < ARRAY_LENGTH(data->links); i++) if (data->links[i].name != NULL) astring_free(data->links[i].name); free(data); /* use the matching entry as our data */ data = match; } /* otherwise, finish making the new entry */ else { /* allocate strings */ data->data = astring_alloc(); data->defdata = astring_alloc(); /* copy the flags, and set the value equal to the default */ data->flags = entrylist->flags; data->description = entrylist->description; /* add us to the end of the sequential list */ *opts->datalist_nextptr = data; opts->datalist_nextptr = &data->next; /* add each name to the appropriate hash table with a link back to us */ for (i = 0; i < ARRAY_LENGTH(data->links); i++) if (data->links[i].name != NULL) { int hash_entry = hash_value(opts, astring_c(data->links[i].name)); /* set up link */ data->links[i].data = data; data->links[i].next = opts->hashtable[hash_entry]; opts->hashtable[hash_entry] = &data->links[i]; } } /* copy in the data and default data values */ if (entrylist->defvalue != NULL) { astring_cpyc(data->data, entrylist->defvalue); astring_cpyc(data->defdata, entrylist->defvalue); } data->priority = OPTION_PRIORITY_DEFAULT; } return TRUE; } /*------------------------------------------------- options_set_option_default_value - change the default value of an option -------------------------------------------------*/ int options_set_option_default_value(core_options *opts, const char *name, const char *defvalue) { options_data *data = find_entry_data(opts, name, TRUE); /* if we don't have an entry for this, fail */ if (data == NULL) return FALSE; /* update the data and default data; note that we assume that data == defdata */ astring_cpyc(data->data, defvalue); astring_cpyc(data->defdata, defvalue); data->priority = OPTION_PRIORITY_DEFAULT; return TRUE; } /*------------------------------------------------- options_set_option_callback - specifies a callback to be invoked when parsing options -------------------------------------------------*/ int options_set_option_callback(core_options *opts, const char *name, void (*callback)(core_options *opts, const char *arg)) { options_data *data = find_entry_data(opts, name, TRUE); /* if we don't find an entry, fail */ if (data == NULL) return FALSE; /* set the callback */ data->callback = callback; return TRUE; } /*************************************************************************** OPTION DATA EXTRACTION ***************************************************************************/ /*------------------------------------------------- options_parse_command_line - parse a series of command line arguments -------------------------------------------------*/ int options_parse_command_line(core_options *opts, int argc, char **argv, int priority) { int unadorned_index = 0; int arg; /* loop over commands, looking for options */ for (arg = 1; arg < argc; arg++) { const char *optionname, *newdata; options_data *data; int is_unadorned; /* determine the entry name to search for */ is_unadorned = (argv[arg][0] != '-'); if (!is_unadorned) optionname = &argv[arg][1]; else optionname = OPTION_UNADORNED(unadorned_index); /* find our entry */ data = find_entry_data(opts, optionname, TRUE); if (data == NULL) { message(opts, OPTMSG_ERROR, "Error: unknown option: %s\n", argv[arg]); return 1; } /* if unadorned, we have to bump the count (unless if the option repeats) */ if (is_unadorned && !(data->flags & OPTION_REPEATS)) unadorned_index++; /* get the data for this argument, special casing booleans */ if ((data->flags & (OPTION_BOOLEAN | OPTION_COMMAND)) != 0) newdata = (strncmp(&argv[arg][1], "no", 2) == 0) ? "0" : "1"; else if (argv[arg][0] != '-') newdata = argv[arg]; else if (arg + 1 < argc) newdata = argv[++arg]; else { message(opts, OPTMSG_ERROR, "Error: option %s expected a parameter\n", argv[arg]); return 1; } /* if the option is deprecated or internal, don't process further */ if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL)) != 0) continue; /* invoke callback, if present */ if (data->callback != NULL) (*data->callback)(opts, newdata); /* allocate a new copy of data for this */ update_data(opts, data, newdata, priority); } return 0; } /*------------------------------------------------- options_parse_ini_file - parse a series of entries in an INI file -------------------------------------------------*/ int options_parse_ini_file(core_options *opts, core_file *inifile, int priority) { char buffer[4096]; /* loop over data */ while (core_fgets(buffer, ARRAY_LENGTH(buffer), inifile) != NULL) { char *optionname, *optiondata, *temp; options_data *data; int inquotes = FALSE; /* find the name */ for (optionname = buffer; *optionname != 0; optionname++) if (!isspace(*optionname)) break; /* skip comments */ if (*optionname == 0 || *optionname == '#') continue; /* scan forward to find the first space */ for (temp = optionname; *temp != 0; temp++) if (isspace(*temp)) break; /* if we hit the end early, print a warning and continue */ if (*temp == 0) { message(opts, OPTMSG_WARNING, "Warning: invalid line in INI: %s", buffer); continue; } /* NULL-terminate */ *temp++ = 0; optiondata = temp; /* scan the data, stopping when we hit a comment */ for (temp = optiondata; *temp != 0; temp++) { if (*temp == '"') inquotes = !inquotes; if (*temp == '#' && !inquotes) break; } *temp = 0; /* find our entry */ data = find_entry_data(opts, optionname, FALSE); if (data == NULL) { message(opts, OPTMSG_WARNING, "Warning: unknown option in INI: %s\n", optionname); continue; } if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL)) != 0) continue; /* allocate a new copy of data for this */ update_data(opts, data, optiondata, priority); } return 0; } /*************************************************************************** OPTIONS OUTPUT ***************************************************************************/ /*------------------------------------------------- options_output_diff_ini_file - output the diff of the current state from a base state to an INI file -------------------------------------------------*/ void options_output_diff_ini_file(core_options *opts, core_options *baseopts, core_file *inifile) { options_data *data; const char *last_header = NULL; const char *name; const char *value; options_data *basedata; /* loop over all items */ for (data = opts->datalist; data != NULL; data = data->next) { /* header: record description */ if ((data->flags & OPTION_HEADER) != 0) last_header = data->description; /* otherwise, output entries for all non-deprecated and non-command items (if not in baseopts) */ else if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL | OPTION_COMMAND)) == 0) { /* get name and data of this value */ name = astring_c(data->links[0].name); value = astring_c(data->data); /* look up counterpart in baseopts, if baseopts is specified */ basedata = (baseopts != NULL) ? find_entry_data(baseopts, name, FALSE) : NULL; /* is our data different, or not in baseopts? */ if ((basedata == NULL) || (strcmp(value, astring_c(basedata->data)) != 0)) { /* output header, if we have one */ if (last_header != NULL) { core_fprintf(inifile, "\n#\n# %s\n#\n", last_header); last_header = NULL; } /* and finally output the data */ if (strchr(value, ' ') != NULL) core_fprintf(inifile, "%-25s \"%s\"\n", name, value); else core_fprintf(inifile, "%-25s %s\n", name, value); } } } } /*------------------------------------------------- options_output_ini_file - output the current state to an INI file -------------------------------------------------*/ void options_output_ini_file(core_options *opts, core_file *inifile) { options_output_diff_ini_file(opts, NULL, inifile); } /*------------------------------------------------- options_output_ini_file - output the current state to an INI file -------------------------------------------------*/ void options_output_ini_stdfile(core_options *opts, FILE *inifile) { options_data *data; /* loop over all items */ for (data = opts->datalist; data != NULL; data = data->next) { /* header: just print */ if ((data->flags & OPTION_HEADER) != 0) fprintf(inifile, "\n#\n# %s\n#\n", data->description); /* otherwise, output entries for all non-deprecated and non-command items */ else if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL | OPTION_COMMAND)) == 0) { if (astring_chr(data->data, 0, ' ') != -1) fprintf(inifile, "%-25s \"%s\"\n", astring_c(data->links[0].name), astring_c(data->data)); else fprintf(inifile, "%-25s %s\n", astring_c(data->links[0].name), astring_c(data->data)); } } } /*------------------------------------------------- options_output_help - output option help to a file -------------------------------------------------*/ void options_output_help(core_options *opts, void (*output)(const char *)) { options_data *data; /* loop over all items */ for (data = opts->datalist; data != NULL; data = data->next) { /* header: just print */ if ((data->flags & OPTION_HEADER) != 0) output_printf(output, "\n#\n# %s\n#\n", data->description); /* otherwise, output entries for all non-deprecated items */ else if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL)) == 0 && data->description != NULL) output_printf(output, "-%-20s%s\n", astring_c(data->links[0].name), data->description); } } /*************************************************************************** OPTIONS READING ***************************************************************************/ /*------------------------------------------------- options_get_string - return data formatted as a string -------------------------------------------------*/ const char *options_get_string(core_options *opts, const char *name) { options_data *data = find_entry_data(opts, name, FALSE); const char *value = ""; /* error if not found */ if (data == NULL) message(opts, OPTMSG_ERROR, "Unexpected option %s queried\n", name); /* copy if non-NULL */ else value = astring_c(data->data); return value; } /*------------------------------------------------- options_get_bool - return data formatted as a boolean -------------------------------------------------*/ int options_get_bool(core_options *opts, const char *name) { options_data *data = find_entry_data(opts, name, FALSE); int value = FALSE; /* error if not found */ if (data == NULL) message(opts, OPTMSG_ERROR, "Unexpected boolean option %s queried\n", name); /* also error if we don't have a valid boolean value */ else if (sscanf(astring_c(data->data), "%d", &value) != 1 || value < 0 || value > 1) { options_set_string(opts, name, astring_c(data->defdata), 0); sscanf(astring_c(data->data), "%d", &value); if (!data->error_reported) { message(opts, OPTMSG_ERROR, "Illegal boolean value for %s; reverting to %d\n", astring_c(data->links[0].name), value); data->error_reported = TRUE; } } return value; } /*------------------------------------------------- options_get_int - return data formatted as an integer -------------------------------------------------*/ int options_get_int(core_options *opts, const char *name) { options_data *data = find_entry_data(opts, name, FALSE); int value = 0; /* error if not found */ if (data == NULL) message(opts, OPTMSG_ERROR, "Unexpected integer option %s queried\n", name); /* also error if we don't have a valid integer value */ else if (sscanf(astring_c(data->data), "%d", &value) != 1) { options_set_string(opts, name, astring_c(data->defdata), 0); sscanf(astring_c(data->data), "%d", &value); if (!data->error_reported) { message(opts, OPTMSG_ERROR, "Illegal integer value for %s; reverting to %d\n", astring_c(data->links[0].name), value); data->error_reported = TRUE; } } return value; } /*------------------------------------------------- options_get_float - return data formatted as a float -------------------------------------------------*/ float options_get_float(core_options *opts, const char *name) { options_data *data = find_entry_data(opts, name, FALSE); float value = 0; /* error if not found */ if (data == NULL) message(opts, OPTMSG_ERROR, "Unexpected float option %s queried\n", name); /* also error if we don't have a valid floating point value */ else if (sscanf(astring_c(data->data), "%f", &value) != 1) { options_set_string(opts, name, astring_c(data->defdata), 0); sscanf(astring_c(data->data), "%f", &value); if (!data->error_reported) { message(opts, OPTMSG_ERROR, "Illegal float value for %s; reverting to %f\n", astring_c(data->links[0].name), (double)value); data->error_reported = TRUE; } } return value; } /*------------------------------------------------- options_get_seqid - return the seqid for an entry -------------------------------------------------*/ UINT32 options_get_seqid(core_options *opts, const char *name) { options_data *data = find_entry_data(opts, name, FALSE); return (data == NULL) ? 0 : data->seqid; } /*************************************************************************** OPTIONS SETTING ***************************************************************************/ /*------------------------------------------------- options_set_string - set a string value -------------------------------------------------*/ void options_set_string(core_options *opts, const char *name, const char *value, int priority) { options_data *data = find_entry_data(opts, name, FALSE); update_data(opts, data, value, priority); } /*------------------------------------------------- options_set_bool - set a boolean value -------------------------------------------------*/ void options_set_bool(core_options *opts, const char *name, int value, int priority) { char temp[4]; sprintf(temp, "%d", value ? 1 : 0); options_set_string(opts, name, temp, priority); } /*------------------------------------------------- options_set_int - set an integer value -------------------------------------------------*/ void options_set_int(core_options *opts, const char *name, int value, int priority) { char temp[20]; sprintf(temp, "%d", value); options_set_string(opts, name, temp, priority); } /*------------------------------------------------- options_set_float - set a float value -------------------------------------------------*/ void options_set_float(core_options *opts, const char *name, float value, int priority) { char temp[100]; sprintf(temp, "%f", value); options_set_string(opts, name, temp, priority); } /*************************************************************************** OPTION DEFINITION QUERIES ***************************************************************************/ /*------------------------------------------------- options_enumerator_begin - retrieve the range of a float option -------------------------------------------------*/ options_enumerator *options_enumerator_begin(core_options *opts) { options_enumerator *enumerator; /* allocate memory for the enumerator */ enumerator = malloc(sizeof(*enumerator)); if (enumerator == NULL) return NULL; /* start at the head of the list */ enumerator->current = opts->datalist; return enumerator; } /*------------------------------------------------- options_enumerator_next - returns the current option and advances the enumerator -------------------------------------------------*/ const char *options_enumerator_next(options_enumerator *enumerator) { astring *option_name = NULL; /* be sure to skip over false options */ while (option_name == NULL && enumerator->current != NULL) { /* retrieve the current option name and advance the enumerator */ option_name = enumerator->current->links[0].name; enumerator->current = enumerator->current->next; } return (option_name != NULL) ? astring_c(option_name) : NULL; } /*------------------------------------------------- options_enumerator_free - disposes an options enumerator -------------------------------------------------*/ void options_enumerator_free(options_enumerator *enumerator) { free(enumerator); } /*------------------------------------------------- options_get_range_type - determine the type of range for a particular option -------------------------------------------------*/ options_range_type options_get_range_type(core_options *opts, const char *name) { options_data *data = find_entry_data(opts, name, FALSE); return data->range_type; } /*------------------------------------------------- options_get_range_int - retrieve the range of an integer option -------------------------------------------------*/ void options_get_range_int(core_options *opts, const char *name, int *minval, int *maxval) { options_data *data = find_entry_data(opts, name, FALSE); *minval = data->range_minimum.i; *maxval = data->range_maximum.i; } /*------------------------------------------------- options_get_range_float - retrieve the range of a float option -------------------------------------------------*/ void options_get_range_float(core_options *opts, const char *name, float *minval, float *maxval) { options_data *data = find_entry_data(opts, name, FALSE); *minval = data->range_minimum.f; *maxval = data->range_maximum.f; } /*************************************************************************** INTERNAL UTILITIES ***************************************************************************/ /*------------------------------------------------- find_entry_data - locate an entry whose name matches the given string -------------------------------------------------*/ static options_data *find_entry_data(core_options *opts, const char *string, int is_command_line) { int hash_entry = hash_value(opts, string); options_hash_entry *link; /* scan all entries */ for (link = opts->hashtable[hash_entry]; link != NULL; link = link->next) if (!(link->data->flags & OPTION_HEADER) && link->name != NULL && astring_cmpc(link->name, string) == 0) return link->data; /* haven't found it? if we are prefixed with "no", then try to search for that */ if (is_command_line && string[0] == 'n' && string[1] == 'o') { options_data *data = find_entry_data(opts, &string[2], FALSE); if (data != NULL && (data->flags & OPTION_BOOLEAN)) return data; } /* didn't find it at all */ return NULL; } /*------------------------------------------------- update_data - update the data value for a given entry -------------------------------------------------*/ static void update_data(core_options *opts, options_data *data, const char *newdata, int priority) { const char *dataend = newdata + strlen(newdata) - 1; const char *datastart = newdata; float f; int i; /* strip off leading/trailing spaces */ while (isspace(*datastart) && datastart <= dataend) datastart++; while (isspace(*dataend) && datastart <= dataend) dataend--; /* strip off quotes */ if (datastart != dataend && *datastart == '"' && *dataend == '"') datastart++, dataend--; /* check against range */ switch (data->range_type) { case OPTION_RANGE_NONE: /* do nothing */ break; case OPTION_RANGE_INT: /* check against integer range */ i = 0; if (sscanf(datastart, "%d", &i) != 1) { message(opts, OPTMSG_ERROR, "Illegal integer value for %s; keeping value of %s\n", astring_c(data->links[0].name), astring_c(data->data)); data->error_reported = TRUE; return; } if (i < data->range_minimum.i || i > data->range_maximum.i) { message(opts, OPTMSG_ERROR, "Invalid %s value (must be between %i and %i); keeping value of %s\n", astring_c(data->links[0].name), data->range_minimum.i, data->range_maximum.i, astring_c(data->data)); data->error_reported = TRUE; return; } break; case OPTION_RANGE_FLOAT: /* check against float range */ f = 0; if (sscanf(datastart, "%f", &f) != 1) { message(opts, OPTMSG_ERROR, "Illegal float value for %s; keeping value of %s\n", astring_c(data->links[0].name), astring_c(data->data)); data->error_reported = TRUE; return; } if (f < data->range_minimum.f || f > data->range_maximum.f) { message(opts, OPTMSG_ERROR, "Invalid %s value (must be between %f and %f); keeping value of %s\n", astring_c(data->links[0].name), data->range_minimum.f, data->range_maximum.f, astring_c(data->data)); data->error_reported = TRUE; return; } break; } /* ignore if we don't have priority */ if (priority < data->priority) return; /* allocate a copy of the data */ astring_cpych(data->data, datastart, dataend + 1 - datastart); data->priority = priority; /* bump the seqid and clear the error reporting */ data->seqid++; data->error_reported = FALSE; } /*------------------------------------------------- parse_option_name - read data from an option_entry name into an option_data structure -------------------------------------------------*/ static int parse_option_name(core_options *opts, const char *srcstring, options_data *data) { const char *start; const char *end = NULL; int curentry; /* start with the original string and loop over entries */ start = srcstring; for (curentry = 0; curentry < ARRAY_LENGTH(data->links); curentry++) { /* find the end of this entry and copy the string */ for (end = start; *end != 0 && *end != ';' && *end != '('; end++) ; data->links[curentry].name = astring_dupch(start, end - start); /* if we hit the end of the source, stop */ if (*end != ';') break; start = end + 1; } /* have we found a range? */ if (end != NULL && *end == '(') { if (sscanf(end, "(%d-%d)", &data->range_minimum.i, &data->range_maximum.i) == 2) data->range_type = OPTION_RANGE_INT; else if (sscanf(end, "(%f-%f)", &data->range_minimum.f, &data->range_maximum.f) == 2) data->range_type = OPTION_RANGE_FLOAT; } return curentry; } /*------------------------------------------------- message - outputs a message to a listener -------------------------------------------------*/ static void message(core_options *opts, options_message msgtype, const char *format, ...) { char buf[1024]; va_list argptr; /* output a message if there is a non-NULL handler for it */ if (opts->output[msgtype] != NULL) { va_start(argptr, format); vsprintf(buf, format, argptr); va_end(argptr); (*opts->output[msgtype])(buf); } } /*------------------------------------------------- hash_value - computes the hash value for a string -------------------------------------------------*/ static UINT32 hash_value(core_options *opts, const char *str) { UINT32 hash = 5381; int c; while ((c = *str++) != 0) hash = ((hash << 5) + hash) + c; return hash % ARRAY_LENGTH(opts->hashtable); } /*------------------------------------------------- output_printf - outputs an arbitrary message to a callback -------------------------------------------------*/ static void output_printf(void (*output)(const char *s), const char *format, ...) { char buf[1024]; va_list argptr; va_start(argptr, format); vsprintf(buf, format, argptr); va_end(argptr); output(buf); }