diff options
Diffstat (limited to 'dep/g3dlite/source/Any.cpp')
-rw-r--r-- | dep/g3dlite/source/Any.cpp | 783 |
1 files changed, 611 insertions, 172 deletions
diff --git a/dep/g3dlite/source/Any.cpp b/dep/g3dlite/source/Any.cpp index 92159bb1862..9204f9efc12 100644 --- a/dep/g3dlite/source/Any.cpp +++ b/dep/g3dlite/source/Any.cpp @@ -1,19 +1,21 @@ /** - @file Any.cpp + \file Any.cpp - @author Morgan McGuire - @author Shawn Yarbrough + \author Morgan McGuire + \author Shawn Yarbrough - @created 2006-06-11 - @edited 2010-07-24 + \created 2006-06-11 + \edited 2013-03-29 - Copyright 2000-2010, Morgan McGuire. + 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" @@ -21,14 +23,40 @@ #include <iostream> namespace G3D { +const char* Any::PAREN = "()"; +const char* Any::BRACKET = "[]"; +const char* Any::BRACE = "{}"; -std::string Any::resolveStringAsFilename() const { +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); - std::string f = FileSystem::resolve(string(), sourceDirectory()); + 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(), false); + const std::string& s = System::findDataFile(string(), errorIfNotFound); if (s.empty()) { return string(); } else { @@ -37,6 +65,47 @@ std::string Any::resolveStringAsFilename() const { } } +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()); @@ -73,7 +142,7 @@ bool Any::nameBeginsWith(const char* s) const { bool Any::nameEquals(const char* s) const { verifyType(Any::ARRAY, Any::TABLE); -#ifdef G3D_WIN32 +#ifdef G3D_WINDOWS return stricmp(name().c_str(), s) == 0; #else return strcasecmp(name().c_str(), s) == 0; @@ -86,16 +155,11 @@ 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"); + KeyNotFound e(m_data); - 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."; + e.message = "Key \"" + m_placeholderName + "\" not found in operator[] lookup."; throw e; } @@ -105,13 +169,18 @@ void Any::beforeRead() const { Any::Data* Any::Data::create(const Data* d) { Data* p = create(d->type); - p->comment = d->comment; - p->name = d->name; + 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 NONE: + case NIL: case BOOLEAN: case NUMBER: + case EMPTY_CONTAINER: // No clone needed break; @@ -134,11 +203,11 @@ Any::Data* Any::Data::create(const Data* d) { } -Any::Data* Any::Data::create(Any::Type t) { +Any::Data* Any::Data::create(Any::Type t, const char* b, char sep) { size_t s = sizeof(Data); switch (t) { - case NONE: + case NIL: case BOOLEAN: case NUMBER: // No extra space needed @@ -148,23 +217,38 @@ Any::Data* Any::Data::create(Any::Type t) { 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); + Data* p = new (MemoryManager::create()->alloc(s)) Data(t, b, sep); - // Create the (empyt) value object at the end of the Data object + // Create the (empty) value object at the end of the Data object switch (t) { - case NONE: + case NIL: case BOOLEAN: case NUMBER: + case EMPTY_CONTAINER: // No value break; @@ -179,7 +263,9 @@ Any::Data* Any::Data::create(Any::Type t) { case TABLE: p->value.t = new (p + 1) AnyTable(); break; - } + } + + if (isContainerType(p->type)) debugAssert(p->separator != '\0'); return p; } @@ -227,7 +313,10 @@ Any::Data::~Data() { 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 @@ -255,16 +344,16 @@ void Any::ensureMutable() { } -Any::Any() : m_type(NONE), m_data(NULL) { +Any::Any() : m_type(NIL), m_data(NULL) { } -Any::Any(TextInput& t) : m_type(NONE), m_data(NULL) { +Any::Any(TextInput& t) : m_type(NIL), m_data(NULL) { deserialize(t); } -Any::Any(const Any& x) : m_type(NONE), m_data(NULL) { +Any::Any(const Any& x) : m_type(NIL), m_data(NULL) { x.beforeRead(); *this = x; } @@ -274,15 +363,15 @@ 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) { +Any::Any(float 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(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) { } @@ -303,7 +392,7 @@ Any::Any(const std::string& s) : m_type(STRING), m_data(Data::create(STRING)) { Any::Any(const char* s) : m_type(STRING), m_data(NULL) { if (s == NULL) { - m_type = NONE; + m_type = NIL; } else { ensureData(); *(m_data->value.s) = s; @@ -311,13 +400,43 @@ Any::Any(const char* s) : m_type(STRING), m_data(NULL) { } -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."); + + +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); + } } @@ -329,10 +448,17 @@ Any::~Any() { void Any::beforeWrite() { if (isPlaceholder()) { // This is no longer a placeholder - m_placeholderName = ""; + 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(); @@ -356,48 +482,20 @@ Any& Any::operator=(const Any& x) { } -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: + case NIL: *this = Any(); break; - case TABLE: - case ARRAY: + case TABLE: // All 3 cases intentionally fall through + case ARRAY: + case EMPTY_CONTAINER: *this = Any(t); break; default: - alwaysAssertM(false, "Can only assign NONE, TABLE, or ARRAY Type enum."); + alwaysAssertM(false, "Can only assign NIL, TABLE, or ARRAY Type enum."); } return *this; @@ -429,18 +527,23 @@ void Any::setComment(const std::string& c) { } -bool Any::isNone() const { +bool Any::isNil() const { beforeRead(); - return (m_type == NONE); + 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(); @@ -479,14 +582,14 @@ int Any::size() const { verifyType(ARRAY, TABLE); switch (m_type) { case TABLE: - return m_data->value.t->size(); + return (int)m_data->value.t->size(); case ARRAY: return m_data->value.a->size(); - default:; + default: return 0; - } // switch (m_type) + } } @@ -500,6 +603,9 @@ 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); } @@ -515,7 +621,6 @@ void Any::clear() { case TABLE: m_data->value.t->clear(); break; - default:; } } @@ -526,6 +631,9 @@ const Any& Any::operator[](int i) const { 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]; } @@ -541,9 +649,13 @@ Any& Any::next() { 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]; } @@ -556,22 +668,25 @@ const Array<Any>& Any::array() const { } -void Any::append(const Any& x0) { +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) { +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) { +void Any::_append(const Any& x0, const Any& x1, const Any& x2) { beforeRead(); append(x0); append(x1); @@ -579,7 +694,7 @@ void Any::append(const Any& x0, const Any& x1, const Any& x2) { } -void Any::append(const Any& x0, const Any& x1, const Any& x2, const Any& x3) { +void Any::_append(const Any& x0, const Any& x1, const Any& x2, const Any& x3) { beforeRead(); append(x0); append(x1); @@ -592,6 +707,12 @@ 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); } @@ -603,13 +724,9 @@ const Any& Any::operator[](const std::string& x) const { 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; - } + KeyNotFound e(m_data); e.key = x; + e.message = "Key not found in operator[] lookup."; throw e; } return *value; @@ -618,6 +735,7 @@ const Any& Any::operator[](const std::string& x) const { Any& Any::operator[](const std::string& key) { beforeRead(); + ensureMutable(); verifyType(TABLE); bool created = false; @@ -637,17 +755,20 @@ Any& Any::operator[](const std::string& key) { } -void Any::set(const std::string& k, const Any& v) { +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); } -const Any& Any::get(const std::string& x, const Any& defaultVal) const { +Any Any::_get(const std::string& x, const Any& defaultVal) const { beforeRead(); defaultVal.beforeRead(); try { @@ -661,12 +782,13 @@ const Any& Any::get(const std::string& x, const Any& defaultVal) const { bool Any::operator==(const Any& x) const { beforeRead(); x.beforeRead(); + if (m_type != x.m_type) { return false; } switch (m_type) { - case NONE: + case NIL: return true; case BOOLEAN: @@ -687,14 +809,17 @@ bool Any::operator==(const Any& x) const { 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) { + 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; } @@ -718,19 +843,19 @@ bool Any::operator==(const Any& x) const { } return true; } - + + case EMPTY_CONTAINER: + 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); + return ! operator==(x); } @@ -740,21 +865,35 @@ static void getDeserializeSettings(TextInput::Settings& settings) { settings.otherLineComments = false; settings.generateCommentTokens = true; settings.singleQuotedStrings = false; - settings.msvcFloatSpecials = false; + settings.msvcFloatSpecials = true; settings.caseSensitive = false; } -std::string Any::unparse() const { +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::Settings settings; TextOutput to(settings); serialize(to); return to.commitString(); } -void Any::parse(const std::string& src) { +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); @@ -814,16 +953,41 @@ static bool needsQuotes(const std::string& s) { } -// TODO: if the output will fit on one line, compress tables and arrays into a single line -void Any::serialize(TextOutput& to) const { +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()) { - to.printf("\n/* %s */\n", m_data->comment.c_str()); + 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 NONE: - to.writeSymbol("NONE"); + case NIL: + if (json) { + to.writeSymbol("null"); + } else { + to.writeSymbol("NIL"); + } break; case BOOLEAN: @@ -831,7 +995,24 @@ void Any::serialize(TextOutput& to) const { break; case NUMBER: - to.writeNumber(m_simpleValue.n); + 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: @@ -841,14 +1022,26 @@ void Any::serialize(TextOutput& to) const { case TABLE: { debugAssert(m_data != NULL); + debugAssert(m_data->separator != '\0'); + if (! m_data->name.empty()) { - if (needsQuotes(m_data->name)) { - to.writeString(m_data->name); + if (json) { + if (! coerce) { + throw "Could not coerce to JSON"; + } } else { - to.writeSymbol(m_data->name); + if (needsQuotes(m_data->name)) { + to.writeString(m_data->name); + } else { + to.writeSymbol(m_data->name); + } } } - to.writeSymbol("{"); + if (json) { + to.writeSymbol("{"); + } else { + to.writeSymbol(m_data->bracket[0]); + } to.writeNewline(); to.pushIndent(); AnyTable& table = *(m_data->value.t); @@ -858,56 +1051,134 @@ void Any::serialize(TextOutput& to) const { for (int i = 0; i < keys.size(); ++i) { - to.writeSymbol(keys[i]); - to.writeSymbol("="); - table[keys[i]].serialize(to); + int prevLine = to.line(); + if (needsQuotes(keys[i]) || json) { + to.writeString(keys[i]); + } else { + to.writeSymbol(keys[i]); + } - if (i < keys.size() - 1) { - to.writeSymbol(","); + 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(); - // Skip a line between table entries to.writeNewline(); } to.popIndent(); - to.writeSymbol("}"); + to.writeSymbol(m_data->bracket[1]); break; } case ARRAY: { debugAssert(m_data != NULL); + debugAssert(m_data->separator != '\0'); + 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())); + 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 { - to.writeSymbol("("); + if (json) { + to.writeSymbol("["); + } else { + to.writeSymbol(m_data->bracket[0]); + } } - to.writeNewline(); + 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(); - Array<Any>& array = *(m_data->value.a); for (int ii = 0; ii < size(); ++ii) { - array[ii].serialize(to); + array[ii].serialize(to, json, coerce); if (ii < size() - 1) { - to.writeSymbol(","); - to.writeNewline(); + 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(); - to.writeSymbol(")"); + 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()) + "\n"; + comment += trimWhitespace(token.string()); // Allow comments to contain newlines. do { @@ -919,6 +1190,7 @@ void Any::deserializeComment(TextInput& ti, Token& token, std::string& comment) comment = trimWhitespace(comment); } + /** True if \a c is an open paren of some form */ static bool isOpen(const char c) { return c == '(' || c == '[' || c == '{'; @@ -961,7 +1233,7 @@ void Any::deserialize(TextInput& ti) { void Any::deserialize(TextInput& ti, Token& token) { // Deallocate old data dropReference(); - m_type = NONE; + m_type = NIL; m_simpleValue.b = false; // Skip leading newlines @@ -1009,7 +1281,7 @@ void Any::deserialize(TextInput& ti, Token& token) { break; case Token::SYMBOL: - // Pragma, Named Array, Named Table, Array, Table, or NONE + // Pragma, Named Array, Named Table, Array, Table, or NIL if (token.string() == "#") { // Pragma @@ -1022,14 +1294,16 @@ void Any::deserialize(TextInput& ti, Token& token) { } 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 = filenamePath(ti.filename()); - std::string t = pathConcat(myPath, includeName); + const std::string& myPath = FilePath::parent(ti.filename()); + + std::string t = FileSystem::resolve(includeName, myPath); if (! FileSystem::exists(t)) { - // Try and find it, starting with cwd + // Try and find the path, starting with the cwd t = System::findDataFile(includeName); } @@ -1038,20 +1312,29 @@ void Any::deserialize(TextInput& ti, Token& token) { // Update the source information ensureData(); - m_data->source.filename += + 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()) == "NONE") { - // Nothing left to do; we initialized to NONE originally + } 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; @@ -1074,7 +1357,7 @@ void Any::deserialize(TextInput& ti, Token& token) { m_data->name = name; } needRead = false; - } // if NONE + } // if NIL break; default: @@ -1107,10 +1390,11 @@ static bool isSeparator(char c) { } -void Any::readUntilCommaOrClose(TextInput& ti, Token& token) { +void Any::readUntilSeparatorOrClose(TextInput& ti, Token& token) { bool atClose = (token.type() == Token::SYMBOL) && isClose(token.string()[0]); - bool atComma = isSeparator(token.string()[0]); - while (! (atClose || atComma)) { + bool atSeparator = isSeparator(token.string()[0]); + + while (! (atClose || atSeparator)) { switch (token.type()) { case Token::NEWLINE: case Token::COMMENT: @@ -1123,28 +1407,85 @@ void Any::readUntilCommaOrClose(TextInput& ti, Token& token) { "Expected a comma or close paren"); } - // Update checks - atComma = isSeparator(token.string()[0]); + // 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 = '}'; + char closeSymbol = '\0'; m_type = TABLE; const char c = token.string()[0]; - - if (c != '{') { - m_type = ARRAY; - // Chose the appropriate close symbol - closeSymbol = (c == '(') ? ')' : ']'; + + // 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(); @@ -1157,12 +1498,12 @@ void Any::deserializeBody(TextInput& ti, Token& token) { 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 + // We're done; this catches the case where the container is empty break; } // Pointer the value being read - Any a = NULL; + Any a; std::string key; if (m_type == TABLE) { @@ -1175,8 +1516,8 @@ void Any::deserializeBody(TextInput& ti, Token& token) { // 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 ="); + 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(); @@ -1196,12 +1537,16 @@ void Any::deserializeBody(TextInput& ti, Token& token) { append(a); } - // Read until the comma or close paren, discarding trailing comments and newlines - readUntilCommaOrClose(ti, token); + // Read until the separator or close paren, discarding trailing comments and newlines + readUntilSeparatorOrClose(ti, token); - // Consume the comma - if (isSeparator(token.string()[0])) { + char s = token.string()[0]; + + // Consume the separator + if (isSeparator(s)) { token = ti.read(); + m_data->separator = s; + debugAssert(m_data->separator != '\0'); } } @@ -1216,6 +1561,12 @@ Any::operator int() const { } +Any::operator uint32() const { + beforeRead(); + return uint32(number() + 0.5); +} + + Any::operator float() const { beforeRead(); return float(number()); @@ -1258,7 +1609,6 @@ std::string Any::sourceDirectory() const { } } - void Any::verify(bool value, const std::string& message) const { beforeRead(); if (! value) { @@ -1286,21 +1636,109 @@ void Any::verify(bool value, const std::string& message) const { void Any::verifyName(const std::string& n) const { beforeRead(); - verify(beginsWith(toUpper(name()), toUpper(n)), "Name must begin with " + n); + verify(name() == n, "Name must be " + 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); + 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) { + if ((type() != t) && ! ((type() == EMPTY_CONTAINER) && isContainerType(t))) { verify(false, "Must have type " + toString(t)); } } @@ -1308,7 +1746,8 @@ void Any::verifyType(Type t) const { void Any::verifyType(Type t0, Type t1) const { beforeRead(); - if (type() != t0 && type() != t1) { + 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)); } } @@ -1334,7 +1773,7 @@ void Any::verifySize(int s) const { std::string Any::toString(Type t) { switch(t) { - case NONE: return "NONE"; + case NIL: return "NIL"; case BOOLEAN: return "BOOLEAN"; case NUMBER: return "NUMBER"; case STRING: return "STRING"; |