mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2025-11-29 17:38:24 +08:00
1351 lines
30 KiB
C++
1351 lines
30 KiB
C++
/**
|
|
@file Any.cpp
|
|
|
|
@author Morgan McGuire
|
|
@author Shawn Yarbrough
|
|
|
|
@created 2006-06-11
|
|
@edited 2010-07-24
|
|
|
|
Copyright 2000-2010, Morgan McGuire.
|
|
All rights reserved.
|
|
*/
|
|
|
|
#include "G3D/Any.h"
|
|
#include "G3D/TextOutput.h"
|
|
#include "G3D/TextInput.h"
|
|
#include "G3D/stringutils.h"
|
|
#include "G3D/fileutils.h"
|
|
#include "G3D/FileSystem.h"
|
|
#include <deque>
|
|
#include <iostream>
|
|
|
|
namespace G3D {
|
|
|
|
std::string Any::resolveStringAsFilename() const {
|
|
verifyType(STRING);
|
|
std::string f = FileSystem::resolve(string(), sourceDirectory());
|
|
if (FileSystem::exists(f)) {
|
|
return f;
|
|
} else {
|
|
const std::string& s = System::findDataFile(string(), false);
|
|
if (s.empty()) {
|
|
return string();
|
|
} else {
|
|
return s;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool Any::nameBeginsWith(const std::string& s) const {
|
|
return nameBeginsWith(s.c_str());
|
|
}
|
|
|
|
|
|
bool Any::nameEquals(const std::string& s) const {
|
|
// If std::string has a fast hash compare, use it first
|
|
return (name() == s) || nameEquals(s.c_str());
|
|
}
|
|
|
|
|
|
inline static char toLower(char c) {
|
|
return ((c >= 'A') && (c <= 'Z')) ? (c - 'A' + 'a') : c;
|
|
}
|
|
|
|
|
|
bool Any::nameBeginsWith(const char* s) const {
|
|
verifyType(Any::ARRAY, Any::TABLE);
|
|
|
|
const char* n = name().c_str();
|
|
// Walk through character-by-character
|
|
while ((*s != '\0') && (*n != '\0')) {
|
|
if (toLower(*s) != toLower(*n)) {
|
|
// Mismatch
|
|
return false;
|
|
}
|
|
++s; ++n;
|
|
}
|
|
// Make sure s ran out no later than n
|
|
return (*s == '\0');
|
|
}
|
|
|
|
|
|
bool Any::nameEquals(const char* s) const {
|
|
verifyType(Any::ARRAY, Any::TABLE);
|
|
#ifdef G3D_WIN32
|
|
return stricmp(name().c_str(), s) == 0;
|
|
#else
|
|
return strcasecmp(name().c_str(), s) == 0;
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
void Any::beforeRead() const {
|
|
if (isPlaceholder()) {
|
|
// Tried to read from a placeholder--throw an exception as if
|
|
// the original operator[] had failed.
|
|
KeyNotFound e;
|
|
alwaysAssertM(m_data, "Corrupt placeholder");
|
|
|
|
e.filename = m_data->source.filename;
|
|
e.line = m_data->source.line;
|
|
e.character = m_data->source.character;
|
|
e.key = m_placeholderName;
|
|
e.message =
|
|
"This exception may have been thrown later than "
|
|
"the actual operator[] invocation.";
|
|
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
|
|
Any::Data* Any::Data::create(const Data* d) {
|
|
Data* p = create(d->type);
|
|
|
|
p->comment = d->comment;
|
|
p->name = d->name;
|
|
|
|
switch (d->type) {
|
|
case NONE:
|
|
case BOOLEAN:
|
|
case NUMBER:
|
|
// No clone needed
|
|
break;
|
|
|
|
case STRING:
|
|
*(p->value.s) = *(d->value.s);
|
|
break;
|
|
|
|
case ARRAY:
|
|
*(p->value.a) = *(d->value.a);
|
|
break;
|
|
|
|
case TABLE:
|
|
*(p->value.t) = *(d->value.t);
|
|
// Note that placeholders may be copied; that is ok--they are still
|
|
// just placeholders.
|
|
break;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
Any::Data* Any::Data::create(Any::Type t) {
|
|
size_t s = sizeof(Data);
|
|
|
|
switch (t) {
|
|
case NONE:
|
|
case BOOLEAN:
|
|
case NUMBER:
|
|
// No extra space needed
|
|
break;
|
|
|
|
case STRING:
|
|
s += sizeof(std::string);
|
|
break;
|
|
|
|
case ARRAY:
|
|
s += sizeof(AnyArray);
|
|
break;
|
|
|
|
case TABLE:
|
|
s += sizeof(AnyTable);
|
|
break;
|
|
}
|
|
|
|
// Allocate the data object
|
|
Data* p = new (MemoryManager::create()->alloc(s)) Data(t);
|
|
|
|
// Create the (empyt) value object at the end of the Data object
|
|
switch (t) {
|
|
case NONE:
|
|
case BOOLEAN:
|
|
case NUMBER:
|
|
// No value
|
|
break;
|
|
|
|
case STRING:
|
|
p->value.s = new (p + 1) std::string();
|
|
break;
|
|
|
|
case ARRAY:
|
|
p->value.a = new (p + 1) AnyArray();
|
|
break;
|
|
|
|
case TABLE:
|
|
p->value.t = new (p + 1) AnyTable();
|
|
break;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
void Any::Data::destroy(Data* d) {
|
|
if (d != NULL) {
|
|
d->~Data();
|
|
MemoryManager::create()->free(d);
|
|
}
|
|
}
|
|
|
|
|
|
Any::Data::~Data() {
|
|
debugAssertM(referenceCount.value() <= 0, "Deleted while still referenced.");
|
|
|
|
// Destruct but do not deallocate children
|
|
switch (type) {
|
|
case STRING:
|
|
debugAssert(value.s != NULL);
|
|
value.s->~basic_string();
|
|
break;
|
|
|
|
case ARRAY:
|
|
debugAssert(value.a != NULL);
|
|
value.a->~Array();
|
|
break;
|
|
|
|
case TABLE:
|
|
debugAssert(value.t != NULL);
|
|
value.t->~Table();
|
|
break;
|
|
|
|
default:
|
|
// All other types should have a NULL value pointer (i.e., they were used just for name and comment fields)
|
|
debugAssertM(value.s == NULL, "Corrupt Any::Data::Value");
|
|
}
|
|
|
|
value.s = NULL;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
bool Any::containsKey(const std::string& x) const {
|
|
beforeRead();
|
|
verifyType(TABLE);
|
|
|
|
Any* a = m_data->value.t->getPointer(x);
|
|
|
|
// Don't return true for placeholder objects
|
|
return (a != NULL) && (! a->isPlaceholder());
|
|
}
|
|
|
|
|
|
void Any::dropReference() {
|
|
if (m_data && m_data->referenceCount.decrement() <= 0) {
|
|
// This was the last reference to the shared data
|
|
Data::destroy(m_data);
|
|
}
|
|
m_data = NULL;
|
|
}
|
|
|
|
|
|
void Any::ensureMutable() {
|
|
if (m_data && (m_data->referenceCount.value() >= 1)) {
|
|
// Copy the data. We must do this before dropping the reference
|
|
// to avoid a race condition
|
|
Data* d = Data::create(m_data);
|
|
dropReference();
|
|
m_data = d;
|
|
}
|
|
}
|
|
|
|
|
|
Any::Any() : m_type(NONE), m_data(NULL) {
|
|
}
|
|
|
|
|
|
Any::Any(TextInput& t) : m_type(NONE), m_data(NULL) {
|
|
deserialize(t);
|
|
}
|
|
|
|
|
|
Any::Any(const Any& x) : m_type(NONE), m_data(NULL) {
|
|
x.beforeRead();
|
|
*this = x;
|
|
}
|
|
|
|
|
|
Any::Any(double x) : m_type(NUMBER), m_simpleValue(x), m_data(NULL) {
|
|
}
|
|
|
|
|
|
#ifdef G3D_32BIT
|
|
Any::Any(int64 x) : m_type(NUMBER), m_simpleValue((double)x), m_data(NULL) {
|
|
}
|
|
#endif // G3D_32BIT
|
|
|
|
|
|
Any::Any(long x) : m_type(NUMBER), m_simpleValue((double)x), m_data(NULL) {
|
|
}
|
|
|
|
|
|
Any::Any(int x) : m_type(NUMBER), m_simpleValue((double)x), m_data(NULL) {
|
|
}
|
|
|
|
|
|
Any::Any(short x) : m_type(NUMBER), m_simpleValue((double)x), m_data(NULL) {
|
|
}
|
|
|
|
|
|
Any::Any(bool x) : m_type(BOOLEAN), m_simpleValue(x), m_data(NULL) {
|
|
}
|
|
|
|
|
|
Any::Any(const std::string& s) : m_type(STRING), m_data(Data::create(STRING)) {
|
|
*(m_data->value.s) = s;
|
|
}
|
|
|
|
|
|
Any::Any(const char* s) : m_type(STRING), m_data(NULL) {
|
|
if (s == NULL) {
|
|
m_type = NONE;
|
|
} else {
|
|
ensureData();
|
|
*(m_data->value.s) = s;
|
|
}
|
|
}
|
|
|
|
|
|
Any::Any(Type t, const std::string& name) : m_type(t), m_data(NULL) {
|
|
alwaysAssertM(t == ARRAY || t == TABLE, "Can only create ARRAY or TABLE from Type enum.");
|
|
|
|
ensureData();
|
|
if (name != "") {
|
|
m_data->name = name;
|
|
}
|
|
}
|
|
|
|
|
|
Any::~Any() {
|
|
dropReference();
|
|
}
|
|
|
|
|
|
void Any::beforeWrite() {
|
|
if (isPlaceholder()) {
|
|
// This is no longer a placeholder
|
|
m_placeholderName = "";
|
|
}
|
|
}
|
|
|
|
Any& Any::operator=(const Any& x) {
|
|
x.beforeRead();
|
|
|
|
if (this == &x) {
|
|
return *this;
|
|
}
|
|
|
|
beforeWrite();
|
|
|
|
dropReference();
|
|
|
|
m_type = x.m_type;
|
|
m_simpleValue = x.m_simpleValue;
|
|
|
|
if (x.m_data != NULL) {
|
|
x.m_data->referenceCount.increment();
|
|
m_data = x.m_data;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
Any& Any::operator=(double x) {
|
|
*this = Any(x);
|
|
return *this;
|
|
}
|
|
|
|
|
|
Any& Any::operator=(int x) {
|
|
return (*this = Any(x));
|
|
}
|
|
|
|
|
|
Any& Any::operator=(bool x) {
|
|
*this = Any(x);
|
|
return *this;
|
|
}
|
|
|
|
|
|
Any& Any::operator=(const std::string& x) {
|
|
*this = Any(x);
|
|
return *this;
|
|
}
|
|
|
|
|
|
Any& Any::operator=(const char* x) {
|
|
*this = Any(x);
|
|
return *this;
|
|
}
|
|
|
|
|
|
Any& Any::operator=(Type t) {
|
|
switch (t) {
|
|
case NONE:
|
|
*this = Any();
|
|
break;
|
|
|
|
case TABLE:
|
|
case ARRAY:
|
|
*this = Any(t);
|
|
break;
|
|
|
|
default:
|
|
alwaysAssertM(false, "Can only assign NONE, TABLE, or ARRAY Type enum.");
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
Any::Type Any::type() const {
|
|
beforeRead();
|
|
return m_type;
|
|
}
|
|
|
|
|
|
const std::string& Any::comment() const {
|
|
beforeRead();
|
|
|
|
static const std::string blank;
|
|
if (m_data != NULL) {
|
|
return m_data->comment;
|
|
} else {
|
|
return blank;
|
|
}
|
|
}
|
|
|
|
|
|
void Any::setComment(const std::string& c) {
|
|
beforeRead();
|
|
ensureData();
|
|
m_data->comment = c;
|
|
}
|
|
|
|
|
|
bool Any::isNone() const {
|
|
beforeRead();
|
|
return (m_type == NONE);
|
|
}
|
|
|
|
|
|
double Any::number() const {
|
|
beforeRead();
|
|
verifyType(NUMBER);
|
|
return m_simpleValue.n;
|
|
}
|
|
|
|
|
|
const std::string& Any::string() const {
|
|
beforeRead();
|
|
verifyType(STRING);
|
|
return *(m_data->value.s);
|
|
}
|
|
|
|
|
|
bool Any::boolean() const {
|
|
beforeRead();
|
|
verifyType(BOOLEAN);
|
|
return m_simpleValue.b;
|
|
}
|
|
|
|
|
|
const std::string& Any::name() const {
|
|
beforeRead();
|
|
static const std::string blank;
|
|
if (m_data != NULL) {
|
|
return m_data->name;
|
|
} else {
|
|
return blank;
|
|
}
|
|
}
|
|
|
|
|
|
void Any::setName(const std::string& n) {
|
|
beforeRead();
|
|
ensureData();
|
|
m_data->name = n;
|
|
}
|
|
|
|
|
|
int Any::size() const {
|
|
beforeRead();
|
|
verifyType(ARRAY, TABLE);
|
|
switch (m_type) {
|
|
case TABLE:
|
|
return m_data->value.t->size();
|
|
|
|
case ARRAY:
|
|
return m_data->value.a->size();
|
|
|
|
default:;
|
|
return 0;
|
|
} // switch (m_type)
|
|
}
|
|
|
|
|
|
int Any::length() const {
|
|
beforeRead();
|
|
return size();
|
|
}
|
|
|
|
|
|
void Any::resize(int n) {
|
|
beforeRead();
|
|
alwaysAssertM(n >= 0, "Cannot resize less than 0.");
|
|
verifyType(ARRAY);
|
|
m_data->value.a->resize(n);
|
|
}
|
|
|
|
|
|
void Any::clear() {
|
|
beforeRead();
|
|
verifyType(ARRAY, TABLE);
|
|
switch (m_type) {
|
|
case ARRAY:
|
|
m_data->value.a->clear();
|
|
break;
|
|
|
|
case TABLE:
|
|
m_data->value.t->clear();
|
|
break;
|
|
|
|
default:;
|
|
}
|
|
}
|
|
|
|
|
|
const Any& Any::operator[](int i) const {
|
|
beforeRead();
|
|
verifyType(ARRAY);
|
|
debugAssert(m_data != NULL);
|
|
Array<Any>& array = *(m_data->value.a);
|
|
return array[i];
|
|
}
|
|
|
|
|
|
Any& Any::next() {
|
|
beforeRead();
|
|
verifyType(ARRAY);
|
|
int n = size();
|
|
resize(n + 1);
|
|
return (*this)[n];
|
|
}
|
|
|
|
|
|
Any& Any::operator[](int i) {
|
|
beforeRead();
|
|
verifyType(ARRAY);
|
|
debugAssert(m_data != NULL);
|
|
Array<Any>& array = *(m_data->value.a);
|
|
return array[i];
|
|
}
|
|
|
|
|
|
const Array<Any>& Any::array() const {
|
|
beforeRead();
|
|
verifyType(ARRAY);
|
|
debugAssert(m_data != NULL);
|
|
return *(m_data->value.a);
|
|
}
|
|
|
|
|
|
void Any::append(const Any& x0) {
|
|
beforeRead();
|
|
verifyType(ARRAY);
|
|
debugAssert(m_data != NULL);
|
|
m_data->value.a->append(x0);
|
|
}
|
|
|
|
|
|
void Any::append(const Any& x0, const Any& x1) {
|
|
beforeRead();
|
|
append(x0);
|
|
append(x1);
|
|
}
|
|
|
|
|
|
void Any::append(const Any& x0, const Any& x1, const Any& x2) {
|
|
beforeRead();
|
|
append(x0);
|
|
append(x1);
|
|
append(x2);
|
|
}
|
|
|
|
|
|
void Any::append(const Any& x0, const Any& x1, const Any& x2, const Any& x3) {
|
|
beforeRead();
|
|
append(x0);
|
|
append(x1);
|
|
append(x2);
|
|
append(x3);
|
|
}
|
|
|
|
|
|
const Table<std::string, Any>& Any::table() const {
|
|
beforeRead();
|
|
verifyType(TABLE);
|
|
debugAssert(m_data != NULL);
|
|
return *(m_data->value.t);
|
|
}
|
|
|
|
|
|
const Any& Any::operator[](const std::string& x) const {
|
|
beforeRead();
|
|
verifyType(TABLE);
|
|
debugAssert(m_data != NULL);
|
|
const Table<std::string, Any>& table = *(m_data->value.t);
|
|
Any* value = table.getPointer(x);
|
|
if (value == NULL) {
|
|
KeyNotFound e;
|
|
if (m_data) {
|
|
e.filename = m_data->source.filename;
|
|
e.line = m_data->source.line;
|
|
e.character = m_data->source.character;
|
|
}
|
|
e.key = x;
|
|
throw e;
|
|
}
|
|
return *value;
|
|
}
|
|
|
|
|
|
Any& Any::operator[](const std::string& key) {
|
|
beforeRead();
|
|
verifyType(TABLE);
|
|
|
|
bool created = false;
|
|
Any& value = m_data->value.t->getCreate(key, created);
|
|
|
|
if (created) {
|
|
// The entry was created by this method; do not allow it to be
|
|
// read before it is written.
|
|
value.m_placeholderName = key;
|
|
|
|
// Write source data for the value
|
|
value.ensureData();
|
|
value.m_data->source = source();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
void Any::set(const std::string& k, const Any& v) {
|
|
beforeRead();
|
|
v.beforeRead();
|
|
verifyType(TABLE);
|
|
debugAssert(m_data != NULL);
|
|
Table<std::string, Any>& table = *(m_data->value.t);
|
|
table.set(k, v);
|
|
}
|
|
|
|
|
|
const Any& Any::get(const std::string& x, const Any& defaultVal) const {
|
|
beforeRead();
|
|
defaultVal.beforeRead();
|
|
try {
|
|
return operator[](x);
|
|
} catch(KeyNotFound) {
|
|
return defaultVal;
|
|
}
|
|
}
|
|
|
|
|
|
bool Any::operator==(const Any& x) const {
|
|
beforeRead();
|
|
x.beforeRead();
|
|
if (m_type != x.m_type) {
|
|
return false;
|
|
}
|
|
|
|
switch (m_type) {
|
|
case NONE:
|
|
return true;
|
|
|
|
case BOOLEAN:
|
|
return (m_simpleValue.b == x.m_simpleValue.b);
|
|
|
|
case NUMBER:
|
|
return (m_simpleValue.n == x.m_simpleValue.n);
|
|
|
|
case STRING:
|
|
debugAssert(m_data != NULL);
|
|
return (*(m_data->value.s) == *(x.m_data->value.s));
|
|
|
|
case TABLE: {
|
|
if (size() != x.size()) {
|
|
return false;
|
|
}
|
|
debugAssert(m_data != NULL);
|
|
if (m_data->name != x.m_data->name) {
|
|
return false;
|
|
}
|
|
Table<std::string, Any>& cmptable = *( m_data->value.t);
|
|
Table<std::string, Any>& xcmptable = *(x.m_data->value.t);
|
|
for (Table<std::string,Any>::Iterator it1 = cmptable.begin(), it2 = xcmptable.begin();
|
|
it1 != cmptable.end() && it2 != xcmptable.end();
|
|
++it1, ++it2) {
|
|
if (*it1 != *it2) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
case ARRAY: {
|
|
if (size() != x.size()) {
|
|
return false;
|
|
}
|
|
debugAssert(m_data != NULL);
|
|
if (m_data->name != x.m_data->name) {
|
|
return false;
|
|
}
|
|
|
|
Array<Any>& cmparray = *( m_data->value.a);
|
|
Array<Any>& xcmparray = *(x.m_data->value.a);
|
|
|
|
for (int ii = 0; ii < size(); ++ii) {
|
|
if (cmparray[ii] != xcmparray[ii]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
alwaysAssertM(false, "Unknown type.");
|
|
return false;
|
|
} // switch (m_type)
|
|
|
|
}
|
|
|
|
|
|
bool Any::operator!=(const Any& x) const {
|
|
beforeRead();
|
|
x.beforeRead();
|
|
return !operator==(x);
|
|
}
|
|
|
|
|
|
static void getDeserializeSettings(TextInput::Settings& settings) {
|
|
settings.cppBlockComments = true;
|
|
settings.cppLineComments = true;
|
|
settings.otherLineComments = false;
|
|
settings.generateCommentTokens = true;
|
|
settings.singleQuotedStrings = false;
|
|
settings.msvcFloatSpecials = false;
|
|
settings.caseSensitive = false;
|
|
}
|
|
|
|
|
|
std::string Any::unparse() const {
|
|
beforeRead();
|
|
TextOutput::Settings settings;
|
|
TextOutput to(settings);
|
|
serialize(to);
|
|
return to.commitString();
|
|
}
|
|
|
|
|
|
void Any::parse(const std::string& src) {
|
|
beforeRead();
|
|
TextInput::Settings settings;
|
|
getDeserializeSettings(settings);
|
|
|
|
TextInput ti(TextInput::FROM_STRING, src, settings);
|
|
deserialize(ti);
|
|
}
|
|
|
|
|
|
void Any::load(const std::string& filename) {
|
|
beforeRead();
|
|
TextInput::Settings settings;
|
|
getDeserializeSettings(settings);
|
|
|
|
TextInput ti(FileSystem::resolve(filename), settings);
|
|
deserialize(ti);
|
|
}
|
|
|
|
|
|
void Any::save(const std::string& filename) const {
|
|
beforeRead();
|
|
TextOutput::Settings settings;
|
|
settings.wordWrap = TextOutput::Settings::WRAP_NONE;
|
|
|
|
TextOutput to(filename,settings);
|
|
serialize(to);
|
|
to.commit();
|
|
}
|
|
|
|
|
|
static bool needsQuotes(const std::string& s) {
|
|
if (! isLetter(s[0]) && (s[0] != '_')) {
|
|
return true;
|
|
}
|
|
|
|
for (int i = 0; i < (int)s.length(); ++i) {
|
|
char c = s[i];
|
|
|
|
// peek character
|
|
char p = (i == (int)s.length() - 1) ? '_' : s[i + 1];
|
|
|
|
// Identify separators
|
|
if ((c == '-' && p == '>') ||
|
|
(c == ':' && p == ':')) {
|
|
// Skip over this symbol
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
if (! isDigit(c) && ! isLetter(c) & (c != '.')) {
|
|
// This is an illegal character for an identifier, so we need quotes
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// TODO: if the output will fit on one line, compress tables and arrays into a single line
|
|
void Any::serialize(TextOutput& to) const {
|
|
beforeRead();
|
|
if (m_data && ! m_data->comment.empty()) {
|
|
to.printf("\n/* %s */\n", m_data->comment.c_str());
|
|
}
|
|
|
|
switch (m_type) {
|
|
case NONE:
|
|
to.writeSymbol("NONE");
|
|
break;
|
|
|
|
case BOOLEAN:
|
|
to.writeBoolean(m_simpleValue.b);
|
|
break;
|
|
|
|
case NUMBER:
|
|
to.writeNumber(m_simpleValue.n);
|
|
break;
|
|
|
|
case STRING:
|
|
debugAssert(m_data != NULL);
|
|
to.writeString(*(m_data->value.s));
|
|
break;
|
|
|
|
case TABLE: {
|
|
debugAssert(m_data != NULL);
|
|
if (! m_data->name.empty()) {
|
|
if (needsQuotes(m_data->name)) {
|
|
to.writeString(m_data->name);
|
|
} else {
|
|
to.writeSymbol(m_data->name);
|
|
}
|
|
}
|
|
to.writeSymbol("{");
|
|
to.writeNewline();
|
|
to.pushIndent();
|
|
AnyTable& table = *(m_data->value.t);
|
|
Array<std::string> keys;
|
|
table.getKeys(keys);
|
|
keys.sort();
|
|
|
|
for (int i = 0; i < keys.size(); ++i) {
|
|
|
|
to.writeSymbol(keys[i]);
|
|
to.writeSymbol("=");
|
|
table[keys[i]].serialize(to);
|
|
|
|
if (i < keys.size() - 1) {
|
|
to.writeSymbol(",");
|
|
}
|
|
to.writeNewline();
|
|
|
|
// Skip a line between table entries
|
|
to.writeNewline();
|
|
}
|
|
|
|
to.popIndent();
|
|
to.writeSymbol("}");
|
|
break;
|
|
}
|
|
|
|
case ARRAY: {
|
|
debugAssert(m_data != NULL);
|
|
if (! m_data->name.empty()) {
|
|
// For arrays, leave no trailing space between the name and the paren
|
|
to.writeSymbol(format("%s(", m_data->name.c_str()));
|
|
} else {
|
|
to.writeSymbol("(");
|
|
}
|
|
to.writeNewline();
|
|
to.pushIndent();
|
|
Array<Any>& array = *(m_data->value.a);
|
|
for (int ii = 0; ii < size(); ++ii) {
|
|
array[ii].serialize(to);
|
|
if (ii < size() - 1) {
|
|
to.writeSymbol(",");
|
|
to.writeNewline();
|
|
}
|
|
|
|
// Put the close paren on an array right behind the last element
|
|
}
|
|
to.popIndent();
|
|
to.writeSymbol(")");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Any::deserializeComment(TextInput& ti, Token& token, std::string& comment) {
|
|
// Parse comments
|
|
while (token.type() == Token::COMMENT) {
|
|
comment += trimWhitespace(token.string()) + "\n";
|
|
|
|
// Allow comments to contain newlines.
|
|
do {
|
|
token = ti.read();
|
|
comment += "\n";
|
|
} while (token.type() == Token::NEWLINE);
|
|
}
|
|
|
|
comment = trimWhitespace(comment);
|
|
}
|
|
|
|
/** True if \a c is an open paren of some form */
|
|
static bool isOpen(const char c) {
|
|
return c == '(' || c == '[' || c == '{';
|
|
}
|
|
|
|
|
|
/** True if \a c is an open paren of some form */
|
|
static bool isClose(const char c) {
|
|
return c == ')' || c == ']' || c == '}';
|
|
}
|
|
|
|
|
|
void Any::deserializeName(TextInput& ti, Token& token, std::string& name) {
|
|
debugAssert(token.type() == Token::SYMBOL);
|
|
std::string s = token.string();
|
|
while (! isOpen(s[0])) {
|
|
name += s;
|
|
|
|
// Skip newlines and comments
|
|
token = ti.readSignificant();
|
|
|
|
if (token.type() != Token::SYMBOL) {
|
|
throw ParseError(ti.filename(), token.line(), token.character(),
|
|
"Expected symbol while parsing Any");
|
|
}
|
|
s = token.string();
|
|
}
|
|
}
|
|
|
|
|
|
void Any::deserialize(TextInput& ti) {
|
|
beforeRead();
|
|
Token token = ti.read();
|
|
deserialize(ti, token);
|
|
// Restore the last token
|
|
ti.push(token);
|
|
}
|
|
|
|
|
|
void Any::deserialize(TextInput& ti, Token& token) {
|
|
// Deallocate old data
|
|
dropReference();
|
|
m_type = NONE;
|
|
m_simpleValue.b = false;
|
|
|
|
// Skip leading newlines
|
|
while (token.type() == Token::NEWLINE) {
|
|
token = ti.read();
|
|
}
|
|
|
|
std::string comment;
|
|
if (token.type() == Token::COMMENT) {
|
|
deserializeComment(ti, token, comment);
|
|
}
|
|
|
|
if (token.type() == Token::END) {
|
|
// There should never be a comment without an Any following it; even
|
|
// if the file ends with some commented out stuff,
|
|
// that should not happen after a comma, so we'd never read that
|
|
// far in a proper file.
|
|
throw ParseError(ti.filename(), token.line(), token.character(),
|
|
"File ended without a properly formed Any");
|
|
}
|
|
|
|
// Do we need to read one more token after the end?
|
|
bool needRead = true;
|
|
|
|
switch (token.type()) {
|
|
case Token::STRING:
|
|
m_type = STRING;
|
|
ensureData();
|
|
*(m_data->value.s) = token.string();
|
|
m_data->source.set(ti, token);
|
|
break;
|
|
|
|
case Token::NUMBER:
|
|
m_type = NUMBER;
|
|
m_simpleValue.n = token.number();
|
|
ensureData();
|
|
m_data->source.set(ti, token);
|
|
break;
|
|
|
|
case Token::BOOLEAN:
|
|
m_type = BOOLEAN;
|
|
m_simpleValue.b = token.boolean();
|
|
ensureData();
|
|
m_data->source.set(ti, token);
|
|
break;
|
|
|
|
case Token::SYMBOL:
|
|
// Pragma, Named Array, Named Table, Array, Table, or NONE
|
|
if (token.string() == "#") {
|
|
// Pragma
|
|
|
|
// Currently, "include" is the only pragma allowed
|
|
token = ti.read();
|
|
if (! ((token.type() == Token::SYMBOL) &&
|
|
(token.string() == "include"))) {
|
|
throw ParseError(ti.filename(), token.line(), token.character(),
|
|
"Expected 'include' pragma after '#'");
|
|
}
|
|
|
|
ti.readSymbol("(");
|
|
const std::string& includeName = ti.readString();
|
|
|
|
// Find the include file
|
|
const std::string& myPath = filenamePath(ti.filename());
|
|
std::string t = pathConcat(myPath, includeName);
|
|
|
|
if (! FileSystem::exists(t)) {
|
|
// Try and find it, starting with cwd
|
|
t = System::findDataFile(includeName);
|
|
}
|
|
|
|
// Read the included file
|
|
load(t);
|
|
|
|
// Update the source information
|
|
ensureData();
|
|
m_data->source.filename +=
|
|
format(" [included from %s:%d(%d)]", ti.filename().c_str(), token.line(), token.character());
|
|
|
|
ti.readSymbol(")");
|
|
|
|
} else if (toUpper(token.string()) == "NONE") {
|
|
// Nothing left to do; we initialized to NONE originally
|
|
ensureData();
|
|
m_data->source.set(ti, token);
|
|
} else {
|
|
// Array or Table
|
|
|
|
// Parse the name
|
|
|
|
// s must have at least one element or this would not have
|
|
// been parsed as a symbol
|
|
std::string name;
|
|
deserializeName(ti, token, name);
|
|
if (token.type() != Token::SYMBOL) {
|
|
throw ParseError(ti.filename(), token.line(), token.character(),
|
|
"Malformed Any TABLE or ARRAY; must start with [, (, or {");
|
|
}
|
|
|
|
if (isOpen(token.string()[0])) {
|
|
// Array or table
|
|
deserializeBody(ti, token);
|
|
} else {
|
|
throw ParseError(ti.filename(), token.line(), token.character(),
|
|
"Malformed Any TABLE or ARRAY; must start with [, (, or {");
|
|
}
|
|
|
|
if (! name.empty()) {
|
|
ensureData();
|
|
m_data->name = name;
|
|
}
|
|
needRead = false;
|
|
} // if NONE
|
|
break;
|
|
|
|
default:
|
|
throw ParseError(ti.filename(), token.line(), token.character(),
|
|
"Unexpected token");
|
|
|
|
} // switch
|
|
|
|
if (! comment.empty()) {
|
|
ensureData();
|
|
m_data->comment = comment;
|
|
}
|
|
|
|
if (needRead) {
|
|
// Array and table already consumed their last token
|
|
token = ti.read();
|
|
}
|
|
}
|
|
|
|
|
|
void Any::ensureData() {
|
|
if (m_data == NULL) {
|
|
m_data = Data::create(m_type);
|
|
}
|
|
}
|
|
|
|
|
|
static bool isSeparator(char c) {
|
|
return c == ',' || c == ';';
|
|
}
|
|
|
|
|
|
void Any::readUntilCommaOrClose(TextInput& ti, Token& token) {
|
|
bool atClose = (token.type() == Token::SYMBOL) && isClose(token.string()[0]);
|
|
bool atComma = isSeparator(token.string()[0]);
|
|
while (! (atClose || atComma)) {
|
|
switch (token.type()) {
|
|
case Token::NEWLINE:
|
|
case Token::COMMENT:
|
|
// Consume
|
|
token = ti.read();
|
|
break;
|
|
|
|
default:
|
|
throw ParseError(ti.filename(), token.line(), token.character(),
|
|
"Expected a comma or close paren");
|
|
}
|
|
|
|
// Update checks
|
|
atComma = isSeparator(token.string()[0]);
|
|
atClose = (token.type() == Token::SYMBOL) && isClose(token.string()[0]);
|
|
}
|
|
}
|
|
|
|
|
|
void Any::deserializeBody(TextInput& ti, Token& token) {
|
|
char closeSymbol = '}';
|
|
m_type = TABLE;
|
|
|
|
const char c = token.string()[0];
|
|
|
|
if (c != '{') {
|
|
m_type = ARRAY;
|
|
// Chose the appropriate close symbol
|
|
closeSymbol = (c == '(') ? ')' : ']';
|
|
}
|
|
|
|
// Allocate the underlying data structure
|
|
ensureData();
|
|
m_data->source.set(ti, token);
|
|
|
|
// Consume the open token
|
|
token = ti.read();
|
|
|
|
while (! ((token.type() == Token::SYMBOL) && (token.string()[0] == closeSymbol))) {
|
|
|
|
// Read any leading comment. This must be done here (and not in the recursive deserialize
|
|
// call) in case the body contains only a comment.
|
|
std::string comment;
|
|
deserializeComment(ti, token, comment);
|
|
|
|
if ((token.type() == Token::SYMBOL) && (token.string()[0] == closeSymbol)) {
|
|
// We're done; this catches the case where the array is empty
|
|
break;
|
|
}
|
|
|
|
// Pointer the value being read
|
|
Any a = NULL;
|
|
std::string key;
|
|
|
|
if (m_type == TABLE) {
|
|
// Read the key
|
|
if (token.type() != Token::SYMBOL && token.type() != Token::STRING) {
|
|
throw ParseError(ti.filename(), token.line(), token.character(), "Expected a name");
|
|
}
|
|
|
|
key = token.string();
|
|
// Consume everything up to the = sign, returning the "=" sign.
|
|
token = ti.readSignificant();
|
|
|
|
if ((token.type() != Token::SYMBOL) || (token.string() != "=")) {
|
|
throw ParseError(ti.filename(), token.line(), token.character(), "Expected =");
|
|
} else {
|
|
// Read the next token, which is the value (don't consume comments--we want the value pointed to by a to get those).
|
|
token = ti.read();
|
|
}
|
|
}
|
|
a.deserialize(ti, token);
|
|
|
|
if (! comment.empty()) {
|
|
// Prepend the comment we read earlier
|
|
a.ensureData();
|
|
a.m_data->comment = trimWhitespace(comment + "\n" + a.m_data->comment);
|
|
}
|
|
|
|
if (m_type == TABLE) {
|
|
set(key, a);
|
|
} else {
|
|
append(a);
|
|
}
|
|
|
|
// Read until the comma or close paren, discarding trailing comments and newlines
|
|
readUntilCommaOrClose(ti, token);
|
|
|
|
// Consume the comma
|
|
if (isSeparator(token.string()[0])) {
|
|
token = ti.read();
|
|
}
|
|
}
|
|
|
|
// Consume the close paren (to match other deserialize methods)
|
|
token = ti.read();
|
|
}
|
|
|
|
|
|
Any::operator int() const {
|
|
beforeRead();
|
|
return iRound(number());
|
|
}
|
|
|
|
|
|
Any::operator float() const {
|
|
beforeRead();
|
|
return float(number());
|
|
}
|
|
|
|
|
|
Any::operator double() const {
|
|
beforeRead();
|
|
return number();
|
|
}
|
|
|
|
|
|
Any::operator bool() const {
|
|
beforeRead();
|
|
return boolean();
|
|
}
|
|
|
|
|
|
Any::operator std::string() const {
|
|
beforeRead();
|
|
return string();
|
|
}
|
|
|
|
|
|
const Any::Source& Any::source() const {
|
|
static Source s;
|
|
if (m_data) {
|
|
return m_data->source;
|
|
} else {
|
|
return s;
|
|
}
|
|
}
|
|
|
|
|
|
std::string Any::sourceDirectory() const {
|
|
if (m_data) {
|
|
return FilePath::parent(m_data->source.filename);
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
|
|
void Any::verify(bool value, const std::string& message) const {
|
|
beforeRead();
|
|
if (! value) {
|
|
ParseError p;
|
|
if (m_data) {
|
|
p.filename = m_data->source.filename;
|
|
p.line = m_data->source.line;
|
|
p.character = m_data->source.character;
|
|
}
|
|
|
|
if (name().empty()) {
|
|
p.message = "Parse error";
|
|
} else {
|
|
p.message = "Parse error while reading the contents of " + name();
|
|
}
|
|
|
|
if (! message.empty()) {
|
|
p.message = p.message + ": " + message;
|
|
}
|
|
|
|
throw p;
|
|
}
|
|
}
|
|
|
|
|
|
void Any::verifyName(const std::string& n) const {
|
|
beforeRead();
|
|
verify(beginsWith(toUpper(name()), toUpper(n)), "Name must begin with " + n);
|
|
}
|
|
|
|
|
|
void Any::verifyName(const std::string& n, const std::string& m) const {
|
|
beforeRead();
|
|
const std::string& x = toUpper(name());
|
|
verify(beginsWith(x, toUpper(n)) ||
|
|
beginsWith(x, toUpper(m)), "Name must begin with " + n + " or " + m);
|
|
}
|
|
|
|
|
|
void Any::verifyType(Type t) const {
|
|
beforeRead();
|
|
if (type() != t) {
|
|
verify(false, "Must have type " + toString(t));
|
|
}
|
|
}
|
|
|
|
|
|
void Any::verifyType(Type t0, Type t1) const {
|
|
beforeRead();
|
|
if (type() != t0 && type() != t1) {
|
|
verify(false, "Must have type " + toString(t0) + " or " + toString(t1));
|
|
}
|
|
}
|
|
|
|
|
|
void Any::verifySize(int low, int high) const {
|
|
beforeRead();
|
|
verifyType(ARRAY, TABLE);
|
|
if (size() < low || size() > high) {
|
|
verify(false, format("Size must be between %d and %d", low, high));
|
|
}
|
|
}
|
|
|
|
|
|
void Any::verifySize(int s) const {
|
|
beforeRead();
|
|
verifyType(ARRAY, TABLE);
|
|
if (size() != s) {
|
|
verify(false, format("Size must be %d", s));
|
|
}
|
|
}
|
|
|
|
|
|
std::string Any::toString(Type t) {
|
|
switch(t) {
|
|
case NONE: return "NONE";
|
|
case BOOLEAN: return "BOOLEAN";
|
|
case NUMBER: return "NUMBER";
|
|
case STRING: return "STRING";
|
|
case ARRAY: return "ARRAY";
|
|
case TABLE: return "TABLE";
|
|
default:
|
|
alwaysAssertM(false, "Illegal Any::Type");
|
|
return "";
|
|
}
|
|
}
|
|
|
|
} // namespace G3D
|
|
|