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.cpp783
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";