464 lines
12 KiB
C++
464 lines
12 KiB
C++
#include "configparser.h"
|
|
|
|
#include <vector>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
|
|
using namespace std;
|
|
|
|
const int OPERATIONSET = 0;
|
|
const int OPERATIONADD = 1;
|
|
const int OPERATION_MERGE = 2;
|
|
|
|
// конструктор ConfigParam
|
|
ConfigParam::ConfigParam()
|
|
{
|
|
name = "";
|
|
this->val.clear();
|
|
}
|
|
|
|
// создаем ConfigParam из строки
|
|
ConfigParam::ConfigParam(string s)
|
|
{
|
|
name = s;
|
|
this->val.clear();
|
|
}
|
|
|
|
// создаем ConfigParam по имени и значению
|
|
ConfigParam::ConfigParam(string name, string value)
|
|
{
|
|
this->name = name;
|
|
this->val.clear();
|
|
ConfigValue v;
|
|
v.type = CONFIGVALUETYPESTRING;
|
|
v.sval = value;
|
|
this->val.push_back(v);
|
|
}
|
|
|
|
bool ConfigParser::IsDelim(char c)
|
|
{
|
|
if (IsWS(c)) return true;
|
|
if ((c == '{') || (c == '}')) return true;
|
|
if ((c == '[') || (c == ']')) return true;
|
|
if (c == ',') return true;
|
|
if (c == ';') return true;
|
|
if (c == '#') return true;
|
|
return false;
|
|
}
|
|
|
|
bool operator<(const ConfigParam &a, const ConfigParam &b)
|
|
{
|
|
return a.name < b.name;
|
|
}
|
|
|
|
string ConfigParser::i2s(int a)
|
|
{
|
|
ostringstream oss;
|
|
oss << a;
|
|
return oss.str();
|
|
}
|
|
|
|
// Является ли символ whitespace-ом
|
|
bool ConfigParser::IsWS(char c)
|
|
{
|
|
return ((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'));
|
|
}
|
|
|
|
// Можно ли такие символы использовать в названии параметров
|
|
bool ConfigParser::GoodForName(char c)
|
|
{
|
|
if (IsWS(c)) return false;
|
|
if ((c == '[') || (c == ']')) return false;
|
|
if ((c == '{') || (c == '}')) return false;
|
|
if ((c == '(') || (c == ')')) return false;
|
|
if (c == '"') return false;
|
|
if (c == '\\') return false;
|
|
if (c == '+') return false;
|
|
if (c == '=') return false;
|
|
return true;
|
|
}
|
|
|
|
// возвращает символ, соответствующий escape-последовательности "\c"
|
|
char ConfigParser::UnSlash(char c)
|
|
{
|
|
if (c == 'n') return '\n';
|
|
if (c == '\\') return '\\';
|
|
if (c == '\"') return '"';
|
|
return c;
|
|
}
|
|
|
|
// разбирает s с l по r, считая, что она должна быть ConfigParam - ом
|
|
// возвращает, какая операция была применена
|
|
int ConfigParser::ParseConfigParam(int &l, int &r, ConfigParam &p)
|
|
{
|
|
while ((l <= r) && (IsWS(s[l])))
|
|
{
|
|
if (s[l] == '\n') cline++;
|
|
l++;
|
|
}
|
|
if (l > r)
|
|
throw "Expected parameter at line " + i2s(cline);
|
|
if (!GoodForName(s[l]))
|
|
{
|
|
throw "Parameter name expected at line " + i2s(cline);
|
|
}
|
|
p.name = "";
|
|
while (GoodForName(s[l]))
|
|
{
|
|
if (l == r) throw "Unfinished param at line " + i2s(cline);
|
|
p.name += s[l];
|
|
l++;
|
|
}
|
|
while ((l <= r) && (IsWS(s[l])))
|
|
{
|
|
if (s[l] == '\n') cline++;
|
|
l++;
|
|
}
|
|
if (l > r) throw "Unfinished param at line " + i2s(cline);
|
|
int op;
|
|
if (s[l] == '+')
|
|
{
|
|
if ((l == r) || (s[l + 1] != '=')) throw "Unrecognized operation: + or += expected at line " + i2s(cline);
|
|
op = OPERATIONADD;
|
|
l += 2;
|
|
}
|
|
else if (s[l] == '=')
|
|
{
|
|
op = OPERATIONSET;
|
|
l++;
|
|
}
|
|
else if (s[l] == '(')
|
|
{
|
|
// this is a function call
|
|
l++;
|
|
string args = "";
|
|
while (l <= r && s[l] != ')')
|
|
{
|
|
args += s[l];
|
|
if (s[l] == '\n') cline++;
|
|
l++;
|
|
}
|
|
if (l > r) throw (string)("Unfinished function args at line " + i2s(cline));
|
|
l++;
|
|
if (p.name == "include")
|
|
{
|
|
// пока так костыльно, годный парсинг аргументов надо вместе с глобальным
|
|
// рефакторингом парсера делать
|
|
TrimSpaces(args);
|
|
TrimQuotes(args);
|
|
ConfigParser t;
|
|
t.Load(args);
|
|
p.val.push_back(t.cfg);
|
|
// в этом случае значение имеет тип "множество"
|
|
p.val[0].type = CONFIGVALUETYPESET;
|
|
return OPERATION_MERGE;
|
|
}
|
|
else
|
|
{
|
|
throw "Unknown function " + p.name;
|
|
}
|
|
}
|
|
else throw "Unrecognized operation: + or += expected at line " + i2s(cline);
|
|
while ((l <= r) && (IsWS(s[l])))
|
|
{
|
|
if (s[l] == '\n') cline++;
|
|
l++;
|
|
}
|
|
if (l > r) throw "Expected parameter value " + i2s(cline);
|
|
if (s[l] == '{')
|
|
{
|
|
ConfigValue a;
|
|
p.val.push_back(a);
|
|
l++;
|
|
// в этом случае значение имеет тип "множество"
|
|
p.val[0].type = CONFIGVALUETYPESET;
|
|
ParseConfigValue(l, r, p.val[0]);
|
|
}
|
|
else if (s[l] == '[')
|
|
{
|
|
l++;
|
|
ParseConfigParamValues(l, r, p);
|
|
}
|
|
else
|
|
{
|
|
ConfigValue a;
|
|
p.val.push_back(a);
|
|
// в этом случае значение имеет тип "строка"
|
|
p.val[0].type = CONFIGVALUETYPESTRING;
|
|
ParseConfigValue(l, r, p.val[0]);
|
|
}
|
|
return op;
|
|
}
|
|
|
|
// Разбирает несколько ConfigValues, заданных через запятую
|
|
void ConfigParser::ParseConfigParamValues(int &l, int &r, ConfigParam &b)
|
|
{
|
|
while(1)
|
|
{
|
|
while ((l <= r) && (IsWS(s[l])))
|
|
{
|
|
if (s[l] == '\n') cline++;
|
|
l++;
|
|
}
|
|
if (l > r) throw "Unfinished array at line " + i2s(cline);
|
|
if (s[l] == ']')
|
|
{
|
|
l++;
|
|
break;
|
|
}
|
|
ConfigValue a;
|
|
b.val.push_back(a);
|
|
if (s[l] == '{')
|
|
{
|
|
l++;
|
|
b.val[b.val.size() - 1].type = CONFIGVALUETYPESET;
|
|
}
|
|
else
|
|
{
|
|
b.val[b.val.size() - 1].type = CONFIGVALUETYPESTRING;
|
|
}
|
|
ParseConfigValue(l, r, b.val[b.val.size() - 1]);
|
|
if (l > r) throw "Unfinished array at line " + i2s(cline);
|
|
if (s[l] == ']')
|
|
{
|
|
l++;
|
|
break;
|
|
}
|
|
l++;
|
|
}
|
|
}
|
|
|
|
// разбирает строку s с l по r, считая, что она - корректный ConfigValue
|
|
void ConfigParser::ParseConfigValue(int &l, int &r, ConfigValue &vl)
|
|
{
|
|
while ((l <= r) && (IsWS(s[l])))
|
|
{
|
|
if (s[l] == '\n') cline++;
|
|
l++;
|
|
}
|
|
if (l > r) return;
|
|
// отсечем комментарии
|
|
if ((s[l] == ';') || (s[l] == '#'))
|
|
{
|
|
while (s[l] != '\n') l++;
|
|
ParseConfigValue(l, r, vl);
|
|
return;
|
|
}
|
|
// если в точке l наличествует символ-разделитель, значит
|
|
// наше Value закончилось
|
|
if ((s[l] == ',') || (s[l] == ']'))
|
|
{
|
|
return;
|
|
}
|
|
if (vl.type == CONFIGVALUETYPESET)
|
|
{
|
|
// нашли скобочку - set закрылся
|
|
if (s[l] == '}')
|
|
{
|
|
l++;
|
|
return;
|
|
}
|
|
if (!GoodForName(s[l]))
|
|
{
|
|
throw "Parameter name expected at line " + i2s(cline);
|
|
}
|
|
ConfigParam p;
|
|
int oper = ParseConfigParam(l, r, p);
|
|
if (oper == OPERATION_MERGE)
|
|
{
|
|
for (set<ConfigParam>::iterator t = p.val[0].pval.begin(); t != p.val[0].pval.end(); t++)
|
|
{
|
|
vl.pval.insert(*t);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
set<ConfigParam>::iterator it = vl.pval.find(p);
|
|
if (it == vl.pval.end())
|
|
{
|
|
vl.pval.insert(p);
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
if (oper == OPERATIONADD)
|
|
{
|
|
reverse(p.val.begin(), p.val.end());
|
|
for (i = ((int)it->val.size() - 1); i >= 0; i--)
|
|
{
|
|
p.val.push_back(it->val[i]);
|
|
}
|
|
reverse(p.val.begin(), p.val.end());
|
|
vl.pval.erase(it);
|
|
vl.pval.insert(p);
|
|
}
|
|
else if (oper == OPERATIONSET)
|
|
{
|
|
vl.pval.erase(it);
|
|
vl.pval.insert(p);
|
|
}
|
|
else
|
|
{
|
|
throw (string)("Internal error (wrong operation returned) at line " + i2s(cline));
|
|
}
|
|
}
|
|
}
|
|
ParseConfigValue(l, r, vl);
|
|
}
|
|
else
|
|
{
|
|
vl.sval = "";
|
|
int isslashed = 0;
|
|
int isquoted = 0;
|
|
while (1)
|
|
{
|
|
if (l > r) throw "Unfinished value at line " + i2s(cline);
|
|
if (isslashed)
|
|
{
|
|
vl.sval += UnSlash(s[l]);
|
|
isslashed = 0;
|
|
}
|
|
else
|
|
{
|
|
if (s[l] == '\\') isslashed = 1;
|
|
else if (s[l] == '"') isquoted = 1 - isquoted;
|
|
else
|
|
{
|
|
if (IsDelim(s[l]))
|
|
{
|
|
if (isquoted == 0) break;
|
|
if (s[l] == '\n') throw "Unexpected end of line at line " + i2s(cline);
|
|
}
|
|
vl.sval += s[l];
|
|
}
|
|
}
|
|
l++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// убирает двойные кавычки в начале/конце строки
|
|
void ConfigParser::TrimQuotes(string &s)
|
|
{
|
|
if (((int)s.length()) < 2) return;
|
|
if ((s[0] == '\"') && (s[((int)s.length()) - 1] == '\"'))
|
|
{
|
|
s = s.substr(1, s.length() - 2);
|
|
}
|
|
}
|
|
|
|
void ConfigParser::Load(string fname)
|
|
{
|
|
// Загружаем весь конфиг в одну строку, теперь будем ее разбирать
|
|
FILE *fin = fopen(fname.c_str(), "rt");
|
|
if (!fin) throw "Config file " + fname + " not found!";
|
|
int c = getc(fin);
|
|
slen = 0;
|
|
while (c != EOF)
|
|
{
|
|
slen++;
|
|
c = getc(fin);
|
|
}
|
|
s = new char[slen + 1];
|
|
int i;
|
|
fclose(fin);
|
|
fin = fopen(fname.c_str(), "rt");
|
|
for (i = 0; i < slen; i++) s[i] = getc(fin);
|
|
fclose(fin);
|
|
s[slen] = '\n';
|
|
slen++;
|
|
// Теперь считаем всю строку конфиг-значением (так оно и должно быть)
|
|
cline = 1;
|
|
cfg.type = CONFIGVALUETYPESET;
|
|
cfg.sval = "";
|
|
cfg.pval.clear();
|
|
try{
|
|
int l = 0;
|
|
int r = slen - 1;
|
|
ParseConfigValue(l, r, cfg);
|
|
delete []s;
|
|
}
|
|
catch(string ss)
|
|
{
|
|
delete []s;
|
|
throw "Error parsing " + fname + ": " + ss;
|
|
}
|
|
catch(...)
|
|
{
|
|
delete []s;
|
|
throw "Unrecognized error parsing " + fname;
|
|
}
|
|
}
|
|
|
|
|
|
// возвращает значение параметра (в старом стиле)
|
|
string ConfigParser::GetParam(string name)
|
|
{
|
|
set<ConfigParam>::iterator p;
|
|
p = this->cfg.pval.find(ConfigParam(name));
|
|
if (p != this->cfg.pval.end())
|
|
{
|
|
if (p->val.size() == 0) throw "There are no values for parameter " + name;
|
|
return p->val[0].sval;
|
|
}
|
|
throw ((string)"No param \"" + name + "\" found!");
|
|
}
|
|
|
|
// возвращает значение параметра (в старом стиле)
|
|
string ConfigParser::GetParamSilent(string name)
|
|
{
|
|
set<ConfigParam>::iterator p;
|
|
p = this->cfg.pval.find(ConfigParam(name));
|
|
if (p != this->cfg.pval.end())
|
|
{
|
|
if (p->val.size() == 0) return "";
|
|
return p->val[0].sval;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
|
|
// возвращает какое-то одно значение параметра
|
|
// генерирует исключение при ошибке
|
|
string ConfigParam::GetSingleVal() const
|
|
{
|
|
if (val.size() == 0) throw ((string)"No value available");
|
|
if (val[0].type == CONFIGVALUETYPESTRING) return val[0].sval;
|
|
else
|
|
{
|
|
throw "Wrong parameter value type (string expected)";
|
|
}
|
|
}
|
|
|
|
// убивает в строке пробелы в начале и в конце
|
|
void ConfigParser::TrimSpaces(string &s)
|
|
{
|
|
string tmp = s;
|
|
s = "";
|
|
int wf = 0, i;
|
|
for (i = 0; i < ((int)tmp.length()); i++)
|
|
{
|
|
if (! IsWS(tmp[i]) )
|
|
{
|
|
wf = 1;
|
|
}
|
|
if (wf == 1) s += tmp[i];
|
|
}
|
|
while ((s.length() > 0) && (IsWS(s[s.length() - 1])))
|
|
{
|
|
s.erase(--s.end());
|
|
}
|
|
}
|
|
|
|
// Ищет в этом value заданный параметр. Возвращает пустую строку, если такого нет.
|
|
string ConfigValue::GetParamVal(string pname) const
|
|
{
|
|
if (this->type == CONFIGVALUETYPESTRING) throw ((string)"Attempted to get param from value, which is not a set");
|
|
set<ConfigParam>::iterator p = this->pval.find((ConfigParam)(pname));
|
|
if (p == pval.end()) return "";
|
|
return p->GetSingleVal();
|
|
}
|
|
|