/* $Id$ $URL$ */ #include "rconfig.h" #ifdef HAVE_BOOST #include #include #else #include "rxstring/rxstring.h" #endif /**\file rconfig.cpp * Implementation of the Readable Configuration parser, and location of * the configuration variable definitions (RConfig). This should be reusable, the * parser is pretty simple -- just using a few regular expressions to match * each line. * \author Trent Apted * $Revision$ * $Date$ */ #include #include //#include #include #include #include #ifndef __GCC_ABI_VERSION /** NDEMANGLE is defined if we can't demangle staff */ #define NDEMANGLE #endif #undef NDEMANGLE #ifndef NDEMANGLE #include #endif /** Environment variable, provided by the OS */ #ifdef __APPLE__ #include #define environ (*_NSGetEnviron()) #else extern char** environ; #endif namespace { #ifdef HAVE_BOOST typedef boost::regex MyRegex; typedef boost::smatch MyMatch; typedef std::string RxString; inline bool mymatch(const RxString& s, MyRegex& r, MyMatch& m) { return regex_match(s, m, r); } inline void mytrim(RxString &s) { boost::algorithm::trim(s); } #else typedef RxString::Regex MyRegex; typedef RxString::match_vec_type MyMatch; inline bool mymatch(const RxString& s, MyRegex& r, MyMatch& m) { return s.match(r, &m); } inline void mytrim(RxString &s) { s.trim(); } #endif } RCParser::RCParser(int aargc, char** aargv, char** eenvv) : argc(aargc), argv(aargv), envv(eenvv ? eenvv : environ) { } int RCParser::getArgc() const { return argc; } char* const * RCParser::getArgv() const { return argv; } char* const * RCParser::getEnvv() const { return envv; } namespace RConfig { /** Demangle a type id name */ const char* demangle (const char* typeid_name) { #ifdef NDEMANGLE return typeid_name; #else static char buf[1024]; int status; char *demangled = abi::__cxa_demangle(typeid_name, 0, 0, &status); strncpy(buf, demangled, 1024); free(demangled); return buf; #endif } /** Call demangle() after getting the typeid for a variable */ #define DEMANGLE(_var) (demangle(typeid(_var).name())) class VarReader; namespace { const bool IDEBUG = true; ///< Enable debugging std::string *mrcfilename = 0; ///< Sentinel filename (default?) typedef std::map VARMAP; /// class VarReaderT : public VarReader { protected: const char* override; ///< Message to print if/when we override \a ref T &ref; ///< Where to write the variable public: /** Create a templatized VarReader, reading into \a rref */ VarReaderT (T &rref, const std::string& nname, const char* ooverride = "Overriding default"); virtual bool readin(const RxString &val); virtual bool readout(RxString &val); }; /** * Instantiate a VarReader based on the variable type */ template VarReader* make_varreader(T &ref, const std::string &name) { return new VarReaderT(ref, name); } /** * A VarReader for reading in collection configuration variables, * defers each instance to the regular VarReader */ template class VarReaderV : public VarReader { protected: std::vector &ref; ///< The vector reference T temp; ///< tempory var, into which we read before pushing back VarReader *vr; ///< The reader for each element public: /** Create a vectorized var reader for \a rref */ VarReaderV (std::vector &rref, const std::string& nname); /** Virtual destructor -- deletes \a vr */ virtual ~VarReaderV() {delete vr;} virtual bool readin(const RxString &val); virtual bool readout(RxString &val); }; /** * Instantiate a VarReaderV based on a vector value type */ template VarReader* make_varreaderv(std::vector &ref, const std::string &name) { std::string localname(name); return new VarReaderV(ref, localname); } /** Add a VarReaderT to the map of variables we want to look for */ template static void addvar(const char* cname, T& var, const char* context) { std::string name(cname); if (IDEBUG) { std::cerr.setf(std::ios_base::left, std::ios_base::adjustfield); std::cerr << "(cfg) " << std::setw(15) << DEMANGLE(T) << ' ' << std::setw(30) << name << " = " << std::setw(20) << var; if (context) std::cerr << " (" << context << ")"; std::cerr << std::endl; } varmap->insert(VARMAP::value_type(name, make_varreader(var, name))); } template static void addvar_uninitialised(const char* cname, T& var, const char* initval, const char* context) { std::string name(cname); if (IDEBUG) { std::cerr.setf(std::ios_base::left, std::ios_base::adjustfield); std::cerr << "(cfg) " << std::setw(15) << DEMANGLE(T) << ' ' << std::setw(30) << name << " = " << std::setw(20) << initval; if (context) std::cerr << " (" << context << ")"; std::cerr << std::endl; } varmap->insert(VARMAP::value_type(name, make_varreader(var, name))); } /** Add a VarReaderV to the map of variables we want to look for */ template static void addvecvar(const char* cname, std::vector& var, const char* context) { std::string name(cname); if (IDEBUG) { std::cerr.setf(std::ios_base::left, std::ios_base::adjustfield); std::cerr << "(cfg) " << std::setw(15) << DEMANGLE(T) << ' ' << std::setw(30) << name << " = "; if (context) std::cerr << " (" << context << ")"; std::cerr << std::endl; } varmap->insert(VARMAP::value_type(cname, make_varreaderv(var, name))); } /** Insert a variable reader for \a varname into the variable lookup table, * where the configuration name is identical to the variable. */ #define WILLPARSE(varname) addvar( #varname , varname, "legacy rconfig.cpp" ) /** Insert a variable reader for \a varname that appears in the config * file as \a match */ #define WILLPARSEN(varname, match) addvar( match , varname, "legacy rconfig.cpp" ) /** Insert a vector variable reader for \a varname that appears in the * config file as \a match */ #define WILLPARSEV(varname, match) addvecvar( match , varname, "legacy rconfig.cpp" ) /** * Initialise the variable lookup table */ int rcfile_init() { varmap = new VARMAP(); mrcfilename = new std::string("rcfile"); return varmap->size(); } /** Cleanup thunks */ void rcfile_cleanup() { if (!varmap) return; const VARMAP::iterator e = varmap->end(); for (VARMAP::iterator it = varmap->begin(); it != e ; ++it) delete it->second; delete varmap; varmap = 0; delete mrcfilename; mrcfilename = 0; } int Init::count = 0; Init::Init() { if (count++ == 0) { rcfile_init(); } } Init::~Init() { if (--count == 0) { rcfile_cleanup(); } } void rcfile_tell(const char* name, bool &ref, const char* context) { addvar(name, ref, context); } void rcfile_tell(const char* name, int &ref, const char* context) { addvar(name, ref, context); } void rcfile_tell(const char* name, float &ref, const char* context) { addvar(name, ref, context); } void rcfile_tell(const char* name, double &ref, const char* context) { addvar(name, ref, context); } void rcfile_tell(const char* name, unsigned &ref, const char* context) { addvar(name, ref, context); } void rcfile_tell(const char* name, std::string &ref, const char* context) { addvar(name, ref, context); } void rcfile_tellstr(const char* name, std::string &ref, const char* defval, const char* context) { addvar_uninitialised(name, ref, defval, context); } /* void rcfile_tell(const char* name, std::vector &ref) { addvecvar(name, ref); }*/ void rcfile_tell(const char* name, std::vector &ref, const char* context) { addvecvar(name, ref, context); } void rcfile_tell(const char* name, std::vector &ref, const char* context) { addvecvar(name, ref, context); } void rcfile_tell(const char* name, std::vector &ref, const char* context) { addvecvar(name, ref, context); } void rcfile_tell(const char* name, std::vector &ref, const char* context) { addvecvar(name, ref, context); } void rcfile_tell(const char* name, std::vector &ref, const char* context) { addvecvar(name, ref, context); } /** Suck out a quoted string from \a str */ static RxString getquote(const RxString &str) { size_t i; bool bs = false; RxString ret; for (i = 0; i < str.size() && str[i] != '"'; ++i) ; for (i = i + 1; i < str.size() && (bs || str[i] != '"'); ++i) { if (bs) ret += '\\'; bs = str[i] == '\\'; if (!bs) ret += str[i]; } return ret; } namespace { /** Parse a single line from the RCFile */ bool rcfile_xparseline(const RxString& line, bool suppress_unknown = false) { static MyRegex line_regex("^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=[[:space:]]*(.*)"); static MyRegex comment_regex("[^#]+"); MyMatch matches; MyMatch rhs; if (mymatch(line, line_regex, matches)) { RxString val = matches[2]; RxString key = matches[1]; mytrim(val); if (val[0] == '"') { val = getquote(val); } else if (mymatch(val, comment_regex, rhs)) { val = rhs[0]; } else { return false; } VARMAP::iterator it = varmap->find(key.c_str()); if (it != varmap->end()) { it->second->readin(val); return true; } else if (!suppress_unknown) { fprintf(stderr, "Unknown CFG param: %s\n", key.c_str()); } } return false; } } void rcfile_getnames(std::vector & names) { VARMAP::const_iterator it = varmap->begin(); const VARMAP::const_iterator end = varmap->end(); for (; it != end; ++it) names.push_back(it->first); } bool rcfile_parseline(const std::string& line) { return rcfile_xparseline(line); } bool rcfile_getvalue(std::string& val, const std::string& name) { VARMAP::const_iterator it = varmap->find(name); if (it != varmap->end()) return it->second->readout(val); return false; } int rcfile_parse(const char* file) { *mrcfilename = file; unsigned ctr = 0; std::ifstream in(file); if (in) { while (in) { RxString line; getline(in, line); if (rcfile_xparseline(line/*, varmap*/)) ctr++; } } else { fprintf(stderr, "Couldn't open %s for reading\n", file); } ctr = 0; if (environ) { for (char** envarg = environ; *envarg; ++envarg) if (rcfile_xparseline(*envarg, true)) ctr++; } return 0; } template VarReaderT::VarReaderT (T &rref, const std::string& nname, const char* ooverride) : VarReader(nname), override(ooverride), ref (rref) { } template bool VarReaderT::readin(const RxString &val) { std::istringstream iss(val); if (!(iss >> ref)) { fprintf(stderr, "Bad format in %s for %s (%s)\n", mrcfilename->c_str(), name.c_str(), val.c_str()); return false; } else { fprintf(stderr, "%s %s to %s\n", override, name.c_str(), val.c_str()); } return true; } template bool VarReaderT::readout(RxString &val) { std::ostringstream oss; if (oss << ref) { val = oss.str(); } return oss; } template <> bool VarReaderT::readin(const RxString &val) { fprintf(stderr, "%s %s to %s\n", override, name.c_str(), val.c_str()); ref = val; return true; } template <> bool VarReaderT::readout(RxString &val) { val = "\""; val += ref; val += "\""; return true; } template VarReaderV::VarReaderV (std::vector &rref, const std::string& nname) : VarReader(nname), ref(rref), vr(new VarReaderT(temp, nname + "[i]", "Adding another")) { } template bool VarReaderV::readin(const RxString &val) { if (vr->readin(val)) { ref.push_back(temp); return true; } return false; } template bool VarReaderV::readout(RxString &val) { val = ""; return true; } /** * Template specialisation for strings (no formatting) */ /* template<> class VarReaderT : public VarReader { protected: const char* override; std::string &ref; public: VarReaderT (std::string &rref, const std::string& nname, const char* ooverride = "Overriding default") : VarReader(nname), override(ooverride), ref(rref) {} virtual bool readin(const RxString &val) { fprintf(stderr, "%s %s to %s\n", override, name.c_str(), val.c_str()); ref = val; return true; } }; */ #ifdef OLDCRUD /** Parse a primitive type into \a varname */ #define PARSE_PRIMITIVE(varname) else if (strcmp(matches[1].c_str(), #varname) == 0) { \ if(!val.convertTo(varname)) { \ fprintf(stderr, "Bad format in %s for %s (%s)\n", file, #varname, val.c_str()); \ } else { \ fprintf(stderr, "Overriding default %s to %s\n", #varname, val.c_str()); \ } \ } /** Parse a string into \a varname */ #define PARSE_STRING(varname) else if (strcmp(matches[1].c_str(), #varname) == 0) { \ fprintf(stderr, "Overriding default %s to %s\n", #varname, val.c_str()); \ varname = val; \ } /** The old rcfile reader with quadratic scalability */ static int oldrcfile_parse(const char* file) { /*FOLD00*/ *mrcfilename = file; rcfile_init(); std::ifstream in(file); if (!in) { fprintf(stderr, "Couldn't open %s for reading\n", file); return 1; } //in.setf(std::ios_base::boolalpha); //doesn't work... while (in) { RxString line; RxString::match_vec_type matches; line.append_line(in); if (line.match("^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=[[:space:]]*(.*)", &matches)) { RxString::match_vec_type rhs; RxString val; matches[2].trim(); if (matches[2][0] == '"') { val = getquote(matches[2]); } else if (matches[2].match("[^#]+", &rhs)) { val = rhs[0]; } else { continue; } if (false) { } /* BEGIN: This is where you put all the variables to look for */ PARSE_PRIMITIVE(DEPTH_DELTA) PARSE_PRIMITIVE(IMAGE_TRANSPARENCY) PARSE_PRIMITIVE(CORNER_PROP) PARSE_PRIMITIVE(TRY_FULLSCREEN) PARSE_PRIMITIVE(DEFAULT_WRITE_DWELL) PARSE_PRIMITIVE(TARGET_SCREEN_WIDTH) PARSE_PRIMITIVE(TARGET_SCREEN_HEIGHT) PARSE_PRIMITIVE(TARGET_SCREEN_BPP) PARSE_PRIMITIVE(SCREEN_NUM_USERS) PARSE_PRIMITIVE(TARGET_ANISOTROPIC) PARSE_PRIMITIVE(TARGET_FSAA) PARSE_PRIMITIVE(RENDER_NICEST) PARSE_PRIMITIVE(COPY_SEPARATION_SQ) PARSE_PRIMITIVE(DWELL_AREA_SQ) PARSE_PRIMITIVE(JUMP_DISTANCE_SQ) PARSE_PRIMITIVE(IGNORE_JUMPS) PARSE_PRIMITIVE(MENU_DWELL_TIME) PARSE_PRIMITIVE(COPY_GESTURE) PARSE_PRIMITIVE(MOUSE_DEBUG) PARSE_PRIMITIVE(MIN_REDRAW) PARSE_PRIMITIVE(SHOW_FPS) PARSE_PRIMITIVE(SHOW_EPS) PARSE_PRIMITIVE(SHOW_NUMIMAGES) PARSE_PRIMITIVE(SHOW_TIMESINCE) PARSE_PRIMITIVE(SHOW_MEM) PARSE_PRIMITIVE(SHOW_ANIM) PARSE_PRIMITIVE(HIDE_CURSOR) PARSE_PRIMITIVE(ANIMATIONS) PARSE_PRIMITIVE(ANIMATION_LOAD_TIME) PARSE_PRIMITIVE(WORMHOLE_MAX) PARSE_PRIMITIVE(MIN_SCALE_FOR_DRAW) PARSE_PRIMITIVE(FIELD_OF_VIEW_Y) PARSE_PRIMITIVE(HARD_TEXTURE_LIMIT) PARSE_PRIMITIVE(CAPTURE_FRAME) PARSE_PRIMITIVE(ANOTO_FRAME) PARSE_PRIMITIVE(JPEG_QUALITY) PARSE_PRIMITIVE(ALWAYS_UPSCALE) PARSE_PRIMITIVE(SINCELOCK_MAX) PARSE_PRIMITIVE(SWANKY_BLACKHOLE) PARSE_PRIMITIVE(CLEARSCREEN_BUG) PARSE_PRIMITIVE(MANUAL_ASPECT) PARSE_PRIMITIVE(DO_MOMENTUM) PARSE_PRIMITIVE(DEFAULT_COEFF) PARSE_PRIMITIVE(GRAVITY) PARSE_PRIMITIVE(VELOCITY_WINDOW) PARSE_PRIMITIVE(ESCAPE_VELOCITY) PARSE_PRIMITIVE(BLACKHOLE_TRAPDIST) PARSE_PRIMITIVE(BLACKHOLE_ACCEL) PARSE_PRIMITIVE(FLIPPER_THRESHOLD) PARSE_PRIMITIVE(TEST_FLIPPING) PARSE_PRIMITIVE(SOCKET_ENABLED) PARSE_PRIMITIVE(LISTEN_TIMEOUTMS) PARSE_PRIMITIVE(IMAGE_SERVER_PORT) PARSE_PRIMITIVE(COMMAND_SERVER_PORT) PARSE_PRIMITIVE(DATAWALL_PORT) PARSE_PRIMITIVE(MAGICMIRROR_PORT) PARSE_PRIMITIVE(MAGICMIRROR_SEND) PARSE_PRIMITIVE(AUDIOBOX_PORT) PARSE_PRIMITIVE(ENABLE_AUDIOBOX) PARSE_PRIMITIVE(LMB_ONLY) PARSE_PRIMITIVE(SAVE_VIDEO) PARSE_PRIMITIVE(MENU_SYSTEM) PARSE_PRIMITIVE(MENU_RADIUS) PARSE_PRIMITIVE(WRITING_WIDTH) PARSE_PRIMITIVE(WRITING_PWIDTH) PARSE_PRIMITIVE(STIPPLE_WRITING) PARSE_PRIMITIVE(DO_WRITING) PARSE_PRIMITIVE(FLIP_WIDGETS) PARSE_PRIMITIVE(LOAD_X) PARSE_PRIMITIVE(LOAD_Y) PARSE_PRIMITIVE(LOAD_Z) PARSE_PRIMITIVE(LOAD_STARTSCALE) PARSE_PRIMITIVE(LOAD_FULLSCALE) PARSE_PRIMITIVE(DATAWALL) PARSE_PRIMITIVE(STEREO) PARSE_PRIMITIVE(CAMERA_FACTOR) PARSE_PRIMITIVE(DATAWALL_SEND) PARSE_PRIMITIVE(STEREO_IOD) PARSE_PRIMITIVE(FUSION_DISTANCE) PARSE_PRIMITIVE(AUDIO_TRACKS) PARSE_PRIMITIVE(X700_SELECT_BUG) PARSE_PRIMITIVE(ENVMENU_DWELLRANGE) PARSE_PRIMITIVE(BH_DELETE_SPEED) PARSE_PRIMITIVE(PERSONAL_SPACE_COPY) PARSE_PRIMITIVE(PSCOPY_RESIST_DIST) PARSE_PRIMITIVE(CIRCLE_SCALE) PARSE_PRIMITIVE(CIRCLE_SEGMENTS) PARSE_PRIMITIVE(AUDIO_CIRCLES) PARSE_PRIMITIVE(BLACK_SCREEN) /* This is how you'd do a string variable */ PARSE_STRING(INITIAL_IMAGE_DIRECTORY) PARSE_STRING(BLACKHOLE_FILE) PARSE_STRING(FRAME_FILE) PARSE_STRING(ANOTOFRAME_FILE) PARSE_STRING(AUDIOBOX_SERVER) PARSE_STRING(DATAWALL_SERVER) PARSE_STRING(MAGICMIRROR_SERVER) PARSE_STRING(FRAME_SOUNDFILE) PARSE_STRING(RECORD_SOUNDFILE) /* This is how you'd do a collection variable */ else if (matches[1] == "FILE") { INITIAL_FILES.push_back(val); } else if (matches[1] == "RFB_HOST") { REMOTE_FRAMEBUFFERS.push_back(val); } else if (matches[1] == "RFB_PORT") { int p; val.convertTo(p); RFB_PORTS.push_back(p); } else if (matches[1] == "RFB_PASS") { RFB_PASSWORDS.push_back(val); } /* END: variable parsing */ else { fprintf(stderr, "Unknown CFG param: %s\n", matches[1].c_str()); } } } return 0; } /*FOLD00*/ #endif }