summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/lib/util/options.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/lib/util/options.c')
-rw-r--r--trunk/src/lib/util/options.c799
1 files changed, 799 insertions, 0 deletions
diff --git a/trunk/src/lib/util/options.c b/trunk/src/lib/util/options.c
new file mode 100644
index 00000000000..ed1a62a6f6c
--- /dev/null
+++ b/trunk/src/lib/util/options.c
@@ -0,0 +1,799 @@
+/***************************************************************************
+
+ options.c
+
+ Core options code code
+
+****************************************************************************
+
+ Copyright Aaron Giles
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name 'MAME' nor the names of its contributors may be
+ used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+***************************************************************************/
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "options.h"
+#include "astring.h"
+
+
+
+//**************************************************************************
+// GLOBAL VARIABLES
+//**************************************************************************
+
+const char *const core_options::s_option_unadorned[MAX_UNADORNED_OPTIONS] =
+{
+ "<UNADORNED0>",
+ "<UNADORNED1>",
+ "<UNADORNED2>",
+ "<UNADORNED3>",
+ "<UNADORNED4>",
+ "<UNADORNED5>",
+ "<UNADORNED6>",
+ "<UNADORNED7>",
+ "<UNADORNED8>",
+ "<UNADORNED9>",
+ "<UNADORNED10>",
+ "<UNADORNED11>",
+ "<UNADORNED12>",
+ "<UNADORNED13>",
+ "<UNADORNED14>",
+ "<UNADORNED15>"
+};
+
+
+
+//**************************************************************************
+// CORE OPTIONS ENTRY
+//**************************************************************************
+
+//-------------------------------------------------
+// entry - constructor
+//-------------------------------------------------
+
+core_options::entry::entry(const options_entry &entrylist)
+ : m_next(NULL),
+ m_flags(entrylist.flags),
+ m_seqid(0),
+ m_error_reported(false),
+ m_priority(OPTION_PRIORITY_DEFAULT),
+ m_description(entrylist.description)
+{
+ // copy in the name(s) as appropriate
+ if (entrylist.name != NULL)
+ {
+ // first extract any range
+ astring namestr(entrylist.name);
+ int lparen = namestr.chr(0, '(');
+ int dash = namestr.chr(lparen + 1, '-');
+ int rparen = namestr.chr(dash + 1, ')');
+ if (lparen != -1 && dash != -1 && rparen != -1)
+ {
+ m_minimum.cpysubstr(namestr, lparen + 1, dash - (lparen + 1)).trimspace();
+ m_maximum.cpysubstr(namestr, dash + 1, rparen - (dash + 1)).trimspace();
+ namestr.del(lparen, rparen + 1 - lparen);
+ }
+
+ // then chop up any semicolon-separated names
+ int semi;
+ int nameindex = 0;
+ while ((semi = namestr.chr(0, ';')) != -1 && nameindex < ARRAY_LENGTH(m_name))
+ {
+ m_name[nameindex++].cpysubstr(namestr, 0, semi);
+ namestr.del(0, semi + 1);
+ }
+
+ // finally add the last item
+ if (nameindex < ARRAY_LENGTH(m_name))
+ m_name[nameindex++] = namestr;
+ }
+
+ // set the default value
+ if (entrylist.defvalue != NULL)
+ m_defdata = entrylist.defvalue;
+ m_data = m_defdata;
+}
+
+
+//-------------------------------------------------
+// set_value - update our data value
+//-------------------------------------------------
+
+void core_options::entry::set_value(const char *newdata, int priority)
+{
+ // ignore if we don't have priority
+ if (priority < m_priority)
+ return;
+
+ // set the data and priority, then bump the sequence
+ m_data = newdata;
+ m_priority = priority;
+ m_seqid++;
+}
+
+
+//-------------------------------------------------
+// set_default_value - set the default value of
+// an option, and reset the current value to it
+//-------------------------------------------------
+
+void core_options::entry::set_default_value(const char *defvalue)
+{
+ m_data = defvalue;
+ m_defdata = defvalue;
+ m_priority = OPTION_PRIORITY_DEFAULT;
+}
+
+
+//-------------------------------------------------
+// revert - revert back to our default if we are
+// at or below the given priority
+//-------------------------------------------------
+
+void core_options::entry::revert(int priority)
+{
+ // if our priority is low enough, revert to the default
+ if (m_priority <= priority)
+ {
+ m_data = m_defdata;
+ m_priority = OPTION_PRIORITY_DEFAULT;
+ }
+}
+
+
+
+//**************************************************************************
+// CORE OPTIONS
+//**************************************************************************
+
+//-------------------------------------------------
+// core_options - constructor
+//-------------------------------------------------
+
+core_options::core_options()
+ : m_entrylist(NULL),
+ m_entrylist_tailptr(&m_entrylist)
+{
+}
+
+core_options::core_options(const options_entry *entrylist)
+ : m_entrylist(NULL),
+ m_entrylist_tailptr(&m_entrylist)
+{
+ add_entries(entrylist);
+}
+
+core_options::core_options(const options_entry *entrylist1, const options_entry *entrylist2)
+ : m_entrylist(NULL),
+ m_entrylist_tailptr(&m_entrylist)
+{
+ add_entries(entrylist1);
+ add_entries(entrylist2);
+}
+
+core_options::core_options(const options_entry *entrylist1, const options_entry *entrylist2, const options_entry *entrylist3)
+ : m_entrylist(NULL),
+ m_entrylist_tailptr(&m_entrylist)
+{
+ add_entries(entrylist1);
+ add_entries(entrylist2);
+ add_entries(entrylist3);
+}
+
+core_options::core_options(const core_options &src)
+ : m_entrylist(NULL),
+ m_entrylist_tailptr(&m_entrylist)
+{
+ copyfrom(src);
+}
+
+
+//-------------------------------------------------
+// ~core_options - destructor
+//-------------------------------------------------
+
+core_options::~core_options()
+{
+ // delete all entries from the list
+ while (m_entrylist != NULL)
+ {
+ core_options::entry *e = m_entrylist;
+ remove_entry(*m_entrylist);
+ delete e;
+ }
+}
+
+
+//-------------------------------------------------
+// operator= - assignment operator
+//-------------------------------------------------
+
+core_options &core_options::operator=(const core_options &rhs)
+{
+ // ignore self-assignment
+ if (this != &rhs)
+ copyfrom(rhs);
+ return *this;
+}
+
+
+//-------------------------------------------------
+// operator== - compare two sets of options
+//-------------------------------------------------
+
+bool core_options::operator==(const core_options &rhs)
+{
+ // iterate over options in the first list
+ for (entry *curentry = m_entrylist; curentry != NULL; curentry = curentry->next())
+ if (!curentry->is_header())
+ {
+ // if the values differ, return false
+ if (strcmp(curentry->m_data, rhs.value(curentry->name())) != 0)
+ return false;
+ }
+
+ return true;
+}
+
+
+//-------------------------------------------------
+// operator!= - compare two sets of options
+//-------------------------------------------------
+
+bool core_options::operator!=(const core_options &rhs)
+{
+ return !operator==(rhs);
+}
+
+
+//-------------------------------------------------
+// add_entries - add entries to the current
+// options sets
+//-------------------------------------------------
+
+void core_options::add_entries(const options_entry *entrylist, bool override_existing)
+{
+ // loop over entries until we hit a NULL name
+ for ( ; entrylist->name != NULL || (entrylist->flags & OPTION_HEADER) != 0; entrylist++)
+ {
+ // allocate a new entry
+ entry *newentry = new entry(*entrylist);
+ if (newentry->name() != NULL)
+ {
+ // see if we match an existing entry
+ entry *existing = m_entrymap.find(newentry->name());
+ if (existing != NULL)
+ {
+ // if we're overriding existing entries, then remove the old one
+ if (override_existing)
+ {
+ core_options::entry *e = m_entrylist;
+ remove_entry(*existing);
+ delete e;
+ }
+
+ // otherwise, just override the default and current values and throw out the new entry
+ else
+ {
+ existing->set_default_value(newentry->value());
+ delete newentry;
+ continue;
+ }
+ }
+ }
+
+ // add us to the list and maps
+ append_entry(*newentry);
+ }
+}
+
+
+//-------------------------------------------------
+// set_default_value - change the default value
+// of an option
+//-------------------------------------------------
+
+void core_options::set_default_value(const char *name, const char *defvalue)
+{
+ // find the entry and bail if we can't
+ entry *curentry = m_entrymap.find(name);
+ if (curentry == NULL)
+ return;
+
+ // update the data and default data
+ curentry->set_default_value(defvalue);
+}
+
+
+//-------------------------------------------------
+// parse_command_line - parse a series of
+// command line arguments
+//-------------------------------------------------
+
+bool core_options::parse_command_line(int argc, char **argv, int priority, astring &error_string)
+{
+ // reset the errors and the command
+ error_string.reset();
+ m_command.reset();
+
+ // iterate through arguments
+ int unadorned_index = 0;
+ for (int arg = 1; arg < argc; arg++)
+ {
+ // determine the entry name to search for
+ const char *curarg = argv[arg];
+ bool is_unadorned = (curarg[0] != '-');
+ const char *optionname = is_unadorned ? core_options::unadorned(unadorned_index++) : &curarg[1];
+
+ // find our entry; if not found, indicate invalid option
+ entry *curentry = m_entrymap.find(optionname);
+ if (curentry == NULL)
+ {
+ error_string.catprintf("Error: unknown option: %s\n", curarg);
+ return false;
+ }
+
+ // process commands first
+ if (curentry->type() == OPTION_COMMAND)
+ {
+ // can only have one command
+ if (m_command)
+ {
+ error_string.catprintf("Error: multiple commands specified -%s and %s\n", m_command.cstr(), curarg);
+ return false;
+ }
+ m_command = curentry->name();
+ continue;
+ }
+
+ // get the data for this argument, special casing booleans
+ const char *newdata;
+ if (curentry->type() == OPTION_BOOLEAN)
+ newdata = (strncmp(&curarg[1], "no", 2) == 0) ? "0" : "1";
+ else if (is_unadorned)
+ newdata = curarg;
+ else if (arg + 1 < argc)
+ newdata = argv[++arg];
+ else
+ {
+ error_string.catprintf("Error: option %s expected a parameter\n", curarg);
+ return false;
+ }
+
+ // set the new data
+ validate_and_set_data(*curentry, newdata, priority, error_string);
+ }
+ return true;
+}
+
+
+//-------------------------------------------------
+// parse_ini_file - parse a series of entries in
+// an INI file
+//-------------------------------------------------
+
+bool core_options::parse_ini_file(core_file &inifile, int priority, int ignore_priority, astring &error_string)
+{
+ // loop over lines in the file
+ char buffer[4096];
+ while (core_fgets(buffer, ARRAY_LENGTH(buffer), &inifile) != NULL)
+ {
+ // find the extent of the name
+ char *optionname;
+ for (optionname = buffer; *optionname != 0; optionname++)
+ if (!isspace((UINT8)*optionname))
+ break;
+
+ // skip comments
+ if (*optionname == 0 || *optionname == '#')
+ continue;
+
+ // scan forward to find the first space
+ char *temp;
+ for (temp = optionname; *temp != 0; temp++)
+ if (isspace((UINT8)*temp))
+ break;
+
+ // if we hit the end early, print a warning and continue
+ if (*temp == 0)
+ {
+ error_string.catprintf("Warning: invalid line in INI: %s", buffer);
+ continue;
+ }
+
+ // NULL-terminate
+ *temp++ = 0;
+ char *optiondata = temp;
+
+ // scan the data, stopping when we hit a comment
+ bool inquotes = false;
+ for (temp = optiondata; *temp != 0; temp++)
+ {
+ if (*temp == '"')
+ inquotes = !inquotes;
+ if (*temp == '#' && !inquotes)
+ break;
+ }
+ *temp = 0;
+
+ // find our entry
+ entry *curentry = m_entrymap.find(optionname);
+ if (curentry == NULL)
+ {
+ if (priority >= ignore_priority)
+ error_string.catprintf("Warning: unknown option in INI: %s\n", optionname);
+ continue;
+ }
+
+ // set the new data
+ validate_and_set_data(*curentry, optiondata, priority, error_string);
+ }
+ return true;
+}
+
+
+//-------------------------------------------------
+// revert - revert options at or below a certain
+// priority back to their defaults
+//-------------------------------------------------
+
+void core_options::revert(int priority)
+{
+ // iterate over options and revert to defaults if below the given priority
+ for (entry *curentry = m_entrylist; curentry != NULL; curentry = curentry->next())
+ curentry->revert(priority);
+}
+
+
+//-------------------------------------------------
+// output_ini - output the options in INI format,
+// only outputting entries that different from
+// the optional diff
+//-------------------------------------------------
+
+const char *core_options::output_ini(astring &buffer, const core_options *diff)
+{
+ // INI files are complete, so always start with a blank buffer
+ buffer.reset();
+
+ int num_valid_headers = 0;
+ int unadorned_index = 0;
+ const char *last_header = NULL;
+
+ // loop over all items
+ for (entry *curentry = m_entrylist; curentry != NULL; curentry = curentry->next())
+ {
+ const char *name = curentry->name();
+ const char *value = curentry->value();
+ bool is_unadorned = false;
+
+ // check if it's unadorned
+ if (name && strlen(name) && !strcmp(name, core_options::unadorned(unadorned_index)))
+ {
+ unadorned_index++;
+ is_unadorned = true;
+ }
+
+ // header: record description
+ if (curentry->is_header())
+ last_header = curentry->description();
+
+ // otherwise, output entries for all non-command items
+ else if (!curentry->is_command())
+ {
+ // look up counterpart in diff, if diff is specified
+ if (diff == NULL || strcmp(value, diff->value(name)) != 0)
+ {
+ // output header, if we have one
+ if (last_header != NULL)
+ {
+ if (num_valid_headers++)
+ buffer.catprintf("\n");
+ buffer.catprintf("#\n# %s\n#\n", last_header);
+ last_header = NULL;
+ }
+
+ // and finally output the data, skip if unadorned
+ if (!is_unadorned)
+ {
+ if (strchr(value, ' ') != NULL)
+ buffer.catprintf("%-25s \"%s\"\n", name, value);
+ else
+ buffer.catprintf("%-25s %s\n", name, value);
+ }
+ }
+ }
+ }
+ return buffer;
+}
+
+
+//-------------------------------------------------
+// output_help - output option help to a string
+//-------------------------------------------------
+
+const char *core_options::output_help(astring &buffer)
+{
+ // start empty
+ buffer.reset();
+
+ // loop over all items
+ for (entry *curentry = m_entrylist; curentry != NULL; curentry = curentry->next())
+ {
+ // header: just print
+ if (curentry->is_header())
+ buffer.catprintf("\n#\n# %s\n#\n", curentry->description());
+
+ // otherwise, output entries for all non-deprecated items
+ else if (curentry->description() != NULL)
+ buffer.catprintf("-%-20s%s\n", curentry->name(), curentry->description());
+ }
+ return buffer;
+}
+
+
+//-------------------------------------------------
+// value - return the raw option value
+//-------------------------------------------------
+
+const char *core_options::value(const char *name) const
+{
+ entry *curentry = m_entrymap.find(name);
+ return (curentry != NULL) ? curentry->value() : "";
+}
+
+
+//-------------------------------------------------
+// seqid - return the seqid for a given option
+//-------------------------------------------------
+
+UINT32 core_options::seqid(const char *name) const
+{
+ entry *curentry = m_entrymap.find(name);
+ return (curentry != NULL) ? curentry->seqid() : 0;
+}
+
+//-------------------------------------------------
+// exists - return if option exists in list
+//-------------------------------------------------
+
+bool core_options::exists(const char *name) const
+{
+ entry *curentry = m_entrymap.find(name);
+ return (curentry != NULL);
+}
+
+//-------------------------------------------------
+// set_value - set the raw option value
+//-------------------------------------------------
+
+bool core_options::set_value(const char *name, const char *value, int priority, astring &error_string)
+{
+ // find the entry first
+ entry *curentry = m_entrymap.find(name);
+ if (curentry == NULL)
+ {
+ error_string.catprintf("Attempted to set unknown option %s\n", name);
+ return false;
+ }
+
+ // validate and set the item normally
+ return validate_and_set_data(*curentry, value, priority, error_string);
+}
+
+bool core_options::set_value(const char *name, int value, int priority, astring &error_string)
+{
+ astring tempstr;
+ tempstr.printf("%d", value);
+ return set_value(name, tempstr.cstr(), priority, error_string);
+}
+
+bool core_options::set_value(const char *name, float value, int priority, astring &error_string)
+{
+ astring tempstr;
+ tempstr.printf("%f", value);
+ return set_value(name, tempstr.cstr(), priority, error_string);
+}
+
+
+//-------------------------------------------------
+// reset - reset the options state, removing
+// everything
+//-------------------------------------------------
+
+void core_options::reset()
+{
+ // remove all entries from the list
+ while (m_entrylist != NULL)
+ {
+ core_options::entry *e = m_entrylist;
+ remove_entry(*m_entrylist);
+ delete e;
+ }
+
+ // reset the map
+ m_entrymap.reset();
+}
+
+
+//-------------------------------------------------
+// append_entry - append an entry to our list
+// and index it in the map
+//-------------------------------------------------
+
+void core_options::append_entry(core_options::entry &newentry)
+{
+ // append to the list
+ *m_entrylist_tailptr = &newentry;
+ m_entrylist_tailptr = &newentry.m_next;
+
+ // if we have names, add them to the map
+ astring tempstr;
+ for (int name = 0; name < ARRAY_LENGTH(newentry.m_name); name++)
+ if (newentry.m_name[name])
+ {
+ m_entrymap.add(newentry.m_name[name], &newentry);
+
+ // for boolean options add a "no" variant as well
+ if (newentry.type() == OPTION_BOOLEAN)
+ m_entrymap.add(tempstr.cpy("no").cat(newentry.m_name[name]), &newentry);
+ }
+}
+
+
+//-------------------------------------------------
+// remove_entry - remove an entry from our list
+// and map
+//-------------------------------------------------
+
+void core_options::remove_entry(core_options::entry &delentry)
+{
+ // remove us from the list
+ entry *preventry = NULL;
+ for (entry *curentry = m_entrylist; curentry != NULL; curentry = curentry->next())
+ if (curentry == &delentry)
+ {
+ // update link from previous to us
+ if (preventry != NULL)
+ preventry->m_next = delentry.m_next;
+ else
+ m_entrylist = delentry.m_next;
+
+ // if we're the last item, update the next pointer
+ if (delentry.m_next == NULL)
+ {
+ if (preventry != NULL)
+ m_entrylist_tailptr = &preventry->m_next;
+ else
+ m_entrylist_tailptr = &m_entrylist;
+ }
+
+ // remove all entries from the map
+ for (int name = 0; name < ARRAY_LENGTH(delentry.m_name); name++)
+ if (delentry.m_name[name])
+ m_entrymap.remove(delentry.m_name[name]);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// copyfrom - copy options from another set
+//-------------------------------------------------
+
+void core_options::copyfrom(const core_options &src)
+{
+ // reset ourselves first
+ reset();
+
+ // iterate through the src options and make our own
+ for (entry *curentry = src.m_entrylist; curentry != NULL; curentry = curentry->next())
+ append_entry(*new entry(*curentry));
+}
+
+
+//-------------------------------------------------
+// validate_and_set_data - make sure the data is
+// of the appropriate type and within range,
+// then set it
+//-------------------------------------------------
+
+bool core_options::validate_and_set_data(core_options::entry &curentry, const char *newdata, int priority, astring &error_string)
+{
+ // trim any whitespace
+ astring data(newdata);
+ data.trimspace();
+
+ // trim quotes
+ if (data.chr(0, '"') == 0 && data.rchr(0, '"') == data.len() - 1)
+ {
+ data.del(0, 1);
+ data.del(data.len() - 1, 1);
+ }
+
+ // validate the type of data and optionally the range
+ float fval;
+ int ival;
+ switch (curentry.type())
+ {
+ // booleans must be 0 or 1
+ case OPTION_BOOLEAN:
+ if (sscanf(data, "%d", &ival) != 1 || ival < 0 || ival > 1)
+ {
+ error_string.catprintf("Illegal boolean value for %s: \"%s\"; reverting to %s\n", curentry.name(), data.cstr(), curentry.value());
+ return false;
+ }
+ break;
+
+ // integers must be integral
+ case OPTION_INTEGER:
+ if (sscanf(data, "%d", &ival) != 1)
+ {
+ error_string.catprintf("Illegal integer value for %s: \"%s\"; reverting to %s\n", curentry.name(), data.cstr(), curentry.value());
+ return false;
+ }
+ if (curentry.has_range() && (ival < atoi(curentry.minimum()) || ival > atoi(curentry.maximum())))
+ {
+ error_string.catprintf("Out-of-range integer value for %s: \"%s\" (must be between %s and %s); reverting to %s\n", curentry.name(), data.cstr(), curentry.minimum(), curentry.maximum(), curentry.value());
+ return false;
+ }
+ break;
+
+ // floating-point values must be numeric
+ case OPTION_FLOAT:
+ if (sscanf(data, "%f", &fval) != 1)
+ {
+ error_string.catprintf("Illegal float value for %s: \"%s\"; reverting to %s\n", curentry.name(), data.cstr(), curentry.value());
+ return false;
+ }
+ if (curentry.has_range() && (fval < atof(curentry.minimum()) || fval > atof(curentry.maximum())))
+ {
+ error_string.catprintf("Out-of-range float value for %s: \"%s\" (must be between %s and %s); reverting to %s\n", curentry.name(), data.cstr(), curentry.minimum(), curentry.maximum(), curentry.value());
+ return false;
+ }
+ break;
+
+ // strings can be anything
+ case OPTION_STRING:
+ break;
+
+ // anything else is invalid
+ case OPTION_INVALID:
+ case OPTION_HEADER:
+ default:
+ error_string.catprintf("Attempted to set invalid option %s\n", curentry.name());
+ return false;
+ }
+
+ // set the data
+ curentry.set_value(data, priority);
+ return true;
+}