diff --git a/src/bakatools.h b/src/bakatools.h index d214c63..b519a87 100644 --- a/src/bakatools.h +++ b/src/bakatools.h @@ -2,6 +2,7 @@ #ifdef BKMOD_ALL #define BKMOD_BASE + #define BKMOD_JSON #define BKMOD_STRING #define BKMOD_CONTAINER #define BKMOD_THREAD @@ -11,6 +12,11 @@ #include #include #endif +#ifdef BKMOD_JSON + #include + #include + #include +#endif #ifdef BKMOD_STRING #include #endif diff --git a/src/bakatools/json/json_lexer.cpp b/src/bakatools/json/json_lexer.cpp new file mode 100644 index 0000000..de3a9bd --- /dev/null +++ b/src/bakatools/json/json_lexer.cpp @@ -0,0 +1,144 @@ +#include "json_lexer.h" + +namespace Bk::Json +{ + std::string Token::to_string() + { + return Tools::string_format("%s %d", this->value, (int)this->type); + } + + char Lexer::get_next_char() + { + char c = data.pull(); + return c; + } + + bool Lexer::has_more_tokens() + { + return (bool)data.size(); + } + + char Lexer::get_without_white_space() + { + char c = ' '; + while ((c == ' ' || c == '\n')) + { + c = get_next_char(); // check + if ((c == ' ' || c == '\n') && !data.size()) + { + throw std::logic_error("Ran out of tokens"); + } + else if (!data.size()) + { + return c; + } + } + return c; + } + + Token Lexer::get_token() + { + char c; + if (!data.size()) + { + throw std::logic_error("Exhausted tokens"); + } + c = get_without_white_space(); + + Token token; + if (c == '"') + { + token.type = TokenType::STRING; + token.value = ""; + c = get_next_char(); + while (c != '"') + { + token.value += c; + c = get_next_char(); + } + } + else if (c == '{') + { + token.type = TokenType::CURLY_OPEN; + } + else if (c == '}') + { + token.type = TokenType::CURLY_CLOSE; + } + else if (c=='-' || (c >= '0' && c <= '9')) + { + token.type = TokenType::NUMBER; + token.value = ""; + token.value += c; + while ((c=='-')||(c >= '0' && c <= '9') || c == '.') + { + c = get_next_char(); + if (!data.size()) + { + break; + } + else + { + if ((c=='-')||(c >= '0' && c <= '9')||(c=='.')) + { + token.value += c; + } + else if (c == ',') + { + roll_back_char(c); + break; + } + else if (c == '\n') break; + else + { + throw std::logic_error("Bad Numeric format"); + } + } + } + } + else if(c=='f'){ + token.type = TokenType::BOOLEAN; + token.value = "false"; + data.pull(4); + } + else if(c=='t'){ + token.type = TokenType::BOOLEAN; + token.value = "true"; + data.pull(3); + } + else if(c=='n'){ + token.type = TokenType::NULL_TYPE; + token.value = "null"; + data.pull(3); + } + else if (c == '[') + { + token.type = TokenType::ARRAY_OPEN; + } + else if (c == ']') + { + token.type = TokenType::ARRAY_CLOSE; + } + else if (c == ':') + { + token.type = TokenType::COLON; + } + else if (c == ',') + { + token.type = TokenType::COMMA; + } + lexed_token.push_back(token); + return token; + } + + Token Lexer::roll_back_token() + { + auto token = lexed_token.back(); + return token; + } + + void Lexer::roll_back_char(char c) + { + data.push(c); + } +} \ No newline at end of file diff --git a/src/bakatools/json/json_lexer.h b/src/bakatools/json/json_lexer.h new file mode 100644 index 0000000..dc08329 --- /dev/null +++ b/src/bakatools/json/json_lexer.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "json_node.h" + +namespace Bk::Json +{ + enum class TokenType + { + CURLY_OPEN, + CURLY_CLOSE, + COLON, + STRING, + NUMBER, + ARRAY_OPEN, + ARRAY_CLOSE, + COMMA, + BOOLEAN, + NULL_TYPE + }; + + struct Token + { + std::string value; + TokenType type; + + std::string to_string(); + }; + + class Lexer + { + std::vector lexed_token; + + public: + Lexer() {} + Lexer(Type::DataStream data) + : data(data) {} + + Type::DataStream data; + + char get_next_char(); + char get_without_white_space(); + Token get_token(); + bool has_more_tokens(); + Token roll_back_token(); + void roll_back_char(char c); + }; +} \ No newline at end of file diff --git a/src/bakatools/json/json_node.cpp b/src/bakatools/json/json_node.cpp new file mode 100644 index 0000000..a9d3380 --- /dev/null +++ b/src/bakatools/json/json_node.cpp @@ -0,0 +1,78 @@ +#include "json_node.h" +namespace Bk::Json +{ + void Node::set_object(Object *object) + { + values.object = object; + type = Type::OBJECT; + } + + Object Node::get_object() + { + if (type == Type::OBJECT) + return *values.object; + throw std::logic_error("Improper return"); + } + + void Node::set_list(List* list) + { + values.list = list; + type = Type::LIST; + } + + List Node::get_list() + { + if (type == Type::LIST) + return *values.list; + throw std::logic_error("Improper return"); + } + + void Node::set_string(std::string* str) + { + values.s = str; + type = Type::STRING; + } + + std::string Node::get_string() + { + if (type == Type::STRING) + return *values.s; + throw std::logic_error("Improper return"); + } + + void Node::set_bool(bool value) + { + values.bValue = value; + type = Type::BOOLEAN; + } + + bool Node::get_bool() + { + if (type == Type::BOOLEAN) + return values.bValue; + throw std::logic_error("Improper return"); + } + + void Node::set_float(float value) + { + values.fValue = value; + type = Type::NUMBER; + } + + float Node::get_float() + { + if (type == Type::NUMBER) + return values.fValue; + throw std::logic_error("Improper return"); + } + + void Node::set_null() + { + type = Type::NULL_TYPE; + } + + bool Node::is_null() + { + return type == Type::NULL_TYPE; + } +} \ No newline at end of file diff --git a/src/bakatools/json/json_node.h b/src/bakatools/json/json_node.h new file mode 100644 index 0000000..7223b71 --- /dev/null +++ b/src/bakatools/json/json_node.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +namespace Bk::Json +{ + class Node; + + using Object = std::map>; + using List = std::vector>; + + class Node + { + public: + enum class Type + { + OBJECT, + LIST, + STRING, + NUMBER, + BOOLEAN, + NULL_TYPE + }; + void set_object(Object* object); + Object get_object(); + + void set_list(List* list); + List get_list(); + + void set_string(std::string* str); + std::string get_string(); + + void set_bool(bool value); + bool get_bool(); + + void set_float(float value); + float get_float(); + + void set_null(); + bool is_null(); + + private: + union Values { + Object* object; + List* list; + std::string* s; + float fValue; + bool bValue; + }; + Values values; + Type type; + }; +} \ No newline at end of file diff --git a/src/bakatools/json/json_parser.cpp b/src/bakatools/json/json_parser.cpp new file mode 100644 index 0000000..499ac59 --- /dev/null +++ b/src/bakatools/json/json_parser.cpp @@ -0,0 +1,264 @@ +#include "json_parser.h" + +namespace Bk::Json +{ + Parser::Parser(File& file) + { + Type::DataStream data; + if (file.exists()) + { + data = file.read(file.size()); + data.reverse(); + } + lexer = Lexer(data); + } + + Parser::Parser(const char* str, int length) + { + Type::DataStream data; + data.payload = std::vector(str, str + length); + data.reverse(); + lexer = Lexer(data); + } + + Parser::Parser(const std::string& str) + { + Type::DataStream data; + data.payload = std::vector(str.c_str(), str.c_str() + str.length()); + data.reverse(); + lexer = Lexer(data); + } + + std::shared_ptr Parser::parse() + { + Token token; + while (lexer.has_more_tokens()) { + try + { + token = lexer.get_token(); + switch (token.type) + { + case TokenType::CURLY_OPEN: + { + std::shared_ptr parsed_object = parse_object(); + if (!root) { + root = parsed_object; + } + break; + } + case TokenType::ARRAY_OPEN: { + std::shared_ptr parsed_list = parse_list(); + if (!root) { + root = parsed_list; + } + break; + } + + case TokenType::STRING: { + + std::shared_ptr parsed_string = parse_string(); + if (!root) { + root = parsed_string; + } + break; + } + case TokenType::NUMBER: { + std::shared_ptr parsed_number = parse_number(); + if (!root) { + root = parsed_number; + } + break; + } + + case TokenType::BOOLEAN: { + std::shared_ptr parsed_boolean = parse_boolean(); + break; + } + } + } + catch(std::logic_error e) + { + BK_INFO(e.what()); + auto node = std::shared_ptr(); + node->set_null(); + root = node; + break; + } + } + return root; + } + + std::shared_ptr Parser::parse_list() + { + std::shared_ptr node = std::make_shared(); + Json::List *list = new Json::List(); + bool has_completed = false; + Token next_token; + while (!has_completed) { + if (!lexer.has_more_tokens()) + { + throw std::logic_error("No more tokens"); + } + else + { + next_token = lexer.get_token(); + if (next_token.type == TokenType::COLON || next_token.type == TokenType::COMMA) + continue; + std::shared_ptr node; + switch (next_token.type) + { + case TokenType::ARRAY_OPEN: + { + node = parse_list(); + break; + } + case TokenType::CURLY_OPEN: + { + node = parse_object(); + break; + } + case TokenType::STRING: + { + node = parse_string(); + break; + } + case TokenType::NUMBER: + { + node = parse_number(); + break; + } + case TokenType::BOOLEAN: + { + node = parse_boolean(); + break; + } + case TokenType::NULL_TYPE: { + node = parse_null(); + break; + } + } + list->push_back(node); + if (next_token.type == TokenType::ARRAY_CLOSE) { + has_completed = true; + } + } + } + node->set_list(list); + return node; + } + + std::shared_ptr Parser::parse_object() + { + std::string key = ""; + std::shared_ptr node = std::make_shared(); + Json::Object *key_object_map = new Json::Object(); + bool has_completed = false; + bool no_key = true; + Token next_token; + while (!has_completed) + { + if (lexer.has_more_tokens()) + { + if (no_key) + { + next_token = lexer.get_token(); + if (next_token.type == TokenType::CURLY_CLOSE) + { + has_completed = true; + break; + } + if (next_token.type == TokenType::COLON || next_token.type == TokenType::COMMA) + continue; + key = next_token.value; + no_key = false; + } + next_token = lexer.get_token(); + if (next_token.type == TokenType::COLON || next_token.type == TokenType::COMMA) + continue; + std::shared_ptr node; + switch (next_token.type) + { + case TokenType::STRING: + { + (*key_object_map)[key] = parse_string(); + break; + } + case TokenType::ARRAY_OPEN: + { + (*key_object_map)[key] = parse_list(); + break; + } + case TokenType::NUMBER: + { + (*key_object_map)[key] = parse_number(); + break; + } + case TokenType::CURLY_OPEN: + { + (*key_object_map)[key] = parse_object(); + break; + } + case TokenType::BOOLEAN: + { + (*key_object_map)[key] = parse_boolean(); + break; + + } + case TokenType::NULL_TYPE: + { + (*key_object_map)[key] = parse_null(); + break; + } + } + if (next_token.type == TokenType::CURLY_CLOSE) + { + has_completed = true; + break; + } + no_key = true; + key = ""; + } + else + { + throw std::logic_error("No more tokens"); + } + } + node->set_object(key_object_map); + return node; + } + + std::shared_ptr Parser::parse_string() + { + std::shared_ptr node = std::make_shared(); + Token token = lexer.roll_back_token(); + std::string *sValue = new std::string(token.value); + node->set_string(sValue); + return node; + } + + std::shared_ptr Parser::parse_number() + { + std::shared_ptr node = std::make_shared(); + Token token = lexer.roll_back_token(); + float fValue = std::stof(token.value); + node->set_float(fValue); + return node; + } + + std::shared_ptr Parser::parse_boolean() + { + std::shared_ptr node = std::make_shared(); + Token token = lexer.roll_back_token(); + bool bValue = token.value == "True" ? true : false; + node->set_bool(bValue); + return node; + } + + std::shared_ptr Parser::parse_null() + { + std::shared_ptr node = std::make_shared(); + Token token = lexer.roll_back_token(); + node->set_null(); + return node; + } +} \ No newline at end of file diff --git a/src/bakatools/json/json_parser.h b/src/bakatools/json/json_parser.h new file mode 100644 index 0000000..d5c3f33 --- /dev/null +++ b/src/bakatools/json/json_parser.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include "json_lexer.h" + +namespace Bk::Json +{ + class Parser { + Type::DataStream data; + std::shared_ptr root; + std::unique_ptr current; + Lexer lexer; + + std::shared_ptr parse_object(); + std::shared_ptr parse_string(); + std::shared_ptr parse_number(); + std::shared_ptr parse_list(); + std::shared_ptr parse_boolean(); + std::shared_ptr parse_null(); + + public: + Parser(const Type::DataStream data) + :lexer(data) {} + Parser(File& file); + Parser(const char* data, int length); + Parser(const std::string& str); + + std::shared_ptr parse(); + }; +} \ No newline at end of file diff --git a/src/bakatools/string/format.h b/src/bakatools/string/format.h index bae24bc..9a7e2a8 100644 --- a/src/bakatools/string/format.h +++ b/src/bakatools/string/format.h @@ -28,9 +28,6 @@ namespace Bk::Tools { if (!std::is_floating_point()) throw std::invalid_argument("%f introduces floating point argument"); break; case 'b' : - if(arg) ss << "true"; - else ss << "false"; - return format_impl(ss, ++format, args...); case 's' : break; // etc. diff --git a/src/bakatoolspch.h b/src/bakatoolspch.h index 2c304ce..6df523c 100644 --- a/src/bakatoolspch.h +++ b/src/bakatoolspch.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include