435 lines
11 KiB
C++
435 lines
11 KiB
C++
|
#include "table.h"
|
||
|
|
||
|
#include <fstream>
|
||
|
#include "convert.h"
|
||
|
#include "unicode.h"
|
||
|
#include "streams.h"
|
||
|
#include "lexal.h"
|
||
|
#include "exceptions.h"
|
||
|
|
||
|
|
||
|
class IntValue: public Value
|
||
|
{
|
||
|
private:
|
||
|
int value;
|
||
|
|
||
|
public:
|
||
|
IntValue(int val) { value = val; };
|
||
|
virtual ~IntValue() { };
|
||
|
|
||
|
public:
|
||
|
virtual Type getType() const { return Value::Integer; };
|
||
|
virtual int asInt() const { return value; };
|
||
|
virtual double asDouble() const { return value; };
|
||
|
virtual std::wstring asString() const { return toString(value); };
|
||
|
virtual ::Table* asTable() const {
|
||
|
throw Exception(L"Can't convert integer to table");
|
||
|
};
|
||
|
virtual Value* clone() const { return new IntValue(value); };
|
||
|
};
|
||
|
|
||
|
|
||
|
class DoubleValue: public Value
|
||
|
{
|
||
|
private:
|
||
|
double value;
|
||
|
|
||
|
public:
|
||
|
DoubleValue(double val) { value = val; };
|
||
|
virtual ~DoubleValue() { };
|
||
|
|
||
|
public:
|
||
|
virtual Type getType() const { return Value::Double; };
|
||
|
virtual int asInt() const { return (int)value; };
|
||
|
virtual double asDouble() const { return value; };
|
||
|
virtual std::wstring asString() const { return toString(value); };
|
||
|
virtual ::Table* asTable() const {
|
||
|
throw Exception(L"Can't convert double to table");
|
||
|
};
|
||
|
virtual Value* clone() const { return new DoubleValue(value); };
|
||
|
};
|
||
|
|
||
|
|
||
|
class StringValue: public Value
|
||
|
{
|
||
|
private:
|
||
|
std::wstring value;
|
||
|
|
||
|
public:
|
||
|
StringValue(const std::wstring& val): value(val) { };
|
||
|
virtual ~StringValue() { };
|
||
|
|
||
|
public:
|
||
|
virtual Type getType() const { return Value::String; };
|
||
|
virtual int asInt() const { return strToInt(value); };
|
||
|
virtual double asDouble() const { return strToDouble(value); };
|
||
|
virtual std::wstring asString() const { return value; };
|
||
|
virtual ::Table* asTable() const {
|
||
|
throw Exception(L"Can't convert string to table");
|
||
|
};
|
||
|
virtual Value* clone() const { return new StringValue(value); };
|
||
|
};
|
||
|
|
||
|
|
||
|
class TableValue: public Value
|
||
|
{
|
||
|
private:
|
||
|
::Table *value;
|
||
|
|
||
|
public:
|
||
|
TableValue(::Table *val) { value = val; };
|
||
|
virtual ~TableValue() { delete value; };
|
||
|
|
||
|
public:
|
||
|
virtual Type getType() const { return Value::Table; };
|
||
|
virtual int asInt() const {
|
||
|
throw Exception(L"Can't convert table to int");
|
||
|
};
|
||
|
virtual double asDouble() const {
|
||
|
throw Exception(L"Can't convert table to double");
|
||
|
};
|
||
|
virtual std::wstring asString() const {
|
||
|
throw Exception(L"Can't convert table to string");
|
||
|
};
|
||
|
virtual ::Table* asTable() const { return value; };
|
||
|
virtual Value* clone() const {
|
||
|
return new TableValue(new ::Table(*value));
|
||
|
};
|
||
|
};
|
||
|
|
||
|
|
||
|
Table::Table(const Table &table)
|
||
|
{
|
||
|
*this = table;
|
||
|
}
|
||
|
|
||
|
Table::Table(const std::string &fileName)
|
||
|
{
|
||
|
lastArrayIndex = 0;
|
||
|
std::ifstream stream(fileName.c_str(),
|
||
|
std::ios::binary | std::ios::in);
|
||
|
if (! stream.good())
|
||
|
throw Exception(L"Error opening file '" + fromMbcs(fileName) + L"");
|
||
|
UtfStreamReader reader(&stream);
|
||
|
Lexal lexal = Lexal(reader);
|
||
|
parse(lexal, false, 0, 0);
|
||
|
}
|
||
|
|
||
|
Table::Table(Lexal &lexal, int line, int pos)
|
||
|
{
|
||
|
lastArrayIndex = 0;
|
||
|
parse(lexal, true, line, pos);
|
||
|
}
|
||
|
|
||
|
Table::Table()
|
||
|
{
|
||
|
lastArrayIndex = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
Table::~Table()
|
||
|
{
|
||
|
for (ValuesMap::iterator i = fields.begin(); i != fields.end(); i++)
|
||
|
delete (*i).second;
|
||
|
}
|
||
|
|
||
|
|
||
|
Table& Table::operator = (const Table &table)
|
||
|
{
|
||
|
if (this == &table)
|
||
|
return *this;
|
||
|
|
||
|
fields.clear();
|
||
|
lastArrayIndex = table.lastArrayIndex;
|
||
|
for (ValuesMap::const_iterator i = table.fields.begin();
|
||
|
i != table.fields.end(); i++)
|
||
|
fields[(*i).first] = (*i).second->clone();
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
|
||
|
static Value* lexToValue(Lexal &lexal, const Lexeme &lexeme)
|
||
|
{
|
||
|
switch (lexeme.getType())
|
||
|
{
|
||
|
case Lexeme::Ident:
|
||
|
case Lexeme::String:
|
||
|
return new StringValue(lexeme.getContent());
|
||
|
case Lexeme::Integer:
|
||
|
return new IntValue(strToInt(lexeme.getContent()));
|
||
|
case Lexeme::Float:
|
||
|
return new DoubleValue(strToDouble(lexeme.getContent()));
|
||
|
case Lexeme::Symbol:
|
||
|
if (L"{" == lexeme.getContent())
|
||
|
return new TableValue(new Table(lexal, lexeme.getLine(),
|
||
|
lexeme.getPos()));
|
||
|
default:
|
||
|
throw Exception(L"Invalid lexeme type at " + lexeme.getPosStr());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void Table::addValuePair(Lexal &lexal, const std::wstring &name)
|
||
|
{
|
||
|
Lexeme lex = lexal.getNext();
|
||
|
if (Lexeme::Eof == lex.getType())
|
||
|
throw Exception(L"Unexpected end of file");
|
||
|
fields[name] = lexToValue(lexal, lex);
|
||
|
}
|
||
|
|
||
|
void Table::addArrayElement(Lexal &lexal, const Lexeme &lexeme)
|
||
|
{
|
||
|
fields[numToStr(lastArrayIndex)] = lexToValue(lexal, lexeme);
|
||
|
lastArrayIndex++;
|
||
|
}
|
||
|
|
||
|
void Table::parse(Lexal &lexal, bool needBracket, int startLine, int startPos)
|
||
|
{
|
||
|
Lexeme lex;
|
||
|
bool read = true;
|
||
|
|
||
|
while (true) {
|
||
|
if (read) {
|
||
|
lex = lexal.getNext();
|
||
|
read = true;
|
||
|
}
|
||
|
Lexeme::Type type = lex.getType();
|
||
|
if (Lexeme::Eof == type) {
|
||
|
if (! needBracket)
|
||
|
return;
|
||
|
else
|
||
|
throw Exception(L"Table started at " + Lexal::posToStr(
|
||
|
startLine, startPos) + L" is never finished");
|
||
|
} else if ((Lexeme::Symbol == type) && (L"}" == lex.getContent())
|
||
|
&& needBracket)
|
||
|
return;
|
||
|
else if ((Lexeme::Symbol == type) && ((L"," == lex.getContent()) ||
|
||
|
(L";" == lex.getContent()))) {
|
||
|
// ignore separator
|
||
|
} else if (Lexeme::Ident == type) {
|
||
|
Lexeme nextLex = lexal.getNext();
|
||
|
if (Lexeme::Symbol == nextLex.getType()) {
|
||
|
if (L"=" == nextLex.getContent())
|
||
|
addValuePair(lexal, lex.getContent());
|
||
|
else {
|
||
|
addArrayElement(lexal, lex);
|
||
|
lex = nextLex;
|
||
|
read = false;
|
||
|
}
|
||
|
} else
|
||
|
throw Exception(L"Unexpected token at " + Lexal::posToStr(
|
||
|
startLine, startPos));
|
||
|
} else
|
||
|
addArrayElement(lexal, lex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
bool Table::hasKey(const std::wstring &key) const
|
||
|
{
|
||
|
return (fields.end() != fields.find(key));
|
||
|
}
|
||
|
|
||
|
|
||
|
static std::wstring encodeString(const std::wstring &str)
|
||
|
{
|
||
|
std::wstring res;
|
||
|
int len = str.length();
|
||
|
|
||
|
res += L"\"";
|
||
|
for (int i = 0; i < len; i++) {
|
||
|
wchar_t ch = str[i];
|
||
|
switch (ch) {
|
||
|
case '\n': res += L"\\n"; break;
|
||
|
case '\r': res += L"\\r"; break;
|
||
|
case '\'': res += L"\\'"; break;
|
||
|
case '\"': res += L"\\\""; break;
|
||
|
case '\\': res += L"\\\\"; break;
|
||
|
default:
|
||
|
res += ch;
|
||
|
}
|
||
|
}
|
||
|
res += L"\"";
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static std::wstring printValue(Value *value, bool butify, int spaces)
|
||
|
{
|
||
|
if (! value)
|
||
|
return L"";
|
||
|
|
||
|
switch (value->getType()) {
|
||
|
case Value::Integer:
|
||
|
return toString(value->asInt());
|
||
|
|
||
|
case Value::Double:
|
||
|
{
|
||
|
std::wstring s = toString(value->asDouble());
|
||
|
if (s.find(L'.') >= s.length())
|
||
|
s.append(L".0");
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
case Value::String:
|
||
|
return encodeString(value->asString());
|
||
|
|
||
|
case Value::Table:
|
||
|
return value->asTable()->toString(true, butify, spaces);
|
||
|
}
|
||
|
|
||
|
return L"";
|
||
|
}
|
||
|
|
||
|
|
||
|
bool Table::isArray() const
|
||
|
{
|
||
|
int size = fields.size();
|
||
|
|
||
|
for (int i = 0; i < size; i++) {
|
||
|
if (! hasKey(::toString(i)))
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
std::wstring Table::toString() const
|
||
|
{
|
||
|
return toString(false, true, 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
static bool isInteger(const std::wstring &str)
|
||
|
{
|
||
|
int sz = str.length();
|
||
|
for (int i = 0; i < sz; i++) {
|
||
|
wchar_t ch = str[i];
|
||
|
if ((ch < L'0') || (ch > L'9'))
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
std::wstring Table::toString(bool printBraces, bool butify, int spaces) const
|
||
|
{
|
||
|
std::wstring res;
|
||
|
|
||
|
if (printBraces)
|
||
|
res += butify ? L"{\n" : L"{";
|
||
|
bool printNames = ! isArray();
|
||
|
|
||
|
for (ValuesMap::const_iterator i = fields.begin(); i != fields.end();
|
||
|
i++)
|
||
|
{
|
||
|
const std::wstring &name = (*i).first;
|
||
|
Value *value = (*i).second;
|
||
|
if (butify)
|
||
|
for (int j = 0; j < spaces; j++)
|
||
|
res += L" ";
|
||
|
else
|
||
|
res += L" ";
|
||
|
if (printNames && (! isInteger(name)))
|
||
|
res += name + L" = ";
|
||
|
res += printValue(value, butify, spaces + 4);
|
||
|
if (printNames)
|
||
|
res += butify ? L";\n" : L";";
|
||
|
else
|
||
|
res += butify ? L",\n" : L",";
|
||
|
}
|
||
|
|
||
|
if (printBraces) {
|
||
|
if (! butify)
|
||
|
res += L" }";
|
||
|
else {
|
||
|
int ident = (spaces >= 4) ? spaces - 4 : 0;
|
||
|
for (int j = 0; j < ident; j++)
|
||
|
res += L" ";
|
||
|
res += L"}";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
|
||
|
Value::Type Table::getType(const std::wstring &key) const
|
||
|
{
|
||
|
ValuesMap::const_iterator i = fields.find(key);
|
||
|
if (i == fields.end())
|
||
|
throw Exception(L"Field '" + key + L"' doesn't exists in the table");
|
||
|
return (*i).second->getType();
|
||
|
}
|
||
|
|
||
|
|
||
|
std::wstring Table::getString(const std::wstring &key,
|
||
|
const std::wstring &dflt) const
|
||
|
{
|
||
|
ValuesMap::const_iterator i = fields.find(key);
|
||
|
return (i != fields.end()) ? (*i).second->asString() : dflt;
|
||
|
}
|
||
|
|
||
|
int Table::getInt(const std::wstring &key, int dflt) const
|
||
|
{
|
||
|
ValuesMap::const_iterator i = fields.find(key);
|
||
|
return (i != fields.end()) ? ((*i).second ? (*i).second->asInt() : dflt) : dflt;
|
||
|
}
|
||
|
|
||
|
double Table::getDouble(const std::wstring &key, double dflt) const
|
||
|
{
|
||
|
ValuesMap::const_iterator i = fields.find(key);
|
||
|
return (i != fields.end()) ? (*i).second->asDouble() : dflt;
|
||
|
}
|
||
|
|
||
|
Table* Table::getTable(const std::wstring &key, Table *dflt) const
|
||
|
{
|
||
|
ValuesMap::const_iterator i = fields.find(key);
|
||
|
return (i != fields.end()) ? (*i).second->asTable() : dflt;
|
||
|
}
|
||
|
|
||
|
void Table::setValue(const std::wstring &key, Value *value)
|
||
|
{
|
||
|
ValuesMap::const_iterator i = fields.find(key);
|
||
|
if (i != fields.end())
|
||
|
delete (*i).second;
|
||
|
fields[key] = value;
|
||
|
}
|
||
|
|
||
|
void Table::setString(const std::wstring &key, const std::wstring &value)
|
||
|
{
|
||
|
setValue(key, new StringValue(value));
|
||
|
}
|
||
|
|
||
|
void Table::setInt(const std::wstring &key, int value)
|
||
|
{
|
||
|
setValue(key, new IntValue(value));
|
||
|
}
|
||
|
|
||
|
void Table::setDouble(const std::wstring &key, double value)
|
||
|
{
|
||
|
setValue(key, new DoubleValue(value));
|
||
|
}
|
||
|
|
||
|
void Table::setTable(const std::wstring &key, Table *value)
|
||
|
{
|
||
|
setValue(key, new TableValue(value));
|
||
|
}
|
||
|
|
||
|
|
||
|
void Table::save(const std::wstring &fileName) const
|
||
|
{
|
||
|
std::ofstream stream(toMbcs(fileName).c_str(), std::ios::out
|
||
|
| std::ios::binary);
|
||
|
if (! stream.good())
|
||
|
throw Exception(L"Can't open '" + fileName + L"' for writing");
|
||
|
std::string utfStr(toUtf8(toString()));
|
||
|
stream.write(utfStr.c_str(), utfStr.length());
|
||
|
if (! stream.good())
|
||
|
throw Exception(L"Can't write table to file '" + fileName + L"'");
|
||
|
stream.close();
|
||
|
}
|
||
|
|