diff --git a/PowderToy++.files b/PowderToy++.files index af47ebb5d..765f38029 100644 --- a/PowderToy++.files +++ b/PowderToy++.files @@ -326,3 +326,10 @@ src/simulation/Air.h src/simulation/Simulation.cpp src/simulation/Gravity.cpp src/simulation/Air.cpp +src/cajun/writer.h +src/cajun/visitor.h +src/cajun/reader.h +src/cajun/elements.h +src/cajun/writer.inl +src/cajun/reader.inl +src/cajun/elements.inl diff --git a/src/cajun/elements.h b/src/cajun/elements.h new file mode 100644 index 000000000..df1a0ca30 --- /dev/null +++ b/src/cajun/elements.h @@ -0,0 +1,299 @@ +/****************************************************************************** + +Copyright (c) 2009-2010, Terry Caton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the projecct nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +/* + +TODO: +* better documentation (doxygen?) +* Unicode support +* parent element accessors + +*/ + +namespace json +{ + +namespace Version +{ + enum { MAJOR = 2 }; + enum { MINOR = 0 }; + enum {ENGINEERING = 2 }; +} + +///////////////////////////////////////////////// +// forward declarations (more info further below) + + +class Visitor; +class ConstVisitor; + +template +class TrivialType_T; + +typedef TrivialType_T Number; +typedef TrivialType_T Boolean; +typedef TrivialType_T String; + +class Object; +class Array; +class Null; + + + +///////////////////////////////////////////////////////////////////////// +// Exception - base class for all JSON-related runtime errors + +class Exception : public std::runtime_error +{ +public: + Exception(const std::string& sMessage); +}; + + + + +///////////////////////////////////////////////////////////////////////// +// UnknownElement - provides a typesafe surrogate for any of the JSON- +// sanctioned element types. This class allows the Array and Object +// class to effectively contain a heterogeneous set of child elements. +// The cast operators provide convenient implicit downcasting, while +// preserving dynamic type safety by throwing an exception during a +// a bad cast. +// The object & array element index operators (operators [std::string] +// and [size_t]) provide convenient, quick access to child elements. +// They are a logical extension of the cast operators. These child +// element accesses can be chained together, allowing the following +// (when document structure is well-known): +// String str = objInvoices[1]["Customer"]["Company"]; + + +class UnknownElement +{ +public: + UnknownElement(); + UnknownElement(const UnknownElement& unknown); + UnknownElement(const Object& object); + UnknownElement(const Array& array); + UnknownElement(const Number& number); + UnknownElement(const Boolean& boolean); + UnknownElement(const String& string); + UnknownElement(const Null& null); + + ~UnknownElement(); + + UnknownElement& operator = (const UnknownElement& unknown); + + // implicit cast to actual element type. throws on failure + operator const Object& () const; + operator const Array& () const; + operator const Number& () const; + operator const Boolean& () const; + operator const String& () const; + operator const Null& () const; + + // implicit cast to actual element type. *converts* on failure, and always returns success + operator Object& (); + operator Array& (); + operator Number& (); + operator Boolean& (); + operator String& (); + operator Null& (); + + // provides quick access to children when real element type is object + UnknownElement& operator[] (const std::string& key); + const UnknownElement& operator[] (const std::string& key) const; + + // provides quick access to children when real element type is array + UnknownElement& operator[] (size_t index); + const UnknownElement& operator[] (size_t index) const; + + // implements visitor pattern + void Accept(ConstVisitor& visitor) const; + void Accept(Visitor& visitor); + + // tests equality. first checks type, then value if possible + bool operator == (const UnknownElement& element) const; + +private: + class Imp; + + template + class Imp_T; + + class CastVisitor; + class ConstCastVisitor; + + template + class CastVisitor_T; + + template + class ConstCastVisitor_T; + + template + const ElementTypeT& CastTo() const; + + template + ElementTypeT& ConvertTo(); + + Imp* m_pImp; +}; + + +///////////////////////////////////////////////////////////////////////////////// +// Array - mimics std::deque. The array contents are effectively +// heterogeneous thanks to the ElementUnknown class. push_back has been replaced +// by more generic insert functions. + +class Array +{ +public: + typedef std::deque Elements; + typedef Elements::iterator iterator; + typedef Elements::const_iterator const_iterator; + + iterator Begin(); + iterator End(); + const_iterator Begin() const; + const_iterator End() const; + + iterator Insert(const UnknownElement& element, iterator itWhere); + iterator Insert(const UnknownElement& element); + iterator Erase(iterator itWhere); + void Resize(size_t newSize); + void Clear(); + + size_t Size() const; + bool Empty() const; + + UnknownElement& operator[] (size_t index); + const UnknownElement& operator[] (size_t index) const; + + bool operator == (const Array& array) const; + +private: + Elements m_Elements; +}; + + +///////////////////////////////////////////////////////////////////////////////// +// Object - mimics std::map. The member value +// contents are effectively heterogeneous thanks to the UnknownElement class + +class Object +{ +public: + struct Member { + Member(const std::string& nameIn = std::string(), const UnknownElement& elementIn = UnknownElement()); + + bool operator == (const Member& member) const; + + std::string name; + UnknownElement element; + }; + + typedef std::list Members; // map faster, but does not preserve order + typedef Members::iterator iterator; + typedef Members::const_iterator const_iterator; + + bool operator == (const Object& object) const; + + iterator Begin(); + iterator End(); + const_iterator Begin() const; + const_iterator End() const; + + size_t Size() const; + bool Empty() const; + + iterator Find(const std::string& name); + const_iterator Find(const std::string& name) const; + + iterator Insert(const Member& member); + iterator Insert(const Member& member, iterator itWhere); + iterator Erase(iterator itWhere); + void Clear(); + + UnknownElement& operator [](const std::string& name); + const UnknownElement& operator [](const std::string& name) const; + +private: + class Finder; + + Members m_Members; +}; + + +///////////////////////////////////////////////////////////////////////////////// +// TrivialType_T - class template for encapsulates a simple data type, such as +// a string, number, or boolean. Provides implicit const & noncost cast operators +// for that type, allowing "DataTypeT type = trivialType;" + + +template +class TrivialType_T +{ +public: + TrivialType_T(const DataTypeT& t = DataTypeT()); + + operator DataTypeT&(); + operator const DataTypeT&() const; + + DataTypeT& Value(); + const DataTypeT& Value() const; + + bool operator == (const TrivialType_T& trivial) const; + +private: + DataTypeT m_tValue; +}; + + + +///////////////////////////////////////////////////////////////////////////////// +// Null - doesn't do much of anything but satisfy the JSON spec. It is the default +// element type of UnknownElement + +class Null +{ +public: + bool operator == (const Null& trivial) const; +}; + + +} // End namespace + + +#include "elements.inl" diff --git a/src/cajun/elements.inl b/src/cajun/elements.inl new file mode 100644 index 000000000..dbfbf2af2 --- /dev/null +++ b/src/cajun/elements.inl @@ -0,0 +1,442 @@ +/****************************************************************************** + +Copyright (c) 2009-2010, Terry Caton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the projecct nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#include "visitor.h" +#include "reader.h" +#include +#include +#include + +/* + +TODO: +* better documentation + +*/ + +namespace json +{ + + +inline Exception::Exception(const std::string& sMessage) : + std::runtime_error(sMessage) {} + + +///////////////////////// +// UnknownElement members + +class UnknownElement::Imp +{ +public: + virtual ~Imp() {} + virtual Imp* Clone() const = 0; + + virtual bool Compare(const Imp& imp) const = 0; + + virtual void Accept(ConstVisitor& visitor) const = 0; + virtual void Accept(Visitor& visitor) = 0; +}; + + +template +class UnknownElement::Imp_T : public UnknownElement::Imp +{ +public: + Imp_T(const ElementTypeT& element) : m_Element(element) {} + virtual Imp* Clone() const { return new Imp_T(*this); } + + virtual void Accept(ConstVisitor& visitor) const { visitor.Visit(m_Element); } + virtual void Accept(Visitor& visitor) { visitor.Visit(m_Element); } + + virtual bool Compare(const Imp& imp) const + { + ConstCastVisitor_T castVisitor; + imp.Accept(castVisitor); + return castVisitor.m_pElement && + m_Element == *castVisitor.m_pElement; + } + +private: + ElementTypeT m_Element; +}; + + +class UnknownElement::ConstCastVisitor : public ConstVisitor +{ + virtual void Visit(const Array& array) {} + virtual void Visit(const Object& object) {} + virtual void Visit(const Number& number) {} + virtual void Visit(const String& string) {} + virtual void Visit(const Boolean& boolean) {} + virtual void Visit(const Null& null) {} +}; + +template +class UnknownElement::ConstCastVisitor_T : public ConstCastVisitor +{ +public: + ConstCastVisitor_T() : m_pElement(0) {} + virtual void Visit(const ElementTypeT& element) { m_pElement = &element; } // we don't know what this is, but it overrides one of the base's no-op functions + const ElementTypeT* m_pElement; +}; + + +class UnknownElement::CastVisitor : public Visitor +{ + virtual void Visit(Array& array) {} + virtual void Visit(Object& object) {} + virtual void Visit(Number& number) {} + virtual void Visit(String& string) {} + virtual void Visit(Boolean& boolean) {} + virtual void Visit(Null& null) {} +}; + +template +class UnknownElement::CastVisitor_T : public CastVisitor +{ +public: + CastVisitor_T() : m_pElement(0) {} + virtual void Visit(ElementTypeT& element) { m_pElement = &element; } // we don't know what this is, but it overrides one of the base's no-op functions + ElementTypeT* m_pElement; +}; + + + + +inline UnknownElement::UnknownElement() : m_pImp( new Imp_T( Null() ) ) {} +inline UnknownElement::UnknownElement(const UnknownElement& unknown) : m_pImp( unknown.m_pImp->Clone()) {} +inline UnknownElement::UnknownElement(const Object& object) : m_pImp( new Imp_T(object) ) {} +inline UnknownElement::UnknownElement(const Array& array) : m_pImp( new Imp_T(array) ) {} +inline UnknownElement::UnknownElement(const Number& number) : m_pImp( new Imp_T(number) ) {} +inline UnknownElement::UnknownElement(const Boolean& boolean) : m_pImp( new Imp_T(boolean) ) {} +inline UnknownElement::UnknownElement(const String& string) : m_pImp( new Imp_T(string) ) {} +inline UnknownElement::UnknownElement(const Null& null) : m_pImp( new Imp_T(null) ) {} + +inline UnknownElement::~UnknownElement() { delete m_pImp; } + +inline UnknownElement::operator const Object& () const { return CastTo(); } +inline UnknownElement::operator const Array& () const { return CastTo(); } +inline UnknownElement::operator const Number& () const { return CastTo(); } +inline UnknownElement::operator const Boolean& () const { return CastTo(); } +inline UnknownElement::operator const String& () const { return CastTo(); } +inline UnknownElement::operator const Null& () const { return CastTo(); } + +inline UnknownElement::operator Object& () { return ConvertTo(); } +inline UnknownElement::operator Array& () { return ConvertTo(); } +inline UnknownElement::operator Number& () { return ConvertTo(); } +inline UnknownElement::operator Boolean& () { return ConvertTo(); } +inline UnknownElement::operator String& () { return ConvertTo(); } +inline UnknownElement::operator Null& () { return ConvertTo(); } + +inline UnknownElement& UnknownElement::operator = (const UnknownElement& unknown) +{ + // always check for this + if (&unknown != this) + { + // we might be copying from a subtree of ourselves. delete the old imp + // only after the clone operation is complete. yes, this could be made + // more efficient, but isn't worth the complexity + Imp* pOldImp = m_pImp; + m_pImp = unknown.m_pImp->Clone(); + delete pOldImp; + } + + return *this; +} + +inline UnknownElement& UnknownElement::operator[] (const std::string& key) +{ + // the people want an object. make us one if we aren't already + Object& object = ConvertTo(); + return object[key]; +} + +inline const UnknownElement& UnknownElement::operator[] (const std::string& key) const +{ + // throws if we aren't an object + const Object& object = CastTo(); + return object[key]; +} + +inline UnknownElement& UnknownElement::operator[] (size_t index) +{ + // the people want an array. make us one if we aren't already + Array& array = ConvertTo(); + return array[index]; +} + +inline const UnknownElement& UnknownElement::operator[] (size_t index) const +{ + // throws if we aren't an array + const Array& array = CastTo(); + return array[index]; +} + + +template +const ElementTypeT& UnknownElement::CastTo() const +{ + ConstCastVisitor_T castVisitor; + m_pImp->Accept(castVisitor); + if (castVisitor.m_pElement == 0) + throw Exception("Bad cast"); + return *castVisitor.m_pElement; +} + + + +template +ElementTypeT& UnknownElement::ConvertTo() +{ + CastVisitor_T castVisitor; + m_pImp->Accept(castVisitor); + if (castVisitor.m_pElement == 0) + { + // we're not the right type. fix it & try again + *this = ElementTypeT(); + m_pImp->Accept(castVisitor); + } + + return *castVisitor.m_pElement; +} + + +inline void UnknownElement::Accept(ConstVisitor& visitor) const { m_pImp->Accept(visitor); } +inline void UnknownElement::Accept(Visitor& visitor) { m_pImp->Accept(visitor); } + + +inline bool UnknownElement::operator == (const UnknownElement& element) const +{ + return m_pImp->Compare(*element.m_pImp); +} + + + +////////////////// +// Object members + + +inline Object::Member::Member(const std::string& nameIn, const UnknownElement& elementIn) : + name(nameIn), element(elementIn) {} + +inline bool Object::Member::operator == (const Member& member) const +{ + return name == member.name && + element == member.element; +} + +class Object::Finder : public std::unary_function +{ +public: + Finder(const std::string& name) : m_name(name) {} + bool operator () (const Object::Member& member) { + return member.name == m_name; + } + +private: + std::string m_name; +}; + + + +inline Object::iterator Object::Begin() { return m_Members.begin(); } +inline Object::iterator Object::End() { return m_Members.end(); } +inline Object::const_iterator Object::Begin() const { return m_Members.begin(); } +inline Object::const_iterator Object::End() const { return m_Members.end(); } + +inline size_t Object::Size() const { return m_Members.size(); } +inline bool Object::Empty() const { return m_Members.empty(); } + +inline Object::iterator Object::Find(const std::string& name) +{ + return std::find_if(m_Members.begin(), m_Members.end(), Finder(name)); +} + +inline Object::const_iterator Object::Find(const std::string& name) const +{ + return std::find_if(m_Members.begin(), m_Members.end(), Finder(name)); +} + +inline Object::iterator Object::Insert(const Member& member) +{ + return Insert(member, End()); +} + +inline Object::iterator Object::Insert(const Member& member, iterator itWhere) +{ + iterator it = Find(member.name); + if (it != m_Members.end()) + throw Exception(std::string("Object member already exists: ") + member.name); + + it = m_Members.insert(itWhere, member); + return it; +} + +inline Object::iterator Object::Erase(iterator itWhere) +{ + return m_Members.erase(itWhere); +} + +inline UnknownElement& Object::operator [](const std::string& name) +{ + + iterator it = Find(name); + if (it == m_Members.end()) + { + Member member(name); + it = Insert(member, End()); + } + return it->element; +} + +inline const UnknownElement& Object::operator [](const std::string& name) const +{ + const_iterator it = Find(name); + if (it == End()) + throw Exception(std::string("Object member not found: ") + name); + return it->element; +} + +inline void Object::Clear() +{ + m_Members.clear(); +} + +inline bool Object::operator == (const Object& object) const +{ + return m_Members == object.m_Members; +} + + +///////////////// +// Array members + +inline Array::iterator Array::Begin() { return m_Elements.begin(); } +inline Array::iterator Array::End() { return m_Elements.end(); } +inline Array::const_iterator Array::Begin() const { return m_Elements.begin(); } +inline Array::const_iterator Array::End() const { return m_Elements.end(); } + +inline Array::iterator Array::Insert(const UnknownElement& element, iterator itWhere) +{ + return m_Elements.insert(itWhere, element); +} + +inline Array::iterator Array::Insert(const UnknownElement& element) +{ + return Insert(element, End()); +} + +inline Array::iterator Array::Erase(iterator itWhere) +{ + return m_Elements.erase(itWhere); +} + +inline void Array::Resize(size_t newSize) +{ + m_Elements.resize(newSize); +} + +inline size_t Array::Size() const { return m_Elements.size(); } +inline bool Array::Empty() const { return m_Elements.empty(); } + +inline UnknownElement& Array::operator[] (size_t index) +{ + size_t nMinSize = index + 1; // zero indexed + if (m_Elements.size() < nMinSize) + m_Elements.resize(nMinSize); + return m_Elements[index]; +} + +inline const UnknownElement& Array::operator[] (size_t index) const +{ + if (index >= m_Elements.size()) + throw Exception("Array out of bounds"); + return m_Elements[index]; +} + +inline void Array::Clear() { + m_Elements.clear(); +} + +inline bool Array::operator == (const Array& array) const +{ + return m_Elements == array.m_Elements; +} + + +//////////////////////// +// TrivialType_T members + +template +TrivialType_T::TrivialType_T(const DataTypeT& t) : + m_tValue(t) {} + +template +TrivialType_T::operator DataTypeT&() +{ + return Value(); +} + +template +TrivialType_T::operator const DataTypeT&() const +{ + return Value(); +} + +template +DataTypeT& TrivialType_T::Value() +{ + return m_tValue; +} + +template +const DataTypeT& TrivialType_T::Value() const +{ + return m_tValue; +} + +template +bool TrivialType_T::operator == (const TrivialType_T& trivial) const +{ + return m_tValue == trivial.m_tValue; +} + + + +////////////////// +// Null members + +inline bool Null::operator == (const Null& trivial) const +{ + return true; +} + + + +} // End namespace diff --git a/src/cajun/reader.h b/src/cajun/reader.h new file mode 100644 index 000000000..f43167028 --- /dev/null +++ b/src/cajun/reader.h @@ -0,0 +1,148 @@ +/****************************************************************************** + +Copyright (c) 2009-2010, Terry Caton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the projecct nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + + + +#pragma once + +#include "elements.h" +#include +#include + +namespace json +{ + +class Reader +{ +public: + // this structure will be reported in one of the exceptions defined below + struct Location + { + Location(); + + unsigned int m_nLine; // document line, zero-indexed + unsigned int m_nLineOffset; // character offset from beginning of line, zero indexed + unsigned int m_nDocOffset; // character offset from entire document, zero indexed + }; + + // thrown during the first phase of reading. generally catches low-level problems such + // as errant characters or corrupt/incomplete documents + class ScanException : public Exception + { + public: + ScanException(const std::string& sMessage, const Reader::Location& locError) : + Exception(sMessage), + m_locError(locError) {} + + Reader::Location m_locError; + }; + + // thrown during the second phase of reading. generally catches higher-level problems such + // as missing commas or brackets + class ParseException : public Exception + { + public: + ParseException(const std::string& sMessage, const Reader::Location& locTokenBegin, const Reader::Location& locTokenEnd) : + Exception(sMessage), + m_locTokenBegin(locTokenBegin), + m_locTokenEnd(locTokenEnd) {} + + Reader::Location m_locTokenBegin; + Reader::Location m_locTokenEnd; + }; + + + // if you know what the document looks like, call one of these... + static void Read(Object& object, std::istream& istr); + static void Read(Array& array, std::istream& istr); + static void Read(String& string, std::istream& istr); + static void Read(Number& number, std::istream& istr); + static void Read(Boolean& boolean, std::istream& istr); + static void Read(Null& null, std::istream& istr); + + // ...otherwise, if you don't know, call this & visit it + static void Read(UnknownElement& elementRoot, std::istream& istr); + +private: + struct Token + { + enum Type + { + TOKEN_OBJECT_BEGIN, // { + TOKEN_OBJECT_END, // } + TOKEN_ARRAY_BEGIN, // [ + TOKEN_ARRAY_END, // ] + TOKEN_NEXT_ELEMENT, // , + TOKEN_MEMBER_ASSIGN, // : + TOKEN_STRING, // "xxx" + TOKEN_NUMBER, // [+/-]000.000[e[+/-]000] + TOKEN_BOOLEAN, // true -or- false + TOKEN_NULL, // null + }; + + Type nType; + std::string sValue; + + // for malformed file debugging + Reader::Location locBegin; + Reader::Location locEnd; + }; + + class InputStream; + class TokenStream; + typedef std::vector Tokens; + + template + static void Read_i(ElementTypeT& element, std::istream& istr); + + // scanning istream into token sequence + void Scan(Tokens& tokens, InputStream& inputStream); + + void EatWhiteSpace(InputStream& inputStream); + std::string MatchString(InputStream& inputStream); + std::string MatchNumber(InputStream& inputStream); + std::string MatchExpectedString(InputStream& inputStream, const std::string& sExpected); + + // parsing token sequence into element structure + void Parse(UnknownElement& element, TokenStream& tokenStream); + void Parse(Object& object, TokenStream& tokenStream); + void Parse(Array& array, TokenStream& tokenStream); + void Parse(String& string, TokenStream& tokenStream); + void Parse(Number& number, TokenStream& tokenStream); + void Parse(Boolean& boolean, TokenStream& tokenStream); + void Parse(Null& null, TokenStream& tokenStream); + + const std::string& MatchExpectedToken(Token::Type nExpected, TokenStream& tokenStream); +}; + + +} // End namespace + + +#include "reader.inl" \ No newline at end of file diff --git a/src/cajun/reader.inl b/src/cajun/reader.inl new file mode 100644 index 000000000..47311ad40 --- /dev/null +++ b/src/cajun/reader.inl @@ -0,0 +1,533 @@ +/****************************************************************************** + +Copyright (c) 2009-2010, Terry Caton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the projecct nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#include +#include +#include + +/* + +TODO: +* better documentation +* unicode character decoding + +*/ + +namespace json +{ + +inline std::istream& operator >> (std::istream& istr, UnknownElement& elementRoot) { + Reader::Read(elementRoot, istr); + return istr; +} + +inline Reader::Location::Location() : + m_nLine(0), + m_nLineOffset(0), + m_nDocOffset(0) +{} + + +////////////////////// +// Reader::InputStream + +class Reader::InputStream // would be cool if we could inherit from std::istream & override "get" +{ +public: + InputStream(std::istream& iStr) : + m_iStr(iStr) {} + + // protect access to the input stream, so we can keeep track of document/line offsets + char Get(); // big, define outside + char Peek() { + assert(m_iStr.eof() == false); // enforce reading of only valid stream data + return m_iStr.peek(); + } + + bool EOS() { + m_iStr.peek(); // apparently eof flag isn't set until a character read is attempted. whatever. + return m_iStr.eof(); + } + + const Location& GetLocation() const { return m_Location; } + +private: + std::istream& m_iStr; + Location m_Location; +}; + + +inline char Reader::InputStream::Get() +{ + assert(m_iStr.eof() == false); // enforce reading of only valid stream data + char c = m_iStr.get(); + + ++m_Location.m_nDocOffset; + if (c == '\n') { + ++m_Location.m_nLine; + m_Location.m_nLineOffset = 0; + } + else { + ++m_Location.m_nLineOffset; + } + + return c; +} + + + +////////////////////// +// Reader::TokenStream + +class Reader::TokenStream +{ +public: + TokenStream(const Tokens& tokens); + + const Token& Peek(); + const Token& Get(); + + bool EOS() const; + +private: + const Tokens& m_Tokens; + Tokens::const_iterator m_itCurrent; +}; + + +inline Reader::TokenStream::TokenStream(const Tokens& tokens) : + m_Tokens(tokens), + m_itCurrent(tokens.begin()) +{} + +inline const Reader::Token& Reader::TokenStream::Peek() { + if (EOS()) + { + const Token& lastToken = *m_Tokens.rbegin(); + std::string sMessage = "Unexpected end of token stream"; + throw ParseException(sMessage, lastToken.locBegin, lastToken.locEnd); // nowhere to point to + } + return *(m_itCurrent); +} + +inline const Reader::Token& Reader::TokenStream::Get() { + const Token& token = Peek(); + ++m_itCurrent; + return token; +} + +inline bool Reader::TokenStream::EOS() const { + return m_itCurrent == m_Tokens.end(); +} + +/////////////////// +// Reader (finally) + + +inline void Reader::Read(Object& object, std::istream& istr) { Read_i(object, istr); } +inline void Reader::Read(Array& array, std::istream& istr) { Read_i(array, istr); } +inline void Reader::Read(String& string, std::istream& istr) { Read_i(string, istr); } +inline void Reader::Read(Number& number, std::istream& istr) { Read_i(number, istr); } +inline void Reader::Read(Boolean& boolean, std::istream& istr) { Read_i(boolean, istr); } +inline void Reader::Read(Null& null, std::istream& istr) { Read_i(null, istr); } +inline void Reader::Read(UnknownElement& unknown, std::istream& istr) { Read_i(unknown, istr); } + + +template +void Reader::Read_i(ElementTypeT& element, std::istream& istr) +{ + Reader reader; + + Tokens tokens; + InputStream inputStream(istr); + reader.Scan(tokens, inputStream); + + TokenStream tokenStream(tokens); + reader.Parse(element, tokenStream); + + if (tokenStream.EOS() == false) + { + const Token& token = tokenStream.Peek(); + std::string sMessage = std::string("Expected End of token stream; found ") + token.sValue; + throw ParseException(sMessage, token.locBegin, token.locEnd); + } +} + + +inline void Reader::Scan(Tokens& tokens, InputStream& inputStream) +{ + while (EatWhiteSpace(inputStream), // ignore any leading white space... + inputStream.EOS() == false) // ...before checking for EOS + { + // if all goes well, we'll create a token each pass + Token token; + token.locBegin = inputStream.GetLocation(); + + // gives us null-terminated string + char sChar = inputStream.Peek(); + switch (sChar) + { + case '{': + token.sValue = MatchExpectedString(inputStream, "{"); + token.nType = Token::TOKEN_OBJECT_BEGIN; + break; + + case '}': + token.sValue = MatchExpectedString(inputStream, "}"); + token.nType = Token::TOKEN_OBJECT_END; + break; + + case '[': + token.sValue = MatchExpectedString(inputStream, "["); + token.nType = Token::TOKEN_ARRAY_BEGIN; + break; + + case ']': + token.sValue = MatchExpectedString(inputStream, "]"); + token.nType = Token::TOKEN_ARRAY_END; + break; + + case ',': + token.sValue = MatchExpectedString(inputStream, ","); + token.nType = Token::TOKEN_NEXT_ELEMENT; + break; + + case ':': + token.sValue = MatchExpectedString(inputStream, ":"); + token.nType = Token::TOKEN_MEMBER_ASSIGN; + break; + + case '"': + token.sValue = MatchString(inputStream); + token.nType = Token::TOKEN_STRING; + break; + + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + token.sValue = MatchNumber(inputStream); + token.nType = Token::TOKEN_NUMBER; + break; + + case 't': + token.sValue = MatchExpectedString(inputStream, "true"); + token.nType = Token::TOKEN_BOOLEAN; + break; + + case 'f': + token.sValue = MatchExpectedString(inputStream, "false"); + token.nType = Token::TOKEN_BOOLEAN; + break; + + case 'n': + token.sValue = MatchExpectedString(inputStream, "null"); + token.nType = Token::TOKEN_NULL; + break; + + default: + { + std::string sErrorMessage = std::string("Unexpected character in stream: ") + sChar; + throw ScanException(sErrorMessage, inputStream.GetLocation()); + } + } + + token.locEnd = inputStream.GetLocation(); + tokens.push_back(token); + } +} + + +inline void Reader::EatWhiteSpace(InputStream& inputStream) +{ + while (inputStream.EOS() == false && + ::isspace(inputStream.Peek())) + inputStream.Get(); +} + +inline std::string Reader::MatchExpectedString(InputStream& inputStream, const std::string& sExpected) +{ + std::string::const_iterator it(sExpected.begin()), + itEnd(sExpected.end()); + for ( ; it != itEnd; ++it) { + if (inputStream.EOS() || // did we reach the end before finding what we're looking for... + inputStream.Get() != *it) // ...or did we find something different? + { + std::string sMessage = std::string("Expected string: ") + sExpected; + throw ScanException(sMessage, inputStream.GetLocation()); + } + } + + // all's well if we made it here + return sExpected; +} + + +inline std::string Reader::MatchString(InputStream& inputStream) +{ + MatchExpectedString(inputStream, "\""); + + std::string string; + while (inputStream.EOS() == false && + inputStream.Peek() != '"') + { + char c = inputStream.Get(); + + // escape? + if (c == '\\' && + inputStream.EOS() == false) // shouldn't have reached the end yet + { + c = inputStream.Get(); + switch (c) { + case '/': string.push_back('/'); break; + case '"': string.push_back('"'); break; + case '\\': string.push_back('\\'); break; + case 'b': string.push_back('\b'); break; + case 'f': string.push_back('\f'); break; + case 'n': string.push_back('\n'); break; + case 'r': string.push_back('\r'); break; + case 't': string.push_back('\t'); break; + //case 'u': string.push_back('\u'); break; // TODO: what do we do with this? + default: { + std::string sMessage = std::string("Unrecognized escape sequence found in string: \\") + c; + throw ScanException(sMessage, inputStream.GetLocation()); + } + } + } + else { + string.push_back(c); + } + } + + // eat the last '"' that we just peeked + MatchExpectedString(inputStream, "\""); + + // all's well if we made it here + return string; +} + + +inline std::string Reader::MatchNumber(InputStream& inputStream) +{ + const char sNumericChars[] = "0123456789.eE-+"; + std::set numericChars; + numericChars.insert(sNumericChars, sNumericChars + sizeof(sNumericChars)); + + std::string sNumber; + while (inputStream.EOS() == false && + numericChars.find(inputStream.Peek()) != numericChars.end()) + { + sNumber.push_back(inputStream.Get()); + } + + return sNumber; +} + + +inline void Reader::Parse(UnknownElement& element, Reader::TokenStream& tokenStream) +{ + const Token& token = tokenStream.Peek(); + switch (token.nType) { + case Token::TOKEN_OBJECT_BEGIN: + { + // implicit non-const cast will perform conversion for us (if necessary) + Object& object = element; + Parse(object, tokenStream); + break; + } + + case Token::TOKEN_ARRAY_BEGIN: + { + Array& array = element; + Parse(array, tokenStream); + break; + } + + case Token::TOKEN_STRING: + { + String& string = element; + Parse(string, tokenStream); + break; + } + + case Token::TOKEN_NUMBER: + { + Number& number = element; + Parse(number, tokenStream); + break; + } + + case Token::TOKEN_BOOLEAN: + { + Boolean& boolean = element; + Parse(boolean, tokenStream); + break; + } + + case Token::TOKEN_NULL: + { + Null& null = element; + Parse(null, tokenStream); + break; + } + + default: + { + std::string sMessage = std::string("Unexpected token: ") + token.sValue; + throw ParseException(sMessage, token.locBegin, token.locEnd); + } + } +} + + +inline void Reader::Parse(Object& object, Reader::TokenStream& tokenStream) +{ + MatchExpectedToken(Token::TOKEN_OBJECT_BEGIN, tokenStream); + + bool bContinue = (tokenStream.EOS() == false && + tokenStream.Peek().nType != Token::TOKEN_OBJECT_END); + while (bContinue) + { + Object::Member member; + + // first the member name. save the token in case we have to throw an exception + const Token& tokenName = tokenStream.Peek(); + member.name = MatchExpectedToken(Token::TOKEN_STRING, tokenStream); + + // ...then the key/value separator... + MatchExpectedToken(Token::TOKEN_MEMBER_ASSIGN, tokenStream); + + // ...then the value itself (can be anything). + Parse(member.element, tokenStream); + + // try adding it to the object (this could throw) + try + { + object.Insert(member); + } + catch (Exception&) + { + // must be a duplicate name + std::string sMessage = std::string("Duplicate object member token: ") + member.name; + throw ParseException(sMessage, tokenName.locBegin, tokenName.locEnd); + } + + bContinue = (tokenStream.EOS() == false && + tokenStream.Peek().nType == Token::TOKEN_NEXT_ELEMENT); + if (bContinue) + MatchExpectedToken(Token::TOKEN_NEXT_ELEMENT, tokenStream); + } + + MatchExpectedToken(Token::TOKEN_OBJECT_END, tokenStream); +} + + +inline void Reader::Parse(Array& array, Reader::TokenStream& tokenStream) +{ + MatchExpectedToken(Token::TOKEN_ARRAY_BEGIN, tokenStream); + + bool bContinue = (tokenStream.EOS() == false && + tokenStream.Peek().nType != Token::TOKEN_ARRAY_END); + while (bContinue) + { + // ...what's next? could be anything + Array::iterator itElement = array.Insert(UnknownElement()); + UnknownElement& element = *itElement; + Parse(element, tokenStream); + + bContinue = (tokenStream.EOS() == false && + tokenStream.Peek().nType == Token::TOKEN_NEXT_ELEMENT); + if (bContinue) + MatchExpectedToken(Token::TOKEN_NEXT_ELEMENT, tokenStream); + } + + MatchExpectedToken(Token::TOKEN_ARRAY_END, tokenStream); +} + + +inline void Reader::Parse(String& string, Reader::TokenStream& tokenStream) +{ + string = MatchExpectedToken(Token::TOKEN_STRING, tokenStream); +} + + +inline void Reader::Parse(Number& number, Reader::TokenStream& tokenStream) +{ + const Token& currentToken = tokenStream.Peek(); // might need this later for throwing exception + const std::string& sValue = MatchExpectedToken(Token::TOKEN_NUMBER, tokenStream); + + std::istringstream iStr(sValue); + double dValue; + iStr >> dValue; + + // did we consume all characters in the token? + if (iStr.eof() == false) + { + char c = iStr.peek(); + std::string sMessage = std::string("Unexpected character in NUMBER token: ") + c; + throw ParseException(sMessage, currentToken.locBegin, currentToken.locEnd); + } + + number = dValue; +} + + +inline void Reader::Parse(Boolean& boolean, Reader::TokenStream& tokenStream) +{ + const std::string& sValue = MatchExpectedToken(Token::TOKEN_BOOLEAN, tokenStream); + boolean = (sValue == "true" ? true : false); +} + + +inline void Reader::Parse(Null&, Reader::TokenStream& tokenStream) +{ + MatchExpectedToken(Token::TOKEN_NULL, tokenStream); +} + + +inline const std::string& Reader::MatchExpectedToken(Token::Type nExpected, Reader::TokenStream& tokenStream) +{ + const Token& token = tokenStream.Get(); + if (token.nType != nExpected) + { + std::string sMessage = std::string("Unexpected token: ") + token.sValue; + throw ParseException(sMessage, token.locBegin, token.locEnd); + } + + return token.sValue; +} + +} // End namespace diff --git a/src/cajun/visitor.h b/src/cajun/visitor.h new file mode 100644 index 000000000..a06299d10 --- /dev/null +++ b/src/cajun/visitor.h @@ -0,0 +1,65 @@ +/****************************************************************************** + +Copyright (c) 2009-2010, Terry Caton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the projecct nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#pragma once + +#include "elements.h" + +namespace json +{ + + +class Visitor +{ +public: + virtual ~Visitor() {} + + virtual void Visit(Array& array) = 0; + virtual void Visit(Object& object) = 0; + virtual void Visit(Number& number) = 0; + virtual void Visit(String& string) = 0; + virtual void Visit(Boolean& boolean) = 0; + virtual void Visit(Null& null) = 0; +}; + +class ConstVisitor +{ +public: + virtual ~ConstVisitor() {} + + virtual void Visit(const Array& array) = 0; + virtual void Visit(const Object& object) = 0; + virtual void Visit(const Number& number) = 0; + virtual void Visit(const String& string) = 0; + virtual void Visit(const Boolean& boolean) = 0; + virtual void Visit(const Null& null) = 0; +}; + + +} // End namespace diff --git a/src/cajun/writer.h b/src/cajun/writer.h new file mode 100644 index 000000000..594d5418f --- /dev/null +++ b/src/cajun/writer.h @@ -0,0 +1,78 @@ +/****************************************************************************** + +Copyright (c) 2009-2010, Terry Caton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the projecct nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#pragma once + +#include "elements.h" +#include "visitor.h" + +namespace json +{ + +class Writer : private ConstVisitor +{ +public: + static void Write(const Object& object, std::ostream& ostr); + static void Write(const Array& array, std::ostream& ostr); + static void Write(const String& string, std::ostream& ostr); + static void Write(const Number& number, std::ostream& ostr); + static void Write(const Boolean& boolean, std::ostream& ostr); + static void Write(const Null& null, std::ostream& ostr); + static void Write(const UnknownElement& elementRoot, std::ostream& ostr); + +private: + Writer(std::ostream& ostr); + + template + static void Write_i(const ElementTypeT& element, std::ostream& ostr); + + void Write_i(const Object& object); + void Write_i(const Array& array); + void Write_i(const String& string); + void Write_i(const Number& number); + void Write_i(const Boolean& boolean); + void Write_i(const Null& null); + void Write_i(const UnknownElement& unknown); + + virtual void Visit(const Array& array); + virtual void Visit(const Object& object); + virtual void Visit(const Number& number); + virtual void Visit(const String& string); + virtual void Visit(const Boolean& boolean); + virtual void Visit(const Null& null); + + std::ostream& m_ostr; + int m_nTabDepth; +}; + + +} // End namespace + + +#include "writer.inl" \ No newline at end of file diff --git a/src/cajun/writer.inl b/src/cajun/writer.inl new file mode 100644 index 000000000..71145da18 --- /dev/null +++ b/src/cajun/writer.inl @@ -0,0 +1,178 @@ +/****************************************************************************** + +Copyright (c) 2009-2010, Terry Caton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the projecct nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#include "writer.h" +#include +#include + +/* + +TODO: +* better documentation +* unicode character encoding + +*/ + +namespace json +{ + + +inline void Writer::Write(const UnknownElement& elementRoot, std::ostream& ostr) { Write_i(elementRoot, ostr); } +inline void Writer::Write(const Object& object, std::ostream& ostr) { Write_i(object, ostr); } +inline void Writer::Write(const Array& array, std::ostream& ostr) { Write_i(array, ostr); } +inline void Writer::Write(const Number& number, std::ostream& ostr) { Write_i(number, ostr); } +inline void Writer::Write(const String& string, std::ostream& ostr) { Write_i(string, ostr); } +inline void Writer::Write(const Boolean& boolean, std::ostream& ostr) { Write_i(boolean, ostr); } +inline void Writer::Write(const Null& null, std::ostream& ostr) { Write_i(null, ostr); } + + +inline Writer::Writer(std::ostream& ostr) : + m_ostr(ostr), + m_nTabDepth(0) +{} + +template +void Writer::Write_i(const ElementTypeT& element, std::ostream& ostr) +{ + Writer writer(ostr); + writer.Write_i(element); + ostr.flush(); // all done +} + +inline void Writer::Write_i(const Array& array) +{ + if (array.Empty()) + m_ostr << "[]"; + else + { + m_ostr << '[' << std::endl; + ++m_nTabDepth; + + Array::const_iterator it(array.Begin()), + itEnd(array.End()); + while (it != itEnd) { + m_ostr << std::string(m_nTabDepth, '\t'); + + Write_i(*it); + + if (++it != itEnd) + m_ostr << ','; + m_ostr << std::endl; + } + + --m_nTabDepth; + m_ostr << std::string(m_nTabDepth, '\t') << ']'; + } +} + +inline void Writer::Write_i(const Object& object) +{ + if (object.Empty()) + m_ostr << "{}"; + else + { + m_ostr << '{' << std::endl; + ++m_nTabDepth; + + Object::const_iterator it(object.Begin()), + itEnd(object.End()); + while (it != itEnd) { + m_ostr << std::string(m_nTabDepth, '\t'); + + Write_i(it->name); + + m_ostr << " : "; + Write_i(it->element); + + if (++it != itEnd) + m_ostr << ','; + m_ostr << std::endl; + } + + --m_nTabDepth; + m_ostr << std::string(m_nTabDepth, '\t') << '}'; + } +} + +inline void Writer::Write_i(const Number& numberElement) +{ + m_ostr << std::setprecision(20) << numberElement.Value(); +} + +inline void Writer::Write_i(const Boolean& booleanElement) +{ + m_ostr << (booleanElement.Value() ? "true" : "false"); +} + +inline void Writer::Write_i(const String& stringElement) +{ + m_ostr << '"'; + + const std::string& s = stringElement.Value(); + std::string::const_iterator it(s.begin()), + itEnd(s.end()); + for (; it != itEnd; ++it) + { + switch (*it) + { + case '"': m_ostr << "\\\""; break; + case '\\': m_ostr << "\\\\"; break; + case '\b': m_ostr << "\\b"; break; + case '\f': m_ostr << "\\f"; break; + case '\n': m_ostr << "\\n"; break; + case '\r': m_ostr << "\\r"; break; + case '\t': m_ostr << "\\t"; break; + //case '\u': m_ostr << "\\u"; break; // uh... + default: m_ostr << *it; break; + } + } + + m_ostr << '"'; +} + +inline void Writer::Write_i(const Null& ) +{ + m_ostr << "null"; +} + +inline void Writer::Write_i(const UnknownElement& unknown) +{ + unknown.Accept(*this); +} + +inline void Writer::Visit(const Array& array) { Write_i(array); } +inline void Writer::Visit(const Object& object) { Write_i(object); } +inline void Writer::Visit(const Number& number) { Write_i(number); } +inline void Writer::Visit(const String& string) { Write_i(string); } +inline void Writer::Visit(const Boolean& boolean) { Write_i(boolean); } +inline void Writer::Visit(const Null& null) { Write_i(null); } + + + +} // End namespace diff --git a/src/client/Client.cpp b/src/client/Client.cpp index 2b742f457..1e2e76fa6 100644 --- a/src/client/Client.cpp +++ b/src/client/Client.cpp @@ -2,12 +2,21 @@ #include #include #include +#include #include + #include "Config.h" #include "Client.h" -#include "interface/Point.h" #include "Graphics.h" +#include "interface/Point.h" + +#include "search/Save.h" + +#include "cajun/reader.h" +#include "cajun/writer.h" +#include "cajun/elements.h" + Client::Client() { int i = 0; @@ -29,6 +38,69 @@ Client::~Client() http_done(); } +std::vector Client::SearchSaves(int start, int count, string query, string sort) +{ + lastError = ""; + std::vector saveArray; + std::stringstream urlStream; + char * data; + int dataStatus, dataLength; + urlStream << "http://" << SERVER << "/Browse.json?Start=" << start << "&Count=" << cout; + if(query.length() || sort.length()) + { + urlStream << "&Search_Query="; + if(query.length()) + urlStream << query; + if(sort.length()) + { + if(query.length()) + urlStream << " "; + urlStream << "sort:" << sort; + } + + } + data = http_simple_get((char *)urlStream.str().c_str(), &dataStatus, &dataLength); + if(dataStatus == 200 && data) + { + try + { + std::istringstream dataStream(data); // missing comma! + json::Object objDocument; + json::Reader::Read(objDocument, dataStream); + + json::Array savesArray = objDocument["Saves"]; + for(int j = 0; j < savesArray.Size(); j++) + { + json::Number tempID = savesArray[j]["ID"]; + json::Number tempScoreUp = savesArray[j]["ScoreUp"]; + json::Number tempScoreDown = savesArray[j]["ScoreDown"]; + json::String tempUsername = savesArray[j]["Username"]; + json::String tempName = savesArray[j]["Name"]; + saveArray.push_back( + Save( + tempID.Value(), + tempScoreUp.Value(), + tempScoreDown.Value(), + tempUsername.Value(), + tempName.Value() + ) + ); + } + } + catch (json::Exception &e) + { + lastError = "Could not read response"; + } + } + else + { + lastError = http_ret_text(dataStatus); + } + if(data) + free(data); + return saveArray; +} + void Client::ClearThumbnailRequests() { for(int i = 0; i < IMGCONNS; i++) diff --git a/src/client/Client.h b/src/client/Client.h index 3aa233385..9a86bfbd9 100644 --- a/src/client/Client.h +++ b/src/client/Client.h @@ -2,14 +2,18 @@ #define CLIENT_H #include +#include + #include "Config.h" #include "HTTP.h" #include "search/Thumbnail.h" +#include "search/Save.h" #include "Singleton.h" class Client: public Singleton { private: + std::string lastError; int thumbnailCacheNextID; Thumbnail * thumbnailCache[THUMB_CACHE_SIZE]; void * activeThumbRequests[IMGCONNS]; @@ -20,7 +24,9 @@ public: Client(); ~Client(); void ClearThumbnailRequests(); + std::vector SearchSaves(int start, int count, string query, string sort); Thumbnail * GetThumbnail(int saveID, int saveDate); + std::string GetLastError() { return lastError; } }; #endif // CLIENT_H diff --git a/src/search/SearchModel.cpp b/src/search/SearchModel.cpp index 494d49028..16a99d4ac 100644 --- a/src/search/SearchModel.cpp +++ b/src/search/SearchModel.cpp @@ -1,18 +1,26 @@ #include "SearchModel.h" #include "Save.h" +#include "client/Client.h" + SearchModel::SearchModel() { } void SearchModel::UpdateSaveList() { + lastError = ""; saveList.clear(); notifySaveListChanged(); - for(int i = 0; i < 16; i++) + saveList = Client::Ref().SearchSaves(0, 12, "", ""); + if(!saveList.size()) + { + lastError = Client::Ref().GetLastError(); + } + /*for(int i = 0; i < 16; i++) { saveList.push_back(Save(2198, 2333, 315, "dima-gord", "Destroyable city 5 (wth metro)")); - } + }*/ notifySaveListChanged(); } diff --git a/src/search/SearchModel.h b/src/search/SearchModel.h index 85d4177ee..e65c15e35 100644 --- a/src/search/SearchModel.h +++ b/src/search/SearchModel.h @@ -2,6 +2,7 @@ #define SEARCHMODEL_H #include +#include #include "Save.h" #include "SearchView.h" @@ -11,6 +12,7 @@ class SearchView; class SearchModel { private: + string lastError; vector observers; vector saveList; void notifySaveListChanged(); @@ -19,6 +21,7 @@ public: void AddObserver(SearchView * observer); void UpdateSaveList(); vector GetSaveList(); + string GetLastError() { return lastError; } }; #endif // SEARCHMODEL_H diff --git a/src/search/SearchView.cpp b/src/search/SearchView.cpp index 29d6eef86..63a579ede 100644 --- a/src/search/SearchView.cpp +++ b/src/search/SearchView.cpp @@ -1,10 +1,12 @@ #include "SearchView.h" #include "interface/SaveButton.h" +#include "interface/Label.h" #include "Misc.h" SearchView::SearchView(): ui::Window(ui::Point(0, 0), ui::Point(XRES+BARSIZE, YRES+MENUSIZE)), - saveButtons(vector()) + saveButtons(vector()), + errorLabel(NULL) { nextButton = new ui::Button(ui::Point(XRES+BARSIZE-52, YRES+MENUSIZE-18), ui::Point(50, 16), "Next \x95"); previousButton = new ui::Button(ui::Point(1, YRES+MENUSIZE-18), ui::Point(50, 16), "\x96 Prev"); @@ -24,37 +26,59 @@ void SearchView::NotifySaveListChanged(SearchModel * sender) int i = 0; int buttonWidth, buttonHeight, saveX = 0, saveY = 0, savesX = 4, savesY = 3, buttonPadding = 2; int buttonAreaWidth, buttonAreaHeight, buttonXOffset, buttonYOffset; - buttonXOffset = 0; - buttonYOffset = 50; - buttonAreaWidth = Size.X; - buttonAreaHeight = Size.Y - buttonYOffset - 18; - buttonWidth = (buttonAreaWidth/savesX) - buttonPadding*2; - buttonHeight = (buttonAreaHeight/savesY) - buttonPadding*2; - for(i = 0; i < saveButtons.size(); i++) - { - RemoveComponent(saveButtons[i]); - delete saveButtons[i]; - } + vector saves = sender->GetSaveList(); - for(i = 0; i < saves.size(); i++) + if(!saves.size()) { - if(saveX == savesX) + if(!errorLabel) { - if(saveY == savesY-1) - break; - saveX = 0; - saveY++; + errorLabel = new ui::Label(ui::Point(((XRES+BARSIZE)/2)-100, ((YRES+MENUSIZE)/2)-6), ui::Point(200, 12), "Error"); + AddComponent(errorLabel); + } + if(sender->GetLastError().length()) + errorLabel->LabelText = sender->GetLastError(); + else + errorLabel->LabelText = "No saves found"; + } + else + { + if(errorLabel) + { + RemoveComponent(errorLabel); + delete errorLabel; + errorLabel = NULL; + } + buttonXOffset = 0; + buttonYOffset = 50; + buttonAreaWidth = Size.X; + buttonAreaHeight = Size.Y - buttonYOffset - 18; + buttonWidth = (buttonAreaWidth/savesX) - buttonPadding*2; + buttonHeight = (buttonAreaHeight/savesY) - buttonPadding*2; + for(i = 0; i < saveButtons.size(); i++) + { + RemoveComponent(saveButtons[i]); + delete saveButtons[i]; + } + for(i = 0; i < saves.size(); i++) + { + if(saveX == savesX) + { + if(saveY == savesY-1) + break; + saveX = 0; + saveY++; + } + ui::SaveButton * saveButton; + saveButton = new ui::SaveButton( + ui::Point( + buttonXOffset + buttonPadding + saveX*(buttonWidth+buttonPadding*2), + buttonYOffset + buttonPadding + saveY*(buttonHeight+buttonPadding*2) + ), + ui::Point(buttonWidth, buttonHeight), + saves[i]); + saveButtons.push_back(saveButton); + AddComponent(saveButton); + saveX++; } - ui::SaveButton * saveButton; - saveButton = new ui::SaveButton( - ui::Point( - buttonXOffset + buttonPadding + saveX*(buttonWidth+buttonPadding*2), - buttonYOffset + buttonPadding + saveY*(buttonHeight+buttonPadding*2) - ), - ui::Point(buttonWidth, buttonHeight), - saves[i]); - saveButtons.push_back(saveButton); - AddComponent(saveButton); - saveX++; } } diff --git a/src/search/SearchView.h b/src/search/SearchView.h index c68d42c21..795fb7479 100644 --- a/src/search/SearchView.h +++ b/src/search/SearchView.h @@ -5,6 +5,7 @@ #include "SearchController.h" #include "interface/SaveButton.h" #include "interface/Button.h" +#include "interface/Label.h" using namespace std; @@ -18,6 +19,7 @@ private: vector saveButtons; ui::Button * nextButton; ui::Button * previousButton; + ui::Label * errorLabel; public: void NotifySaveListChanged(SearchModel * sender); SearchView();