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 {
void Visit(Array&) { }
void Visit(Object&) { }
void Visit(Number&) { }
void Visit(Integer&) { }
void Visit(Double&) { }
void Visit(String&) { }
void Visit(Boolean&) { }
void Visit(Null&) { is_null = true; }
void Visit(Array const&) { }
void Visit(Object const&) { }
void Visit(Number const&) { }
void Visit(Integer const&) { }
void Visit(Double const&) { }
void Visit(String const&) { }
void Visit(Boolean const&) { }
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 Object& object) : m_pImp( new Imp_T<Object>(object) ) {}
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(int number) : m_pImp( new Imp_T<Number>(number) ) {}
UnknownElement::UnknownElement(long 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<Integer>(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(const char *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 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 String const&() const { return CastTo<String>(); }
UnknownElement::operator Null const&() const { return CastTo<Null>(); }
UnknownElement::operator Object&() { return CastTo<Object>(); }
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 String&() { return CastTo<String>(); }
UnknownElement::operator Null&() { return CastTo<Null>(); }

View file

@ -22,27 +22,15 @@ TODO:
*/
namespace json
{
namespace json {
std::istream& operator >> (std::istream& istr, UnknownElement& elementRoot) {
Reader::Read(elementRoot, istr);
return istr;
}
Reader::Location::Location() :
m_nLine(0),
m_nLineOffset(0),
m_nDocOffset(0)
{}
//////////////////////
// Reader::InputStream
// wrapper around istream to keep track of document/line offsets
class Reader::InputStream
{
/// Wrapper around istream to keep track of document/line offsets
class Reader::InputStream {
std::istream& m_iStr;
Location m_Location;
public:
@ -63,6 +51,7 @@ public:
return c;
}
int Peek() {
assert(!m_iStr.eof());
return m_iStr.peek();
@ -73,49 +62,40 @@ public:
return m_iStr.eof();
}
const Location& GetLocation() const { return m_Location; }
Location const& GetLocation() const { return m_Location; }
};
//////////////////////
// Reader::TokenStream
class Reader::TokenStream
{
const Tokens& m_Tokens;
class Reader::TokenStream {
Tokens const& m_Tokens;
Tokens::const_iterator m_itCurrent;
public:
TokenStream(const Tokens& tokens)
: m_Tokens(tokens), m_itCurrent(tokens.begin())
TokenStream(Tokens const& tokens) : m_Tokens(tokens), m_itCurrent(tokens.begin())
{ }
const Token& Peek() {
Token const& Peek() {
assert(!EOS());
return *m_itCurrent;
}
const Token& Get() {
Token const& Get() {
assert(!EOS());
return *m_itCurrent++;
}
bool EOS() const {
return m_itCurrent == m_Tokens.end();
}
bool EOS() const { return m_itCurrent == m_Tokens.end(); }
};
///////////////////
// Reader (finally)
void Reader::Read(Object& object, std::istream& istr) { Read_i(object, istr); }
void Reader::Read(Array& array, std::istream& istr) { Read_i(array, istr); }
void Reader::Read(String& string, std::istream& istr) { Read_i(string, istr); }
void Reader::Read(Number& number, std::istream& istr) { Read_i(number, istr); }
void Reader::Read(Integer& number, std::istream& istr) { Read_i(number, istr); }
void Reader::Read(Double& number, std::istream& istr) { Read_i(number, 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(UnknownElement& unknown, std::istream& istr) { Read_i(unknown, istr); }
template <typename ElementTypeT>
void Reader::Read_i(ElementTypeT& element, std::istream& istr)
{
void Reader::Read_i(ElementTypeT& element, std::istream& istr) {
Reader reader;
Tokens tokens;
@ -125,17 +105,14 @@ void Reader::Read_i(ElementTypeT& element, std::istream& istr)
TokenStream tokenStream(tokens);
element = reader.Parse(tokenStream);
if (!tokenStream.EOS())
{
const Token& token = tokenStream.Peek();
if (!tokenStream.EOS()) {
Token const& token = tokenStream.Peek();
throw ParseException("Expected End of token stream; found " + token.sValue, token.locBegin, token.locEnd);
}
}
void Reader::Scan(Tokens& tokens, InputStream& inputStream)
{
while (EatWhiteSpace(inputStream), !inputStream.EOS())
{
void Reader::Scan(Tokens& tokens, InputStream& inputStream) {
while (EatWhiteSpace(inputStream), !inputStream.EOS()) {
// if all goes well, we'll create a token each pass
Token token;
token.locBegin = inputStream.GetLocation();
@ -144,8 +121,7 @@ void Reader::Scan(Tokens& tokens, InputStream& inputStream)
std::string sChar;
sChar.push_back(inputStream.Peek());
switch (sChar[0])
{
switch (sChar[0]) {
case '{':
token.sValue = sChar[0];
MatchExpectedString(sChar, inputStream);
@ -230,16 +206,13 @@ void Reader::Scan(Tokens& tokens, InputStream& inputStream)
}
void Reader::EatWhiteSpace(InputStream& inputStream)
{
void Reader::EatWhiteSpace(InputStream& inputStream) {
while (!inputStream.EOS() && ::isspace(inputStream.Peek()))
inputStream.Get();
}
void Reader::MatchExpectedString(const std::string& sExpected, InputStream& inputStream)
{
std::string::const_iterator it(sExpected.begin()),
itEnd(sExpected.end());
void Reader::MatchExpectedString(std::string const& sExpected, InputStream& inputStream) {
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?
@ -247,24 +220,16 @@ void Reader::MatchExpectedString(const std::string& sExpected, InputStream& inpu
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)
{
void Reader::MatchString(std::string& string, InputStream& inputStream) {
MatchExpectedString("\"", inputStream);
while (inputStream.EOS() == false &&
inputStream.Peek() != '"')
{
while (!inputStream.EOS() && inputStream.Peek() != '"') {
char c = inputStream.Get();
// escape?
if (c == '\\' &&
inputStream.EOS() == false) // shouldn't have reached the end yet
{
if (c == '\\' && !inputStream.EOS()) { // shouldn't have reached the end yet
c = inputStream.Get();
switch (c) {
case '/': string.push_back('/'); break;
@ -285,27 +250,20 @@ void Reader::MatchString(std::string& string, InputStream& inputStream)
}
}
// eat the last '"' that we just peeked
// eat the last '"' that we hopefully just peeked
MatchExpectedString("\"", inputStream);
}
void Reader::MatchNumber(std::string& sNumber, InputStream& inputStream)
{
void Reader::MatchNumber(std::string& sNumber, InputStream& inputStream) {
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())
{
while (!inputStream.EOS()&& numericChars.count(inputStream.Peek()))
sNumber.push_back(inputStream.Get());
}
}
UnknownElement Reader::Parse(Reader::TokenStream& tokenStream)
{
UnknownElement Reader::Parse(Reader::TokenStream& tokenStream) {
if (tokenStream.EOS())
throw ParseException("Unexpected end of token stream", Location(), Location()); // nowhere to point to
@ -322,16 +280,14 @@ UnknownElement Reader::Parse(Reader::TokenStream& tokenStream)
}
}
Object Reader::ParseObject(Reader::TokenStream& tokenStream)
{
UnknownElement Reader::ParseObject(Reader::TokenStream& tokenStream) {
MatchExpectedToken(Token::TOKEN_OBJECT_BEGIN, tokenStream);
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
const Token& tokenName = tokenStream.Peek();
Token const& tokenName = tokenStream.Peek();
std::string const& name = MatchExpectedToken(Token::TOKEN_STRING, tokenStream);
if (object.count(name))
@ -352,8 +308,7 @@ Object Reader::ParseObject(Reader::TokenStream& tokenStream)
return object;
}
Array Reader::ParseArray(Reader::TokenStream& tokenStream)
{
UnknownElement Reader::ParseArray(Reader::TokenStream& tokenStream) {
MatchExpectedToken(Token::TOKEN_ARRAY_BEGIN, tokenStream);
Array array;
@ -371,53 +326,53 @@ Array Reader::ParseArray(Reader::TokenStream& tokenStream)
return array;
}
String Reader::ParseString(Reader::TokenStream& tokenStream)
{
UnknownElement Reader::ParseString(Reader::TokenStream& tokenStream) {
return MatchExpectedToken(Token::TOKEN_STRING, tokenStream);
}
Number Reader::ParseNumber(Reader::TokenStream& tokenStream)
{
const Token& currentToken = tokenStream.Peek(); // might need this later for throwing exception
const std::string& sValue = MatchExpectedToken(Token::TOKEN_NUMBER, tokenStream);
UnknownElement Reader::ParseNumber(Reader::TokenStream& tokenStream) {
Token const& currentToken = tokenStream.Peek(); // might need this later for throwing exception
std::string const& sValue = MatchExpectedToken(Token::TOKEN_NUMBER, tokenStream);
// First try to parse it as an int
std::istringstream iStr(sValue);
int64_t iValue;
iStr >> iValue;
// If the entire token was consumed then it's not a double
if (iStr.eof())
return iValue;
// Try again as a double
iStr.seekg(0, std::ios::beg);
double dValue;
iStr >> dValue;
// did we consume all characters in the token?
// 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";
}
Null Reader::ParseNull(Reader::TokenStream& tokenStream)
{
UnknownElement Reader::ParseNull(Reader::TokenStream& tokenStream) {
MatchExpectedToken(Token::TOKEN_NULL, tokenStream);
return Null();
}
const std::string& Reader::MatchExpectedToken(Token::Type nExpected, Reader::TokenStream& tokenStream)
{
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 Token& token = tokenStream.Get();
Token const& token = tokenStream.Get();
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"
#ifndef LAGI_PRE
#include <cmath>
#include <iostream>
#include <iomanip>
#endif
@ -24,23 +25,22 @@ TODO:
namespace json
{
Writer::Writer(std::ostream& ostr) :
m_ostr(ostr),
m_nTabDepth(0)
{}
void Writer::Write(const Array& array)
Writer::Writer(std::ostream& ostr)
: m_ostr(ostr)
, tab_depth(0)
{
}
void Writer::Write(Array const& array) {
if (array.empty())
m_ostr << "[]";
else
{
else {
m_ostr << '[' << std::endl;
++m_nTabDepth;
++tab_depth;
Array::const_iterator it(array.begin()), itend(array.end());
while (it != itend) {
m_ostr << std::string(m_nTabDepth, '\t');
m_ostr << std::string(tab_depth, '\t');
Write(*it);
@ -49,23 +49,21 @@ void Writer::Write(const Array& array)
m_ostr << std::endl;
}
--m_nTabDepth;
m_ostr << std::string(m_nTabDepth, '\t') << ']';
--tab_depth;
m_ostr << std::string(tab_depth, '\t') << ']';
}
}
void Writer::Write(const Object& object)
{
void Writer::Write(Object const& object) {
if (object.empty())
m_ostr << "{}";
else
{
else {
m_ostr << '{' << std::endl;
++m_nTabDepth;
++tab_depth;
Object::const_iterator it(object.begin()), itend(object.end());
while (it != itend) {
m_ostr << std::string(m_nTabDepth, '\t') << '"' << it->first << "\" : ";
m_ostr << std::string(tab_depth, '\t') << '"' << it->first << "\" : ";
Write(it->second);
if (++it != itend)
@ -73,31 +71,33 @@ void Writer::Write(const Object& object)
m_ostr << std::endl;
}
--m_nTabDepth;
m_ostr << std::string(m_nTabDepth, '\t') << '}';
--tab_depth;
m_ostr << std::string(tab_depth, '\t') << '}';
}
}
void Writer::Write(const Number& numberElement)
{
void Writer::Write(Double const& numberElement) {
m_ostr << std::setprecision(20) << numberElement;
double unused;
if (!std::modf(numberElement, &unused))
m_ostr << ".0";
}
void Writer::Write(const Boolean& booleanElement)
{
void Writer::Write(Integer const& numberElement) {
m_ostr << numberElement;
}
void Writer::Write(Boolean const& booleanElement) {
m_ostr << (booleanElement ? "true" : "false");
}
void Writer::Write(const String& stringElement)
{
void Writer::Write(String const& stringElement) {
m_ostr << '"';
const std::string& s = stringElement;
std::string::const_iterator it(s.begin()), itend(s.end());
for (; it != itend; ++it)
{
switch (*it)
{
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;
@ -105,7 +105,6 @@ void Writer::Write(const String& stringElement)
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;
}
}
@ -113,21 +112,20 @@ void Writer::Write(const String& stringElement)
m_ostr << '"';
}
void Writer::Write(const Null& )
{
void Writer::Write(Null const&) {
m_ostr << "null";
}
void Writer::Write(const UnknownElement& unknown)
{
void Writer::Write(UnknownElement const& unknown) {
unknown.Accept(*this);
}
void Writer::Visit(const Array& array) { Write(array); }
void Writer::Visit(const Object& object) { Write(object); }
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::Visit(const Null& null) { Write(null); }
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

View file

@ -21,10 +21,11 @@
#include "libaegisub/option.h"
#ifndef LAGI_PRE
#include <cassert>
#include <fstream>
#include <sstream>
#include <map>
#include <memory>
#include <sstream>
#endif
#include "libaegisub/cajun/reader.h"
@ -38,6 +39,39 @@
#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 {
Options::Options(const std::string &file, const std::string& default_config, const OptionSetting setting)
@ -108,92 +142,43 @@ void Options::Flush() {
for (OptionValueMap::const_iterator i = values.begin(); i != values.end(); ++i) {
switch (i->second->GetType()) {
case OptionValue::Type_String:
PutOption(obj_out, i->first, i->second->GetString());
put_option(obj_out, i->first, i->second->GetString());
break;
case OptionValue::Type_Int:
PutOption(obj_out, i->first, (double)i->second->GetInt());
put_option(obj_out, i->first, i->second->GetInt());
break;
case OptionValue::Type_Double:
PutOption(obj_out, i->first, i->second->GetDouble());
put_option(obj_out, i->first, i->second->GetDouble());
break;
case OptionValue::Type_Colour:
PutOption(obj_out, i->first, i->second->GetColour());
put_option(obj_out, i->first, i->second->GetColour());
break;
case OptionValue::Type_Bool:
PutOption(obj_out, i->first, i->second->GetBool());
put_option(obj_out, i->first, i->second->GetBool());
break;
case OptionValue::Type_List_String: {
std::vector<std::string> const& array_string(i->second->GetListString());
json::Array array;
for (std::vector<std::string>::const_iterator i_str = array_string.begin(); i_str != array_string.end(); ++i_str) {
json::Object obj;
obj["string"] = *i_str;
array.push_back(obj);
}
PutOption(obj_out, i->first, array);
}
case OptionValue::Type_List_String:
put_array(obj_out, i->first, "string", i->second->GetListString());
break;
case OptionValue::Type_List_Int: {
std::vector<int64_t> const& array_int(i->second->GetListInt());
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);
}
case OptionValue::Type_List_Int:
put_array(obj_out, i->first, "int", i->second->GetListInt());
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);
}
case OptionValue::Type_List_Double:
put_array(obj_out, i->first, "double", i->second->GetListDouble());
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);
}
case OptionValue::Type_List_Colour:
put_array(obj_out, i->first, "colour", i->second->GetListColour());
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);
}
case OptionValue::Type_List_Bool:
put_array(obj_out, i->first, "bool", i->second->GetListBool());
break;
}
}
@ -202,19 +187,4 @@ void Options::Flush() {
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

View file

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

View file

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

View file

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

View file

@ -18,51 +18,49 @@ Author: Terry Caton
namespace json
{
class Reader
{
class Reader {
public:
// this structure will be reported in one of the exceptions defined below
struct Location
{
Location();
struct Location {
Location() : m_nLine(0), m_nLineOffset(0), m_nDocOffset(0) { }
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
{
// 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) {}
ScanException(std::string const& sMessage, Reader::Location const& 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
{
// 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) {}
ParseException(std::string const& sMessage, Reader::Location const& locTokenBegin, Reader::Location const& 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(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);
@ -70,10 +68,8 @@ public:
static void Read(UnknownElement& elementRoot, std::istream& istr);
private:
struct Token
{
enum Type
{
struct Token {
enum Type {
TOKEN_OBJECT_BEGIN, // {
TOKEN_OBJECT_END, // }
TOKEN_ARRAY_BEGIN, // [
@ -107,18 +103,18 @@ private:
void EatWhiteSpace(InputStream& inputStream);
void MatchString(std::string& sValue, 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
UnknownElement Parse(TokenStream& tokenStream);
Object ParseObject(TokenStream& tokenStream);
Array ParseArray(TokenStream& tokenStream);
String ParseString(TokenStream& tokenStream);
Number ParseNumber(TokenStream& tokenStream);
Boolean ParseBoolean(TokenStream& tokenStream);
Null ParseNull(TokenStream& tokenStream);
UnknownElement ParseObject(TokenStream& tokenStream);
UnknownElement ParseArray(TokenStream& tokenStream);
UnknownElement ParseString(TokenStream& tokenStream);
UnknownElement ParseNumber(TokenStream& tokenStream);
UnknownElement ParseBoolean(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
{
class Visitor
{
public:
struct Visitor {
virtual ~Visitor() { }
virtual void Visit(Array& array) = 0;
virtual void Visit(Object& object) = 0;
virtual void Visit(Number& number) = 0;
virtual void Visit(Integer& number) = 0;
virtual void Visit(Double& number) = 0;
virtual void Visit(String& string) = 0;
virtual void Visit(Boolean& boolean) = 0;
virtual void Visit(Null& null) = 0;
};
class ConstVisitor
{
public:
struct ConstVisitor {
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 Integer& number) = 0;
virtual void Visit(const Double& 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

View file

@ -14,36 +14,35 @@ Author: Terry Caton
namespace json
{
class Writer : private ConstVisitor
{
public:
template <typename ElementTypeT>
static void Write(const ElementTypeT& element, std::ostream& ostr)
{
Writer writer(ostr);
writer.Write(element);
ostr.flush(); // all done
}
private:
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 Number& number);
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 Number& number);
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 m_nTabDepth;
int tab_depth;
public:
template <typename ElementTypeT>
static void Write(const ElementTypeT& element, std::ostream& ostr) {
Writer writer(ostr);
writer.Write(element);
ostr.flush(); // all done
}
};

View file

@ -78,12 +78,6 @@ private:
/// @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);
/// @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:
/// @brief Constructor
/// @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();
Aegisub a;
json::Object general;
general["Signature"] = json::String(p->Signature());
general["Date"] = json::String(p->Date());
general["Architecture"] = json::String(p->ArchName());
general["OS Family"] = json::String(p->OSFamily());
general["OS Name"] = json::String(p->OSName());
general["Endian"] = json::String(p->Endian());
general["OS Version"] = json::String(p->OSVersion());
general["wx Version"] = json::String(p->wxVersion());
general["Locale"] = json::String(p->Locale());
general["Language"] = json::String(p->Language());
general["System Language"] = json::String(p->SystemLanguage());
root["General"] = general;
json::Object& general = root["General"];
general["Signature"] = p->Signature();
general["Date"] = p->Date();
general["Architecture"] = p->ArchName();
general["OS Family"] = p->OSFamily();
general["OS Name"] = p->OSName();
general["Endian"] = p->Endian();
general["OS Version"] = p->OSVersion();
general["wx Version"] = p->wxVersion();
general["Locale"] = p->Locale();
general["Language"] = p->Language();
general["System Language"] = p->SystemLanguage();
try {
json::Object aegisub;
aegisub["Last Version"] = json::String(a.GetString("Version/Last Version"));
aegisub["Spelling Language"] = json::String(a.GetString("Tool/Spell Checker/Language"));
aegisub["Thesaurus Language"] = json::String(a.GetString("Tool/Thesaurus/Language"));
aegisub["Audio Player"] = json::String(a.GetString("Audio/Player"));
aegisub["Audio Provider"] = json::String(a.GetString("Audio/Provider"));
aegisub["Video Provider"] = json::String(a.GetString("Video/Provider"));
aegisub["Subtitles Provider"] = json::String(a.GetString("Subtitle/Provider"));
aegisub["Save Charset"] = json::String(a.GetString("App/Save Charset"));
aegisub["Grid Font Size"] = json::Number(a.GetInt("Grid/Font Size"));
aegisub["Edit Font Size"] = json::Number(a.GetInt("Subtitle/Edit Box/Font Size"));
aegisub["Spectrum Enabled"] = json::Boolean(a.GetBool("Audio/Spectrum"));
aegisub["Spectrum Quality"] = json::Number(a.GetInt("Audio/Renderer/Spectrum/Quality"));
aegisub["Call Tips Enabled"] = json::Boolean(a.GetBool("App/Call Tips"));
aegisub["Medusa Hotkeys Enabled"] = json::Boolean(a.GetBool("Audio/Medusa Timing Hotkeys"));
root["Aegisub"] = aegisub;
json::Object& aegisub = root["Aegisub"];
aegisub["Last Version"] = a.GetString("Version/Last Version");
aegisub["Spelling Language"] = a.GetString("Tool/Spell Checker/Language");
aegisub["Thesaurus Language"] = a.GetString("Tool/Thesaurus/Language");
aegisub["Audio Player"] = a.GetString("Audio/Player");
aegisub["Audio Provider"] = a.GetString("Audio/Provider");
aegisub["Video Provider"] = a.GetString("Video/Provider");
aegisub["Subtitles Provider"] = a.GetString("Subtitle/Provider");
aegisub["Save Charset"] = a.GetString("App/Save Charset");
aegisub["Grid Font Size"] = a.GetInt("Grid/Font Size");
aegisub["Edit Font Size"] = a.GetInt("Subtitle/Edit Box/Font Size");
aegisub["Spectrum Enabled"] = a.GetBool("Audio/Spectrum");
aegisub["Spectrum Quality"] = a.GetInt("Audio/Renderer/Spectrum/Quality");
aegisub["Call Tips Enabled"] = a.GetBool("App/Call Tips");
aegisub["Medusa Hotkeys Enabled"] = a.GetBool("Audio/Medusa Timing Hotkeys");
} catch(...) {
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;
hardware["Memory Size"] = json::Number();
root["Hardware"] = hardware;
json::Object& display = root["Display"];
display["Depth"] = p->DisplayDepth();
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());
json::Object& gl = display["OpenGL"];
gl["Vendor"] = p->OpenGLVendor();
gl["Renderer"] = p->OpenGLRenderer();
gl["Version"] = p->OpenGLVersion();
gl["Extensions"] = p->OpenGLExt();
display["OpenGL"] = gl;
root["Display"] = display;
#ifdef __WINDOWS__
json::Object windows;
json::Object& windows = root["Windows"];
windows["Service Pack"] = json::String();
windows["Graphics Driver Version"] = json::String();
windows["DirectShow Filters"] = json::String();
windows["AntiVirus Installed"] = json::Boolean();
windows["Firewall Installed"] = json::Boolean();
windows["DLL"] = json::String();
root["Windows"] = windows;
#endif
#ifdef __UNIX__
json::Object u_nix;
u_nix["Desktop Environment"] = json::String(p->DesktopEnvironment());
u_nix["Libraries"] = json::String(p->UnixLibraries());
root["Unix"] = u_nix;
json::Object& u_nix = root["Unix"];
u_nix["Desktop Environment"] = p->DesktopEnvironment();
u_nix["Libraries"] = p->UnixLibraries();
#endif
#ifdef __APPLE__
json::Object osx;
osx["Patch"] = json::String(p->PatchLevel());
osx["QuickTime Extensions"] = json::String(p->QuickTimeExt());
osx["Model"] = json::String(p->HardwareModel());
root["OS X"] = osx;
json::Object& osx = root["OS X"];
osx["Patch"] = p->PatchLevel();
osx["QuickTime Extensions"] = p->QuickTimeExt();
osx["Model"] = p->HardwareModel();
#endif
agi::io::Save file("./t.json");

View file

@ -98,18 +98,21 @@ TEST_F(lagi_cajun, Compare) {
TEST_F(lagi_cajun, CastNonConst) {
json::UnknownElement Integer = 0;
json::UnknownElement Double = 0.0;
json::UnknownElement String = "1";
json::UnknownElement Boolean = false;
json::UnknownElement Array = json::Array();
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::Boolean&>(Boolean));
EXPECT_NO_THROW(static_cast<json::Array&>(Array));
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::Boolean const&>(Boolean));
EXPECT_NO_THROW(static_cast<json::Array const&>(Array));
@ -118,26 +121,30 @@ TEST_F(lagi_cajun, CastNonConst) {
TEST_F(lagi_cajun, CastConst) {
const json::UnknownElement Integer = 10;
const json::UnknownElement Double = 10.0;
const json::UnknownElement String = "1";
const json::UnknownElement Boolean = false;
const json::UnknownElement Array = json::Array();
const json::UnknownElement Object = json::Object();
/* 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::Boolean&>(Boolean));
EXPECT_NO_THROW(static_cast<json::Array&>(Array));
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::Boolean const&>(Boolean));
EXPECT_NO_THROW(static_cast<json::Array const&>(Array));
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_EQ(false, static_cast<json::Boolean const&>(Boolean));
EXPECT_EQ(true, static_cast<json::Array const&>(Array).empty());
@ -150,7 +157,7 @@ TEST_F(lagi_cajun, UnknownIsIndexable) {
json::UnknownElement unk_obj = obj;
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_NO_THROW(unk_obj["Nonexistent Key"]);
@ -163,20 +170,20 @@ TEST_F(lagi_cajun, UnknownIsIndexable) {
json::UnknownElement unk_arr = arr;
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);
json::Number number = 1;
json::Integer number = 1;
json::UnknownElement const& unk_num = number;
EXPECT_THROW(unk_num[0], json::Exception);
EXPECT_THROW(unk_num[""], json::Exception);
}
TEST_F(lagi_cajun, ObjectStoreNumber) {
TEST_F(lagi_cajun, ObjectStoreInteger) {
json::Object obj;
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::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);
}
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) {
json::Object obj;
obj["String"] = "test";
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::Null>(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_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::Array 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"]));
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();
EXPECT_NO_THROW(static_cast<json::Boolean>(obj["Null"]));
@ -303,7 +325,7 @@ TEST_F(lagi_cajun, ReaderParserErrors) {
std::istringstream missing_comma("[1 2]");
EXPECT_THROW(json::Reader::Read(arr, missing_comma), json::Exception);
json::Number num;
json::Double num;
std::istringstream garbage_after_number("123eee");
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);
json::Number num;
json::Double num;
std::istringstream garbage_after_number("123abc");
EXPECT_THROW(json::Reader::Read(num, garbage_after_number), json::Exception);