14 Commits

11 changed files with 799 additions and 364 deletions

View File

@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.18...3.28)
project(jjx
project(json
VERSION 1.0
LANGUAGES CXX)
@@ -11,7 +11,7 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
#include(cmake/CPM.cmake)
include(cmake/CPM.cmake)
file(GLOB_RECURSE HEADERS "include/*.hpp")
@@ -21,13 +21,28 @@ include_directories("include")
if (UNIX)
add_library(jjx SHARED ${SOURCES})
add_library(json SHARED ${SOURCES})
endif()
if (WIN32)
add_library(jjx STATIC ${SOURCES})
add_library(json STATIC ${SOURCES})
endif()
add_executable(jjx_demo main.cpp)
add_executable(json_cli main.cpp)
target_link_libraries(jjx_demo PUBLIC jjx)
CPMAddPackage(
NAME ArgsParser
URL https://git.redacted.cc/josh/ArgsParser/archive/Prerelease-3.zip
)
CPMAddPackage(
NAME mcolor
URL https://git.redacted.cc/maxine/mcolor/archive/Release-1.zip
)
target_include_directories(json_cli PUBLIC
${ArgsParser_SOURCE_DIR}/include
${mcolor_SOURCE_DIR}/include
)
target_link_libraries(json_cli PUBLIC json ArgsParser mcolor)

View File

@@ -1,6 +1,6 @@
# JJX - Josh's JSON and XML Library
# Josh's JSON Library
A bare-minimal, yet industrial-strength C++ 20 library for reading, writing, and serialization of JSON and XML file formats.
A bare-minimal, yet industrial-strength C++ 20 library for reading, writing, and serialization of JSON files.
![Static Badge](https://img.shields.io/badge/Lit-Based-%20)
![Static Badge](https://img.shields.io/badge/License-Public%20Domain-%20)
@@ -12,10 +12,27 @@ A bare-minimal, yet industrial-strength C++ 20 library for reading, writing, and
* Modern C++ (20)
* Simple, well-documented API.
* Included parsing, validation, and manipulation tool for the CLI.
* Static Library
* GCC and MSVC support
* Tested on Fedora Linux and Windows 10.
## API Overview
```cpp
tuple<value, string> parse(string);
string deparse(value, string whitespace = "");
struct json::value {...};
struct json::string {...};
struct json::number {...};
struct json::boolean {...};
struct json::object {...};
struct json::array {...};
```
## Contributing

24
cmake/CPM.cmake Normal file
View File

@@ -0,0 +1,24 @@
# SPDX-License-Identifier: MIT
#
# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors
set(CPM_DOWNLOAD_VERSION 0.38.7)
set(CPM_HASH_SUM "83e5eb71b2bbb8b1f2ad38f1950287a057624e385c238f6087f94cdfc44af9c5")
if(CPM_SOURCE_CACHE)
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
elseif(DEFINED ENV{CPM_SOURCE_CACHE})
set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
else()
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
endif()
# Expand relative path. This is important if the provided path contains a tilde (~)
get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE)
file(DOWNLOAD
https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM}
)
include(${CPM_DOWNLOAD_LOCATION})

View File

@@ -1,9 +0,0 @@
#pragma once
#include <iostream>
#include <vector>
#include <memory>
#include <optional>
#include <map>
#include <JJX/JSON.hpp>

View File

@@ -0,0 +1,86 @@
#pragma once
#include <exception>
#include <sstream>
#include <format>
namespace json
{
class Exception : public std::exception {
private:
std::string whatstr;
public:
Exception(std::string base, std::string source, int index) {
//std::ostringstream s;
int counter = 0;
int line = 1;
int column = 0;
std::string lastline = "";
std::string whitespace = "";
for (auto c: source) {
if (counter == index) {
break;
}
if (c == '\n') {
line++;
column = 0;
lastline = "";
whitespace = "";
} else if (c == '\t') {
column++;
lastline += " ";
whitespace += " ";
} else {
column++;
lastline += c;
whitespace += " ";
}
counter++;
}
while (counter < source.size()) {
auto c = source[counter];
if (c == '\n') {
break;
}
lastline += c;
counter++;
}
// TODO: Migrate the below code bits to std::format
/*
s << base << " at line " << line << ", column " << column << std::endl;
s << lastline << std::endl;
s << whitespace << "^";*/
whatstr = std::format("{} at line {}, column {}\n{}\n{}^",
base, line, column,
lastline,
whitespace);
};
virtual const char* what() const throw() {
return whatstr.c_str();
}
};
class file_error : public std::runtime_error
{
};
class io_error : public std::runtime_error
{
};
class parse_error : public std::runtime_error
{
};
class json_runtime_error : public std::runtime_error
{
};
}

View File

@@ -12,12 +12,16 @@
#include <optional>
#include <string>
#include <cassert>
#include <vector>
// TODO: API compatibility with json::value and derived types.
// TODO: Auto-conversion from json::value *to* represented type.
/// Redacted Software JSON API - Parse, Serialize, Validate, and Build JSON files.
namespace JJX::json {
namespace json {
std::string format_error(std::string base, std::string source, int index);
/// An enumeration that represents the different types of tokens that exist.
/// A token is an atomic chunk of a JSON file's text, which is the smallest piece of syntax that can be considered in isolation.
@@ -80,24 +84,29 @@ namespace JJX::json {
std::optional<std::vector<value>> array;
std::optional<std::map<std::string, value>> object;
/// The default constructor initializes to a JSON null value.
/// Constructs a JSON value that represents null, aka an empty field.
explicit value();
/// Constructs a json value that represents the corresponding number.
value(double v);
value(int v);
value(float v);
/// Constructs a json value that represents a string.
value(const std::string& v);
explicit value(double v);
/// Constructs a JSON value that represents the corresponding number.
/// @note JSON has a unified number type, so this integer will be stored as a double.
explicit value(int v);
value(bool v);
/// Constructs a JSON value that represents the corresponding number.
/// @note JSON has a unified number type, so this float will be stored as a double.
explicit value(float v);
/// Constructs a json value that represents a string.
explicit value(const std::string& v);
/// Constructs a JSON value that represents a boolean.
explicit value(bool v);
/// Constructs a json value that represents an array.
explicit value(const std::vector<value>& v);
/// Constructs a json value that represents an object.
explicit value(const std::map<std::string, value>& v);
/// Constructs a json value that represents an object, from a key-value pair.
explicit value(const std::pair<std::string, value>& kvp) : object({kvp}), type(value_type::object) {}
/// Constructs a json value that represents an object, from a list of key-value pairs.
//explicit value(const std::vector<std::pair<std::string, value>>& kvp_list);
/// Assigns this value to a number, and sets the value_type::number.
value& operator=(double in);
@@ -110,6 +119,7 @@ namespace JJX::json {
/// Assigns this value to a map, and sets the value_type::map.
value& operator=(const std::map<std::string, value>& in);
explicit operator double() const;
explicit operator std::string() const;
explicit operator bool() const;
@@ -118,32 +128,86 @@ namespace JJX::json {
/// Returns this value's data, converted to a json::object, which is a specialization of this type.
/// @see struct object
[[nodiscard]] struct object as_object() const;
[[nodiscard]] struct object as_object_value() const;
/// Returns this value's data, converted to a json::array, which is a specialization of this type.
[[nodiscard]] struct array as_array() const;
[[nodiscard]] struct string as_string() const;
[[nodiscard]] struct number as_number() const;
[[nodiscard]] struct boolean as_boolean() const;
[[nodiscard]] struct array as_array_value() const;
/// Returns this value's data, converted to a json::string, which is a specialization of this type.
[[nodiscard]] struct string as_string_value() const;
/// Returns this value's data, converted to a json::number, which is a specialization of this type.
[[nodiscard]] struct number as_number_value() const;
/// Returns this value's data, converted to a json::boolean, which is a specialization of this type.
[[nodiscard]] struct boolean as_boolean_value() const;
/// Returns this value's object data as a standard map of JSON objects.
std::map<std::string, value> as_object() const;
/// Returns this value's array data as a standard vector of JSON objects.
std::vector<value> as_array() const;
/// Returns this value's stored number in floating-point format.
float as_float() const;
/// Returns this value's stored number in floating-point format.
/// @param def A default value to return if this object does not contain a number.
float as_float_or(float def) const;
double as_double() const;
double as_double_or(double def) const;
int as_int() const { return number.value(); }
int as_int_or(int def) const {return number.value_or(def);}
std::string as_string() const;
std::string as_string_or(const std::string& def) const;
bool as_bool() const { return boolean.value(); }
bool as_bool_or(bool def) const { return boolean.value_or(def);}
/// Converts all JSON values contained in this object / array to the derived json value types.
void convert_descendants();
#pragma region Object Access Members
/// @return The json value associated with the given key.
/// @throws std::runtime_error if value_type != object
value& at(const std::string& key);
/// @throws std::runtime_error if value_type != array
value& at(int index);
/// @see object::at()
/// @throws std::runtime_error if value_type != object
value& operator[] (const std::string& key);
/// @throws std::runtime_error if value_type != object
const value& operator[] (const std::string& key) const;
/// @throws std::runtime_error if value_type != array
value& operator[] (int index);
/// @throws std::runtime_error if value_type != array
const value& operator[] (int index) const;
/// @return True if a key-value pair in this object has a key that matches the given value.
/// @throws std::runtime_error if value_type != array
bool contains(const std::string& key);
#pragma endregion
bool is(value_type type) const;
bool is_number() const;
#pragma region Array Access Members
value& operator[] (int key);
bool is_string() const;
bool is_array() const;
bool is_object() const;
bool is_boolean() const;
bool is_null() const;
#pragma endregion
};
@@ -158,13 +222,14 @@ namespace JJX::json {
};
struct number : value {
/// The default constructor initializes to floating-point literal zero.
explicit number() : value(0.0) {}
explicit number(double v) : value(v) {}
explicit number();
explicit number(double v);
};
struct boolean : value {
/// The default constructor initializes to false.
explicit boolean() : value(false) {}
explicit boolean(bool v) : value(v) {}
explicit boolean();
explicit boolean(bool v);
};
/// A specialized json::value which provides STL compatibility with std::map, and other functions for convenience.
@@ -172,18 +237,36 @@ namespace JJX::json {
struct object : value
{
/// The default constructor initializes to an empty map.
explicit object() : value(std::map<std::string, value>{}) {}
explicit object();
/// Constructs this object from a list of key-value pairs, in the form of std::map.
explicit object(const std::map<std::string, value>& v) : value(v) {}
explicit object(const std::map<std::string, value>& v);
/// Adds a key-value pair to this object.
void insert(const std::pair<std::string, value>& kvp);
/// Adds a key-value pair to this object.
/// @param key The key to associate with the value.
void insert(const std::string& key, const value& val);
void insert(const std::string& key, float value)
{
insert(key, json::number(value));
}
void insert(const std::string& key, int value)
{
insert(key, json::number(value));
}
void insert(const std::string& key, double value)
{
insert(key, json::number(value));
}
void insert(const std::string& key, const std::string& value)
{
insert(key, json::string(value));
}
/// Shorthand operator for adding a key-value pair to this object,
json::object& operator+=(const std::pair<std::string, value>& kvp);
object& operator+=(const std::pair<std::string, value>& kvp);
/// Shorthand operator for adding a boolean-value to this object, with a string key.
/*json::object& operator+=(const std::pair<std::string, bool>& kvp);
@@ -229,6 +312,7 @@ namespace JJX::json {
explicit array(const array& v) = default;
void push_back(const value& element);
size_t size() const;
value& operator+= (const value& v);

122
include/lexer.hpp Normal file
View File

@@ -0,0 +1,122 @@
#include <json.hpp>
#pragma once
/// @namespace json::lexers
///
/// @brief This namespace provides functions for lexical analysis (tokenizing)
///
/// Each function in this namespace is responsible for "lexing" a specific type of token,
/// from a raw JSON string, starting from a given index. The typically return a tuple
/// containing the recognized token, the new index in the string after consuming the token,
/// and the string value of the 'lexeme'.
namespace json::lexers {
/// Lexes and skips over whitespace characters in a JSON string.
///
/// This function consumes all consecutive whitespace characters (space, tab, newline,
/// carriage return) starting from the given index.
/// @param raw_json The complete JSON string to be lexed.
/// @param index The starting index in `raw_json` from which to begin lexing.
/// @return The index in `raw_json` immediately after the last consumed whitespace character.
/// If no whitespace is found at the starting index, the original index is returned.
int lex_whitespace(std::string raw_json, int index);
/// Lexes a single JSON syntax character.
///
/// This function expects to find a specific JSON structural character (`{`, `}`, `[`, `]`,
/// `:`, or `,`) at the given index and tokenizes it.
///
/// @param raw_json The complete JSON string to be lexed.
/// @param index The starting point in `raw_json` where the syntax character is expected.
/// @return A `std::tuple` containing:
/// - `json::token`: The token representing the lexed syntax character.
/// - `int`: The index in `raw_json` immediately after the consumed character/
/// - `std::string`: The string value of the lexed syntax character (e.g. "{", ":").
/// @throws std::runtime_error if an unexpected character is encountered at the given index.
std::tuple<json::token, int, std::string> lex_syntax(std::string raw_json, int index);
/// Lexes a JSON string literal.
///
/// This function expects a double-quoted string (`"..."`) at the given index, including handling of escape sequences.
///
/// @param raw_json The complete JSON string to be lexed.
/// @param original_index The starting index in `raw_json` where the string literal is expected to begin (at the opening quote).
/// @return A `std::tuple` containing:
/// - `json::token`: The token representing the lexed string literal.
/// - `int`: The index in `raw_json` immediately after the closing quote of the string.
/// - `std::string`: The unescaped string value of the literal.
/// @throws std::runtime_error if the string is malformed (e.g., unclosed quote, invalid escape sequence).
std::tuple<json::token, int, std::string> lex_string(std::string raw_json, int original_index);
/// Lexes a JSON number literal.
///
/// This function expects a valid JSON number (integer, float, scientific notation)
/// at the given index and tokenizes it.
///
/// @param raw_json The complete JSON string to be lexed.
/// @param original_index The starting index in `raw_json` where the number literal is expected to begin.
/// @return A `std::tuple` containing:
/// - `json::token` The token representing the lexed number literal.
/// - `int`: The index in `raw_json` immediately after the last digit or part of the number.
/// - `std::string`: The string representation of the lexed number (e.g., "123", "3.14", "1e-6").
/// @throws std::runtime_error if the number if malformed.
std::tuple<json::token, int, std::string> lex_number(std::string raw_json, int original_index);
/// Lexes a specific keyword in the JSON string.
///
/// This is a general-purpose function for lexing fixed-string keywords like "true", "false, or "null"
/// It checks if the `keyword` matches the substring at `original_index`.
///
/// @param raw_json The complete JSON string to be lexed.
/// @param keyword The specific keyword string to match (e.g., "true", "false", "null").
/// @param type The `json::token_type` to assign if the keyword is successfully matched.
/// @param original_index The starting index in `raw_json` where the keyword is expected to begin.
/// @return A `std::tuple` containing:
/// - `json::token`: The token representing the lexed keyword.
/// - `int`: The index in `raw_json` immediately after the consumed keyword.
/// - `std::string`: The string value of the matched keyword.
/// @throws std::runtime_error if the expected keyword is not found at the given index.
std::tuple<json::token, int, std::string> lex_keyword(std::string raw_json, std::string keyword, json::token_type type, int original_index);
/// Lexes the JSON `null` literal.
///
/// This is a specialized version of `lex_keyword` for the "null" literal.
///
/// @param raw_json The complete JSON string to be lexed.
/// @param index The starting index in `raw_json` where "null" is expected to begin.
/// @return A `std::tuple` containing:
/// - `json::token`: The token representing the lexed `null` literal.
/// - `int`: The index in `raw_json` immediately after "null".
/// - `std::string`: The string value "null".
/// @throws std::runtime_error if "null" is not found at the given index.
std::tuple<json::token, int, std::string> lex_null(std::string raw_json, int index);
/// Lexes the JSON `true` literal.
///
/// This is a specialized version of `lex_keyword` for the "true" literal.
///
/// @param raw_json The complete JSON string to be lexed.
/// @param index The starting index in `raw_json` where "true" is expected to begin.
/// @return A `std::tuple` containing:
/// - `json::token`: The token representing the lexed `true` literal.
/// - `int`: The index in `raw_json` immediately after "true".
/// - `std::string`: The string value "true".
/// @throws std::runtime_error if "true" is not found at the given index.
std::tuple<json::token, int, std::string> lex_true(std::string raw_json, int index);
/// Lexes the JSON `false` literal.
///
/// This is a specialized version of `lex_keyword` for the "false" literal.
///
/// @param raw_json The complete JSON string to be lexed.
/// @param index The starting index in `raw_json` where "false" is expected to begin.
/// @return A `std::tuple` containing:
/// - `json::token`: The token representing the lexed `false` literal.
/// - `int`: The index in `raw_json` immediately after "false".
/// - `std::string`: The string value "false".
/// @throws std::runtime_error if "false: is not found at the given index.
std::tuple<json::token, int, std::string> lex_false(std::string raw_json, int index);
}

332
main.cpp
View File

@@ -1,8 +1,17 @@
#include <JJX/JJX.hpp>
/// A command-line tool for JSON parsing and file manipulation.
#include <json.hpp>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <vector>
#include <string>
using namespace JJX;
#include <Colors.hpp>
#include <ArgsParser.hpp>
#include <filesystem>
#include "AnsiEscapeCodes.hpp"
/// Open a text file and return the contents.
std::string read_file(const std::string& file_path)
@@ -11,9 +20,8 @@ std::string read_file(const std::string& file_path)
if (!file)
throw std::runtime_error("We couldn't find the file: " + file_path);
std::streamsize file_size;
file.seekg(0, std::ios::end);
file_size = file.tellg();
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::string file_content(file_size, '\0');
@@ -34,175 +42,169 @@ void parse_json_file(const std::string& file_path) {
std::cout << json::deparse(text) << std::endl;
}
struct Vector3
void show_help() {
std::cout << "Usage:" << std::endl;
std::cout << ">json [--options] content" << std::endl;
std::cout << "Options: " << std::endl;
std::cout << "--help --about --license " << std::endl;
}
void show_license() {
std::cout << "" << std::endl;
}
void show_version()
{
float x, y, z;
};
/// Creates json array out of Vector3 type.
json::value vector3_to_json(const Vector3& v) {
//auto x = json::number(input.x);
//auto y = json::number(input.y);
//auto z = json::number(input.z);
return json::array({v.x, v.y, v.z});
}
/// Creates Vector3 type out of json array object.
Vector3 json_to_vector3(const json::value& obj) {
Vector3 value;
value.x = obj.array->at(0).number.value_or(0);
value.y = obj.array->at(1).number.value_or(0);
value.z = obj.array->at(2).number.value_or(0);
return {value};
}
/// I personally recommend using the following signatures for serializing and deserializing your objects
/// as this will be considered the standard interface for the library, going forward.
/// It may end up being a template so don't worry so much about inheriting these structs, specifically.
struct json_serializable {
// json::value serialize() const { ... }
virtual json::value serialize() const = 0; //{ /* Create a json::value, add data, and return. */; };
};
struct json_deserializable {
explicit json_deserializable(const json::value&) = delete;
};
struct json_convertible : json_serializable, json_deserializable {};
struct product_info
{
[[nodiscard]] json::value serialize() const;
product_info() = default;
/// @schema object {string category, string description, boolean is_available, string name, number price, array[@review] reviews}
/// @note This class design assumes all entries in `reviews` are serializable to @struct review,
///
explicit product_info(const json::value& jv);
struct rating_metrics {
double average;
int total;
};
/// @schema { rating : number, review_text : string, user : string }
struct review {
[[nodiscard]] json::value serialize() const;
explicit review(const json::value& jv);
int rating;
std::string review_text;
std::string user;
};
std::string category;
std::string description;
bool is_available;
std::string manufacturer;
std::string name;
double price;
std::string product_id;
rating_metrics ratings;
std::string release_date;
std::vector<review> reviews;
};
json::value product_info::serialize() const {
json::object root;
root += {"category", this->category};
root += {"manufacturer", this->manufacturer};
root += {"is_available", this->is_available};
root += {"description", this->description};
root += {"name", this->name};
auto review_list = json::array();
for (auto& rev : this->reviews)
review_list += rev.serialize();
root["ratings"] = {
{"average", this->ratings.average},
{"total", this->ratings.total}
};
root += {"reviews", review_list};
root += {"taters", 5.0};
return root;
}
product_info::product_info(const json::value &jv) {
if (jv.type != json::value_type::object)
throw std::runtime_error("Malformed JSON for product info!");
auto jvo = jv.as_object();
this->category = jvo["category"].string.value();
this->description = jvo["description"].string.value();
this->is_available = jvo["is_available"].boolean.value();
this->manufacturer = jvo["manufacturer"].string.value();
this->name = jvo["name"].string.value();
this->price = double(jvo["price"]);
for (auto& review_json : jvo["reviews"].as_array()) {
this->reviews.emplace_back(review_json);
}
this->ratings.average = jvo["ratings"]["average"].number.value();
this->ratings.total = jvo["ratings"]["total"].number.value();
/// Parses a string of JSON and outputs any syntax errors, if present.
void validate_json_string(const std::string& input) {
}
json::value product_info::review::serialize() const {
json::object obj = json::object();
obj["rating"] = json::number(rating);
obj["review_text"] = json::string(review_text);
obj["user"] = json::string(user);
return obj;
}
product_info::review::review(const json::value &jv) {
if (jv.type != json::value_type::object)
throw std::runtime_error("Malformed JSON for review!");
}
void test_product_info()
{
std::cout << "Testing parsing of product_info file." << std::endl;
//parse_json_file("../samples/product_info.json");
auto file_contents = read_file("../samples/product_info.json");
auto [text, err] = json::parse(file_contents);
if (!err.empty()) {
std::cerr << err << std::endl;
return;
}
std::cout << "Testing deserialization of product_info json." << std::endl;
// Construct from json object.
product_info test(text);
std::cout << "Testing serialization of product_info struct." << std::endl;
json::value result = test.serialize();
result.convert_descendants();
std::cout << json::deparse(result);
}
void test_tile_data() {
//parse_json_file("../samples/tiles.json");
}
void test_widgets() {
void echo_json_formatted(const std::string& input) {
}
// Some progress has been made on small scale.
void show_readme() {
std::cout << Colors::Oranges::Coral.ToEscapeCode(false);
std::cout << "A cli tool for parsing json files." << std::endl;
std::cout << "Developed & maintained by josh@redacted.cc" << std::endl;
std::cout << "This program is included with the Redacted Software JSON Library for C++." << std::endl;
std::cout << "https://git.redacted.cc/josh/json" << std::endl;
std::cout << "Run `json --help` for help information." << std::endl;
std::cout << "Run `json --license` for license information." << std::endl;
std::cout << mcolor::AnsiEscapeCodes::ResetAll;
}
///
void print_all_args(int argc, char* argv[]) {
}
/// Read options and their args, remove from master args list.
int json_app(std::vector<std::string> params) {
bool color_output = true;
std::string input_content;
bool read_from_stdin = true;
bool verbose = false;
ArgsParser args(params);
//std::vector<std::string> args(argv, argv+argc);
// Terminal Options - Parsed first, close the program w/o further action.
if (args.has_flag({"--about", "--info", "--readme"})) {
show_readme(); return 0;
}
if (args.has_flag({"--help", "-h", "--h"})) {
show_help(); return 0;
}
if (args.has_flag({"--license", "-L"})) {
show_license(); return 0;
}
if (args.has_flag("--raw")) color_output = false;
if (args.has_flag("--colorful")) color_output = true;
if (args.has_flag("--quiet")) verbose = false;
if (args.has_flag("--verbose")) verbose = true;
if (args.has_flag("--file")) {
if (args.has_flag_arg("--file"))
{
auto file_name = args.consume_flag_arg("--file").value();
if (!std::filesystem::exists(file_name)) {
std::cout << "Error: File " << file_name << "not found!";
}
input_content = read_file(file_name);
read_from_stdin = false;
}
}
if (read_from_stdin)
{
std::string line;
std::getline(std::cin, line);
// TODO: Add mode for merge?
input_content = line;
//while(std::getline(std::cin, line) && !line.empty()) {
// input_content += line;
//}
}
if (args.has_flag("--validate"))
{
validate_json_string(input_content); return 0;
}
std::vector<std::string> remaining = args.get_remaining_args();
for (auto& e : remaining) {
std::cout << e << " ";
}
std::cout << std::endl;
if (input_content.empty()) {
if (!verbose)
std::cout << "No input provided!" << std::endl;;
return 0;
}
auto query_tokens = args.get_remaining_args();
if (!query_tokens.empty())
{
}
auto[val, err] = json::parse(input_content);
if (!err.empty()) {
std::cerr << "Error reading JSON: " << err << std::endl;
return -1;
}
std::string roundtripped = json::deparse(val);
std::cout << roundtripped << std::endl;
return 0;
}
int main(int argc, char* argv[]) {
test_product_info();
test_tile_data();
ArgsParser pre_parser(argc, argv);
if (pre_parser.has_flag("--test-all")) {
int readme = json_app({"--readme"});
int help = json_app({"--help"});
int license = json_app({"--license"});
int version = json_app({"--version"});
int result_0 = json_app({"--file", "nonexistant.json"});
int result_1 = json_app({"--validate", "{\"test\":42069}"});
return 0;
} else
return json_app(pre_parser.get_remaining_args());
}

View File

@@ -1,6 +0,0 @@
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>

View File

@@ -1,8 +1,23 @@
#include <JJX/JJX.hpp>
#include <JJX/JSON.hpp>
#include <json.hpp>
#include <lexer.hpp>
#include <sstream>
#include <format>
namespace json {
struct serializable {
virtual json::value serialize() const = 0;
};
struct deserializable {
virtual void deserialize(const json::value&) = 0;
};
namespace JJX::json {
std::string format_error(std::string base, std::string source, int index) {
std::ostringstream s;
int counter = 0;
@@ -48,130 +63,9 @@ namespace JJX::json {
s << whitespace << "^";
return s.str();
}
int lex_whitespace(std::string raw_json, int index) {
while (std::isspace(raw_json[index])) {
if (index == raw_json.length()) {
break;
}
index++;
}
return index;
}
std::tuple<json::token, int, std::string> lex_syntax(std::string raw_json, int index)
{
json::token token{"", token_type::syntax, index};
std::string value = "";
auto c = raw_json[index];
if (c == '[' || c == ']' || c == '{' || c == '}' || c == ':' || c == ',') {
token.value += c;
index++;
}
return {token, index, ""};
}
std::tuple<json::token, int, std::string> lex_string(std::string raw_json, int original_index) {
int index = original_index;
json::token token {"", token_type::string, index};
std::string value = "";
auto c = raw_json[index];
if (c != '"') {
return {token, original_index, ""};
}
index++;
// TODO: handle nested quotes
while (c = raw_json[index], c != '"') {
if (index == raw_json.length()) {
return {token, index, format_error("Unexpected EOF while lexing string", raw_json, index)};
}
token.value += c;
index++;
}
index++;
return {token, index, ""};
}
std::tuple<json::token, int, std::string> lex_number(std::string raw_json, int original_index) {
int index = original_index;
json::token token {"", token_type::number, index};
std::string value = "";
bool decimal_present = false;
// Negative numbers.
if (raw_json[index] == '-') {
token.value += raw_json[index];
index++;
}
while(true) {
if (index == raw_json.length())
break;
auto c = raw_json[index];
if (c == '.') {
if (decimal_present)
break;
decimal_present = true;
token.value += c;
}
// Scientific notation.
else if (c == 'E' || c == 'e') {
token.value += c;
index++;
if (raw_json[index] == '-') {
token.value += raw_json[index];
index++;
}
continue; // Loop early.
}
// Only regex non-numeric values if we didn't catch it earlier.
else if (!(c >= '0' && c <= '9'))
break;
token.value += c;
index++;
}
return {token, index, ""};
}
std::tuple<json::token, int, std::string> lex_keyword(std::string raw_json, std::string keyword, json::token_type type, int original_index) {
int index = original_index;
json::token token{"", type, index};
while (keyword[index - original_index] == raw_json[index]) {
if (index == raw_json.length()) {
break;
}
index++;
}
if (index - original_index == keyword.length()) {
token.value = keyword;
}
return {token, index, ""};
}
std::tuple<json::token, int, std::string> lex_null(std::string raw_json, int index)
{
return lex_keyword(raw_json, "null", token_type::null, index);
}
std::tuple<json::token, int, std::string> lex_true(std::string raw_json, int index)
{
return lex_keyword(raw_json, "true", token_type::boolean, index);
}
std::tuple<json::token, int, std::string> lex_false(std::string raw_json, int index) {
return lex_keyword(raw_json, "false", token_type::boolean, index);
}
json::value& value::operator=(double in)
{
@@ -218,26 +112,52 @@ namespace JJX::json {
value::operator std::map<std::string, value>() const { return object.value(); }
struct json::object json::value::as_object() const {
struct json::object json::value::as_object_value() const {
return json::object(object.value());
}
struct json::array json::value::as_array() const {
struct json::array json::value::as_array_value() const {
return json::array(array.value());
}
struct json::string json::value::as_string() const {
struct json::string json::value::as_string_value() const {
return json::string(string.value());
}
struct json::number json::value::as_number() const {
struct json::number json::value::as_number_value() const {
return json::number(number.value());
}
struct json::boolean json::value::as_boolean() const {
struct json::boolean json::value::as_boolean_value() const {
return json::boolean(boolean.value());
}
std::map<std::string, value> value::as_object() const
{
return object.value();
}
std::vector<value> value::as_array() const
{ return array.value(); }
float value::as_float() const
{ return number.value(); }
float value::as_float_or(float def) const
{ return number.value_or(def); }
double value::as_double() const { return number.value();}
double value::as_double_or(double def) const { return number.value_or(def); }
std::string value::as_string() const
{ return string.value();}
std::string value::as_string_or(const std::string& def) const
{ return string.value_or(def);}
void value::convert_descendants() {
if (type == value_type::object)
{
@@ -245,27 +165,27 @@ namespace JJX::json {
{
if (v.type == value_type::object)
{
auto converted = v.as_object();
auto converted = v.as_object_value();
converted.convert_descendants();
object.value().emplace(k, converted);
}
if (v.type == value_type::array)
{
auto converted = v.as_array();
auto converted = v.as_array_value();
converted.convert_descendants();
object.value().emplace(k, converted);
}
if (v.type == value_type::string)
object.value().emplace(k, v.as_string());
object.value().emplace(k, v.as_string_value());
if (v.type == value_type::number)
object.value().emplace(k, v.as_number());
object.value().emplace(k, v.as_number_value());
if (v.type == value_type::boolean)
object.value().emplace(k, v.as_boolean());
object.value().emplace(k, v.as_boolean_value());
}
}
@@ -275,19 +195,19 @@ namespace JJX::json {
{
auto v = array.value()[i];
if (v.type == value_type::object) {
auto converted = v.as_object();
auto converted = v.as_object_value();
converted.convert_descendants();
array.value()[i] = converted;
} else if (v.type == value_type::array) {
auto converted = v.as_array();
auto converted = v.as_array_value();
converted.convert_descendants();
array.value()[i] = converted;
} else if (v.type == value_type::string)
array.value()[i] = v.as_string();
array.value()[i] = v.as_string_value();
else if (v.type == value_type::number)
array.value()[i] = v.as_number();
array.value()[i] = v.as_number_value();
else if (v.type == value_type::boolean)
array.value()[i] = v.as_boolean();
array.value()[i] = v.as_boolean_value();
}
}
}
@@ -333,6 +253,7 @@ namespace JJX::json {
}
std::tuple<std::vector<json::token>, std::string> lex(std::string raw_json) {
using namespace lexers;
std::vector<json::token> tokens;
// All tokens will embed a pointer to the raw JSON for debugging purposes
auto original_copy = std::make_shared<std::string>(raw_json);
@@ -351,7 +272,7 @@ namespace JJX::json {
if (auto [token, new_index, error] = lexer(raw_json, i); i != new_index) {
// Error while lexing, return early
if (error.length()) {
return std::make_tuple(std::vector<JJX::json::token>{}, error);
return std::make_tuple(std::vector<json::token>{}, error);
}
// Store reference to the original source
@@ -366,7 +287,7 @@ namespace JJX::json {
if (found) {
continue;
}
return std::make_tuple(std::vector<JJX::json::token>{}, format_error("Unable to lex", raw_json, i));
return std::make_tuple(std::vector<json::token>{}, format_error("Unable to lex", raw_json, i));
}
return {tokens, ""};
}
@@ -508,7 +429,7 @@ namespace JJX::json {
auto [tokens, error] = json::lex(source);
if (error.size())
{
return std::make_tuple(JJX::json::value{}, error);
return std::make_tuple(json::value{}, error);
}
auto [ast, _, error1] = json::parse(tokens);
@@ -573,30 +494,59 @@ namespace JJX::json {
value::value(float v) : number(v), type(value_type::number) {}
value &value::at(const std::string &key) {
assert(type == value_type::object);
return this->as_object().at(key);
assert(type == value_type::object && "Not a JSON object");
return this->value::object->at(key);
}
value &value::operator[](const std::string &key) {
assert(type == value_type::object);
return this->as_object()[key];
// TODO: utilize std::map operator
return value::object->at(key);
}
const value &value::operator[](const std::string &key) const {
assert(type == value_type::object);
return this->as_object()[key];
// TODO: utilize std::map operator
return value::object->at(key);
}
bool value::contains(const std::string &key) {
assert(type == value_type::object);
return this->as_object().contains(key);
return this->value::object->contains(key);
}
value &value::operator[](int key) {
assert(type == value_type::array);
return as_array()[key];
bool value::is(value_type type) const { return type == this->type; }
bool value::is_number() const { return is(value_type::number);}
bool value::is_string() const { return is(value_type::string);}
bool value::is_array() const { return is(value_type::array); }
bool value::is_object() const { return is(value_type::object); }
bool value::is_boolean() const { return is(value_type::boolean); }
bool value::is_null() const { return is(value_type::null);}
value &value::operator[](int index) {
assert(is_array());
return value::array->operator[](index);
}
value &value::at(int index) {
assert(type == value_type::array && "Not a JSON array!");
return value::array->at(index);
}
const value& value::operator[](int index) const
{
assert(type == value_type::array && "Not a JSON array!");
return value::array->operator[](index);
}
struct string string(const std::string &text) {
struct string out;
out.type = value_type::string;
@@ -678,6 +628,19 @@ namespace JJX::json {
return this->value::object->at(key).type;
}
object::object() : value(std::map<std::string, value>{}) {}
object::object(const std::map<std::string, value> &v) : value(v) {}
array::array(): value(std::vector<value>{}) {}
size_t array::size() const { return value::array->size();}
number::number() : value(0.0) {}
number::number(double v) : value(v) {}
boolean::boolean() : value(false) {}
boolean::boolean(bool v) : value(v) {}
}

137
src/lexer.cpp Normal file
View File

@@ -0,0 +1,137 @@
#include <lexer.hpp>
#include <json.hpp>
#include <tuple>
#include <vector>
#include <error_handling.hpp>
namespace json {
int lexers::lex_whitespace(std::string raw_json, int index)
{
while (std::isspace(raw_json[index])) {
if (index == raw_json.length()) {
break;
}
index++;
}
return index;
}
std::tuple<json::token, int, std::string> lexers::lex_syntax(std::string raw_json, int index)
{
json::token token{"", token_type::syntax, index};
std::string value = "";
auto c = raw_json[index];
if (c == '[' || c == ']' || c == '{' || c == '}' || c == ':' || c == ',') {
token.value += c;
index++;
}
return {token, index, ""};
}
std::tuple<json::token, int, std::string> lexers::lex_string(std::string raw_json, int original_index)
{
int index = original_index;
json::token token {"", token_type::string, index};
std::string value = "";
auto c = raw_json[index];
if (c != '"') {
return {token, original_index, ""};
}
index++;
// TODO: handle nested quotes
while (c = raw_json[index], c != '"') {
if (index == raw_json.length()) {
//return {token, index, format_error("Unexpected EOF while lexing string", raw_json, index)};
throw Exception("Unexpected EOF while lexing string", raw_json, index);
}
token.value += c;
index++;
}
index++;
return {token, index, ""};
}
std::tuple<json::token, int, std::string> lexers::lex_number(std::string raw_json, int original_index)
{
int index = original_index;
json::token token {"", token_type::number, index};
std::string value = "";
bool decimal_present = false;
// Negative numbers.
if (raw_json[index] == '-') {
token.value += raw_json[index];
index++;
}
while(true) {
if (index == raw_json.length())
break;
auto c = raw_json[index];
if (c == '.') {
if (decimal_present)
break;
decimal_present = true;
//token.value += c; // This caused two dots to be inserted.
}
// Scientific notation.
else if (c == 'E' || c == 'e') {
token.value += c;
index++;
if (raw_json[index] == '-') {
token.value += raw_json[index];
index++;
}
continue; // Loop early.
}
// Only regex non-numeric values if we didn't catch it earlier.
else if (!(c >= '0' && c <= '9'))
break;
token.value += c;
index++;
}
return {token, index, ""};
}
std::tuple<json::token, int, std::string> lexers::lex_keyword(std::string raw_json, std::string keyword,
json::token_type type, int original_index)
{
int index = original_index;
json::token token{"", type, index};
while (keyword[index - original_index] == raw_json[index]) {
if (index == raw_json.length()) {
break;
}
index++;
}
if (index - original_index == keyword.length()) {
token.value = keyword;
}
return {token, index, ""};
}
std::tuple<json::token, int, std::string> lexers::lex_null(std::string raw_json, int index)
{
return lex_keyword(raw_json, "null", token_type::null, index);
}
std::tuple<json::token, int, std::string> lexers::lex_true(std::string raw_json, int index)
{
return lex_keyword(raw_json, "true", token_type::boolean, index);
}
std::tuple<json::token, int, std::string> lexers::lex_false(std::string raw_json, int index)
{
return lex_keyword(raw_json, "false", token_type::boolean, index);
}
}