From 53c4105ae5d96da8031e548dc1380e8b38c4e38f Mon Sep 17 00:00:00 2001 From: randomdan Date: Fri, 12 Sep 2014 18:56:09 +0000 Subject: Fixes parser bug with escape sequences, improves error reporting (slightly) and adds some covering unit tests --- libjsonpp/Jamfile.jam | 14 ++++- libjsonpp/initial/sample1.json | 57 +++++++++++++++++++++ libjsonpp/jsonpp.h | 6 +++ libjsonpp/parse.cpp | 58 +++++++++++---------- libjsonpp/test1.cpp | 114 +++++++++++++++++++++++++++++++++++++++++ libjsonpp/testpch.hpp | 12 +++++ 6 files changed, 234 insertions(+), 27 deletions(-) create mode 100644 libjsonpp/initial/sample1.json create mode 100644 libjsonpp/test1.cpp create mode 100644 libjsonpp/testpch.hpp diff --git a/libjsonpp/Jamfile.jam b/libjsonpp/Jamfile.jam index f3d0c6c..da6de95 100644 --- a/libjsonpp/Jamfile.jam +++ b/libjsonpp/Jamfile.jam @@ -1,4 +1,5 @@ import package ; +import testing ; alias glibmm : : : : "`pkg-config --cflags glibmm-2.4`" @@ -9,7 +10,7 @@ cpp-pch pch : pch.hpp : glibmm ; lib jsonpp : - pch [ glob *.cpp ] + pch [ glob *.cpp : test*.cpp ] : . glibmm @@ -17,4 +18,15 @@ lib jsonpp : . ; +cpp-pch testpch : testpch.hpp : + glibmm + jsonpp + ; +unit-test test1 : + testpch + test1.cpp + : + jsonpp + ; + package.install install : . : : jsonpp : [ glob *.h ] ; diff --git a/libjsonpp/initial/sample1.json b/libjsonpp/initial/sample1.json new file mode 100644 index 0000000..dd68064 --- /dev/null +++ b/libjsonpp/initial/sample1.json @@ -0,0 +1,57 @@ +{ + "adult": false, + "backdrop_path": "/8uO0gUM8aNqYLs1OsTBQiXu0fEv.jpg", + "belongs_to_collection": null, + "budget": 63000000, + "genres": [ + { + "id": 28, + "name": "Action" + }, + { + "id": 18, + "name": "Drama" + }, + { + "id": 53, + "name": "Thriller" + } + ], + "homepage": "", + "id": 550, + "imdb_id": "tt0137523", + "original_title": "Fight Club", + "overview": "A ticking-time-bomb insomniac and a slippery soap salesman channel primal male aggression into a shocking new form of therapy. Their concept catches on, with underground \"fight clubs\" forming in every town, until an eccentric gets in the way and ignites an out-of-control spiral toward oblivion.", + "popularity": 61151.745000000003, + "poster_path": "/2lECpi35Hnbpa4y46JX0aY3AWTy.jpg", + "production_companies": [ + { + "name": "20th Century Fox", + "id": 25 + } + ], + "production_countries": [ + { + "iso_3166_1": "DE", + "name": "Germany" + }, + { + "iso_3166_1": "US", + "name": "United States of America" + } + ], + "release_date": "1999-10-15", + "revenue": 100853753, + "runtime": 139, + "spoken_languages": [ + { + "iso_639_1": "en", + "name": "English" + } + ], + "status": "Released", + "tagline": "How much can you know about yourself if you've never been in a fight?", + "title": "Fight Club", + "vote_average": 9.0999999999999996, + "vote_count": 174 +} diff --git a/libjsonpp/jsonpp.h b/libjsonpp/jsonpp.h index 33f5dab..a872fd9 100644 --- a/libjsonpp/jsonpp.h +++ b/libjsonpp/jsonpp.h @@ -6,8 +6,14 @@ #include #include #include +#include namespace json { + class ParseError : public std::invalid_argument { + public: + ParseError(gunichar); + }; + typedef Glib::ustring String; typedef double Number; typedef bool Boolean; diff --git a/libjsonpp/parse.cpp b/libjsonpp/parse.cpp index 565c9e1..f127e9d 100644 --- a/libjsonpp/parse.cpp +++ b/libjsonpp/parse.cpp @@ -2,13 +2,17 @@ #include "jsonpp.h" namespace json { - class ParseError { }; + ParseError::ParseError(gunichar c) : + std::invalid_argument(Glib::ustring("Parse error at or near ") + Glib::ustring(1, c)) + { + } String parseString(Glib::ustring::const_iterator & s) { while (Glib::Unicode::isspace(*s)) s++; - if (*s++ != '"') throw ParseError(); + if (*s++ != '"') throw ParseError(*--s); String str; while (*s != '"') { if (*s == '\\') { + ++s; switch (*s) { case '"': str += '"'; @@ -49,7 +53,7 @@ namespace json { c += (*s - 'A'); } else { - throw ParseError(); + throw ParseError(*s); } s++; } @@ -58,7 +62,7 @@ namespace json { } break; default: - throw ParseError(); + throw ParseError(*s); } } else { @@ -66,7 +70,7 @@ namespace json { } s++; } - if (*s++ != '"') throw ParseError(); + if (*s++ != '"') throw ParseError(*--s); return str; } Number parseNumber(Glib::ustring::const_iterator & s) { @@ -93,7 +97,7 @@ namespace json { } } else if (*s == '.') { - if (dot || e) throw ParseError(); + if (dot || e) throw ParseError(*s); dot = true; } else if (*s == 'e' || *s == 'E') { @@ -127,7 +131,7 @@ namespace json { } s++; } - if (digits < 1) throw ParseError(); + if (digits < 1) throw ParseError(*s); return neg ? -v : v; } Value parseValue(Glib::ustring::const_iterator & s) { @@ -151,21 +155,21 @@ namespace json { Object parseObject(const Glib::ustring & s) { Glib::ustring::const_iterator i = s.begin(); Object o = parseObject(i); - if (i != s.end()) throw ParseError(); + if (i != s.end()) throw ParseError(*i); return o; } Object parseObject(Glib::ustring::const_iterator & s) { Object o; while (Glib::Unicode::isspace(*s)) s++; - if (*s != '{') throw ParseError(); + if (*s != '{') throw ParseError(*s); do { s++; while (Glib::Unicode::isspace(*s)) s++; if (*s == '}') return o; String key = parseString(s); while (Glib::Unicode::isspace(*s)) s++; - if (*s++ != ':') throw ParseError(); - if (!o.insert(Object::value_type(key, ValuePtr(new Value(parseValue(s))))).second) throw ParseError(); + if (*s++ != ':') throw ParseError(*--s); + if (!o.insert(Object::value_type(key, ValuePtr(new Value(parseValue(s))))).second) throw ParseError(*s); while (Glib::Unicode::isspace(*s)) s++; } while (*s == ','); if (*s == '}') { @@ -173,12 +177,12 @@ namespace json { while (Glib::Unicode::isspace(*s)) s++; return o; } - throw ParseError(); + throw ParseError(*s); } Array parseArray(Glib::ustring::const_iterator & s) { Array a; while (Glib::Unicode::isspace(*s)) s++; - if (*s != '[') throw ParseError(); + if (*s != '[') throw ParseError(*s); do { s++; while (Glib::Unicode::isspace(*s)) s++; @@ -193,31 +197,33 @@ namespace json { s++; return a; } - throw ParseError(); + throw ParseError(*s); } Null parseNull(Glib::ustring::const_iterator & s) { - if (*s++ != 'n') throw ParseError(); - if (*s++ != 'u') throw ParseError(); - if (*s++ != 'l') throw ParseError(); - if (*s++ != 'l') throw ParseError(); + while (Glib::Unicode::isspace(*s)) s++; + if (*s++ != 'n') throw ParseError(*--s); + if (*s++ != 'u') throw ParseError(*--s); + if (*s++ != 'l') throw ParseError(*--s); + if (*s++ != 'l') throw ParseError(*--s); return Null(); } Boolean parseBoolean(Glib::ustring::const_iterator & s) { + while (Glib::Unicode::isspace(*s)) s++; if (*s == 't') { s++; - if (*s++ != 'r') throw ParseError(); - if (*s++ != 'u') throw ParseError(); - if (*s++ != 'e') throw ParseError(); + if (*s++ != 'r') throw ParseError(*--s); + if (*s++ != 'u') throw ParseError(*--s); + if (*s++ != 'e') throw ParseError(*--s); return true; } else if (*s == 'f') { s++; - if (*s++ != 'a') throw ParseError(); - if (*s++ != 'l') throw ParseError(); - if (*s++ != 's') throw ParseError(); - if (*s++ != 'e') throw ParseError(); + if (*s++ != 'a') throw ParseError(*--s); + if (*s++ != 'l') throw ParseError(*--s); + if (*s++ != 's') throw ParseError(*--s); + if (*s++ != 'e') throw ParseError(*--s); return false; } - throw ParseError(); + throw ParseError(*s); } } diff --git a/libjsonpp/test1.cpp b/libjsonpp/test1.cpp new file mode 100644 index 0000000..eaeccf7 --- /dev/null +++ b/libjsonpp/test1.cpp @@ -0,0 +1,114 @@ +#define BOOST_TEST_MODULE parsing +#include + +#include "jsonpp.h" + +BOOST_AUTO_TEST_CASE( parse_bool_true ) +{ + const Glib::ustring val(" true "); + auto itr = val.begin(); + BOOST_REQUIRE_EQUAL(true, json::parseBoolean(itr)); +} + +BOOST_AUTO_TEST_CASE( parse_bool_false ) +{ + const Glib::ustring val(" false "); + auto itr = val.begin(); + BOOST_REQUIRE_EQUAL(false, json::parseBoolean(itr)); +} + +BOOST_AUTO_TEST_CASE( parse_bool_invalid ) +{ + const Glib::ustring val(" meh "); + auto itr = val.begin(); + BOOST_CHECK_THROW(json::parseBoolean(itr), json::ParseError ); +} + +BOOST_AUTO_TEST_CASE( parse_int_48 ) +{ + const Glib::ustring val(" 48 "); + auto itr = val.begin(); + BOOST_REQUIRE_EQUAL(48, json::parseNumber(itr)); +} + +BOOST_AUTO_TEST_CASE( parse_float_pi ) +{ + const Glib::ustring val(" 3.14159265359 "); + auto itr = val.begin(); + BOOST_REQUIRE_CLOSE(3.14159, json::parseNumber(itr), 0.001); +} + +BOOST_AUTO_TEST_CASE( parse_array ) +{ + const Glib::ustring val(" [ 1, 2, 3, [ 4, 5 ], {\"val\": 6} ] "); + auto itr = val.begin(); + auto arr = json::parseArray(itr); + BOOST_REQUIRE_EQUAL(5, arr.size()); +} + +BOOST_AUTO_TEST_CASE( parse_null ) +{ + const Glib::ustring val(" null "); + auto itr = val.begin(); + json::parseNull(itr); +} + +BOOST_AUTO_TEST_CASE( parse_null_invalid ) +{ + const Glib::ustring val(" meh "); + auto itr = val.begin(); + BOOST_CHECK_THROW(json::parseBoolean(itr), json::ParseError); +} + +BOOST_AUTO_TEST_CASE( parse_object ) +{ + const Glib::ustring val(" { \"a\": 1, \"b\": 2 } "); + auto itr = val.begin(); + auto obj = json::parseObject(itr); + BOOST_REQUIRE_EQUAL(2, obj.size()); + BOOST_REQUIRE_EQUAL(1, boost::get(*obj["a"])); + BOOST_REQUIRE_EQUAL(2, boost::get(*obj["b"])); +} + +BOOST_AUTO_TEST_CASE( parse_string_simple ) +{ + const Glib::ustring val(" \"simple string\" "); + auto itr = val.begin(); + BOOST_REQUIRE_EQUAL("simple string", json::parseString(itr)); +} + +BOOST_AUTO_TEST_CASE( parse_object_withStringContainingQuote ) +{ + const Glib::ustring val(" { \"key1\": \"value1\", \"key2\": \"value\\\"2\\\"\", \"key3\": 3 } "); + auto itr = val.begin(); + auto obj = json::parseObject(itr); + BOOST_REQUIRE_EQUAL(3, obj.size()); + BOOST_REQUIRE_EQUAL("value1", boost::get(*obj["key1"])); + BOOST_REQUIRE_EQUAL("value\"2\"", boost::get(*obj["key2"])); + BOOST_REQUIRE_EQUAL(3, boost::get(*obj["key3"])); +} + +BOOST_AUTO_TEST_CASE( parse_string_invalid_missingOpeningQuote ) +{ + const Glib::ustring val(" simple string\" "); + auto itr = val.begin(); + BOOST_CHECK_THROW(json::parseString(itr), json::ParseError); +} + +BOOST_AUTO_TEST_CASE( parse_string_escapedQuote ) +{ + const Glib::ustring val(" \"A \\\"quoted\\\" string.\" "); + auto itr = val.begin(); + BOOST_REQUIRE_EQUAL("A \"quoted\" string.", json::parseString(itr)); +} + +BOOST_AUTO_TEST_CASE( parse_sample_complexFile ) +{ + std::ifstream inFile("initial/sample1.json"); + std::stringstream buffer; + buffer << inFile.rdbuf(); + Glib::ustring doc(buffer.str()); + Glib::ustring::const_iterator itr = doc.begin(); + json::Value obj = json::parseValue(itr); +} + diff --git a/libjsonpp/testpch.hpp b/libjsonpp/testpch.hpp new file mode 100644 index 0000000..442c866 --- /dev/null +++ b/libjsonpp/testpch.hpp @@ -0,0 +1,12 @@ +#ifdef BOOST_BUILD_PCH_ENABLED +#ifndef JSON_TESTPCH +#define JSON_TESTPCH + +#define BOOST_TEST_MODULE example +#include +#include "jsonpp.h" + +#endif +#endif + + -- cgit v1.2.3