Split json::Number into json::Double and json::Integer

Trying to decide whether an option should be an int or double after
discarding the differences between "1.0" and "1" simply isn't possible,
and even if an option was initialized correctly, if it was changed to a
round number it could get written as an int and break later.

Also convert cajun to tabs because three spaces to indent is terrible.

Originally committed to SVN as r6018.
This commit is contained in:
Thomas Goyne 2011-12-22 21:12:25 +00:00
parent daff67b150
commit 07da6f6f1b
13 changed files with 658 additions and 739 deletions

View file

@ -20,13 +20,15 @@ namespace {
class CastVisitorBase : public Visitor, public ConstVisitor { class CastVisitorBase : public Visitor, public ConstVisitor {
void Visit(Array&) { } void Visit(Array&) { }
void Visit(Object&) { } void Visit(Object&) { }
void Visit(Number&) { } void Visit(Integer&) { }
void Visit(Double&) { }
void Visit(String&) { } void Visit(String&) { }
void Visit(Boolean&) { } void Visit(Boolean&) { }
void Visit(Null&) { is_null = true; } void Visit(Null&) { is_null = true; }
void Visit(Array const&) { } void Visit(Array const&) { }
void Visit(Object const&) { } void Visit(Object const&) { }
void Visit(Number const&) { } void Visit(Integer const&) { }
void Visit(Double const&) { }
void Visit(String const&) { } void Visit(String const&) { }
void Visit(Boolean const&) { } void Visit(Boolean const&) { }
void Visit(Null const&) { is_null = true; } void Visit(Null const&) { is_null = true; }
@ -86,9 +88,10 @@ UnknownElement::UnknownElement() : m_pImp( new Imp
UnknownElement::UnknownElement(const UnknownElement& unknown) : m_pImp( unknown.m_pImp->Clone()) {} UnknownElement::UnknownElement(const UnknownElement& unknown) : m_pImp( unknown.m_pImp->Clone()) {}
UnknownElement::UnknownElement(const Object& object) : m_pImp( new Imp_T<Object>(object) ) {} UnknownElement::UnknownElement(const Object& object) : m_pImp( new Imp_T<Object>(object) ) {}
UnknownElement::UnknownElement(const Array& array) : m_pImp( new Imp_T<Array>(array) ) {} UnknownElement::UnknownElement(const Array& array) : m_pImp( new Imp_T<Array>(array) ) {}
UnknownElement::UnknownElement(double number) : m_pImp( new Imp_T<Number>(number) ) {} UnknownElement::UnknownElement(double number) : m_pImp( new Imp_T<Double>(number) ) {}
UnknownElement::UnknownElement(int number) : m_pImp( new Imp_T<Number>(number) ) {} UnknownElement::UnknownElement(int number) : m_pImp( new Imp_T<Integer>(number) ) {}
UnknownElement::UnknownElement(long number) : m_pImp( new Imp_T<Number>(number) ) {} UnknownElement::UnknownElement(int64_t number) : m_pImp( new Imp_T<Integer>(number) ) {}
UnknownElement::UnknownElement(long number) : m_pImp( new Imp_T<Integer>(number) ) {}
UnknownElement::UnknownElement(bool boolean) : m_pImp( new Imp_T<Boolean>(boolean) ) {} UnknownElement::UnknownElement(bool boolean) : m_pImp( new Imp_T<Boolean>(boolean) ) {}
UnknownElement::UnknownElement(const char *string) : m_pImp( new Imp_T<String>(string) ) {} UnknownElement::UnknownElement(const char *string) : m_pImp( new Imp_T<String>(string) ) {}
UnknownElement::UnknownElement(const String& string) : m_pImp( new Imp_T<String>(string) ) {} UnknownElement::UnknownElement(const String& string) : m_pImp( new Imp_T<String>(string) ) {}
@ -98,14 +101,16 @@ UnknownElement::~UnknownElement() { delete m_pImp; }
UnknownElement::operator Object const&() const { return CastTo<Object>(); } UnknownElement::operator Object const&() const { return CastTo<Object>(); }
UnknownElement::operator Array const&() const { return CastTo<Array>(); } UnknownElement::operator Array const&() const { return CastTo<Array>(); }
UnknownElement::operator Number const&() const { return CastTo<Number>(); } UnknownElement::operator Integer const&() const { return CastTo<Integer>(); }
UnknownElement::operator Double const&() const { return CastTo<Double>(); }
UnknownElement::operator Boolean const&() const { return CastTo<Boolean>(); } UnknownElement::operator Boolean const&() const { return CastTo<Boolean>(); }
UnknownElement::operator String const&() const { return CastTo<String>(); } UnknownElement::operator String const&() const { return CastTo<String>(); }
UnknownElement::operator Null const&() const { return CastTo<Null>(); } UnknownElement::operator Null const&() const { return CastTo<Null>(); }
UnknownElement::operator Object&() { return CastTo<Object>(); } UnknownElement::operator Object&() { return CastTo<Object>(); }
UnknownElement::operator Array&() { return CastTo<Array>(); } UnknownElement::operator Array&() { return CastTo<Array>(); }
UnknownElement::operator Number&() { return CastTo<Number>(); } UnknownElement::operator Integer&() { return CastTo<Integer>(); }
UnknownElement::operator Double&() { return CastTo<Double>(); }
UnknownElement::operator Boolean&() { return CastTo<Boolean>(); } UnknownElement::operator Boolean&() { return CastTo<Boolean>(); }
UnknownElement::operator String&() { return CastTo<String>(); } UnknownElement::operator String&() { return CastTo<String>(); }
UnknownElement::operator Null&() { return CastTo<Null>(); } UnknownElement::operator Null&() { return CastTo<Null>(); }

View file

@ -22,402 +22,357 @@ TODO:
*/ */
namespace json namespace json {
{
std::istream& operator >> (std::istream& istr, UnknownElement& elementRoot) { std::istream& operator >> (std::istream& istr, UnknownElement& elementRoot) {
Reader::Read(elementRoot, istr); Reader::Read(elementRoot, istr);
return istr; return istr;
} }
Reader::Location::Location() : /// Wrapper around istream to keep track of document/line offsets
m_nLine(0), class Reader::InputStream {
m_nLineOffset(0), std::istream& m_iStr;
m_nDocOffset(0) Location m_Location;
{}
//////////////////////
// Reader::InputStream
// wrapper around istream to keep track of document/line offsets
class Reader::InputStream
{
std::istream& m_iStr;
Location m_Location;
public: public:
InputStream(std::istream& iStr) : m_iStr(iStr) { } InputStream(std::istream& iStr) : m_iStr(iStr) { }
int Get() { int Get() {
assert(!m_iStr.eof()); assert(!m_iStr.eof());
int c = m_iStr.get(); int c = m_iStr.get();
++m_Location.m_nDocOffset; ++m_Location.m_nDocOffset;
if (c == '\n') { if (c == '\n') {
++m_Location.m_nLine; ++m_Location.m_nLine;
m_Location.m_nLineOffset = 0; m_Location.m_nLineOffset = 0;
} }
else { else {
++m_Location.m_nLineOffset; ++m_Location.m_nLineOffset;
} }
return c; return c;
} }
int Peek() {
assert(!m_iStr.eof());
return m_iStr.peek();
}
bool EOS() { int Peek() {
m_iStr.peek(); // apparently eof flag isn't set until a character read is attempted. whatever. assert(!m_iStr.eof());
return m_iStr.eof(); return m_iStr.peek();
} }
const Location& GetLocation() const { return m_Location; } bool EOS() {
m_iStr.peek(); // apparently eof flag isn't set until a character read is attempted. whatever.
return m_iStr.eof();
}
Location const& GetLocation() const { return m_Location; }
}; };
////////////////////// class Reader::TokenStream {
// Reader::TokenStream Tokens const& m_Tokens;
Tokens::const_iterator m_itCurrent;
class Reader::TokenStream
{
const Tokens& m_Tokens;
Tokens::const_iterator m_itCurrent;
public: public:
TokenStream(const Tokens& tokens) TokenStream(Tokens const& tokens) : m_Tokens(tokens), m_itCurrent(tokens.begin())
: m_Tokens(tokens), m_itCurrent(tokens.begin()) { }
{ }
const Token& Peek() { Token const& Peek() {
assert(!EOS()); assert(!EOS());
return *m_itCurrent; return *m_itCurrent;
} }
const Token& Get() { Token const& Get() {
assert(!EOS()); assert(!EOS());
return *m_itCurrent++; return *m_itCurrent++;
} }
bool EOS() const { bool EOS() const { return m_itCurrent == m_Tokens.end(); }
return m_itCurrent == m_Tokens.end();
}
}; };
/////////////////// void Reader::Read(Object& object, std::istream& istr) { Read_i(object, istr); }
// Reader (finally) void Reader::Read(Array& array, std::istream& istr) { Read_i(array, istr); }
void Reader::Read(Object& object, std::istream& istr) { Read_i(object, istr); } void Reader::Read(String& string, std::istream& istr) { Read_i(string, istr); }
void Reader::Read(Array& array, std::istream& istr) { Read_i(array, istr); } void Reader::Read(Integer& number, std::istream& istr) { Read_i(number, istr); }
void Reader::Read(String& string, std::istream& istr) { Read_i(string, istr); } void Reader::Read(Double& number, std::istream& istr) { Read_i(number, istr); }
void Reader::Read(Number& number, std::istream& istr) { Read_i(number, istr); } void Reader::Read(Boolean& boolean, std::istream& istr) { Read_i(boolean, istr); }
void Reader::Read(Boolean& boolean, std::istream& istr) { Read_i(boolean, istr); } void Reader::Read(Null& null, std::istream& istr) { Read_i(null, istr); }
void Reader::Read(Null& null, std::istream& istr) { Read_i(null, istr); } void Reader::Read(UnknownElement& unknown, std::istream& istr) { Read_i(unknown, istr); }
void Reader::Read(UnknownElement& unknown, std::istream& istr) { Read_i(unknown, istr); }
template <typename ElementTypeT> template <typename ElementTypeT>
void Reader::Read_i(ElementTypeT& element, std::istream& istr) void Reader::Read_i(ElementTypeT& element, std::istream& istr) {
{ Reader reader;
Reader reader;
Tokens tokens; Tokens tokens;
InputStream inputStream(istr); InputStream inputStream(istr);
reader.Scan(tokens, inputStream); reader.Scan(tokens, inputStream);
TokenStream tokenStream(tokens); TokenStream tokenStream(tokens);
element = reader.Parse(tokenStream); element = reader.Parse(tokenStream);
if (!tokenStream.EOS()) if (!tokenStream.EOS()) {
{ Token const& token = tokenStream.Peek();
const Token& token = tokenStream.Peek(); throw ParseException("Expected End of token stream; found " + token.sValue, token.locBegin, token.locEnd);
throw ParseException("Expected End of token stream; found " + token.sValue, token.locBegin, token.locEnd); }
}
} }
void Reader::Scan(Tokens& tokens, InputStream& inputStream) void Reader::Scan(Tokens& tokens, InputStream& inputStream) {
{ while (EatWhiteSpace(inputStream), !inputStream.EOS()) {
while (EatWhiteSpace(inputStream), !inputStream.EOS()) // if all goes well, we'll create a token each pass
{ Token token;
// if all goes well, we'll create a token each pass token.locBegin = inputStream.GetLocation();
Token token;
token.locBegin = inputStream.GetLocation();
// gives us null-terminated string // gives us null-terminated string
std::string sChar; std::string sChar;
sChar.push_back(inputStream.Peek()); sChar.push_back(inputStream.Peek());
switch (sChar[0]) switch (sChar[0]) {
{ case '{':
case '{': token.sValue = sChar[0];
token.sValue = sChar[0]; MatchExpectedString(sChar, inputStream);
MatchExpectedString(sChar, inputStream); token.nType = Token::TOKEN_OBJECT_BEGIN;
token.nType = Token::TOKEN_OBJECT_BEGIN; break;
break;
case '}': case '}':
token.sValue = sChar[0]; token.sValue = sChar[0];
MatchExpectedString(sChar, inputStream); MatchExpectedString(sChar, inputStream);
token.nType = Token::TOKEN_OBJECT_END; token.nType = Token::TOKEN_OBJECT_END;
break; break;
case '[': case '[':
token.sValue = sChar[0]; token.sValue = sChar[0];
MatchExpectedString(sChar, inputStream); MatchExpectedString(sChar, inputStream);
token.nType = Token::TOKEN_ARRAY_BEGIN; token.nType = Token::TOKEN_ARRAY_BEGIN;
break; break;
case ']': case ']':
token.sValue = sChar[0]; token.sValue = sChar[0];
MatchExpectedString(sChar, inputStream); MatchExpectedString(sChar, inputStream);
token.nType = Token::TOKEN_ARRAY_END; token.nType = Token::TOKEN_ARRAY_END;
break; break;
case ',': case ',':
token.sValue = sChar[0]; token.sValue = sChar[0];
MatchExpectedString(sChar, inputStream); MatchExpectedString(sChar, inputStream);
token.nType = Token::TOKEN_NEXT_ELEMENT; token.nType = Token::TOKEN_NEXT_ELEMENT;
break; break;
case ':': case ':':
token.sValue = sChar[0]; token.sValue = sChar[0];
MatchExpectedString(sChar, inputStream); MatchExpectedString(sChar, inputStream);
token.nType = Token::TOKEN_MEMBER_ASSIGN; token.nType = Token::TOKEN_MEMBER_ASSIGN;
break; break;
case '"': case '"':
MatchString(token.sValue, inputStream); MatchString(token.sValue, inputStream);
token.nType = Token::TOKEN_STRING; token.nType = Token::TOKEN_STRING;
break; break;
case '-': case '-':
case '0': case '0':
case '1': case '1':
case '2': case '2':
case '3': case '3':
case '4': case '4':
case '5': case '5':
case '6': case '6':
case '7': case '7':
case '8': case '8':
case '9': case '9':
MatchNumber(token.sValue, inputStream); MatchNumber(token.sValue, inputStream);
token.nType = Token::TOKEN_NUMBER; token.nType = Token::TOKEN_NUMBER;
break; break;
case 't': case 't':
token.sValue = "true"; token.sValue = "true";
MatchExpectedString(token.sValue, inputStream); MatchExpectedString(token.sValue, inputStream);
token.nType = Token::TOKEN_BOOLEAN; token.nType = Token::TOKEN_BOOLEAN;
break; break;
case 'f': case 'f':
token.sValue = "false"; token.sValue = "false";
MatchExpectedString(token.sValue, inputStream); MatchExpectedString(token.sValue, inputStream);
token.nType = Token::TOKEN_BOOLEAN; token.nType = Token::TOKEN_BOOLEAN;
break; break;
case 'n': case 'n':
token.sValue = "null"; token.sValue = "null";
MatchExpectedString(token.sValue, inputStream); MatchExpectedString(token.sValue, inputStream);
token.nType = Token::TOKEN_NULL; token.nType = Token::TOKEN_NULL;
break; break;
default: default:
throw ScanException("Unexpected character in stream: " + sChar, inputStream.GetLocation()); throw ScanException("Unexpected character in stream: " + sChar, inputStream.GetLocation());
} }
token.locEnd = inputStream.GetLocation(); token.locEnd = inputStream.GetLocation();
tokens.push_back(token); tokens.push_back(token);
} }
} }
void Reader::EatWhiteSpace(InputStream& inputStream) void Reader::EatWhiteSpace(InputStream& inputStream) {
{ while (!inputStream.EOS() && ::isspace(inputStream.Peek()))
while (!inputStream.EOS() && ::isspace(inputStream.Peek())) inputStream.Get();
inputStream.Get();
} }
void Reader::MatchExpectedString(const std::string& sExpected, InputStream& inputStream) void Reader::MatchExpectedString(std::string const& sExpected, InputStream& inputStream) {
{ std::string::const_iterator it(sExpected.begin()), itEnd(sExpected.end());
std::string::const_iterator it(sExpected.begin()), for ( ; it != itEnd; ++it) {
itEnd(sExpected.end()); if (inputStream.EOS() || // did we reach the end before finding what we're looking for...
for ( ; it != itEnd; ++it) { inputStream.Get() != *it) // ...or did we find something different?
if (inputStream.EOS() || // did we reach the end before finding what we're looking for... {
inputStream.Get() != *it) // ...or did we find something different? throw ScanException("Expected string: " + sExpected, inputStream.GetLocation());
{ }
throw ScanException("Expected string: " + sExpected, inputStream.GetLocation()); }
}
}
// all's well if we made it here, return quietly
} }
void Reader::MatchString(std::string& string, InputStream& inputStream) {
MatchExpectedString("\"", inputStream);
void Reader::MatchString(std::string& string, InputStream& inputStream) while (!inputStream.EOS() && inputStream.Peek() != '"') {
{ char c = inputStream.Get();
MatchExpectedString("\"", inputStream);
while (inputStream.EOS() == false && // escape?
inputStream.Peek() != '"') if (c == '\\' && !inputStream.EOS()) { // shouldn't have reached the end yet
{ c = inputStream.Get();
char 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': // TODO: what do we do with this?
default:
throw ScanException("Unrecognized escape sequence found in string: \\" + c, inputStream.GetLocation());
}
}
else {
string.push_back(c);
}
}
// escape? // eat the last '"' that we hopefully just peeked
if (c == '\\' && MatchExpectedString("\"", inputStream);
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': // TODO: what do we do with this?
default:
throw ScanException("Unrecognized escape sequence found in string: \\" + c, inputStream.GetLocation());
}
}
else {
string.push_back(c);
}
}
// eat the last '"' that we just peeked
MatchExpectedString("\"", inputStream);
} }
void Reader::MatchNumber(std::string& sNumber, InputStream& inputStream) {
const char sNumericChars[] = "0123456789.eE-+";
std::set<char> numericChars;
numericChars.insert(sNumericChars, sNumericChars + sizeof(sNumericChars));
void Reader::MatchNumber(std::string& sNumber, InputStream& inputStream) while (!inputStream.EOS()&& numericChars.count(inputStream.Peek()))
{ sNumber.push_back(inputStream.Get());
const char sNumericChars[] = "0123456789.eE-+";
std::set<char> numericChars;
numericChars.insert(sNumericChars, sNumericChars + sizeof(sNumericChars));
while (inputStream.EOS() == false &&
numericChars.find(inputStream.Peek()) != numericChars.end())
{
sNumber.push_back(inputStream.Get());
}
} }
UnknownElement Reader::Parse(Reader::TokenStream& tokenStream) {
if (tokenStream.EOS())
throw ParseException("Unexpected end of token stream", Location(), Location()); // nowhere to point to
UnknownElement Reader::Parse(Reader::TokenStream& tokenStream) Token const& token = tokenStream.Peek();
{ switch (token.nType) {
if (tokenStream.EOS()) case Token::TOKEN_OBJECT_BEGIN: return ParseObject(tokenStream);
throw ParseException("Unexpected end of token stream", Location(), Location()); // nowhere to point to case Token::TOKEN_ARRAY_BEGIN: return ParseArray(tokenStream);
case Token::TOKEN_STRING: return ParseString(tokenStream);
Token const& token = tokenStream.Peek(); case Token::TOKEN_NUMBER: return ParseNumber(tokenStream);
switch (token.nType) { case Token::TOKEN_BOOLEAN: return ParseBoolean(tokenStream);
case Token::TOKEN_OBJECT_BEGIN: return ParseObject(tokenStream); case Token::TOKEN_NULL: return ParseNull(tokenStream);
case Token::TOKEN_ARRAY_BEGIN: return ParseArray(tokenStream); default:
case Token::TOKEN_STRING: return ParseString(tokenStream); throw ParseException("Unexpected token: " + token.sValue, token.locBegin, token.locEnd);
case Token::TOKEN_NUMBER: return ParseNumber(tokenStream); }
case Token::TOKEN_BOOLEAN: return ParseBoolean(tokenStream);
case Token::TOKEN_NULL: return ParseNull(tokenStream);
default:
throw ParseException("Unexpected token: " + token.sValue, token.locBegin, token.locEnd);
}
} }
Object Reader::ParseObject(Reader::TokenStream& tokenStream) UnknownElement Reader::ParseObject(Reader::TokenStream& tokenStream) {
{ MatchExpectedToken(Token::TOKEN_OBJECT_BEGIN, tokenStream);
MatchExpectedToken(Token::TOKEN_OBJECT_BEGIN, tokenStream);
Object object; Object object;
while (!tokenStream.EOS() && tokenStream.Peek().nType != Token::TOKEN_OBJECT_END) while (!tokenStream.EOS() && tokenStream.Peek().nType != Token::TOKEN_OBJECT_END) {
{ // first the member name. save the token in case we have to throw an exception
// first the member name. save the token in case we have to throw an exception Token const& tokenName = tokenStream.Peek();
const Token& tokenName = tokenStream.Peek(); std::string const& name = MatchExpectedToken(Token::TOKEN_STRING, tokenStream);
std::string const& name = MatchExpectedToken(Token::TOKEN_STRING, tokenStream);
if (object.count(name)) if (object.count(name))
throw ParseException("Duplicate object member token: " + name, tokenName.locBegin, tokenName.locEnd); throw ParseException("Duplicate object member token: " + name, tokenName.locBegin, tokenName.locEnd);
// ...then the key/value separator... // ...then the key/value separator...
MatchExpectedToken(Token::TOKEN_MEMBER_ASSIGN, tokenStream); MatchExpectedToken(Token::TOKEN_MEMBER_ASSIGN, tokenStream);
// ...then the value itself (can be anything). // ...then the value itself (can be anything).
object[name] = Parse(tokenStream); object[name] = Parse(tokenStream);
if (!tokenStream.EOS() && tokenStream.Peek().nType != Token::TOKEN_OBJECT_END) if (!tokenStream.EOS() && tokenStream.Peek().nType != Token::TOKEN_OBJECT_END)
MatchExpectedToken(Token::TOKEN_NEXT_ELEMENT, tokenStream); MatchExpectedToken(Token::TOKEN_NEXT_ELEMENT, tokenStream);
} }
MatchExpectedToken(Token::TOKEN_OBJECT_END, tokenStream); MatchExpectedToken(Token::TOKEN_OBJECT_END, tokenStream);
return object; return object;
} }
Array Reader::ParseArray(Reader::TokenStream& tokenStream) UnknownElement Reader::ParseArray(Reader::TokenStream& tokenStream) {
{ MatchExpectedToken(Token::TOKEN_ARRAY_BEGIN, tokenStream);
MatchExpectedToken(Token::TOKEN_ARRAY_BEGIN, tokenStream);
Array array; Array array;
while (!tokenStream.EOS() && tokenStream.Peek().nType != Token::TOKEN_ARRAY_END) while (!tokenStream.EOS() && tokenStream.Peek().nType != Token::TOKEN_ARRAY_END)
{ {
array.push_back(Parse(tokenStream)); array.push_back(Parse(tokenStream));
if (!tokenStream.EOS() && tokenStream.Peek().nType != Token::TOKEN_ARRAY_END) if (!tokenStream.EOS() && tokenStream.Peek().nType != Token::TOKEN_ARRAY_END)
MatchExpectedToken(Token::TOKEN_NEXT_ELEMENT, tokenStream); MatchExpectedToken(Token::TOKEN_NEXT_ELEMENT, tokenStream);
} }
MatchExpectedToken(Token::TOKEN_ARRAY_END, tokenStream); MatchExpectedToken(Token::TOKEN_ARRAY_END, tokenStream);
return array; return array;
} }
String Reader::ParseString(Reader::TokenStream& tokenStream) UnknownElement Reader::ParseString(Reader::TokenStream& tokenStream) {
{ return MatchExpectedToken(Token::TOKEN_STRING, tokenStream);
return MatchExpectedToken(Token::TOKEN_STRING, tokenStream);
} }
Number Reader::ParseNumber(Reader::TokenStream& tokenStream) UnknownElement Reader::ParseNumber(Reader::TokenStream& tokenStream) {
{ Token const& currentToken = tokenStream.Peek(); // might need this later for throwing exception
const Token& currentToken = tokenStream.Peek(); // might need this later for throwing exception std::string const& sValue = MatchExpectedToken(Token::TOKEN_NUMBER, tokenStream);
const std::string& sValue = MatchExpectedToken(Token::TOKEN_NUMBER, tokenStream);
std::istringstream iStr(sValue); // First try to parse it as an int
double dValue; std::istringstream iStr(sValue);
iStr >> dValue; int64_t iValue;
iStr >> iValue;
// did we consume all characters in the token? // If the entire token was consumed then it's not a double
if (!iStr.eof()) if (iStr.eof())
throw ParseException("Unexpected character in NUMBER token: " + iStr.peek(), currentToken.locBegin, currentToken.locEnd); return iValue;
return dValue; // Try again as a double
iStr.seekg(0, std::ios::beg);
double dValue;
iStr >> dValue;
// If there's still stuff left in the token then it's malformed
if (!iStr.eof())
throw ParseException("Unexpected character in NUMBER token: " + iStr.peek(), currentToken.locBegin, currentToken.locEnd);
return dValue;
} }
Boolean Reader::ParseBoolean(Reader::TokenStream& tokenStream) UnknownElement Reader::ParseBoolean(Reader::TokenStream& tokenStream) {
{ return MatchExpectedToken(Token::TOKEN_BOOLEAN, tokenStream) == "true";
return MatchExpectedToken(Token::TOKEN_BOOLEAN, tokenStream) == "true";
} }
Null Reader::ParseNull(Reader::TokenStream& tokenStream) UnknownElement Reader::ParseNull(Reader::TokenStream& tokenStream) {
{ MatchExpectedToken(Token::TOKEN_NULL, tokenStream);
MatchExpectedToken(Token::TOKEN_NULL, tokenStream); return Null();
return Null();
} }
std::string const& Reader::MatchExpectedToken(Token::Type nExpected, Reader::TokenStream& tokenStream) {
if (tokenStream.EOS())
throw ParseException("Unexpected End of token stream", Location(), Location()); // nowhere to point to
const std::string& Reader::MatchExpectedToken(Token::Type nExpected, Reader::TokenStream& tokenStream) Token const& token = tokenStream.Get();
{ if (token.nType != nExpected)
if (tokenStream.EOS()) throw ParseException("Unexpected token: " + token.sValue, token.locBegin, token.locEnd);
{
throw ParseException("Unexpected End of token stream", Location(), Location()); // nowhere to point to
}
const Token& token = tokenStream.Get(); return token.sValue;
if (token.nType != nExpected)
{
throw ParseException("Unexpected token: " + token.sValue, token.locBegin, token.locEnd);
}
return token.sValue;
} }
} // End namespace }

View file

@ -9,6 +9,7 @@ Author: Terry Caton
#include "libaegisub/cajun/writer.h" #include "libaegisub/cajun/writer.h"
#ifndef LAGI_PRE #ifndef LAGI_PRE
#include <cmath>
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
#endif #endif
@ -24,110 +25,107 @@ TODO:
namespace json namespace json
{ {
Writer::Writer(std::ostream& ostr) : Writer::Writer(std::ostream& ostr)
m_ostr(ostr), : m_ostr(ostr)
m_nTabDepth(0) , tab_depth(0)
{}
void Writer::Write(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(*it);
if (++it != itend)
m_ostr << ',';
m_ostr << std::endl;
}
--m_nTabDepth;
m_ostr << std::string(m_nTabDepth, '\t') << ']';
}
} }
void Writer::Write(const Object& object) void Writer::Write(Array const& array) {
{ if (array.empty())
if (object.empty()) m_ostr << "[]";
m_ostr << "{}"; else {
else m_ostr << '[' << std::endl;
{ ++tab_depth;
m_ostr << '{' << std::endl;
++m_nTabDepth;
Object::const_iterator it(object.begin()), itend(object.end()); Array::const_iterator it(array.begin()), itend(array.end());
while (it != itend) { while (it != itend) {
m_ostr << std::string(m_nTabDepth, '\t') << '"' << it->first << "\" : "; m_ostr << std::string(tab_depth, '\t');
Write(it->second);
if (++it != itend) Write(*it);
m_ostr << ',';
m_ostr << std::endl;
}
--m_nTabDepth; if (++it != itend)
m_ostr << std::string(m_nTabDepth, '\t') << '}'; m_ostr << ',';
} m_ostr << std::endl;
}
--tab_depth;
m_ostr << std::string(tab_depth, '\t') << ']';
}
} }
void Writer::Write(const Number& numberElement) void Writer::Write(Object const& object) {
{ if (object.empty())
m_ostr << std::setprecision(20) << numberElement; m_ostr << "{}";
else {
m_ostr << '{' << std::endl;
++tab_depth;
Object::const_iterator it(object.begin()), itend(object.end());
while (it != itend) {
m_ostr << std::string(tab_depth, '\t') << '"' << it->first << "\" : ";
Write(it->second);
if (++it != itend)
m_ostr << ',';
m_ostr << std::endl;
}
--tab_depth;
m_ostr << std::string(tab_depth, '\t') << '}';
}
} }
void Writer::Write(const Boolean& booleanElement) void Writer::Write(Double const& numberElement) {
{ m_ostr << std::setprecision(20) << numberElement;
m_ostr << (booleanElement ? "true" : "false");
double unused;
if (!std::modf(numberElement, &unused))
m_ostr << ".0";
} }
void Writer::Write(const String& stringElement) void Writer::Write(Integer const& numberElement) {
{ m_ostr << numberElement;
m_ostr << '"';
const std::string& s = stringElement;
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 << ""; break; ??
default: m_ostr << *it; break;
}
}
m_ostr << '"';
} }
void Writer::Write(const Null& ) void Writer::Write(Boolean const& booleanElement) {
{ m_ostr << (booleanElement ? "true" : "false");
m_ostr << "null";
} }
void Writer::Write(const UnknownElement& unknown) void Writer::Write(String const& stringElement) {
{ m_ostr << '"';
unknown.Accept(*this);
std::string::const_iterator it(stringElement.begin()), itend(stringElement.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;
default: m_ostr << *it; break;
}
}
m_ostr << '"';
} }
void Writer::Visit(const Array& array) { Write(array); } void Writer::Write(Null const&) {
void Writer::Visit(const Object& object) { Write(object); } m_ostr << "null";
void Writer::Visit(const Number& number) { Write(number); } }
void Writer::Visit(const String& string) { Write(string); }
void Writer::Visit(const Boolean& boolean) { Write(boolean); } void Writer::Write(UnknownElement const& unknown) {
void Writer::Visit(const Null& null) { Write(null); } unknown.Accept(*this);
}
void Writer::Visit(Array const& array) { Write(array); }
void Writer::Visit(Object const& object) { Write(object); }
void Writer::Visit(Integer const& integer) { Write(integer); }
void Writer::Visit(Double const& dbl) { Write(dbl); }
void Writer::Visit(String const& string) { Write(string); }
void Writer::Visit(Boolean const& boolean) { Write(boolean); }
void Writer::Visit(Null const& null) { Write(null); }
} // end namespace } // end namespace

View file

@ -21,10 +21,11 @@
#include "libaegisub/option.h" #include "libaegisub/option.h"
#ifndef LAGI_PRE #ifndef LAGI_PRE
#include <cassert>
#include <fstream> #include <fstream>
#include <sstream>
#include <map> #include <map>
#include <memory> #include <memory>
#include <sstream>
#endif #endif
#include "libaegisub/cajun/reader.h" #include "libaegisub/cajun/reader.h"
@ -38,6 +39,39 @@
#include "option_visit.h" #include "option_visit.h"
namespace {
/// @brief Write an option to a json object
/// @param[out] obj Parent object
/// @param[in] path Path option should be stored in.
/// @param[in] value Value to write.
void put_option(json::Object &obj, const std::string &path, const json::UnknownElement &value) {
std::string::size_type pos = path.find('/');
// Not having a '/' denotes it is a leaf.
if (pos == std::string::npos) {
assert(obj.find(path) == obj.end());
obj[path] = value;
}
else {
put_option(
obj[path.substr(0, pos)],
path.substr(pos + 1),
value);
}
}
template<class T>
void put_array(json::Object &obj, const std::string &path, const char *element_key, std::vector<T> const& value) {
json::Array array;
for (typename std::vector<T>::const_iterator it = value.begin(); it != value.end(); ++it) {
json::Object obj;
obj[element_key] = *it;
array.push_back(obj);
}
put_option(obj, path, array);
}
}
namespace agi { namespace agi {
Options::Options(const std::string &file, const std::string& default_config, const OptionSetting setting) Options::Options(const std::string &file, const std::string& default_config, const OptionSetting setting)
@ -108,93 +142,44 @@ void Options::Flush() {
for (OptionValueMap::const_iterator i = values.begin(); i != values.end(); ++i) { for (OptionValueMap::const_iterator i = values.begin(); i != values.end(); ++i) {
switch (i->second->GetType()) { switch (i->second->GetType()) {
case OptionValue::Type_String: case OptionValue::Type_String:
PutOption(obj_out, i->first, i->second->GetString()); put_option(obj_out, i->first, i->second->GetString());
break; break;
case OptionValue::Type_Int: case OptionValue::Type_Int:
PutOption(obj_out, i->first, (double)i->second->GetInt()); put_option(obj_out, i->first, i->second->GetInt());
break; break;
case OptionValue::Type_Double: case OptionValue::Type_Double:
PutOption(obj_out, i->first, i->second->GetDouble()); put_option(obj_out, i->first, i->second->GetDouble());
break; break;
case OptionValue::Type_Colour: case OptionValue::Type_Colour:
PutOption(obj_out, i->first, i->second->GetColour()); put_option(obj_out, i->first, i->second->GetColour());
break; break;
case OptionValue::Type_Bool: case OptionValue::Type_Bool:
PutOption(obj_out, i->first, i->second->GetBool()); put_option(obj_out, i->first, i->second->GetBool());
break; break;
case OptionValue::Type_List_String: { case OptionValue::Type_List_String:
std::vector<std::string> const& array_string(i->second->GetListString()); put_array(obj_out, i->first, "string", i->second->GetListString());
break;
json::Array array; case OptionValue::Type_List_Int:
put_array(obj_out, i->first, "int", i->second->GetListInt());
break;
for (std::vector<std::string>::const_iterator i_str = array_string.begin(); i_str != array_string.end(); ++i_str) { case OptionValue::Type_List_Double:
json::Object obj; put_array(obj_out, i->first, "double", i->second->GetListDouble());
obj["string"] = *i_str; break;
array.push_back(obj);
}
PutOption(obj_out, i->first, array); case OptionValue::Type_List_Colour:
} put_array(obj_out, i->first, "colour", i->second->GetListColour());
break; break;
case OptionValue::Type_List_Int: { case OptionValue::Type_List_Bool:
std::vector<int64_t> const& array_int(i->second->GetListInt()); put_array(obj_out, i->first, "bool", i->second->GetListBool());
break;
json::Array array;
for (std::vector<int64_t>::const_iterator i_int = array_int.begin(); i_int != array_int.end(); ++i_int) {
json::Object obj;
obj["int"] = (double)*i_int;
array.push_back(obj);
}
PutOption(obj_out, i->first, array);
}
break;
case OptionValue::Type_List_Double: {
std::vector<double> const& array_double(i->second->GetListDouble());
json::Array array;
for (std::vector<double>::const_iterator i_double = array_double.begin(); i_double != array_double.end(); ++i_double) {
json::Object obj;
obj["double"] = *i_double;
array.push_back(obj);
}
PutOption(obj_out, i->first, array);
}
break;
case OptionValue::Type_List_Colour: {
std::vector<Colour> const& array_colour(i->second->GetListColour());
json::Array array;
for (std::vector<Colour>::const_iterator i_colour = array_colour.begin(); i_colour != array_colour.end(); ++i_colour) {
json::Object obj;
obj["colour"] = *i_colour;
array.push_back(obj);
}
PutOption(obj_out, i->first, array);
}
break;
case OptionValue::Type_List_Bool: {
std::vector<bool> const& array_bool(i->second->GetListBool());
json::Array array;
for (std::vector<bool>::const_iterator i_bool = array_bool.begin(); i_bool != array_bool.end(); ++i_bool) {
json::Object obj;
obj["bool"] = *i_bool;
array.push_back(obj);
}
PutOption(obj_out, i->first, array);
}
break;
} }
} }
@ -202,19 +187,4 @@ void Options::Flush() {
json::Writer::Write(obj_out, file.Get()); json::Writer::Write(obj_out, file.Get());
} }
void Options::PutOption(json::Object &obj, const std::string &path, const json::UnknownElement &value) {
std::string::size_type pos = path.find('/');
// Not having a '/' denotes it is a leaf.
if (pos == std::string::npos) {
assert(obj.find(path) == obj.end());
obj[path] = value;
}
else {
PutOption(
obj[path.substr(0, pos)],
path.substr(pos + 1),
value);
}
}
} // namespace agi } // namespace agi

View file

@ -22,6 +22,7 @@
#include "option_visit.h" #include "option_visit.h"
#ifndef LAGI_PRE #ifndef LAGI_PRE
#include <cassert>
#include <cmath> #include <cmath>
#include <memory> #include <memory>
#endif #endif
@ -59,16 +60,6 @@ void ConfigVisitor::Visit(const json::Object& object) {
} }
} }
template<class T>
static inline T convert_unknown(json::UnknownElement const& ue) {
return ue;
}
template<>
inline int64_t convert_unknown(json::UnknownElement const& ue) {
return (int64_t)(double)ue;
}
template<class OptionValueType, class ValueType> template<class OptionValueType, class ValueType>
OptionValue *ConfigVisitor::ReadArray(json::Array const& src, std::string const& array_type, void (OptionValueType::*set_list)(const std::vector<ValueType>&)) { OptionValue *ConfigVisitor::ReadArray(json::Array const& src, std::string const& array_type, void (OptionValueType::*set_list)(const std::vector<ValueType>&)) {
std::vector<ValueType> arr; std::vector<ValueType> arr;
@ -86,7 +77,7 @@ OptionValue *ConfigVisitor::ReadArray(json::Array const& src, std::string const&
return 0; return 0;
} }
arr.push_back(convert_unknown<ValueType>(obj.begin()->second)); arr.push_back(obj.begin()->second);
} }
OptionValueType *ret = new OptionValueType(name); OptionValueType *ret = new OptionValueType(name);
@ -122,13 +113,12 @@ void ConfigVisitor::Visit(const json::Array& array) {
Error<OptionJsonValueArray>("Array type not handled"); Error<OptionJsonValueArray>("Array type not handled");
} }
void ConfigVisitor::Visit(const json::Integer& number) {
AddOptionValue(new OptionValueInt(name, number));
}
void ConfigVisitor::Visit(const json::Number& number) { void ConfigVisitor::Visit(const json::Double& number) {
if (int64_t(number) == number) { AddOptionValue(new OptionValueDouble(name, number));
AddOptionValue(new OptionValueInt(name, int64_t(number)));
} else {
AddOptionValue(new OptionValueDouble(name, number));
}
} }
void ConfigVisitor::Visit(const json::String& string) { void ConfigVisitor::Visit(const json::String& string) {

View file

@ -23,6 +23,10 @@
#include "libaegisub/cajun/elements.h" #include "libaegisub/cajun/elements.h"
#include "libaegisub/cajun/visitor.h" #include "libaegisub/cajun/visitor.h"
#ifndef LAGI_PRE
#include <vector>
#endif
namespace agi { namespace agi {
DEFINE_BASE_EXCEPTION_NOINNER(OptionJsonValueError, Exception) DEFINE_BASE_EXCEPTION_NOINNER(OptionJsonValueError, Exception)
@ -47,7 +51,8 @@ public:
void Visit(const json::Array& array); void Visit(const json::Array& array);
void Visit(const json::Object& object); void Visit(const json::Object& object);
void Visit(const json::Number& number); void Visit(const json::Integer& number);
void Visit(const json::Double& number);
void Visit(const json::String& string); void Visit(const json::String& string);
void Visit(const json::Boolean& boolean); void Visit(const json::Boolean& boolean);
void Visit(const json::Null& null); void Visit(const json::Null& null);

View file

@ -14,6 +14,8 @@ Author: Terry Caton
#include <map> #include <map>
#include <string> #include <string>
#include <stdexcept> #include <stdexcept>
#include "stdint.h"
#endif #endif
namespace json namespace json
@ -21,14 +23,15 @@ namespace json
///////////////////////////////////////////////// /////////////////////////////////////////////////
// forward declarations (more info further below) // forward declarations (more info further below)
class Visitor; struct Visitor;
class ConstVisitor; struct ConstVisitor;
class UnknownElement; class UnknownElement;
template <typename ValueTypeT> template <typename ValueTypeT>
class TrivialType_T; class TrivialType_T;
typedef double Number; typedef int64_t Integer;
typedef double Double;
typedef bool Boolean; typedef bool Boolean;
typedef std::string String; typedef std::string String;
typedef std::deque<UnknownElement> Array; typedef std::deque<UnknownElement> Array;
@ -69,6 +72,7 @@ public:
UnknownElement(double number); UnknownElement(double number);
UnknownElement(int number); UnknownElement(int number);
UnknownElement(long number); UnknownElement(long number);
UnknownElement(int64_t number);
UnknownElement(bool boolean); UnknownElement(bool boolean);
UnknownElement(const char *string); UnknownElement(const char *string);
UnknownElement(const String& string); UnknownElement(const String& string);
@ -81,13 +85,15 @@ public:
// implicit cast to actual element type. throws on failure // implicit cast to actual element type. throws on failure
operator Object const&() const; operator Object const&() const;
operator Array const&() const; operator Array const&() const;
operator Number const&() const; operator Integer const&() const;
operator Double const&() const;
operator Boolean const&() const; operator Boolean const&() const;
operator String const&() const; operator String const&() const;
operator Null const&() const; operator Null const&() const;
operator Object&(); operator Object&();
operator Array&(); operator Array&();
operator Number&(); operator Integer&();
operator Double&();
operator Boolean&(); operator Boolean&();
operator String&(); operator String&();
operator Null&(); operator Null&();

View file

@ -18,107 +18,103 @@ Author: Terry Caton
namespace json namespace json
{ {
class Reader class Reader {
{
public: public:
// this structure will be reported in one of the exceptions defined below // this structure will be reported in one of the exceptions defined below
struct Location struct Location {
{ Location() : m_nLine(0), m_nLineOffset(0), m_nDocOffset(0) { }
Location();
unsigned int m_nLine; // document line, zero-indexed unsigned int m_nLine; // document line, zero-indexed
unsigned int m_nLineOffset; // character offset from beginning of 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 unsigned int m_nDocOffset; // character offset from entire document, zero indexed
}; };
// thrown during the first phase of reading. generally catches low-level problems such // thrown during the first phase of reading. generally catches low-level
// as errant characters or corrupt/incomplete documents // problems such as errant characters or corrupt/incomplete documents
class ScanException : public Exception class ScanException : public Exception {
{ public:
public: ScanException(std::string const& sMessage, Reader::Location const& locError)
ScanException(const std::string& sMessage, const Reader::Location& locError) : : Exception(sMessage)
Exception(sMessage), , m_locError(locError)
m_locError(locError) {} { }
Reader::Location m_locError; Reader::Location m_locError;
}; };
// thrown during the second phase of reading. generally catches higher-level problems such // thrown during the second phase of reading. generally catches
// as missing commas or brackets // higher-level problems such as missing commas or brackets
class ParseException : public Exception class ParseException : public Exception {
{ public:
public: ParseException(std::string const& sMessage, Reader::Location const& locTokenBegin, Reader::Location const& locTokenEnd)
ParseException(const std::string& sMessage, const Reader::Location& locTokenBegin, const Reader::Location& locTokenEnd) : : Exception(sMessage)
Exception(sMessage), , m_locTokenBegin(locTokenBegin)
m_locTokenBegin(locTokenBegin), , m_locTokenEnd(locTokenEnd)
m_locTokenEnd(locTokenEnd) {} { }
Reader::Location m_locTokenBegin; Reader::Location m_locTokenBegin;
Reader::Location m_locTokenEnd; 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(Integer& number, std::istream& istr);
static void Read(Double& number, std::istream& istr);
static void Read(Boolean& boolean, std::istream& istr);
static void Read(Null& null, std::istream& istr);
// if you know what the document looks like, call one of these... // ...otherwise, if you don't know, call this & visit it
static void Read(Object& object, std::istream& istr); static void Read(UnknownElement& elementRoot, 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: private:
struct Token struct Token {
{ enum Type {
enum Type TOKEN_OBJECT_BEGIN, // {
{ TOKEN_OBJECT_END, // }
TOKEN_OBJECT_BEGIN, // { TOKEN_ARRAY_BEGIN, // [
TOKEN_OBJECT_END, // } TOKEN_ARRAY_END, // ]
TOKEN_ARRAY_BEGIN, // [ TOKEN_NEXT_ELEMENT, // ,
TOKEN_ARRAY_END, // ] TOKEN_MEMBER_ASSIGN, // :
TOKEN_NEXT_ELEMENT, // , TOKEN_STRING, // "xxx"
TOKEN_MEMBER_ASSIGN, // : TOKEN_NUMBER, // [+/-]000.000[e[+/-]000]
TOKEN_STRING, // "xxx" TOKEN_BOOLEAN, // true -or- false
TOKEN_NUMBER, // [+/-]000.000[e[+/-]000] TOKEN_NULL // null
TOKEN_BOOLEAN, // true -or- false };
TOKEN_NULL // null
};
Type nType; Type nType;
std::string sValue; std::string sValue;
// for malformed file debugging // for malformed file debugging
Reader::Location locBegin; Reader::Location locBegin;
Reader::Location locEnd; Reader::Location locEnd;
}; };
class InputStream; class InputStream;
class TokenStream; class TokenStream;
typedef std::vector<Token> Tokens; typedef std::vector<Token> Tokens;
template <typename ElementTypeT> template <typename ElementTypeT>
static void Read_i(ElementTypeT& element, std::istream& istr); static void Read_i(ElementTypeT& element, std::istream& istr);
// scanning istream into token sequence // scanning istream into token sequence
void Scan(Tokens& tokens, InputStream& inputStream); void Scan(Tokens& tokens, InputStream& inputStream);
void EatWhiteSpace(InputStream& inputStream); void EatWhiteSpace(InputStream& inputStream);
void MatchString(std::string& sValue, InputStream& inputStream); void MatchString(std::string& sValue, InputStream& inputStream);
void MatchNumber(std::string& sNumber, InputStream& inputStream); void MatchNumber(std::string& sNumber, InputStream& inputStream);
void MatchExpectedString(const std::string& sExpected, InputStream& inputStream); void MatchExpectedString(std::string const& sExpected, InputStream& inputStream);
// parsing token sequence into element structure // parsing token sequence into element structure
UnknownElement Parse(TokenStream& tokenStream); UnknownElement Parse(TokenStream& tokenStream);
Object ParseObject(TokenStream& tokenStream); UnknownElement ParseObject(TokenStream& tokenStream);
Array ParseArray(TokenStream& tokenStream); UnknownElement ParseArray(TokenStream& tokenStream);
String ParseString(TokenStream& tokenStream); UnknownElement ParseString(TokenStream& tokenStream);
Number ParseNumber(TokenStream& tokenStream); UnknownElement ParseNumber(TokenStream& tokenStream);
Boolean ParseBoolean(TokenStream& tokenStream); UnknownElement ParseBoolean(TokenStream& tokenStream);
Null ParseNull(TokenStream& tokenStream); UnknownElement ParseNull(TokenStream& tokenStream);
const std::string& MatchExpectedToken(Token::Type nExpected, TokenStream& tokenStream); std::string const& MatchExpectedToken(Token::Type nExpected, TokenStream& tokenStream);
}; };
} // End namespace }

View file

@ -13,32 +13,28 @@ Author: Terry Caton
namespace json namespace json
{ {
struct Visitor {
virtual ~Visitor() { }
class Visitor virtual void Visit(Array& array) = 0;
{ virtual void Visit(Object& object) = 0;
public: virtual void Visit(Integer& number) = 0;
virtual ~Visitor() {} virtual void Visit(Double& number) = 0;
virtual void Visit(String& string) = 0;
virtual void Visit(Array& array) = 0; virtual void Visit(Boolean& boolean) = 0;
virtual void Visit(Object& object) = 0; virtual void Visit(Null& null) = 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 struct ConstVisitor {
{ virtual ~ConstVisitor() { }
public:
virtual ~ConstVisitor() {}
virtual void Visit(const Array& array) = 0; virtual void Visit(const Array& array) = 0;
virtual void Visit(const Object& object) = 0; virtual void Visit(const Object& object) = 0;
virtual void Visit(const Number& number) = 0; virtual void Visit(const Integer& number) = 0;
virtual void Visit(const String& string) = 0; virtual void Visit(const Double& number) = 0;
virtual void Visit(const Boolean& boolean) = 0; virtual void Visit(const String& string) = 0;
virtual void Visit(const Null& null) = 0; virtual void Visit(const Boolean& boolean) = 0;
virtual void Visit(const Null& null) = 0;
}; };
} // End namespace } // End namespace

View file

@ -14,36 +14,35 @@ Author: Terry Caton
namespace json namespace json
{ {
class Writer : private ConstVisitor class Writer : private ConstVisitor {
{ Writer(std::ostream& ostr);
void Write(const Object& object);
void Write(const Array& array);
void Write(const String& string);
void Write(const Integer& number);
void Write(const Double& number);
void Write(const Boolean& boolean);
void Write(const Null& null);
void Write(const UnknownElement& unknown);
void Visit(const Array& array);
void Visit(const Object& object);
void Visit(const Integer& number);
void Visit(const Double& number);
void Visit(const String& string);
void Visit(const Boolean& boolean);
void Visit(const Null& null);
std::ostream& m_ostr;
int tab_depth;
public: public:
template <typename ElementTypeT> template <typename ElementTypeT>
static void Write(const ElementTypeT& element, std::ostream& ostr) static void Write(const ElementTypeT& element, std::ostream& ostr) {
{ Writer writer(ostr);
Writer writer(ostr); writer.Write(element);
writer.Write(element); ostr.flush(); // all done
ostr.flush(); // all done }
}
private:
Writer(std::ostream& ostr);
void Write(const Object& object);
void Write(const Array& array);
void Write(const String& string);
void Write(const Number& number);
void Write(const Boolean& boolean);
void Write(const Null& null);
void Write(const UnknownElement& unknown);
void Visit(const Array& array);
void Visit(const Object& object);
void Visit(const Number& number);
void Visit(const String& string);
void Visit(const Boolean& boolean);
void Visit(const Null& null);
std::ostream& m_ostr;
int m_nTabDepth;
}; };

View file

@ -78,12 +78,6 @@ private:
/// @param ignore_errors Log invalid entires in the option file and continue rather than throwing an exception /// @param ignore_errors Log invalid entires in the option file and continue rather than throwing an exception
void LoadConfig(std::istream& stream, bool ignore_errors = false); void LoadConfig(std::istream& stream, bool ignore_errors = false);
/// @brief Write an option to file.
/// @param[out] obj Parent object
/// @param[in] path Path option should be stored in.
/// @param[in] value Value to write.
static void PutOption(::json::Object &obj, const std::string &path, const ::json::UnknownElement &value);
public: public:
/// @brief Constructor /// @brief Constructor
/// @param file User config that will be loaded from and written back to. /// @param file User config that will be loaded from and written back to.

View file

@ -40,100 +40,83 @@ Report::Report() {
Platform *p = Platform::GetPlatform(); Platform *p = Platform::GetPlatform();
Aegisub a; Aegisub a;
json::Object general; json::Object& general = root["General"];
general["Signature"] = json::String(p->Signature()); general["Signature"] = p->Signature();
general["Date"] = json::String(p->Date()); general["Date"] = p->Date();
general["Architecture"] = json::String(p->ArchName()); general["Architecture"] = p->ArchName();
general["OS Family"] = json::String(p->OSFamily()); general["OS Family"] = p->OSFamily();
general["OS Name"] = json::String(p->OSName()); general["OS Name"] = p->OSName();
general["Endian"] = json::String(p->Endian()); general["Endian"] = p->Endian();
general["OS Version"] = json::String(p->OSVersion()); general["OS Version"] = p->OSVersion();
general["wx Version"] = json::String(p->wxVersion()); general["wx Version"] = p->wxVersion();
general["Locale"] = json::String(p->Locale()); general["Locale"] = p->Locale();
general["Language"] = json::String(p->Language()); general["Language"] = p->Language();
general["System Language"] = json::String(p->SystemLanguage()); general["System Language"] = p->SystemLanguage();
root["General"] = general;
try { try {
json::Object aegisub; json::Object& aegisub = root["Aegisub"];
aegisub["Last Version"] = json::String(a.GetString("Version/Last Version")); aegisub["Last Version"] = a.GetString("Version/Last Version");
aegisub["Spelling Language"] = json::String(a.GetString("Tool/Spell Checker/Language")); aegisub["Spelling Language"] = a.GetString("Tool/Spell Checker/Language");
aegisub["Thesaurus Language"] = json::String(a.GetString("Tool/Thesaurus/Language")); aegisub["Thesaurus Language"] = a.GetString("Tool/Thesaurus/Language");
aegisub["Audio Player"] = json::String(a.GetString("Audio/Player")); aegisub["Audio Player"] = a.GetString("Audio/Player");
aegisub["Audio Provider"] = json::String(a.GetString("Audio/Provider")); aegisub["Audio Provider"] = a.GetString("Audio/Provider");
aegisub["Video Provider"] = json::String(a.GetString("Video/Provider")); aegisub["Video Provider"] = a.GetString("Video/Provider");
aegisub["Subtitles Provider"] = json::String(a.GetString("Subtitle/Provider")); aegisub["Subtitles Provider"] = a.GetString("Subtitle/Provider");
aegisub["Save Charset"] = json::String(a.GetString("App/Save Charset")); aegisub["Save Charset"] = a.GetString("App/Save Charset");
aegisub["Grid Font Size"] = json::Number(a.GetInt("Grid/Font Size")); aegisub["Grid Font Size"] = a.GetInt("Grid/Font Size");
aegisub["Edit Font Size"] = json::Number(a.GetInt("Subtitle/Edit Box/Font Size")); aegisub["Edit Font Size"] = a.GetInt("Subtitle/Edit Box/Font Size");
aegisub["Spectrum Enabled"] = json::Boolean(a.GetBool("Audio/Spectrum")); aegisub["Spectrum Enabled"] = a.GetBool("Audio/Spectrum");
aegisub["Spectrum Quality"] = json::Number(a.GetInt("Audio/Renderer/Spectrum/Quality")); aegisub["Spectrum Quality"] = a.GetInt("Audio/Renderer/Spectrum/Quality");
aegisub["Call Tips Enabled"] = json::Boolean(a.GetBool("App/Call Tips")); aegisub["Call Tips Enabled"] = a.GetBool("App/Call Tips");
aegisub["Medusa Hotkeys Enabled"] = json::Boolean(a.GetBool("Audio/Medusa Timing Hotkeys")); aegisub["Medusa Hotkeys Enabled"] = a.GetBool("Audio/Medusa Timing Hotkeys");
root["Aegisub"] = aegisub;
} catch(...) { } catch(...) {
root["Aegisub"]["Error"] = json::String("Config file is corrupted"); root["Aegisub"]["Error"] = json::String("Config file is corrupted");
} }
json::Object& hardware = root["Hardware"];
hardware["Memory Size"] = 0;
json::Object& cpu = root["CPU"];
cpu["Id"] = p->CPUId();
cpu["Speed"] = p->CPUSpeed();
cpu["Count"] = p->CPUCount();
cpu["Cores"] = p->CPUCores();
cpu["Features"] = p->CPUFeatures();
cpu["Features2"] = p->CPUFeatures2();
json::Object hardware; json::Object& display = root["Display"];
hardware["Memory Size"] = json::Number(); display["Depth"] = p->DisplayDepth();
root["Hardware"] = hardware; display["Size"] = p->DisplaySize();
display["Pixels Per Inch"] = p->DisplayPPI();
json::Object cpu;
cpu["Id"] = json::String(p->CPUId());
cpu["Speed"] = json::String(p->CPUSpeed());
cpu["Count"] = json::Number(p->CPUCount());
cpu["Cores"] = json::Number(p->CPUCores());
cpu["Features"] = json::String(p->CPUFeatures());
cpu["Features2"] = json::String(p->CPUFeatures2());
root["CPU"] = cpu;
json::Object display;
display["Depth"] = json::Number(p->DisplayDepth());
display["Size"] = json::String(p->DisplaySize());
display["Pixels Per Inch"] = json::String(p->DisplayPPI());
json::Object gl;
gl["Vendor"] = json::String(p->OpenGLVendor());
gl["Renderer"] = json::String(p->OpenGLRenderer());
gl["Version"] = json::String(p->OpenGLVersion());
gl["Extensions"] = json::String(p->OpenGLExt());
display["OpenGL"] = gl;
root["Display"] = display;
json::Object& gl = display["OpenGL"];
gl["Vendor"] = p->OpenGLVendor();
gl["Renderer"] = p->OpenGLRenderer();
gl["Version"] = p->OpenGLVersion();
gl["Extensions"] = p->OpenGLExt();
display["OpenGL"] = gl;
#ifdef __WINDOWS__ #ifdef __WINDOWS__
json::Object windows; json::Object& windows = root["Windows"];
windows["Service Pack"] = json::String(); windows["Service Pack"] = json::String();
windows["Graphics Driver Version"] = json::String(); windows["Graphics Driver Version"] = json::String();
windows["DirectShow Filters"] = json::String(); windows["DirectShow Filters"] = json::String();
windows["AntiVirus Installed"] = json::Boolean(); windows["AntiVirus Installed"] = json::Boolean();
windows["Firewall Installed"] = json::Boolean(); windows["Firewall Installed"] = json::Boolean();
windows["DLL"] = json::String(); windows["DLL"] = json::String();
root["Windows"] = windows;
#endif #endif
#ifdef __UNIX__ #ifdef __UNIX__
json::Object u_nix; json::Object& u_nix = root["Unix"];
u_nix["Desktop Environment"] = json::String(p->DesktopEnvironment()); u_nix["Desktop Environment"] = p->DesktopEnvironment();
u_nix["Libraries"] = json::String(p->UnixLibraries()); u_nix["Libraries"] = p->UnixLibraries();
root["Unix"] = u_nix;
#endif #endif
#ifdef __APPLE__ #ifdef __APPLE__
json::Object osx; json::Object& osx = root["OS X"];
osx["Patch"] = json::String(p->PatchLevel()); osx["Patch"] = p->PatchLevel();
osx["QuickTime Extensions"] = json::String(p->QuickTimeExt()); osx["QuickTime Extensions"] = p->QuickTimeExt();
osx["Model"] = json::String(p->HardwareModel()); osx["Model"] = p->HardwareModel();
root["OS X"] = osx;
#endif #endif
agi::io::Save file("./t.json"); agi::io::Save file("./t.json");

View file

@ -98,18 +98,21 @@ TEST_F(lagi_cajun, Compare) {
TEST_F(lagi_cajun, CastNonConst) { TEST_F(lagi_cajun, CastNonConst) {
json::UnknownElement Integer = 0; json::UnknownElement Integer = 0;
json::UnknownElement Double = 0.0;
json::UnknownElement String = "1"; json::UnknownElement String = "1";
json::UnknownElement Boolean = false; json::UnknownElement Boolean = false;
json::UnknownElement Array = json::Array(); json::UnknownElement Array = json::Array();
json::UnknownElement Object = json::Object(); json::UnknownElement Object = json::Object();
EXPECT_NO_THROW(static_cast<json::Number&>(Integer)); EXPECT_NO_THROW(static_cast<json::Integer&>(Integer));
EXPECT_NO_THROW(static_cast<json::Double&>(Double));
EXPECT_NO_THROW(static_cast<json::String&>(String)); EXPECT_NO_THROW(static_cast<json::String&>(String));
EXPECT_NO_THROW(static_cast<json::Boolean&>(Boolean)); EXPECT_NO_THROW(static_cast<json::Boolean&>(Boolean));
EXPECT_NO_THROW(static_cast<json::Array&>(Array)); EXPECT_NO_THROW(static_cast<json::Array&>(Array));
EXPECT_NO_THROW(static_cast<json::Object&>(Object)); EXPECT_NO_THROW(static_cast<json::Object&>(Object));
EXPECT_NO_THROW(static_cast<json::Number const&>(Integer)); EXPECT_NO_THROW(static_cast<json::Integer const&>(Integer));
EXPECT_NO_THROW(static_cast<json::Double const&>(Double));
EXPECT_NO_THROW(static_cast<json::String const&>(String)); EXPECT_NO_THROW(static_cast<json::String const&>(String));
EXPECT_NO_THROW(static_cast<json::Boolean const&>(Boolean)); EXPECT_NO_THROW(static_cast<json::Boolean const&>(Boolean));
EXPECT_NO_THROW(static_cast<json::Array const&>(Array)); EXPECT_NO_THROW(static_cast<json::Array const&>(Array));
@ -118,26 +121,30 @@ TEST_F(lagi_cajun, CastNonConst) {
TEST_F(lagi_cajun, CastConst) { TEST_F(lagi_cajun, CastConst) {
const json::UnknownElement Integer = 10; const json::UnknownElement Integer = 10;
const json::UnknownElement Double = 10.0;
const json::UnknownElement String = "1"; const json::UnknownElement String = "1";
const json::UnknownElement Boolean = false; const json::UnknownElement Boolean = false;
const json::UnknownElement Array = json::Array(); const json::UnknownElement Array = json::Array();
const json::UnknownElement Object = json::Object(); const json::UnknownElement Object = json::Object();
/* these shouldn't compile /* these shouldn't compile
EXPECT_NO_THROW(static_cast<json::Number&>(Integer)); EXPECT_NO_THROW(static_cast<json::Integer&>(Integer));
EXPECT_NO_THROW(static_cast<json::Double&>(Double));
EXPECT_NO_THROW(static_cast<json::String&>(String)); EXPECT_NO_THROW(static_cast<json::String&>(String));
EXPECT_NO_THROW(static_cast<json::Boolean&>(Boolean)); EXPECT_NO_THROW(static_cast<json::Boolean&>(Boolean));
EXPECT_NO_THROW(static_cast<json::Array&>(Array)); EXPECT_NO_THROW(static_cast<json::Array&>(Array));
EXPECT_NO_THROW(static_cast<json::Object&>(Object)); EXPECT_NO_THROW(static_cast<json::Object&>(Object));
*/ */
EXPECT_NO_THROW(static_cast<json::Number const&>(Integer)); EXPECT_NO_THROW(static_cast<json::Integer const&>(Integer));
EXPECT_NO_THROW(static_cast<json::Double const&>(Double));
EXPECT_NO_THROW(static_cast<json::String const&>(String)); EXPECT_NO_THROW(static_cast<json::String const&>(String));
EXPECT_NO_THROW(static_cast<json::Boolean const&>(Boolean)); EXPECT_NO_THROW(static_cast<json::Boolean const&>(Boolean));
EXPECT_NO_THROW(static_cast<json::Array const&>(Array)); EXPECT_NO_THROW(static_cast<json::Array const&>(Array));
EXPECT_NO_THROW(static_cast<json::Object const&>(Object)); EXPECT_NO_THROW(static_cast<json::Object const&>(Object));
EXPECT_EQ(10, static_cast<json::Number const&>(Integer)); EXPECT_EQ(10, static_cast<json::Integer const&>(Integer));
EXPECT_EQ(10, static_cast<json::Double const&>(Double));
EXPECT_STREQ("1", static_cast<json::String const&>(String).c_str()); EXPECT_STREQ("1", static_cast<json::String const&>(String).c_str());
EXPECT_EQ(false, static_cast<json::Boolean const&>(Boolean)); EXPECT_EQ(false, static_cast<json::Boolean const&>(Boolean));
EXPECT_EQ(true, static_cast<json::Array const&>(Array).empty()); EXPECT_EQ(true, static_cast<json::Array const&>(Array).empty());
@ -150,7 +157,7 @@ TEST_F(lagi_cajun, UnknownIsIndexable) {
json::UnknownElement unk_obj = obj; json::UnknownElement unk_obj = obj;
EXPECT_NO_THROW(unk_obj["Integer"]); EXPECT_NO_THROW(unk_obj["Integer"]);
EXPECT_EQ(1, (json::Number)unk_obj["Integer"]); EXPECT_EQ(1, (json::Integer)unk_obj["Integer"]);
EXPECT_THROW(unk_obj[0], json::Exception); EXPECT_THROW(unk_obj[0], json::Exception);
EXPECT_NO_THROW(unk_obj["Nonexistent Key"]); EXPECT_NO_THROW(unk_obj["Nonexistent Key"]);
@ -163,20 +170,20 @@ TEST_F(lagi_cajun, UnknownIsIndexable) {
json::UnknownElement unk_arr = arr; json::UnknownElement unk_arr = arr;
EXPECT_NO_THROW(unk_arr[0]); EXPECT_NO_THROW(unk_arr[0]);
EXPECT_EQ(1, (json::Number)unk_arr[0]); EXPECT_EQ(1, (json::Integer)unk_arr[0]);
EXPECT_THROW(unk_arr["Integer"], json::Exception); EXPECT_THROW(unk_arr["Integer"], json::Exception);
json::Number number = 1; json::Integer number = 1;
json::UnknownElement const& unk_num = number; json::UnknownElement const& unk_num = number;
EXPECT_THROW(unk_num[0], json::Exception); EXPECT_THROW(unk_num[0], json::Exception);
EXPECT_THROW(unk_num[""], json::Exception); EXPECT_THROW(unk_num[""], json::Exception);
} }
TEST_F(lagi_cajun, ObjectStoreNumber) { TEST_F(lagi_cajun, ObjectStoreInteger) {
json::Object obj; json::Object obj;
obj["Integer"] = 1; obj["Integer"] = 1;
EXPECT_EQ(1, static_cast<json::Number>(obj["Integer"])); EXPECT_EQ(1, static_cast<json::Integer>(obj["Integer"]));
EXPECT_THROW(static_cast<json::String const&>(obj["Integer"]), json::Exception); EXPECT_THROW(static_cast<json::String const&>(obj["Integer"]), json::Exception);
EXPECT_THROW(static_cast<json::Boolean>(obj["Integer"]), json::Exception); EXPECT_THROW(static_cast<json::Boolean>(obj["Integer"]), json::Exception);
@ -185,12 +192,24 @@ TEST_F(lagi_cajun, ObjectStoreNumber) {
EXPECT_THROW(static_cast<json::Object const&>(obj["Integer"]), json::Exception); EXPECT_THROW(static_cast<json::Object const&>(obj["Integer"]), json::Exception);
} }
TEST_F(lagi_cajun, ObjectStoreDouble) {
json::Object obj;
obj["Double"] = 1.0;
EXPECT_EQ(1.0, static_cast<json::Double>(obj["Double"]));
EXPECT_THROW(static_cast<json::String const&>(obj["Double"]), json::Exception);
EXPECT_THROW(static_cast<json::Boolean>(obj["Double"]), json::Exception);
EXPECT_THROW(static_cast<json::Null>(obj["Double"]), json::Exception);
EXPECT_THROW(static_cast<json::Array const&>(obj["Double"]), json::Exception);
EXPECT_THROW(static_cast<json::Object const&>(obj["Double"]), json::Exception);
}
TEST_F(lagi_cajun, ObjectStoreString) { TEST_F(lagi_cajun, ObjectStoreString) {
json::Object obj; json::Object obj;
obj["String"] = "test"; obj["String"] = "test";
EXPECT_STREQ("test", static_cast<std::string>(obj["String"]).c_str()); EXPECT_STREQ("test", static_cast<std::string>(obj["String"]).c_str());
EXPECT_THROW(static_cast<json::Number>(obj["String"]), json::Exception); EXPECT_THROW(static_cast<json::Integer>(obj["String"]), json::Exception);
EXPECT_THROW(static_cast<json::Boolean>(obj["String"]), json::Exception); EXPECT_THROW(static_cast<json::Boolean>(obj["String"]), json::Exception);
EXPECT_THROW(static_cast<json::Null>(obj["String"]), json::Exception); EXPECT_THROW(static_cast<json::Null>(obj["String"]), json::Exception);
EXPECT_THROW(static_cast<json::Array const&>(obj["String"]), json::Exception); EXPECT_THROW(static_cast<json::Array const&>(obj["String"]), json::Exception);
@ -203,7 +222,7 @@ TEST_F(lagi_cajun, ObjectStoreBoolean) {
EXPECT_EQ(true, static_cast<json::Boolean>(obj["Boolean"])); EXPECT_EQ(true, static_cast<json::Boolean>(obj["Boolean"]));
EXPECT_THROW(static_cast<json::String const&>(obj["Boolean"]), json::Exception); EXPECT_THROW(static_cast<json::String const&>(obj["Boolean"]), json::Exception);
EXPECT_THROW(static_cast<json::Number>(obj["Boolean"]), json::Exception); EXPECT_THROW(static_cast<json::Integer>(obj["Boolean"]), json::Exception);
EXPECT_THROW(static_cast<json::Null>(obj["Boolean"]), json::Exception); EXPECT_THROW(static_cast<json::Null>(obj["Boolean"]), json::Exception);
EXPECT_THROW(static_cast<json::Array const&>(obj["Boolean"]), json::Exception); EXPECT_THROW(static_cast<json::Array const&>(obj["Boolean"]), json::Exception);
EXPECT_THROW(static_cast<json::Object const&>(obj["Boolean"]), json::Exception); EXPECT_THROW(static_cast<json::Object const&>(obj["Boolean"]), json::Exception);
@ -219,7 +238,10 @@ TEST_F(lagi_cajun, ObjectStoreNull) {
EXPECT_NO_THROW(static_cast<json::String const&>(obj["Null"])); EXPECT_NO_THROW(static_cast<json::String const&>(obj["Null"]));
obj["Null"] = json::Null(); obj["Null"] = json::Null();
EXPECT_NO_THROW(static_cast<json::Number>(obj["Null"])); EXPECT_NO_THROW(static_cast<json::Integer>(obj["Null"]));
obj["Null"] = json::Null();
EXPECT_NO_THROW(static_cast<json::Double>(obj["Null"]));
obj["Null"] = json::Null(); obj["Null"] = json::Null();
EXPECT_NO_THROW(static_cast<json::Boolean>(obj["Null"])); EXPECT_NO_THROW(static_cast<json::Boolean>(obj["Null"]));
@ -303,7 +325,7 @@ TEST_F(lagi_cajun, ReaderParserErrors) {
std::istringstream missing_comma("[1 2]"); std::istringstream missing_comma("[1 2]");
EXPECT_THROW(json::Reader::Read(arr, missing_comma), json::Exception); EXPECT_THROW(json::Reader::Read(arr, missing_comma), json::Exception);
json::Number num; json::Double num;
std::istringstream garbage_after_number("123eee"); std::istringstream garbage_after_number("123eee");
EXPECT_THROW(json::Reader::Read(num, garbage_after_number), json::Exception); EXPECT_THROW(json::Reader::Read(num, garbage_after_number), json::Exception);
@ -335,7 +357,7 @@ TEST_F(lagi_cajun, ReaderScanErrors) {
EXPECT_THROW(json::Reader::Read(obj, doc), json::Exception); EXPECT_THROW(json::Reader::Read(obj, doc), json::Exception);
json::Number num; json::Double num;
std::istringstream garbage_after_number("123abc"); std::istringstream garbage_after_number("123abc");
EXPECT_THROW(json::Reader::Read(num, garbage_after_number), json::Exception); EXPECT_THROW(json::Reader::Read(num, garbage_after_number), json::Exception);