/** * @file Props.cc * * Implements property file handling * * Author: Peter Helfer * Date: 2012-02-09 */ #include <stdio.h> #include <string.h> #include <limits.h> #include <libgen.h> #include <string> #include <fmt/format.h> #include "Trace.hh" #include "Util.hh" #include "Props.hh" /** * Remove comment, if present, from a line of text. A comment consists * of an unquoted # character and all that follows it. * @param line The line of text. * @param fname File name, used for error messaging only. * @param lineNum Line number, used for error messaging only. */ static void stripComment(char *line, const char *fname, uint lineNum) { char quoteChar = 0; for (char *p = line; *p != 0; p++) { if (quoteChar == '\\') { quoteChar = 0; } else if (*p == quoteChar) { quoteChar = 0; } else if (*p == '\\' || *p == '"' || *p == '\'') { quoteChar = *p; } else if (quoteChar == 0 && *p == '#') { *p = 0; return; } } if (quoteChar != 0) { fmt::print(stderr, "{}: line {} - Warning: unclosed quote: {}\n", fname, lineNum, line); } } // Replace occurrences of prop names with their values in s // string Props::substProps(string s) { for (auto &prop : props) { for (size_t pos = s.find(prop.name); pos < string::npos; pos = s.find(prop.name, pos + 1)) { s.replace(pos, prop.name.length(), prop.value); prop.used = true; } } return s; } void Props::fatal(const string &file, uint lineNum, string errMsg, string exhibit) { TRACE_FATAL("{} line {} - {}: '{}'", file, lineNum, errMsg, exhibit); } void Props::fatal(Props::Prop *p, string errMsg) { fatal(p->propsFile, p->lineNum, errMsg, p->value); } /** * Constructor */ Props::Props(const char *fname) { readProps(fname); } void Props::readProps(const char *fname) { FILE *fp = fopen(fname, "r"); if (fp == NULL) { perror(fname); TRACE_FATAL("Failed to open {}\n", fname); } this->fname = fname; if (topLevelFname.empty()) { topLevelFname = fname; } char line[1000]; for (uint lineNum = 1; fgets(line, sizeof(line), fp) != NULL; lineNum++) { stripComment(line, fname, lineNum); if (Util::isBlank(line)) continue; char *colonPos = strchr(line, ':'); if (colonPos == NULL) { TRACE_FATAL("{}: line {} - Bad property (no colon): {}\n", fname, lineNum, line); } // A double colon suppresses the "Duplicate property" warning // when overwriting aproperty's value // bool doubleColon = (*(colonPos + 1) == ':'); string name = Util::wstrip(string(line).substr(0, colonPos - line)); string value = Util::wstrip(doubleColon ? colonPos + 2 : colonPos + 1); if (Util::strCiEq(name, "include")) { // Include directive // string path = value; if (path[0] != '/') { // Relative path. Prepend directory path of current file. // char fnameCopy[strlen(fname)+1]; strcpy(fnameCopy, fname); path = string(dirname(fnameCopy)) + '/' + path; } readProps(path.c_str()); } else { // Ordinary prop // if (value.size() == 0) { TRACE_FATAL("{}: line {} - Bad property (no value): {}\n", fname, lineNum, line); } value = substProps(value); Prop *p = findProp(name, false); if (p == NULL) { uint n = props.size(); props.resize(n + 1); p = &props[n]; p->name = name; } else if (!p->immutable && !doubleColon) { TRACE_WARN("{}: line {} - Duplicate property: {}\n", fname, lineNum, name); } if (!p->immutable) { p->value = value; p->propsFile = fname; p->lineNum = lineNum; } } } } Props::Prop *Props::findProp(const string &name, bool required) { for (uint i = 0; i < props.size(); i++) { if (Util::strCiEq(props[i].name, name)) { props[i].used = true; return &props[i]; } } if (required) { TRACE_FATAL("Property '{}' not found in {}\n", name, topLevelFname); } else { return NULL; } } string Props::getString(const string &name, const string &defaultVal) { Prop *p = findProp(name, false); if (p != NULL) { return (p->value); } else { return defaultVal; } } string Props::getString(const string &name) { return findProp(name, true)->value; } int Props::propToInt(Props::Prop *p) { string errMsg; int val = Util::strToInt(p->value, errMsg); if (!errMsg.empty()) { fatal(p, errMsg); } return val; } int Props::getInt(const string &name, int defaultVal) { Prop *p = findProp(name, false); if (p != NULL) { return propToInt(p); } else { return defaultVal; } } int Props::getInt(const string &name) { return propToInt(findProp(name, true)); } uint Props::propToUint(Props::Prop *p) { string errMsg; uint val = Util::strToUint(p->value, errMsg); if (!errMsg.empty()) { fatal(p, errMsg); } return val; } uint Props::getUint(const string &name, uint defaultVal) { Prop *p = findProp(name, false); if (p != NULL) { return propToUint(p); } else { return defaultVal; } } uint Props::getUint(const string &name) { return propToUint(findProp(name, true)); } double Props::propToDouble(Props::Prop *p) { string errMsg; double val = Util::strToDouble(p->value, errMsg); if (!errMsg.empty()) { fatal(p, errMsg); } return val; } double Props::getDouble(const string &name, double defaultVal) { Prop *p = findProp(name, false); if (p != NULL) { return propToDouble(p); } else { return defaultVal; } } double Props::getDouble(const string &name) { return propToDouble(findProp(name, true)); } bool Props::propToBool(Props::Prop *p) { string errMsg; bool val = Util::strToBool(p->value, errMsg); if (!errMsg.empty()) { fatal(p, errMsg); } return val; } bool Props::getBool(const string &name, bool defaultVal) { Prop *p = findProp(name, false); if (p != NULL) { return propToBool(p); } else { return defaultVal; } } bool Props::getBool(const string &name) { return propToBool(findProp(name, true)); } /** * Remove initial "{" and final "}" from a vector of tokens (destructive) * @param tokens */ void Props::removeBraces(std::vector<string> &tokens, Props::Prop *p) { if (tokens[0] == "{" && tokens[tokens.size() - 1] == "}") { tokens.pop_back(); tokens.erase(tokens.begin()); } for (uint i = 0; i < tokens.size(); i++) { if(Util::strEq(tokens[i], "{") || Util::strEq(tokens[i], "}")) { fatal(p, "Unbalanced or misplaced brace"); } } } std::vector<string> Props::propToStringVector(Props::Prop *p) { char copy[p->value.size() + 1]; strcpy(copy, p->value.c_str()); std::vector<string> vals; // List of values may be enclosed in { }. // This makes it possible to specify an empty list. const char *errMsg = NULL; std::vector<string> tokens = Util::tokenize(copy, " \t", errMsg, "", "{}"); if (errMsg != NULL) { fatal(p, errMsg); } removeBraces(tokens, p); for (uint i = 0; i < tokens.size(); i++) { vals.push_back(tokens[i]); } return vals; } std::vector<string> Props::getStringVector(const string &name, const std::vector<string> defaultVal) { Prop *p = findProp(name, false); if (p != NULL) { return propToStringVector(p); } else { return defaultVal; } } std::vector<string> Props::getStringVector(const string &name) { return propToStringVector(findProp(name, true)); } std::vector<int> Props::propToIntVector(Props::Prop *p) { char copy[p->value.size() + 1]; strcpy(copy, p->value.c_str()); Prop dummyProp = *p; std::vector<int> vals; // List of values may be enclosed in { }. // This makes it possible to specify an empty list. const char *errMsg = NULL; std::vector<string> tokens = Util::tokenize(copy, " \t", errMsg, "", "{}"); if (errMsg != NULL) { fatal(p, errMsg); } removeBraces(tokens, p); for (uint i = 0; i < tokens.size(); i++) { dummyProp.value = tokens[i]; vals.push_back(propToInt(&dummyProp)); } return vals; } std::vector<int> Props::getIntVector(const string &name, const std::vector<int> defaultVal) { Prop *p = findProp(name, false); if (p != NULL) { return propToIntVector(p); } else { return defaultVal; } } std::vector<int> Props::getIntVector(const string &name) { return propToIntVector(findProp(name, true)); } std::vector<uint> Props::propToUintVector(Props::Prop *p) { char copy[p->value.size() + 1]; strcpy(copy, p->value.c_str()); Prop dummyProp = *p; std::vector<uint> vals; // List of values may be enclosed in { } as separate // tokens. This makes it possible to specify an empty list. const char *errMsg = NULL; std::vector<string> tokens = Util::tokenize(copy, " \t", errMsg); if (errMsg != NULL) { fatal(p, errMsg); } removeBraces(tokens, p); for (uint i = 0; i < tokens.size(); i++) { dummyProp.value = tokens[i]; vals.push_back(propToUint(&dummyProp)); } return vals; } std::vector<uint> Props::getUintVector(const string &name, const std::vector<uint> defaultVal) { Prop *p = findProp(name, false); if (p != NULL) { return propToUintVector(p); } else { return defaultVal; } } std::vector<uint> Props::getUintVector(const string &name) { return propToUintVector(findProp(name, true)); } std::vector<double> Props::propToDoubleVector(Props::Prop *p) { char copy[p->value.size() + 1]; strcpy(copy, p->value.c_str()); Prop dummyProp = *p; std::vector<double> vals; // List of values may be enclosed in { } as separate // tokens. This makes it possible to specify an empty list. const char *errMsg = NULL; std::vector<string> tokens = Util::tokenize(copy, " \t", errMsg); if (errMsg != NULL) { fatal(p, errMsg); } removeBraces(tokens, p); for (uint i = 0; i < tokens.size(); i++) { dummyProp.value = tokens[i]; vals.push_back(propToDouble(&dummyProp)); } return vals; } std::vector<double> Props::getDoubleVector(const string &name, const std::vector<double> defaultVal) { Prop *p = findProp(name, false); if (p != NULL) { return propToDoubleVector(p); } else { return defaultVal; } } std::vector<double> Props::getDoubleVector(const string &name) { return propToDoubleVector(findProp(name, true)); } std::vector<std::vector<double> > Props::propToDoubleMatrix(Props::Prop *p) { char copy[p->value.size() + 1]; strcpy(copy, p->value.c_str()); Prop dummyProp = *p; // The whole matrix and the individual row vectors must be // delimited by braces, like so: // { { 1.0 2.0 3.0 } { 4.0 5.0 6.0 } } // Furthermore, the row vectors must be of equal size. const char *errMsg = NULL; std::vector<string> tokens = Util::tokenize(copy, " \t", errMsg, "", "{}"); if(errMsg != NULL) { fatal(p, errMsg); } // strip the initial and final braces // if(tokens[0] != "{" || tokens[tokens.size() - 1] != "}") { fatal(p, "Matrix not brace-enclosed."); } tokens.pop_back(); tokens.erase(tokens.begin()); // Parse the row vectors // std::vector<std::vector<double> > vals; std::vector<double> row; enum TokenType { LBRACE, ELEM }; TokenType expect = LBRACE; for (uint i = 0; i < tokens.size(); i++) { switch (expect) { case LBRACE: if(tokens[i] != "{") { fatal(p->propsFile, p->lineNum, "Expected '{', got", tokens[i]); } expect = ELEM; break; case ELEM: if (tokens[i] == "{") { fatal(p, "Expected element, got '{'"); } else if (tokens[i] == "}") { vals.push_back(row); row.clear(); expect = LBRACE; } else { dummyProp.value = tokens[i]; row.push_back(propToDouble(&dummyProp)); } break; default: TRACE_FATAL("logic error"); } } if(expect != LBRACE) { fatal(p, "unbalanced braces"); } for (uint i = 1; i < vals.size(); i++) { if (vals[i].size() != vals[0].size()) { fatal(p, "rows don't have equal size."); } } return vals; } std::vector<std::vector<double> > Props::getDoubleMatrix(const string &name, const std::vector<std::vector<double> > defaultVal) { Prop *p = findProp(name, false); if (p != NULL) { return propToDoubleMatrix(p); } else { return defaultVal; } } std::vector<std::vector<double> > Props::getDoubleMatrix(const string &name) { return propToDoubleMatrix(findProp(name, true)); } /** * Associate a name with a string value. * @param name The name * @param value The value * @param immutable If true, then subsequent attempts to change the value * will have no effect. This is useful for setting properties by a * command line option and not have them be overwritten by * subsequent property file reading. */ void Props::setString(const string &name, string value, bool immutable) { Prop *p = findProp(name, false); if (p == NULL) { uint n = props.size(); props.resize(n + 1); p = &props[n]; p->name = name; } if (!p->immutable) { p->value = value; p->lineNum = -1; p->immutable = immutable; } } // TODO: change all the buf and sprintf stuff to fmt::format void Props::setInt(const string &name, int value) { char buf[64]; sprintf(buf, "%d", value); setString(name, buf); } void Props::setUint(const string &name, uint value) { char buf[64]; sprintf(buf, "%d", value); setString(name, buf); } void Props::setDouble(const string &name, double value) { char buf[64]; sprintf(buf, "%f", value); setString(name, buf); } void Props::setBool(const string &name, bool value) { char buf[64]; sprintf(buf, "%s", value ? "true" : "false"); setString(name, buf); } void Props::setStringVector(const string &name, std::vector<string> v) { char buf[1024]; char buf1[32]; sprintf(buf, "{ "); for (uint i = 0; i < v.size(); i++) { sprintf(buf1, "%s ", v[i].c_str()); strcat(buf, buf1); } strcat(buf, " }"); setString(name, buf); } void Props::setIntVector(const string &name, std::vector<int> v) { char buf[1024]; char buf1[32]; sprintf(buf, "{ "); for (uint i = 0; i < v.size(); i++) { sprintf(buf1, "%d ", v[i]); strcat(buf, buf1); } strcat(buf, " }"); setString(name, buf); } void Props::setUintVector(const string &name, std::vector<uint> v) { char buf[1024]; char buf1[32]; sprintf(buf, "{ "); for (uint i = 0; i < v.size(); i++) { sprintf(buf1, "%u ", v[i]); strcat(buf, buf1); } strcat(buf, " }"); setString(name, buf); } void Props::setDoubleVector(const string &name, std::vector<double> v) { char buf[1024]; char buf1[32]; sprintf(buf, "{ "); for (uint i = 0; i < v.size(); i++) { sprintf(buf1, "%f ", v[i]); strcat(buf, buf1); } strcat(buf, " }"); setString(name, buf); } void Props::setDoubleMatrix(const string &name, std::vector<std::vector<double> > m) { char buf[1024]; char buf1[32]; sprintf(buf, "{ "); for (uint i = 0; i < m.size(); i++) { strcat(buf, "{ "); for (uint j = 0; j < m[i].size(); j++) { sprintf(buf1, "%f ", m[i][j]); strcat(buf, buf1); } strcat(buf, "} "); } strcat(buf, "}"); setString(name, buf); } void Props::reportUnused(bool isFatal) { bool someUnused = false; for (uint i = 0; i < props.size(); i++) { if (!props[i].used) { TRACE_WARN("{}: line {} - Unused property: {}", props[i].propsFile, props[i].lineNum, props[i].name); someUnused = true; } } ABORT_IF(someUnused && isFatal, "Unused properties", 0); } string Props::toString() { string s; for (uint i = 0; i < props.size(); i++) { s.append(props[i].name); s.append(": "); s.append(props[i].value); s.append("\n"); } return s; }