mxw_wotlk_azerothcore/deps/g3dlite/source/Any.cpp

1790 lines
44 KiB
C++
Raw Permalink Normal View History

2020-10-30 23:45:46 -04:00
/**
\file Any.cpp
\author Morgan McGuire
\author Shawn Yarbrough
\created 2006-06-11
\edited 2013-03-29
Copyright 2000-2013, Morgan McGuire.
All rights reserved.
*/
#include "G3D/Any.h"
#include "G3D/TextOutput.h"
#include "G3D/TextInput.h"
#include "G3D/BinaryOutput.h"
#include "G3D/BinaryInput.h"
#include "G3D/stringutils.h"
#include "G3D/fileutils.h"
#include "G3D/FileSystem.h"
#include <deque>
#include <iostream>
namespace G3D {
const char* Any::PAREN = "()";
const char* Any::BRACKET = "[]";
const char* Any::BRACE = "{}";
static bool isContainerType(Any::Type& t) {
return (t == Any::ARRAY) || (t == Any::TABLE) || (t == Any::EMPTY_CONTAINER);
}
void Any::serialize(BinaryOutput& b) const {
TextOutput::Settings s;
s.wordWrap = TextOutput::Settings::WRAP_NONE;
b.writeInt32(1);
b.writeString32(unparse(s));
}
void Any::deserialize(BinaryInput& b) {
const int version = b.readInt32();
alwaysAssertM(version == 1, "Wrong Any serialization version");
_parse(b.readString32());
}
std::string Any::resolveStringAsFilename(bool errorIfNotFound) const {
verifyType(STRING);
if ((string().length() > 0) && (string()[0] == '<') && (string()[string().length() - 1] == '>')) {
return string();
}
const std::string& f = FileSystem::resolve(string(), sourceDirectory());
if (FileSystem::exists(f)) {
return f;
} else {
const std::string& s = System::findDataFile(string(), errorIfNotFound);
if (s.empty()) {
return string();
} else {
return s;
}
}
}
void Any::become(const Type& t) {
if ((t == ARRAY) || (t == TABLE)) {
debugAssert(m_type == EMPTY_CONTAINER);
m_type = t;
m_data->type = m_type;
if(t == ARRAY) {
m_data->value.a = new AnyArray();
} else {
m_data->value.t = new AnyTable();
}
}
}
void Any::remove(const std::string& key) {
verifyType(TABLE);
ensureMutable();
m_data->value.t->remove(key);
}
void Any::remove(int i) {
verifyType(ARRAY);
ensureMutable();
m_data->value.a->remove(i);
}
Any Any::fromFile(const std::string& filename) {
Any a;
a.load(filename);
return a;
}
void Any::loadIfExists(const std::string& filename) {
if (FileSystem::exists(filename)) {
load(filename);
}
}
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_WINDOWS
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.
alwaysAssertM(m_data, "Corrupt placeholder");
KeyNotFound e(m_data);
e.key = m_placeholderName;
e.message = "Key \"" + m_placeholderName + "\" not found in operator[] lookup.";
throw e;
}
}
Any::Data* Any::Data::create(const Data* d) {
Data* p = create(d->type);
p->includeLine = d->includeLine;
p->bracket = d->bracket;
p->separator = d->separator;
p->comment = d->comment;
p->name = d->name;
p->source = d->source;
switch (d->type) {
case NIL:
case BOOLEAN:
case NUMBER:
case EMPTY_CONTAINER:
// 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, const char* b, char sep) {
size_t s = sizeof(Data);
switch (t) {
case NIL:
case BOOLEAN:
case NUMBER:
// No extra space needed
break;
case STRING:
s += sizeof(std::string);
break;
case EMPTY_CONTAINER:
// We need to allocate space for the worst-case
s += max(sizeof(AnyArray), sizeof(AnyTable));
// If no separator and brackets were provided, substitute defaults
if (b == NULL) { b = PAREN; }
if (sep == '\0') { sep = ','; }
break;
case ARRAY:
s += sizeof(AnyArray);
// If no separator and brackets were provided, substitute defaults
if (b == NULL) { b = PAREN; }
if (sep == '\0') { sep = ','; }
break;
case TABLE:
s += sizeof(AnyTable);
// If no separator and brackets were provided, substitute defaults
if (b == NULL) { b = BRACE; }
if (sep == '\0') { sep = ';'; }
break;
}
// Allocate the data object
Data* p = new (MemoryManager::create()->alloc(s)) Data(t, b, sep);
// Create the (empty) value object at the end of the Data object
switch (t) {
case NIL:
case BOOLEAN:
case NUMBER:
case EMPTY_CONTAINER:
// 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;
}
if (isContainerType(p->type)) debugAssert(p->separator != '\0');
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);
if (size() == 0) {
//catches the case of an empty container, where value.t is null
return false;
}
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(NIL), m_data(NULL) {
}
Any::Any(TextInput& t) : m_type(NIL), m_data(NULL) {
deserialize(t);
}
Any::Any(const Any& x) : m_type(NIL), m_data(NULL) {
x.beforeRead();
*this = x;
}
Any::Any(double x) : m_type(NUMBER), m_simpleValue(x), m_data(NULL) {
}
Any::Any(float x) : m_type(NUMBER), m_simpleValue(double(x)), m_data(NULL) {
}
Any::Any(char x) : m_type(NUMBER), m_simpleValue(double(x)), m_data(NULL) {
}
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 = NIL;
} else {
ensureData();
*(m_data->value.s) = s;
}
}
Any::Any(Type t, const std::string& name, const std::string& brackets, char separator) : m_type(t), m_data(NULL) {
alwaysAssertM(isContainerType(t), "Can only create ARRAY or TABLE from Type enum.");
ensureData();
if (name != "") {
m_data->name = name;
}
if (brackets == "") {
if (t == ARRAY) {
m_data->bracket = PAREN;
} else {
m_data->bracket = BRACE;
}
} else if (brackets == PAREN) {
m_data->bracket = PAREN;
} else if (brackets == BRACE) {
m_data->bracket = BRACE;
} else if (brackets == BRACKET) {
m_data->bracket = BRACKET;
} else {
alwaysAssertM(false, std::string("illegal brackets: ") + brackets);
}
if (separator == '\0') {
if (t == ARRAY) {
m_data->separator = ',';
} else {
m_data->separator = ';';
}
} else if (separator == ',' || separator == ';') {
m_data->separator = separator;
} else {
alwaysAssertM(false, std::string("illegal separator: ") + separator);
}
}
Any::~Any() {
dropReference();
}
void Any::beforeWrite() {
if (isPlaceholder()) {
// This is no longer a placeholder
m_placeholderName.clear();
}
if (m_data && ! m_data->includeLine.empty()) {
// Forget where this Any was included from...it no
// longer matches the contents there.
m_data->includeLine = "";
}
}
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=(Type t) {
switch (t) {
case NIL:
*this = Any();
break;
case TABLE: // All 3 cases intentionally fall through
case ARRAY:
case EMPTY_CONTAINER:
*this = Any(t);
break;
default:
alwaysAssertM(false, "Can only assign NIL, 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::isNil() const {
beforeRead();
return (m_type == NIL);
}
double Any::number() const {
beforeRead();
verifyType(NUMBER);
return m_simpleValue.n;
}
float Any::floatValue() const {
beforeRead();
verifyType(NUMBER);
return (float)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 (int)m_data->value.t->size();
case ARRAY:
return m_data->value.a->size();
default:
return 0;
}
}
int Any::length() const {
beforeRead();
return size();
}
void Any::resize(int n) {
beforeRead();
alwaysAssertM(n >= 0, "Cannot resize less than 0.");
verifyType(ARRAY);
if (type() == EMPTY_CONTAINER) {
become(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);
if (i < 0 || i >= array.size()) {
throw IndexOutOfBounds(m_data, i, array.size());
}
return array[i];
}
Any& Any::next() {
beforeRead();
verifyType(ARRAY);
int n = size();
resize(n + 1);
return (*this)[n];
}
Any& Any::operator[](int i) {
beforeRead();
ensureMutable();
verifyType(ARRAY);
debugAssert(m_data != NULL);
Array<Any>& array = *(m_data->value.a);
if (i < 0 || i >= array.size()) {
throw IndexOutOfBounds(m_data, i, array.size());
}
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);
if (type() == EMPTY_CONTAINER) {
become(ARRAY);
}
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);
if (type() == Any::EMPTY_CONTAINER) {
// if empty, m_data->value.t will not be initialized as it is unknown whether this is supposed to be an array or a table
static const Table<std::string, Any> emptyTable;
return emptyTable;
}
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(m_data);
e.key = x;
e.message = "Key not found in operator[] lookup.";
throw e;
}
return *value;
}
Any& Any::operator[](const std::string& key) {
beforeRead();
ensureMutable();
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);
if ( type() == EMPTY_CONTAINER) {
become(TABLE);
}
Table<std::string, Any>& table = *(m_data->value.t);
table.set(k, v);
}
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 NIL:
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;
}
const Table<std::string, Any>& table1 = table();
const Table<std::string, Any>& table2 = x.table();
for (Table<std::string, Any>::Iterator it = table1.begin(); it.isValid(); ++it) {
const Any* p2 = table2.getPointer(it->key);
if (p2 == NULL) {
// Key not found
return false;
} else if (*p2 != it->value) {
// Different value
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;
}
case EMPTY_CONTAINER:
return true;
default:
alwaysAssertM(false, "Unknown type.");
return false;
}
}
bool Any::operator!=(const Any& x) const {
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 = true;
settings.caseSensitive = false;
}
std::string Any::unparseJSON(const TextOutput::Settings& settings, bool allowCoercion) const {
beforeRead();
TextOutput to(settings);
serialize(to, true, allowCoercion);
return to.commitString();
}
std::string Any::unparse(const TextOutput::Settings& settings) const {
beforeRead();
TextOutput to(settings);
serialize(to);
return to.commitString();
}
Any Any::parse(const std::string& src) {
Any a;
a._parse(src);
return a;
}
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;
}
void Any::serialize(TextOutput& to, bool json, bool coerce) const {
beforeRead();
if (m_data && ! m_data->includeLine.empty()) {
if (json) {
if (! coerce) {
throw "Could not coerce to JSON";
}
// Silently fall through
} else {
// This value came from a #include...preserve it. This includes the comment
// if any.
to.printf("%s", m_data->includeLine.c_str());
return;
}
}
if (m_data && ! m_data->comment.empty()) {
if (json) {
if (! coerce) {
throw "Could not coerce to JSON";
}
} else {
to.printf("\n/* %s */\n", m_data->comment.c_str());
}
}
switch (m_type) {
case NIL:
if (json) {
to.writeSymbol("null");
} else {
to.writeSymbol("NIL");
}
break;
case BOOLEAN:
to.writeBoolean(m_simpleValue.b);
break;
case NUMBER:
if (json) {
// Specials have no legal syntax in JSON, so insert values that will parse
// to something close to what we want (there is no good solution for NaN, unfortunately)
if (m_simpleValue.n == inf()) {
to.writeSymbol("1e10000");
} else if (m_simpleValue.n == -inf()) {
to.writeSymbol("-1e10000");
} else if (isNaN(m_simpleValue.n)) {
if (! coerce) {
throw "There is no way to represent NaN in JSON";
}
to.writeSymbol("null");
} else {
to.writeNumber(m_simpleValue.n);
}
} else {
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);
debugAssert(m_data->separator != '\0');
if (! m_data->name.empty()) {
if (json) {
if (! coerce) {
throw "Could not coerce to JSON";
}
} else {
if (needsQuotes(m_data->name)) {
to.writeString(m_data->name);
} else {
to.writeSymbol(m_data->name);
}
}
}
if (json) {
to.writeSymbol("{");
} else {
to.writeSymbol(m_data->bracket[0]);
}
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) {
int prevLine = to.line();
if (needsQuotes(keys[i]) || json) {
to.writeString(keys[i]);
} else {
to.writeSymbol(keys[i]);
}
if (json) {
to.writeSymbol(":");
} else {
to.writeSymbol("=");
}
table[keys[i]].serialize(to, json, coerce);
to.deleteSpace();
if (json) {
// Don't put a separator after the last
if (i != keys.size() - 1) {
to.writeSymbol(",");
}
} else {
to.writeSymbol(m_data->separator);
}
// Skip an extra line between table entries that are longer than a line
if (prevLine != to.line()) {
to.writeNewline();
}
to.writeNewline();
}
to.popIndent();
to.writeSymbol(m_data->bracket[1]);
break;
}
case ARRAY: {
debugAssert(m_data != NULL);
debugAssert(m_data->separator != '\0');
if (! m_data->name.empty()) {
if (json) {
if (! coerce) {
throw "Could not coerce to JSON";
}
to.writeSymbol("[");
} else {
// For arrays, leave no trailing space between the name and the paren
to.printf("%s%c", m_data->name.c_str(), m_data->bracket[0]);
}
} else {
if (json) {
to.writeSymbol("[");
} else {
to.writeSymbol(m_data->bracket[0]);
}
}
const Array<Any>& array = *(m_data->value.a);
const bool longForm = (array.size() > 0) && ((array[0].type() == ARRAY) || (array[0].type() == TABLE));
if (longForm) {
to.writeNewline();
}
to.pushIndent();
for (int ii = 0; ii < size(); ++ii) {
array[ii].serialize(to, json, coerce);
if (ii < size() - 1) {
to.deleteSpace();
if (longForm) {
// Probably a long-form array
if (json) {
to.writeSymbol(",");
} else {
to.writeSymbol(m_data->separator);
}
to.writeNewline();
} else {
// Probably a short-form array
if (json) {
to.writeSymbol(",");
} else {
to.writeSymbol(m_data->separator);
}
}
}
// Put the close paren on an array right behind the last element
}
to.popIndent();
if (json) {
to.writeSymbol("]");
} else {
to.writeSymbol(m_data->bracket[1]);
}
break;
}
case EMPTY_CONTAINER: {
debugAssert(m_data != NULL);
debugAssert(m_data->separator != '\0');
if (json) {
if (! coerce) {
throw "Cannot strictly convert the ambiguous Any empty container to JSON";
}
to.writeSymbols("[", "]");
} else {
if (! m_data->name.empty()) {
// Leave no trailing space between the name and the paren
to.printf("%s%c", m_data->name.c_str(), m_data->bracket[0]);
} else {
to.writeSymbol(m_data->bracket[0]);
}
to.writeSymbol(m_data->bracket[1]);
}
break;
}
}
}
void Any::deserializeComment(TextInput& ti, Token& token, std::string& comment) {
// Parse comments
while (token.type() == Token::COMMENT) {
comment += trimWhitespace(token.string());
// 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 = NIL;
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 NIL
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("(");
// The string typed into the file, which may be relative
const std::string& includeName = ti.readString();
// Find the include file
const std::string& myPath = FilePath::parent(ti.filename());
std::string t = FileSystem::resolve(includeName, myPath);
if (! FileSystem::exists(t)) {
// Try and find the path, starting with the cwd
t = System::findDataFile(includeName);
}
// Read the included file
load(t);
// Update the source information
ensureData();
if (! comment.empty()) {
m_data->includeLine = format("\n/* %s */\n", comment.c_str());
}
m_data->includeLine += format("#include(\"%s\")", includeName.c_str());
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()) == "NIL" || token.string() == "None") {
// Nothing left to do; we initialized to NIL originally
ensureData();
m_data->source.set(ti, token);
} else if (isValidIdentifier(token.string()) && !isOpen(ti.peek().string()[0]) && ti.peek().string() != "::") {
// Valid unquoted string
m_type = STRING;
ensureData();
*(m_data->value.s) = token.string();
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 NIL
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::readUntilSeparatorOrClose(TextInput& ti, Token& token) {
bool atClose = (token.type() == Token::SYMBOL) && isClose(token.string()[0]);
bool atSeparator = isSeparator(token.string()[0]);
while (! (atClose || atSeparator)) {
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
atSeparator = isSeparator(token.string()[0]);
atClose = (token.type() == Token::SYMBOL) && isClose(token.string()[0]);
}
}
/**
Determines the type [TABLE, ARRAY, or EMPTY_CONTAINER] from the given TextInput
TextInput is the same at the end as it was beofr
*/
static Any::Type findType(TextInput& ti, const char closeSymbol) {
Any::Type type = Any::NIL;
std::vector<Token> tokens;
// consumes the open symbol
Token token = ti.read();
tokens.push_back(token);
bool hasAnElement = false;
while (type == Any::NIL) {
if (token.type() == Token::COMMENT) {
// consumes tokens and prepares to push them back onto the TextInput
token = ti.read();
tokens.push_back(token);
} else if ((token.type() == Token::SYMBOL) && ((token.string() == "=") || (token.string() == ":"))) {
// an '=' indicates a key = value pairing, and thus a table
type = Any::TABLE;
} else if (hasAnElement) {
// any non-comment, non-'=' token after any element indicates an array
type = Any::ARRAY;
} else if ((token.type() == Token::SYMBOL) && (token.string()[0] == closeSymbol)) {
// catches no previous element and a closing symbol (e.g [])
type = Any::EMPTY_CONTAINER;
} else {
// consumes elements, indicating one has been seen, and prepares to push them back onto TextInput
hasAnElement = true;
token = ti.read();
tokens.push_back(token);
}
}
// pushes everything back to the TextInput
while (!tokens.empty()) {
token = tokens.back();
ti.push(token);
tokens.pop_back();
}
return type;
}
void Any::deserializeBody(TextInput& ti, Token& token) {
char closeSymbol = '\0';
m_type = TABLE;
const char c = token.string()[0];
// Chose the appropriate close symbol based on the open symbol
const char* bracket;
if (c == '(') {
bracket = PAREN;
} else if (c == '[') {
bracket = BRACKET;
} else if (c == '{') {
bracket = BRACE;
} else {
debugAssertM(false, "Illegal bracket type");
bracket = PAREN;
}
closeSymbol = bracket[1];
// We must set the type before we allocate m_data in ensureData().
m_type = findType(ti, closeSymbol);
// Allocate the underlying data structure
ensureData();
m_data->source.set(ti, token);
m_data->bracket = bracket;
debugAssert(m_data->type == m_type);
debugAssert(m_data->separator != '\0');
// 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 container is empty
break;
}
// Pointer the value being read
Any a;
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() != "=") && token.string() != ":")) {
throw ParseError(ti.filename(), token.line(), token.character(), "Expected = or :");
} 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 separator or close paren, discarding trailing comments and newlines
readUntilSeparatorOrClose(ti, token);
char s = token.string()[0];
// Consume the separator
if (isSeparator(s)) {
token = ti.read();
m_data->separator = s;
debugAssert(m_data->separator != '\0');
}
}
// Consume the close paren (to match other deserialize methods)
token = ti.read();
}
Any::operator int() const {
beforeRead();
return iRound(number());
}
Any::operator uint32() const {
beforeRead();
return uint32(number() + 0.5);
}
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(name() == n, "Name must be " + n);
}
void Any::verifyName(const std::string& n, const std::string& m) const {
beforeRead();
const std::string& x = name();
verify(x == n ||
x == m, "Name must be " + n + " or " + m);
}
void Any::verifyName(const std::string& n, const std::string& m, const std::string& p) const {
beforeRead();
const std::string& x = name();
verify(x == n ||
x == m ||
x == p, "Name must be " + n + ", " + m + ", or " + p);
}
void Any::verifyName(const std::string& n, const std::string& m, const std::string& p, const std::string& q) const {
beforeRead();
const std::string& x = name();
verify(x == n ||
x == m ||
x == p ||
x == q, "Name must be " + n + ", " + m + ", " + p + ", or " + q);
}
void Any::verifyNameBeginsWith(const std::string& n) const {
beforeRead();
verify(beginsWith(name(), n), "Name must begin with " + n);
}
void Any::verifyNameBeginsWith(const std::string& n, const std::string& m) const {
beforeRead();
const std::string& x = name();
verify(beginsWith(x, n) ||
beginsWith(x, m), "Name must be " + n + " or " + m);
}
void Any::verifyNameBeginsWith(const std::string& n, const std::string& m, const std::string& p) const {
beforeRead();
const std::string& x = name();
verify(beginsWith(x, n) ||
beginsWith(x, m) ||
beginsWith(x, p), "Name must be " + n + ", " + m + ", or " + p);
}
void Any::verifyNameBeginsWith(const std::string& n, const std::string& m, const std::string& p, const std::string& q) const {
beforeRead();
const std::string& x = name();
verify(beginsWith(x, n) ||
beginsWith(x, m) ||
beginsWith(x, p) ||
beginsWith(x, q), "Name must be " + n + ", " + m + ", " + p + ", or " + q);
}
void Any::verifyNameBeginsWith(const std::string& n, const std::string& m, const std::string& p, const std::string& q, const std::string& r) const {
beforeRead();
const std::string& x = name();
verify(beginsWith(x, n) ||
beginsWith(x, m) ||
beginsWith(x, p) ||
beginsWith(x, q) ||
beginsWith(x, r), "Name must be " + n + ", " + m + ", " + p + ", or " + q + ", or " + r);
}
void Any::verifyNameBeginsWith(const std::string& n, const std::string& m, const std::string& p, const std::string& q, const std::string& r, const std::string& s) const {
beforeRead();
const std::string& x = name();
verify(beginsWith(x, n) ||
beginsWith(x, m) ||
beginsWith(x, p) ||
beginsWith(x, q) ||
beginsWith(x, r) ||
beginsWith(x, s), "Name must be " + n + ", " + m + ", " + p + ", or " + q + ", or " + r + ", or " + s);
}
void Any::verifyNameBeginsWith(const std::string& n, const std::string& m, const std::string& p, const std::string& q, const std::string& r, const std::string& s, const std::string& t) const {
beforeRead();
const std::string& x = name();
verify(beginsWith(x, n) ||
beginsWith(x, m) ||
beginsWith(x, p) ||
beginsWith(x, q) ||
beginsWith(x, r) ||
beginsWith(x, s) ||
beginsWith(x, t), "Name must be " + n + ", " + m + ", " + p + ", or " + q + ", or " + r + ", or " + s + ", or " + t);
}
void Any::verifyType(Type t) const {
beforeRead();
if ((type() != t) && ! ((type() == EMPTY_CONTAINER) && isContainerType(t))) {
verify(false, "Must have type " + toString(t));
}
}
void Any::verifyType(Type t0, Type t1) const {
beforeRead();
if ((type() != t0 && !((type() == EMPTY_CONTAINER) && isContainerType(t0))) &&
(type() != t1 && !((type() == EMPTY_CONTAINER) && isContainerType(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 NIL: return "NIL";
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