From a51f13ebd65bfaab634d65c8ecd73bf643c006b4 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 1 Mar 2016 00:06:59 -0500 Subject: [PATCH] JSON: Inherited JSON object from Taskwarrior - Migrated from Nibbler to Pig. --- src/CMakeLists.txt | 1 + src/JSON.cpp | 490 +++++++++++++++++++++++++++++++++++++++++++++ src/JSON.h | 135 +++++++++++++ 3 files changed, 626 insertions(+) create mode 100644 src/JSON.cpp create mode 100644 src/JSON.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c347dd63..0ac06170 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ include_directories (${CMAKE_SOURCE_DIR} ${TIMEW_INCLUDE_DIRS}) set (timew_SRCS Grammar.cpp Grammar.h + JSON.cpp JSON.h Lexer.cpp Lexer.h LR0.cpp LR0.h Rules.cpp Rules.h) diff --git a/src/JSON.cpp b/src/JSON.cpp new file mode 100644 index 00000000..d8c7702e --- /dev/null +++ b/src/JSON.cpp @@ -0,0 +1,490 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2016, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +const char *json_encode[] = { + "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", + "\\b", "\\t", "\\n", "\x0b", "\\f", "\\r", "\x0e", "\x0f", + "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", + "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", + "\x20", "\x21", "\\\"", "\x23", "\x24", "\x25", "\x26", "\x27", + "\x28", "\x29", "\x2a", "\x2b", "\x2c", "\x2d", "\x2e", "\\/", + "\x30", "\x31", "\x32", "\x33", "\x34", "\x35", "\x36", "\x37", + "\x38", "\x39", "\x3a", "\x3b", "\x3c", "\x3d", "\x3e", "\x3f", + "\x40", "\x41", "\x42", "\x43", "\x44", "\x45", "\x46", "\x47", + "\x48", "\x49", "\x4a", "\x4b", "\x4c", "\x4d", "\x4e", "\x4f", + "\x50", "\x51", "\x52", "\x53", "\x54", "\x55", "\x56", "\x57", + "\x58", "\x59", "\x5a", "\x5b", "\\\\", "\x5d", "\x5e", "\x5f", + "\x60", "\x61", "\x62", "\x63", "\x64", "\x65", "\x66", "\x67", + "\x68", "\x69", "\x6a", "\x6b", "\x6c", "\x6d", "\x6e", "\x6f", + "\x70", "\x71", "\x72", "\x73", "\x74", "\x75", "\x76", "\x77", + "\x78", "\x79", "\x7a", "\x7b", "\x7c", "\x7d", "\x7e", "\x7f", + "\x80", "\x81", "\x82", "\x83", "\x84", "\x85", "\x86", "\x87", + "\x88", "\x89", "\x8a", "\x8b", "\x8c", "\x8d", "\x8e", "\x8f", + "\x90", "\x91", "\x92", "\x93", "\x94", "\x95", "\x96", "\x97", + "\x98", "\x99", "\x9a", "\x9b", "\x9c", "\x9d", "\x9e", "\x9f", + "\xa0", "\xa1", "\xa2", "\xa3", "\xa4", "\xa5", "\xa6", "\xa7", + "\xa8", "\xa9", "\xaa", "\xab", "\xac", "\xad", "\xae", "\xaf", + "\xb0", "\xb1", "\xb2", "\xb3", "\xb4", "\xb5", "\xb6", "\xb7", + "\xb8", "\xb9", "\xba", "\xbb", "\xbc", "\xbd", "\xbe", "\xbf", + "\xc0", "\xc1", "\xc2", "\xc3", "\xc4", "\xc5", "\xc6", "\xc7", + "\xc8", "\xc9", "\xca", "\xcb", "\xcc", "\xcd", "\xce", "\xcf", + "\xd0", "\xd1", "\xd2", "\xd3", "\xd4", "\xd5", "\xd6", "\xd7", + "\xd8", "\xd9", "\xda", "\xdb", "\xdc", "\xdd", "\xde", "\xdf", + "\xe0", "\xe1", "\xe2", "\xe3", "\xe4", "\xe5", "\xe6", "\xe7", + "\xe8", "\xe9", "\xea", "\xeb", "\xec", "\xed", "\xee", "\xef", + "\xf0", "\xf1", "\xf2", "\xf3", "\xf4", "\xf5", "\xf6", "\xf7", + "\xf8", "\xf9", "\xfa", "\xfb", "\xfc", "\xfd", "\xfe", "\xff" +}; + +//////////////////////////////////////////////////////////////////////////////// +json::value* json::value::parse (Pig& pig) +{ + json::value* v; + if ((v = json::object::parse (pig)) || + (v = json::array::parse (pig)) || + (v = json::string::parse (pig)) || + (v = json::number::parse (pig)) || + (v = json::literal::parse (pig))) + return v; + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +json::jtype json::value::type () +{ + return json::j_value; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::value::dump () const +{ + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +json::string::string (const std::string& other) +{ + _data = other; +} + +//////////////////////////////////////////////////////////////////////////////// +json::string* json::string::parse (Pig& pig) +{ + std::string value; + if (pig.getQuoted ('"', value)) + { + json::string* s = new json::string (); + s->_data = value; + return s; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +json::jtype json::string::type () +{ + return json::j_string; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::string::dump () const +{ + return std::string ("\"") + _data + "\""; +} + +//////////////////////////////////////////////////////////////////////////////// +json::number* json::number::parse (Pig& pig) +{ + double d; + if (pig.getNumber (d)) + { + json::number* s = new json::number (); + s->_dvalue = d; + return s; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +json::jtype json::number::type () +{ + return json::j_number; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::number::dump () const +{ + return format (_dvalue); +} + +//////////////////////////////////////////////////////////////////////////////// +json::number::operator double () const +{ + return _dvalue; +} + +//////////////////////////////////////////////////////////////////////////////// +json::literal* json::literal::parse (Pig& pig) +{ + if (pig.skipLiteral ("null")) + { + json::literal* s = new json::literal (); + s->_lvalue = nullvalue; + return s; + } + else if (pig.skipLiteral ("false")) + { + json::literal* s = new json::literal (); + s->_lvalue = falsevalue; + return s; + } + else if (pig.skipLiteral ("true")) + { + json::literal* s = new json::literal (); + s->_lvalue = truevalue; + return s; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +json::jtype json::literal::type () +{ + return json::j_literal; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::literal::dump () const +{ + if (_lvalue == nullvalue) return "null"; + else if (_lvalue == falsevalue) return "false"; + else return "true"; +} + +//////////////////////////////////////////////////////////////////////////////// +json::array::~array () +{ + for (auto& i : _data) + delete i; +} + +//////////////////////////////////////////////////////////////////////////////// +json::array* json::array::parse (Pig& pig) +{ + auto checkpoint = pig.cursor (); + + pig.skipWS (); + if (pig.skip ('[')) + { + pig.skipWS (); + + json::array* arr = new json::array (); + + json::value* value; + if ((value = json::value::parse (pig))) + { + arr->_data.push_back (value); + value = NULL; // Not a leak. Looks like a leak. + pig.skipWS (); + while (pig.skip (',')) + { + pig.skipWS (); + + if ((value = json::value::parse (pig))) + { + arr->_data.push_back (value); + pig.skipWS (); + } + else + { + delete arr; + throw format ("Error: missing value after ',' at position {1}", (int) pig.cursor ()); + } + } + } + + if (pig.skip (']')) + return arr; + else + throw format ("Error: missing ']' at position {1}", (int) pig.cursor ()); + + delete arr; + } + + pig.restoreTo (checkpoint); + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +json::jtype json::array::type () +{ + return json::j_array; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::array::dump () const +{ + std::string output; + output += "["; + + for (auto i = _data.begin (); i != _data.end (); ++i) + { + if (i != _data.begin ()) + output += ","; + + output += (*i)->dump (); + } + + output += "]"; + return output; +} + +//////////////////////////////////////////////////////////////////////////////// +json::object::~object () +{ + for (auto& i : _data) + delete i.second; +} + +//////////////////////////////////////////////////////////////////////////////// +json::object* json::object::parse (Pig& pig) +{ + auto checkpoint = pig.cursor (); + + pig.skipWS (); + if (pig.skip ('{')) + { + pig.skipWS (); + + json::object* obj = new json::object (); + + std::string name; + json::value* value; + if (json::object::parse_pair (pig, name, value)) + { + obj->_data.insert (std::pair (name, value)); + value = NULL; // Not a leak. Looks like a leak. + + pig.skipWS (); + while (pig.skip (',')) + { + pig.skipWS (); + + if (json::object::parse_pair (pig, name, value)) + { + obj->_data.insert (std::pair (name, value)); + pig.skipWS (); + } + else + { + delete obj; + throw format ("Error: missing value after ',' at position {1}", (int) pig.cursor ()); + } + } + } + + if (pig.skip ('}')) + return obj; + else + throw format ("Error: missing '}' at position {1}", (int) pig.cursor ()); + + delete obj; + } + + pig.restoreTo (checkpoint); + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +bool json::object::parse_pair ( + Pig& pig, + std::string& name, + json::value*& val) +{ + auto checkpoint = pig.cursor (); + + if (pig.getQuoted ('"', name)) + { + pig.skipWS (); + if (pig.skip (':')) + { + pig.skipWS (); + if ((val = json::value::parse (pig))) + return true; + else + throw format ("Error: missing value at position {1}", (int) pig.cursor ()); + } + else + throw format ("Error: missing ':' at position {1}", (int) pig.cursor ()); + } + + pig.restoreTo (checkpoint); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +json::jtype json::object::type () +{ + return json::j_object; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::object::dump () const +{ + std::string output; + output += "{"; + + for (auto i = _data.begin (); i != _data.end (); ++i) + { + if (i != _data.begin ()) + output += ","; + + output += "\"" + i->first + "\":"; + output += i->second->dump (); + } + + output += "}"; + return output; +} + +//////////////////////////////////////////////////////////////////////////////// +json::value* json::parse (const std::string& input) +{ + json::value* root = NULL; + + Pig n (input); + n.skipWS (); + + if (n.peek () == '{') root = json::object::parse (n); + else if (n.peek () == '[') root = json::array::parse (n); + else + throw format ("Error: expected '{' or '[' at position {1}", (int) n.cursor ()); + + // Check for end condition. + n.skipWS (); + if (!n.eos ()) + { + delete root; + throw format ("Error: extra characters found at position {1}", (int) n.cursor ()); + } + + return root; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::encode (const std::string& input) +{ + std::string output; + output.reserve ((input.size () * 6) / 5); // 20% increase. + + auto last = input.begin (); + for (auto i = input.begin (); i != input.end (); ++i) + { + switch (*i) + { + // Simple translations. + case '"': + case '\\': + case '/': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + output.append (last, i); + output += json_encode[(unsigned char)(*i)]; + last = i + 1; + + // Default NOP. + } + } + + output.append (last, input.end ()); + + return output; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string json::decode (const std::string& input) +{ + std::string output; + output.reserve (input.size ()); // Same size. + + size_t pos = 0; + + while (pos < input.length ()) + { + if (input[pos] == '\\') + { + ++pos; + switch (input[pos]) + { + // Simple translations. + case '"': output += '"'; break; + case '\\': output += '\\'; break; + case '/': output += '/'; break; + case 'b': output += '\b'; break; + case 'f': output += '\f'; break; + case 'n': output += '\n'; break; + case 'r': output += '\r'; break; + case 't': output += '\t'; break; + + // Compose a UTF8 unicode character. + case 'u': + output += utf8_character (utf8_codepoint (input.substr (++pos))); + pos += 3; + break; + + // If it is an unrecognized sequence, do nothing. + default: + output += '\\'; + output += input[pos]; + break; + } + ++pos; + } + else + { + size_t next_backslash = input.find ('\\', pos); + output.append (input, pos, next_backslash - pos); + pos = next_backslash; + } + } + + return output; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/JSON.h b/src/JSON.h new file mode 100644 index 00000000..34788a20 --- /dev/null +++ b/src/JSON.h @@ -0,0 +1,135 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2016, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_JSON +#define INCLUDED_JSON + +#include +#include +#include +#include + +namespace json +{ + enum jtype + { + j_value, // 0 + j_object, // 1 + j_array, // 2 + j_string, // 3 + j_number, // 4 + j_literal // 5 + }; + + class value + { + public: + value () {} + virtual ~value () {} + static value* parse (Pig&); + virtual jtype type (); + virtual std::string dump () const; + }; + + class string : public value + { + public: + string () {} + string (const std::string&); + ~string () {} + static string* parse (Pig&); + jtype type (); + std::string dump () const; + + public: + std::string _data; + }; + + class number : public value + { + public: + number () : _dvalue (0.0) {} + ~number () {} + static number* parse (Pig&); + jtype type (); + std::string dump () const; + operator double () const; + + public: + double _dvalue; + }; + + class literal : public value + { + public: + literal () : _lvalue (none) {} + ~literal () {} + static literal* parse (Pig&); + jtype type (); + std::string dump () const; + + public: + enum literal_value {none, nullvalue, falsevalue, truevalue}; + literal_value _lvalue; + }; + + class array : public value + { + public: + array () {} + ~array (); + static array* parse (Pig&); + jtype type (); + std::string dump () const; + + public: + std::vector _data; + }; + + class object : public value + { + public: + object () {} + ~object (); + static object* parse (Pig&); + static bool parse_pair (Pig&, std::string&, value*&); + jtype type (); + std::string dump () const; + + public: + std::map _data; + }; + + // Parser entry point. + value* parse (const std::string&); + + // Encode/decode for JSON entities. + std::string encode (const std::string&); + std::string decode (const std::string&); +} + +#endif +////////////////////////////////////////////////////////////////////////////////