diff --git a/src/coders/json.cpp b/src/coders/json.cpp new file mode 100644 index 00000000..7c96ed73 --- /dev/null +++ b/src/coders/json.cpp @@ -0,0 +1,639 @@ +#include "json.h" + +#include +#include +#include + +using namespace json; + +using std::string; +using std::vector; +using std::unique_ptr; +using std::unordered_map; +using std::stringstream; +using std::make_pair; + +inline bool is_digit(int c) { + return (c >= '0' && c <= '9'); +} + +inline bool is_whitespace(int c) { + return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\f'; +} + +inline bool is_identifier_start(int c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == '-' || c == '.'; +} + +inline bool is_identifier_part(int c) { + return is_identifier_start(c) || is_digit(c); +} + +inline int is_box(int c) { + switch (c) { + case 'B': + case 'b': + return 2; + case 'O': + case 'o': + return 8; + case 'X': + case 'x': + return 16; + } + return 10; +} + +inline int char2int(int c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'a' && c <= 'f') { + return 10 + c - 'a'; + } + if (c >= 'A' && c <= 'F') { + return 10 + c - 'A'; + } + return -1; +} + +inline number_t power(number_t base, int64_t power) { + number_t result = 1.0; + for (int64_t i = 0; i < power; i++) { + result *= base; + } + return result; +} + +string json::escape(string s) { + std::stringstream ss; + ss << '"'; + for (char c : s) { + switch (c) { + case '\n': ss << "\\n"; break; + case '\r': ss << "\\r"; break; + case '\t': ss << "\\t"; break; + case '\f': ss << "\\f"; break; + case '\b': ss << "\\b"; break; + case '"': ss << "\\\""; break; + case '\\': ss << "\\\\"; break; + default: + if (c < ' ') { + ss << "\\" << std::oct << (int)c; + break; + } + ss << c; + break; + } + } + ss << '"'; + return ss.str(); +} + +inline void newline(std::stringstream& ss, bool nice, uint indent, const std::string indentstr) { + if (nice) { + ss << "\n"; + for (uint i = 0; i < indent; i++) { + ss << indentstr; + } + } else { + ss << ' '; + } +} + +void stringify(Value* value, stringstream& ss, int indent, string indentstr, bool nice); +void stringifyObj(JObject* obj, stringstream& ss, int indent, string indentstr, bool nice); + +void stringify(Value* value, stringstream& ss, int indent, string indentstr, bool nice) { + if (value->type == valtype::object) { + stringifyObj(value->value.obj, ss, indent, indentstr, nice); + } + else if (value->type == valtype::array) { + vector& list = value->value.arr->values; + if (list.empty()) { + ss << "[]"; + return; + } + ss << '['; + for (uint i = 0; i < list.size(); i++) { + Value* value = list[i]; + if (i > 0 || nice) { + newline(ss, nice, indent, indentstr); + } + stringify(value, ss, indent+1, indentstr, nice); + if (i + 1 < list.size()) { + ss << ','; + } + } + if (nice) { + newline(ss, true, indent - 1, indentstr); + } + ss << ']'; + } else if (value->type == valtype::boolean) { + ss << (value->value.boolean ? "true" : "false"); + } else if (value->type == valtype::number) { + ss << value->value.num; + } else if (value->type == valtype::string) { + ss << escape(*value->value.str); + } +} + +void stringifyObj(JObject* obj, stringstream& ss, int indent, string indentstr, bool nice) { + if (obj->map.empty()) { + ss << "{}"; + return; + } + ss << "{"; + uint index = 0; + for (auto entry : obj->map) { + const std::string& key = entry.first; + if (index > 0 || nice) { + newline(ss, nice, indent, indentstr); + } + Value* value = entry.second; + ss << escape(key) << ": "; + stringify(value, ss, indent+1, indentstr, nice); + index++; + if (index < obj->map.size()) { + ss << ','; + } + } + if (nice) { + newline(ss, true, indent-1, indentstr); + } + ss << '}'; +} + +string json::stringify(JObject* obj, bool nice, string indent) { + stringstream ss; + stringifyObj(obj, ss, 1, indent, nice); + return ss.str(); +} + + +parsing_error::parsing_error(string message, + string filename, + string source, + uint pos, + uint line, + uint linestart) + : std::runtime_error(message), filename(filename), source(source), + pos(pos), line(line), linestart(linestart) { +} + +string parsing_error::errorLog() const { + std::stringstream ss; + uint linepos = pos - linestart; + ss << "parsing error in file '" << filename; + ss << "' at " << (line+1) << ":" << linepos << ": " << this->what() << "\n"; + size_t end = source.find("\n", linestart); + if (end == string::npos) { + end = source.length(); + } + ss << source.substr(linestart, end-linestart) << "\n"; + for (uint i = 0; i < linepos; i++) { + ss << " "; + } + ss << "^"; + return ss.str(); + +} + +JArray::~JArray() { + for (auto value : values) { + delete value; + } +} + +std::string JArray::str(size_t index) const { + return *values[index]->value.str; +} + +number_t JArray::num(size_t index) const { + return values[index]->value.num; +} + +JObject* JArray::obj(size_t index) const { + return values[index]->value.obj; +} + +JArray* JArray::arr(size_t index) const { + return values[index]->value.arr; +} + +bool JArray::flag(size_t index) const { + return values[index]->value.boolean; +} + +JArray& JArray::put(string value) { + valvalue val; + val.str = new string(value); + values.push_back(new Value(valtype::string, val)); + return *this; +} + +JArray& JArray::put(number_t value) { + valvalue val; + val.num = value; + values.push_back(new Value(valtype::number, val)); + return *this; +} + +JArray& JArray::put(bool value) { + valvalue val; + val.boolean = value; + values.push_back(new Value(valtype::boolean, val)); + return *this; +} + +JArray& JArray::put(JObject* value) { + valvalue val; + val.obj = value; + values.push_back(new Value(valtype::object, val)); + return *this; +} + +JArray& JArray::put(JArray* value) { + valvalue val; + val.arr = value; + values.push_back(new Value(valtype::array, val)); + return *this; +} + +JObject::~JObject() { + for (auto entry : map) { + delete entry.second; + } +} + +std::string JObject::str(std::string key, std::string def) const { + auto found = map.find(key); + if (found != map.end()) + return *found->second->value.str; + return def; +} + +number_t JObject::num(std::string key, number_t def) const { + auto found = map.find(key); + if (found != map.end()) + return found->second->value.num; + return def; +} + +JObject* JObject::obj(std::string key) const { + auto found = map.find(key); + if (found != map.end()) + return found->second->value.obj; + return nullptr; +} + +JArray* JObject::arr(std::string key) const { + auto found = map.find(key); + if (found != map.end()) + return found->second->value.arr; + return nullptr; +} + +bool JObject::flag(std::string key, bool def) const { + auto found = map.find(key); + if (found != map.end()) + return found->second->value.boolean; + return def; +} + +JObject& JObject::put(string key, number_t value) { + auto found = map.find(key); + if (found != map.end()) delete found->second; + valvalue val; + val.num = value; + map.insert(make_pair(key, new Value(valtype::number, val))); + return *this; +} + +JObject& JObject::put(string key, string value){ + auto found = map.find(key); + if (found != map.end()) delete found->second; + valvalue val; + val.str = new string(value); + map.insert(make_pair(key, new Value(valtype::string, val))); + return *this; +} + +JObject& JObject::put(string key, JObject* value){ + auto found = map.find(key); + if (found != map.end()) delete found->second; + valvalue val; + val.obj = value; + map.insert(make_pair(key, new Value(valtype::object, val))); + return *this; +} + +JObject& JObject::put(string key, JArray* value){ + auto found = map.find(key); + if (found != map.end()) delete found->second; + valvalue val; + val.arr = value; + map.insert(make_pair(key, new Value(valtype::array, val))); + return *this; +} + +JObject& JObject::put(string key, bool value){ + auto found = map.find(key); + if (found != map.end()) delete found->second; + valvalue val; + val.boolean = value; + map.insert(make_pair(key, new Value(valtype::boolean, val))); + return *this; +} + +Value::Value(valtype type, valvalue value) : type(type), value(value) { +} + +Value::~Value() { + switch (type) { + case valtype::object: delete value.obj; break; + case valtype::array: delete value.arr; break; + case valtype::string: delete value.str; break; + default: + break; + } +} + +Parser::Parser(string filename, string source) : filename(filename), source(source) { +} + +JObject* Parser::parse() { + char next = peek(); + if (next != '{') { + throw error("'{' expected"); + } + return parseObject(); +} + +bool Parser::hasNext() { + return pos < source.length(); +} + +char Parser::nextChar() { + if (!hasNext()) { + throw error("unexpected end"); + } + return source[pos++]; +} + +void Parser::expect(char expected) { + char c = peek(); + if (c != expected) { + throw error("'"+string({expected})+"' expected"); + } + pos++; +} + +char Parser::peek() { + skipWhitespace(); + if (pos >= source.length()) { + throw error("unexpected end"); + } + return source[pos]; +} + +parsing_error Parser::error(std::string message) { + return parsing_error(message, filename, source, pos, line, linestart); +} + +void Parser::skipWhitespace() { + while (hasNext()) { + char next = source[pos]; + if (next == '\n') { + line++; + linestart = ++pos; + continue; + } + if (is_whitespace(next)) { + pos++; + } else { + break; + } + } +} + +string Parser::parseName() { + char c = peek(); + if (!is_identifier_start(c)) { + if (c == '"') { + pos++; + return parseString(c); + } + throw error("identifier expected"); + } + int start = pos; + while (hasNext() && is_identifier_part(source[pos])) { + pos++; + } + return source.substr(start, pos-start); +} + +int64_t Parser::parseSimpleInt(int base) { + char c = peek(); + int index = char2int(c); + if (index == -1 || index >= base) { + throw error("invalid number literal"); + } + int64_t value = index; + pos++; + while (hasNext()) { + c = source[pos]; + while (c == '_') { + c = source[++pos]; + } + index = char2int(c); + if (index == -1 || index >= base) { + return value; + } + value *= base; + value += index; + pos++; + } + return value; +} + +number_t Parser::parseNumber(int sign) { + char c = peek(); + int base = 10; + if (c == '0' && pos + 1 < source.length() && + (base = is_box(source[pos+1])) != 10) { + pos += 2; + return parseSimpleInt(base); + } + int64_t value = parseSimpleInt(base); + if (!hasNext()) { + return value * sign; + } + c = source[pos]; + if (c == 'e' || c == 'E') { + pos++; + int s = 1; + if (peek() == '-') { + s = -1; + pos++; + } else if (peek() == '+'){ + pos++; + } + return sign * value * power(10.0, s * parseSimpleInt(10)); + } + if (c == '.') { + pos++; + int64_t expo = 1; + while (hasNext() && source[pos] == '0') { + expo *= 10; + pos++; + } + int64_t afterdot = 0; + if (hasNext() && is_digit(source[pos])) { + afterdot = parseSimpleInt(10); + } + expo *= power(10, fmax(0, log10(afterdot) + 1)); + c = source[pos]; + + number_t dvalue = (value + (afterdot / (number_t)expo)); + if (c == 'e' || c == 'E') { + pos++; + int s = 1; + if (peek() == '-') { + s = -1; + pos++; + } else if (peek() == '+'){ + pos++; + } + return sign * dvalue * power(10.0, s * parseSimpleInt(10)); + } + return dvalue; + } + return value; +} + +string Parser::parseString(char quote) { + std::stringstream ss; + while (hasNext()) { + char c = source[pos]; + if (c == quote) { + pos++; + return ss.str(); + } + if (c == '\\') { + pos++; + c = nextChar(); + if (c >= '0' && c <= '7') { + pos--; + ss << (char)parseSimpleInt(8); + continue; + } + switch (c) { + case 'n': ss << '\n'; break; + case 'r': ss << '\r'; break; + case 'b': ss << '\b'; break; + case 't': ss << '\t'; break; + case 'f': ss << '\f'; break; + case '\'': ss << '\\'; break; + case '"': ss << '"'; break; + case '\\': ss << '\\'; break; + case '/': ss << '/'; break; + case '\n': pos++; continue; + default: + throw error("'\\" + string({c}) + "' is an illegal escape"); + } + continue; + } + if (c == '\n') { + throw error("non-closed string literal"); + } + ss << c; + pos++; + } + throw error("unexpected end"); +} + +JObject* Parser::parseObject() { + expect('{'); + unique_ptr obj(new JObject()); + unordered_map& map = obj->map; + while (peek() != '}') { + string key = parseName(); + char next = peek(); + if (next != ':') { + throw error("':' expected"); + } + pos++; + map.insert(make_pair(key, parseValue())); + next = peek(); + if (next == ',') { + pos++; + } else if (next == '}') { + break; + } else { + throw error("',' expected"); + } + } + pos++; + return obj.release(); +} + +JArray* Parser::parseArray() { + expect('['); + unique_ptr arr(new JArray()); + vector& values = arr->values; + while (peek() != ']') { + values.push_back(parseValue()); + + char next = peek(); + if (next == ',') { + pos++; + } else if (next == ']') { + break; + } else { + throw error("',' expected"); + } + } + pos++; + return arr.release(); +} + +Value* Parser::parseValue() { + char next = peek(); + valvalue val; + if (is_identifier_start(next)) { + string literal = parseName(); + if (literal == "true") { + val.boolean = true; + return new Value(valtype::boolean, val); + } + if (literal == "false") { + val.boolean = false; + return new Value(valtype::boolean, val); + } + throw error("invalid literal"); + } + if (next == '{') { + val.obj = parseObject(); + return new Value(valtype::object, val); + } + if (next == '[') { + val.arr = parseArray(); + return new Value(valtype::array, val); + } + if (next == '-' || next == '+') { + pos++; + val.num = parseNumber(next == '-' ? -1 : 1); + return new Value(valtype::number, val); + } + if (is_digit(next)) { + val.num = parseNumber(1); + return new Value(valtype::number, val); + } + if (next == '"' || next == '\'') { + pos++; + val.str = new string(parseString(next)); + return new Value(valtype::string, val); + } + throw error("unexpected character '"+string({next})+"'"); +} \ No newline at end of file diff --git a/src/coders/json.h b/src/coders/json.h new file mode 100644 index 00000000..dc0343cb --- /dev/null +++ b/src/coders/json.h @@ -0,0 +1,129 @@ +#ifndef CODERS_JSON_H_ +#define CODERS_JSON_H_ + +#include +#include +#include +#include +#include + +typedef unsigned int uint; + +namespace json { + typedef double number_t; + class JObject; + class JArray; + class Value; + + extern std::string escape(std::string s); + extern std::string stringify(JObject* obj, bool nice, std::string indent); + class parsing_error : public std::runtime_error { + public: + std::string filename; + std::string source; + uint pos; + uint line; + uint linestart; + + parsing_error(std::string message, + std::string filename, + std::string source, + uint pos, + uint line, + uint linestart); + + std::string errorLog() const; + }; + + enum class valtype { + object, array, number, string, boolean + }; + + union valvalue { + JObject* obj; + JArray* arr; + std::string* str; + number_t num; + bool boolean; + }; + + class Value { + public: + valtype type; + valvalue value; + Value(valtype type, valvalue value); + ~Value(); + }; + + class JArray { + public: + std::vector values; + ~JArray(); + + std::string str(size_t index) const; + number_t num(size_t index) const; + JObject* obj(size_t index) const; + JArray* arr(size_t index) const; + bool flag(size_t index) const; + + inline size_t size() const { + return values.size(); + } + + JArray& put(number_t value); + JArray& put(std::string value); + JArray& put(JObject* value); + JArray& put(JArray* value); + JArray& put(bool value); + }; + + class JObject { + public: + std::unordered_map map; + ~JObject(); + + std::string str(std::string key, std::string def) const; + number_t num(std::string key, number_t def) const; + JObject* obj(std::string key) const; + JArray* arr(std::string key) const; + bool flag(std::string key, bool def) const; + + JObject& put(std::string key, number_t value); + JObject& put(std::string key, std::string value); + JObject& put(std::string key, JObject* value); + JObject& put(std::string key, JArray* value); + JObject& put(std::string key, bool value); + }; + + class Parser { + std::string filename; + std::string source; + uint pos = 0; + uint line = 0; + uint linestart = 0; + + void skipWhitespace(); + void expect(char expected); + char peek(); + char nextChar(); + bool hasNext(); + + std::string parseName(); + int64_t parseSimpleInt(int base); + number_t parseNumber(int sign); + std::string parseString(char chr); + + JArray* parseArray(); + JObject* parseObject(); + Value* parseValue(); + + parsing_error error(std::string message); + + public: + Parser(std::string filename, std::string source); + + JObject* parse(); + }; +} + +#endif // CODERS_JSON_H_ \ No newline at end of file