mame/src/lib/util/options.c
Aaron Giles 06ee6804dd Converted core_options to a class. Removed a bunch of marginal
functionality in favor of alternate mechanisms. Errors are
now reported via an astring rather than via callbacks. Every
option must now specify a type (command, integer, float, string,
boolean, etc). Command behavior has changed so that only one
command is permitted. [Aaron Giles]

Changed fileio system to accept just a raw searchpath instead of
an options/option name combination. [Aaron Giles]

Created emu_options class dervied from core_options which wraps
core emulator options. Added mechanisms to cleanly change the
system name and add/remove system-specific options, versus the
old way using callbacks. Also added read accessors for all the
options, to ensure consistency in how parameters are handled.
Changed most core systems to access emu_options instead of
core_options. Also changed machine->options() to return emu_options.
[Aaron Giles]
 
Created cli_options class derived from emu_options which adds the
command-line specific options. Updated clifront code to leverage
the new class and the new core behaviors. cli_execute() now accepts
a cli_options object when called. [Aaron Giles]

Updated both SDL and Windows to have their own options classes,
derived from cli_options, which add the OSD-specific options on
top of everything else. Added accessors for all the options so
that queries are strongly typed and simplified. [Aaron Giles]

Out of whatsnew: I've surely screwed up some stuff, though I have
smoke tested a bunch of things. Let me know if you hit anything odd.
Also I know this change will impact the WINUI stuff, please let me 
know if there are issues. All the functionality necessary should 
still be present. If it's not obvious, please talk to me before 
adding stuff to the core_options class.
2011-03-03 17:05:24 +00:00

755 lines
21 KiB
C

/***************************************************************************
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)
remove_entry(*m_entrylist);
}
//-------------------------------------------------
// 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)
remove_entry(*existing);
// 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();
// loop over all items
const char *last_header = NULL;
for (entry *curentry = m_entrylist; curentry != NULL; curentry = curentry->next())
{
// 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
const char *name = curentry->name();
const char *value = curentry->value();
if (diff == NULL || strcmp(value, diff->value(name)) != 0)
{
// output header, if we have one
if (last_header != NULL)
{
buffer.catprintf("\n#\n# %s\n#\n", last_header);
last_header = NULL;
}
// and finally output the data
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;
}
//-------------------------------------------------
// 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)
remove_entry(*m_entrylist);
// 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
for (int name = 0; name < ARRAY_LENGTH(newentry.m_name); name++)
if (newentry.m_name[name])
m_entrymap.add(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;
}