aboutsummaryrefslogtreecommitdiff
path: root/dep/g3dlite/source/Any.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dep/g3dlite/source/Any.cpp')
-rw-r--r--dep/g3dlite/source/Any.cpp1350
1 files changed, 1350 insertions, 0 deletions
diff --git a/dep/g3dlite/source/Any.cpp b/dep/g3dlite/source/Any.cpp
new file mode 100644
index 00000000000..92159bb1862
--- /dev/null
+++ b/dep/g3dlite/source/Any.cpp
@@ -0,0 +1,1350 @@
+/**
+ @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
+