Compare commits
21 Commits
Prerelease
...
master
Author | SHA1 | Date | |
---|---|---|---|
92e97ab6fe | |||
efd8ba9cd8 | |||
c2f95ad146 | |||
b938bebafe | |||
324ca06cf9 | |||
ddaf153340 | |||
c2a83ded35 | |||
02cdf697fc | |||
fdda938352 | |||
933d988cf6 | |||
e73d31a783 | |||
b2d78e5b33 | |||
3357409aa7 | |||
24a6034564 | |||
9546b2bcfe | |||
c643eb167f | |||
07dd98a268 | |||
6d700fcf97 | |||
7bbcb98914 | |||
1253861ad1 | |||
c20c60f934 |
@@ -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)
|
22
README.md
22
README.md
@@ -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.
|
||||
|
||||

|
||||

|
||||
@@ -11,17 +11,27 @@ A bare-minimal, yet industrial-strength C++ 20 library for reading, writing, and
|
||||
## Features
|
||||
|
||||
* 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.
|
||||
|
||||
## Benchmarks
|
||||
## API Overview
|
||||
|
||||
## Using JJX
|
||||
```cpp
|
||||
|
||||
### Installing via CPM
|
||||
tuple<value, string> parse(string);
|
||||
string deparse(value, string whitespace = "");
|
||||
|
||||
### Usage Sample
|
||||
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
24
cmake/CPM.cmake
Normal 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})
|
@@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <map>
|
||||
|
||||
#include <JJX/JSON.hpp>
|
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace JJX::json {
|
||||
enum class token_type { string, number, syntax, boolean, null };
|
||||
enum class value_type { string, number, object, array, boolean, null};
|
||||
struct token {
|
||||
std::string value;
|
||||
token_type type;
|
||||
int location;
|
||||
std::shared_ptr<std::string> full_source;
|
||||
};
|
||||
|
||||
struct value
|
||||
{
|
||||
std::optional<std::string> string;
|
||||
std::optional<double> number;
|
||||
std::optional<bool> boolean;
|
||||
std::optional<std::vector<value>> array;
|
||||
std::optional<std::map<std::string, value>> object;
|
||||
value_type type;
|
||||
|
||||
|
||||
void operator=(double in);
|
||||
void operator=(const std::string& in);
|
||||
void operator=(bool in);
|
||||
void operator=(std::vector<value> in);
|
||||
void operator=(std::map<std::string, value> in);
|
||||
|
||||
explicit operator double() const { return number.value(); }
|
||||
operator std::string() const { return string.value(); }
|
||||
explicit operator bool() const { return boolean.value(); }
|
||||
explicit operator std::vector<value>() const { return array.value(); }
|
||||
explicit operator std::map<std::string, value>() const { return object.value(); }
|
||||
|
||||
|
||||
};
|
||||
|
||||
struct string_val : value
|
||||
{
|
||||
void operator=(const std::string& me);
|
||||
|
||||
operator std::string();
|
||||
};
|
||||
struct number_val : value {};
|
||||
struct boolean_val : value {};
|
||||
struct object_val : value
|
||||
{
|
||||
void add(const std::string& key, value val);
|
||||
void add(const std::string& key, const std::string& val);
|
||||
value& operator[] (const std::string& key);
|
||||
|
||||
template <typename T>
|
||||
T get(const std::string& key);
|
||||
|
||||
};
|
||||
struct array_val : value
|
||||
{
|
||||
void add(value val);
|
||||
value& operator[] (int key);
|
||||
};
|
||||
|
||||
std::tuple<std::vector<json::token>, std::string> lex(std::string);
|
||||
std::tuple<json::value, int, std::string> parse(std::vector<json::token>, int index = 0);
|
||||
std::tuple<json::value, std::string> parse(std::string);
|
||||
std::string deparse(json::value, std::string whitespace = "");
|
||||
|
||||
string_val string(const std::string& text);
|
||||
|
||||
number_val number(double input);
|
||||
|
||||
boolean_val boolean(bool input);
|
||||
|
||||
array_val array(std::vector<value> input);
|
||||
array_val array();
|
||||
|
||||
object_val object(std::map<std::string, value> input);
|
||||
object_val object();
|
||||
|
||||
template <typename T>
|
||||
value array_of(const std::vector<T>& input);
|
||||
}
|
86
include/error_handling.hpp
Normal file
86
include/error_handling.hpp
Normal 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
|
||||
{
|
||||
|
||||
};
|
||||
}
|
341
include/json.hpp
Normal file
341
include/json.hpp
Normal file
@@ -0,0 +1,341 @@
|
||||
/// JJX - Josh's JSON and XML API for C++20
|
||||
/// Written and maintained by josh@redacted.cc
|
||||
/// Edit 3 - Revised March 24, 2025
|
||||
/// (c) 2025 redacted.cc
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#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 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.
|
||||
/// Tokens may be JSON primitives such as string, number, boolean, null,
|
||||
/// Or may be a syntax symbol, such as quotations, brackets, commas, etc.
|
||||
enum class token_type { string, number, syntax, boolean, null };
|
||||
|
||||
/// An enumeration that represents the different types of JSON values that can exist.
|
||||
/// JSON allows for two composite data types: arrays and objects.
|
||||
/// Arrays are sequential lists of other JSON values, with integer indexing.
|
||||
/// Objects are collections of named JSON values, with string indexing.
|
||||
/// string, number, boolean, and null types are considered self-explanatory.
|
||||
enum class value_type { string, number, object, array, boolean, null};
|
||||
|
||||
|
||||
struct token {
|
||||
std::string value;
|
||||
token_type type;
|
||||
int location;
|
||||
std::shared_ptr<std::string> full_source;
|
||||
};
|
||||
|
||||
/// The base class for a set of classes representing the various JSON primitives.
|
||||
struct value;
|
||||
struct string;
|
||||
struct number;
|
||||
struct boolean;
|
||||
struct object;
|
||||
struct array;
|
||||
|
||||
/// Tokenizes (or Lexes) a string of text into a list of JSON tokens.
|
||||
/// @return Token list, and an optional error message.
|
||||
/// @see struct token
|
||||
std::tuple<std::vector<json::token>, std::string> lex(std::string);
|
||||
|
||||
/// Parses a sequence of JSON tokens.
|
||||
/// @return The root JSON value, which will contain descendant JSON values if applicable,
|
||||
/// an integer indicating the recursion index, and an error code, if applicable.
|
||||
std::tuple<json::value, int, std::string> parse(std::vector<json::token>, int index = 0);
|
||||
|
||||
/// Parses a sequence of JSON tokens.
|
||||
/// @return The root JSON value, which will contain descendant JSON values if applicable,
|
||||
/// an integer indicating the recursion index, and an error code, if applicable.
|
||||
std::tuple<json::value, std::string> parse(std::string);
|
||||
std::string deparse(json::value, std::string whitespace = "");
|
||||
|
||||
/// This object holds JSON constructs, known as 'values', and associates them to a type.
|
||||
/// A value represents a single piece of JSON information.
|
||||
struct value
|
||||
{
|
||||
/// This member denotes what type of JSON value this object represents.
|
||||
value_type type;
|
||||
|
||||
/// The following member variables store the actual data this object represents.
|
||||
/// Only one member at a time should contain any data, depending on the value_type.
|
||||
|
||||
std::optional<std::string> string;
|
||||
std::optional<double> number;
|
||||
std::optional<bool> boolean;
|
||||
std::optional<std::vector<value>> array;
|
||||
std::optional<std::map<std::string, value>> object;
|
||||
|
||||
/// Constructs a JSON value that represents null, aka an empty field.
|
||||
explicit value();
|
||||
/// Constructs a json value that represents the corresponding number.
|
||||
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);
|
||||
|
||||
/// 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) {}
|
||||
|
||||
/// Assigns this value to a number, and sets the value_type::number.
|
||||
value& operator=(double in);
|
||||
/// Assigns this value to a string, and sets the value_type::string.
|
||||
value& operator=(const std::string& in);
|
||||
/// Assigns this value to a boolean, and sets the value_type::boolean.
|
||||
value& operator=(bool in);
|
||||
/// Assigns this value to an array, and sets the value_type::array.
|
||||
value& operator=(const std::vector<value>& in);
|
||||
/// 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;
|
||||
explicit operator std::vector<value>() const;
|
||||
explicit operator std::map<std::string, value>() const;
|
||||
|
||||
/// 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_value() const;
|
||||
/// Returns this value's data, converted to a json::array, which is a specialization of this type.
|
||||
[[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);
|
||||
|
||||
bool is(value_type type) const;
|
||||
bool is_number() const;
|
||||
|
||||
bool is_string() const;
|
||||
|
||||
bool is_array() const;
|
||||
|
||||
bool is_object() const;
|
||||
|
||||
bool is_boolean() const;
|
||||
|
||||
bool is_null() const;
|
||||
#pragma endregion
|
||||
};
|
||||
|
||||
/// A specialized json::value which provides STL-compatibility with std::string, and other functions for convenience.
|
||||
struct string : value {
|
||||
/// The default constructor initializes to an empty string literal.
|
||||
explicit string() : value("") {}
|
||||
explicit string(const std::string& v) : value(v) {}
|
||||
|
||||
|
||||
operator std::string();
|
||||
};
|
||||
struct number : value {
|
||||
/// The default constructor initializes to floating-point literal zero.
|
||||
explicit number();
|
||||
explicit number(double v);
|
||||
|
||||
};
|
||||
struct boolean : value {
|
||||
/// The default constructor initializes to false.
|
||||
explicit boolean();
|
||||
explicit boolean(bool v);
|
||||
};
|
||||
|
||||
/// A specialized json::value which provides STL compatibility with std::map, and other functions for convenience.
|
||||
/// As per JSON specification, only std::strings are allowed to be used as keys in this data type.
|
||||
struct object : value
|
||||
{
|
||||
/// The default constructor initializes to an empty map.
|
||||
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);
|
||||
|
||||
/// 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,
|
||||
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);
|
||||
|
||||
json::object& operator+=(const std::pair<std::string, double>& kvp);
|
||||
|
||||
json::object& operator+=(const std::pair<std::string, std::string>& kvp);
|
||||
*/
|
||||
/// @return The json value associated with the given key.
|
||||
value& at(const std::string& key);
|
||||
|
||||
/// @see object::at()
|
||||
value& operator[] (const std::string& key);
|
||||
const value& operator[] (const std::string& key) const;
|
||||
|
||||
/// @return True if a key-value pair in this object has a key that matches the given value.
|
||||
bool contains(const std::string& key);
|
||||
|
||||
/// @return The json-type of the value of the first key-value pair where the key matches the given string.
|
||||
json::value_type type_of(const std::string& key);
|
||||
|
||||
using map_spec = std::map<std::string, value>;
|
||||
using iterator = map_spec::iterator;
|
||||
using const_iterator = map_spec::const_iterator;
|
||||
|
||||
iterator begin() { return value::object->begin();}
|
||||
iterator end() { return value::object->end();}
|
||||
|
||||
const_iterator begin() const { return value::object->cbegin(); }
|
||||
const_iterator end() const { return value::object->cend(); }
|
||||
|
||||
size_t size() const { return value::object->size();}
|
||||
bool empty() const { return value::object->empty(); }
|
||||
|
||||
};
|
||||
|
||||
struct array : value {
|
||||
/// The default constructor
|
||||
explicit array();
|
||||
|
||||
explicit array(const std::vector<value>& v);
|
||||
|
||||
/// Default Copy Constructor
|
||||
explicit array(const array& v) = default;
|
||||
|
||||
void push_back(const value& element);
|
||||
size_t size() const;
|
||||
|
||||
|
||||
value& operator+= (const value& v);
|
||||
|
||||
value& operator+= (bool v) { push_back(json::boolean(v)); return *this; }
|
||||
value& operator+= (double v) { push_back(json::number(v)); return *this; }
|
||||
value& operator+= (const std::string& v) { push_back(json::string(v)); return *this; }
|
||||
|
||||
value& operator[] (int key);
|
||||
|
||||
json::value_type type_of(int index);
|
||||
|
||||
using iterator = std::vector<value>::iterator;
|
||||
using const_iterator = std::vector<value>::const_iterator;
|
||||
|
||||
iterator begin() { return value::array->begin(); }
|
||||
iterator end() { return value::array->end(); }
|
||||
|
||||
[[nodiscard]] const_iterator begin() const;
|
||||
[[nodiscard]] const_iterator end() const;
|
||||
|
||||
[[nodiscard]] const_iterator cbegin() const;
|
||||
[[nodiscard]] const_iterator cend() const;
|
||||
};
|
||||
|
||||
}
|
122
include/lexer.hpp
Normal file
122
include/lexer.hpp
Normal 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);
|
||||
}
|
277
main.cpp
277
main.cpp
@@ -1,18 +1,27 @@
|
||||
#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)
|
||||
{
|
||||
std::ifstream file(file_path, std::ios::binary);
|
||||
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');
|
||||
@@ -30,136 +39,172 @@ void parse_json_file(const std::string& file_path) {
|
||||
if (!parse_error.empty())
|
||||
std::cerr << "Error while parsing json: " << parse_error << std::endl;
|
||||
|
||||
|
||||
std::cout << json::deparse(text) << std::endl;
|
||||
}
|
||||
|
||||
struct Vector3
|
||||
{
|
||||
float x, y, z;
|
||||
};
|
||||
|
||||
|
||||
/// Creates json array out of Vector3 type.
|
||||
json::value vector3_to_json(const Vector3& input)
|
||||
{
|
||||
auto x = json::number(input.x);
|
||||
auto y = json::number(input.y);
|
||||
auto z = json::number(input.z);
|
||||
return json::array({x, y, z});
|
||||
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;
|
||||
}
|
||||
|
||||
/// 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;
|
||||
void show_license() {
|
||||
std::cout << "" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
struct product_info
|
||||
void show_version()
|
||||
{
|
||||
struct rating_metrics {
|
||||
double average;
|
||||
int total;
|
||||
};
|
||||
|
||||
struct review
|
||||
{
|
||||
int rating;
|
||||
std::string review_text;
|
||||
std::string user;
|
||||
|
||||
operator json::object_val() const
|
||||
{
|
||||
json::object_val obj = json::object();
|
||||
obj["rating"] = json::number(rating);
|
||||
obj["review_text"] = json::string(review_text);
|
||||
obj["user"] = json::string(user);
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
product_info pinfo_fromjson(json::value root)
|
||||
{
|
||||
if (root.type != json::value_type::object)
|
||||
std::cerr << "Malformed product info file!" << std::endl;
|
||||
|
||||
product_info this_product;
|
||||
|
||||
json::object_val better_root = static_cast<json::object_val>(root);
|
||||
|
||||
this_product.category = better_root["category"];
|
||||
this_product.description = better_root["description"];
|
||||
this_product.is_available = bool(better_root["is_available"]);
|
||||
this_product.manufacturer = better_root["manufacturer"];
|
||||
this_product.name = better_root["name"];
|
||||
this_product.price = double(better_root["price"]);
|
||||
|
||||
json::array_val subobj = static_cast<json::array_val>(better_root["reviews"]);
|
||||
|
||||
for (auto& review_data_pre : subobj.array.value())
|
||||
{
|
||||
json::object_val review_data = static_cast<json::object_val>(review_data_pre);
|
||||
product_info::review going_in;
|
||||
going_in.rating = double(review_data["rating"]);
|
||||
going_in.user = review_data["user"];
|
||||
going_in.review_text = review_data["review_text"];
|
||||
this_product.reviews.push_back(going_in);
|
||||
}
|
||||
|
||||
return this_product;
|
||||
|
||||
}
|
||||
|
||||
json::value pinfo_tojson(product_info input)
|
||||
{
|
||||
json::object_val root = json::object();
|
||||
root["category"] = json::string(input.category);
|
||||
root["manufacturer"] = json::string(input.manufacturer);
|
||||
root["is_available"] = input.is_available;
|
||||
//root.add("category", input.category);
|
||||
root.add("description", input.description);
|
||||
root["name"] = input.name;
|
||||
root["reviews"] = json::array();
|
||||
for (auto& rev : input.reviews) {
|
||||
root["reviews"].array.value().push_back(rev);
|
||||
}
|
||||
/// Parses a string of JSON and outputs any syntax errors, if present.
|
||||
void validate_json_string(const std::string& input) {
|
||||
|
||||
root["taters"] = 5.0;
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
void echo_json_formatted(const std::string& input) {
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Some progress has been made on small scale.
|
||||
int main(int argc, char* argv[]) {
|
||||
//parse_json_file("../samples/product_info.json");
|
||||
auto file_contents = read_file("../samples/product_info.json");
|
||||
ArgsParser pre_parser(argc, argv);
|
||||
if (pre_parser.has_flag("--test-all")) {
|
||||
int readme = json_app({"--readme"});
|
||||
|
||||
auto [text, err] = json::parse(file_contents);
|
||||
int help = json_app({"--help"});
|
||||
|
||||
product_info test = pinfo_fromjson(text);
|
||||
int license = json_app({"--license"});
|
||||
|
||||
int version = json_app({"--version"});
|
||||
|
||||
auto result = pinfo_tojson(test);
|
||||
int result_0 = json_app({"--file", "nonexistant.json"});
|
||||
|
||||
std::cout << json::deparse(result);
|
||||
int result_1 = json_app({"--validate", "{\"test\":42069}"});
|
||||
|
||||
return 0;
|
||||
} else
|
||||
return json_app(pre_parser.get_remaining_args());
|
||||
}
|
||||
|
@@ -1,29 +1,16 @@
|
||||
{
|
||||
|
||||
"product_id": "SKU12345",
|
||||
|
||||
"name": "Example Product",
|
||||
|
||||
"description": "This is an example product.",
|
||||
|
||||
"category": "Electronics",
|
||||
|
||||
"price": 199.99,
|
||||
|
||||
"stock_quantity": 50,
|
||||
|
||||
"manufacturer": "TechCo",
|
||||
|
||||
"release_date": "2022-05-10",
|
||||
|
||||
"is_available": true,
|
||||
|
||||
"ratings": {
|
||||
|
||||
"average": 4.5,
|
||||
|
||||
"total": 100
|
||||
|
||||
},
|
||||
"reviews": [
|
||||
{
|
||||
|
@@ -1,6 +0,0 @@
|
||||
<note>
|
||||
<to>Tove</to>
|
||||
<from>Jani</from>
|
||||
<heading>Reminder</heading>
|
||||
<body>Don't forget me this weekend!</body>
|
||||
</note>
|
55
samples/tiles.json
Normal file
55
samples/tiles.json
Normal file
@@ -0,0 +1,55 @@
|
||||
[
|
||||
{
|
||||
"mnemonic-id" : "void",
|
||||
"display-name" : "VOID",
|
||||
"solid": true,
|
||||
"does-random-ticc": false,
|
||||
"does-forced-ticc": false,
|
||||
"color": "#FFFFFF",
|
||||
"pallet": [],
|
||||
"hardcoded-id": 65536
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "air",
|
||||
"display-name" : "AIR",
|
||||
"solid": true,
|
||||
"does-random-ticc": false,
|
||||
"does-forced-ticc": false,
|
||||
"color": "#FFFFFF",
|
||||
"pallet": [],
|
||||
"hardcoded-id": 0
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "stone",
|
||||
"display-name" : "",
|
||||
"solid": true,
|
||||
"does-random-ticc": false,
|
||||
"does-forced-ticc": false,
|
||||
"color": "#FFFFFF",
|
||||
"pallet": []
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "oak-wood-plank",
|
||||
"display-name" : "",
|
||||
"solid": true,
|
||||
"does-random-ticc": false,
|
||||
"does-forced-ticc": false,
|
||||
"color": "#FFFFFF",
|
||||
"pallet": [],
|
||||
"random-ticc-func": "zzyyzz",
|
||||
"forced-ticc-func": "zzyyzz",
|
||||
"drops" : null
|
||||
},
|
||||
{
|
||||
"mnemonic-id" : "gravel",
|
||||
"display-name" : "",
|
||||
"solid": true,
|
||||
"does-random-ticc": false,
|
||||
"does-forced-ticc": false,
|
||||
"color": "#FFFFFF",
|
||||
"pallet": [],
|
||||
"random-ticc-func": "zzyyzz",
|
||||
"forced-ticc-func": "zzyyzz",
|
||||
"drops" : null
|
||||
}
|
||||
]
|
523
src/JSON.cpp
523
src/JSON.cpp
@@ -1,523 +0,0 @@
|
||||
#include <JJX/JJX.hpp>
|
||||
#include <JJX/JSON.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace JJX::json {
|
||||
std::string format_error(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 << "^";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void value::operator=(double in)
|
||||
{
|
||||
type = value_type::number;
|
||||
number = in;
|
||||
}
|
||||
|
||||
void value::operator=(const std::string& in)
|
||||
{
|
||||
type = value_type::string;
|
||||
string = in;
|
||||
}
|
||||
|
||||
void value::operator=(bool in)
|
||||
{
|
||||
type = value_type::boolean;
|
||||
boolean = in;
|
||||
}
|
||||
|
||||
void value::operator=(std::vector<value> in)
|
||||
{
|
||||
type = value_type::array;
|
||||
array = in;
|
||||
}
|
||||
|
||||
void value::operator=(std::map<std::string, value> in)
|
||||
{
|
||||
type = value_type::object;
|
||||
object = in;
|
||||
}
|
||||
|
||||
void string_val::operator=(const std::string& me)
|
||||
{
|
||||
string = me;
|
||||
}
|
||||
|
||||
string_val::operator std::string()
|
||||
{
|
||||
return string.value();
|
||||
}
|
||||
|
||||
void object_val::add(const std::string& key, value val)
|
||||
{
|
||||
object.value().emplace(key, val);
|
||||
}
|
||||
|
||||
void object_val::add(const std::string& key, const std::string& val)
|
||||
{
|
||||
object.value().emplace(key, json::string(val));
|
||||
}
|
||||
|
||||
value& object_val::operator[](const std::string& key)
|
||||
{
|
||||
return object.value()[key];
|
||||
}
|
||||
|
||||
void array_val::add(value val)
|
||||
{
|
||||
array.value().push_back(val);
|
||||
}
|
||||
|
||||
value& array_val::operator[](int key)
|
||||
{
|
||||
return array.value()[key];
|
||||
}
|
||||
|
||||
std::tuple<std::vector<json::token>, std::string> lex(std::string raw_json) {
|
||||
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);
|
||||
|
||||
auto generic_lexers = {lex_syntax, lex_string, lex_number, lex_null, lex_true, lex_false};
|
||||
|
||||
for (int i = 0; i < raw_json.length(); i++) {
|
||||
// Skip past whitespace
|
||||
if (auto new_index = lex_whitespace(raw_json, i); i != new_index) {
|
||||
i = new_index - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto found = false;
|
||||
for (auto lexer: generic_lexers) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Store reference to the original source
|
||||
token.full_source = original_copy;
|
||||
tokens.push_back(token);
|
||||
i = new_index - 1;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
continue;
|
||||
}
|
||||
return std::make_tuple(std::vector<JJX::json::token>{}, format_error("Unable to lex", raw_json, i));
|
||||
}
|
||||
return {tokens, ""};
|
||||
}
|
||||
|
||||
// It's very annoying when languages doesn't give you
|
||||
// stringifier methods for enums by default for debugging.
|
||||
// There are ways to do this with reflection but it seems hairy.
|
||||
// There's a better procedure IIRC.
|
||||
std::string token_type_tostring(token_type tok)
|
||||
{
|
||||
switch(tok) {
|
||||
case token_type::string: return "String";
|
||||
case token_type::number: return "Number";
|
||||
case token_type::syntax: return "Syntax";
|
||||
case token_type::boolean: return "Boolean";
|
||||
case token_type::null: return "Null";
|
||||
}
|
||||
}
|
||||
|
||||
std::string format_parse_error(std::string base, json::token token)
|
||||
{
|
||||
std::ostringstream s;
|
||||
s << "Unexpected token '" << token.value << "', type' '"
|
||||
<< token_type_tostring(token.type) << "', index'";
|
||||
s << std::endl << base;
|
||||
return format_error(s.str(), *token.full_source, token.location);
|
||||
}
|
||||
|
||||
std::tuple<std::vector<json::value>, int, std::string> parse_array(std::vector<json::token> tokens, int index) {
|
||||
std::vector<json::value> children = {};
|
||||
while (index < tokens.size()) {
|
||||
auto t = tokens[index];
|
||||
if (t.type == token_type::syntax) {
|
||||
if (t.value == "]") {
|
||||
return {children, index + 1, ""};
|
||||
}
|
||||
if (t.value == ",") {
|
||||
index++;
|
||||
t = tokens[index];
|
||||
} else if (children.size() > 0) {
|
||||
return {{}, index,
|
||||
format_parse_error("Expected comma after element in array", t)};
|
||||
}
|
||||
}
|
||||
|
||||
auto [child, new_index, error] = parse(tokens, index);
|
||||
if (error.size()) { return {{}, index, error}; }
|
||||
children.push_back(child);
|
||||
index = new_index;
|
||||
}
|
||||
return {
|
||||
{}, index,
|
||||
format_parse_error("Unexpected EOF while parsing array", tokens[index])};
|
||||
|
||||
}
|
||||
|
||||
std::tuple<std::map<std::string, value>, int, std::string> parse_object(std::vector<token> tokens, int index) {
|
||||
std::map<std::string, value> values = {};
|
||||
while (index < tokens.size()) {
|
||||
auto t = tokens[index];
|
||||
if (t.type == token_type::syntax) {
|
||||
if (t.value == "}") {
|
||||
return {values, index + 1, ""};
|
||||
}
|
||||
if (t.value == ",") {
|
||||
index++;
|
||||
t = tokens[index];
|
||||
} else if (values.size() > 0) {
|
||||
return {{}, index,
|
||||
format_parse_error("Expected comma after element in object", t)
|
||||
};
|
||||
} else {
|
||||
return {{}, index,
|
||||
format_parse_error("Expected key-value pair or closing brace in object", t)
|
||||
};
|
||||
}
|
||||
}
|
||||
auto [key, new_index, error] = parse(tokens, index);
|
||||
if (error.size())
|
||||
{
|
||||
return {{}, index, error};
|
||||
}
|
||||
|
||||
if (key.type != value_type::string) {
|
||||
return {{}, index,
|
||||
format_parse_error("Expected string key in object", t)};
|
||||
}
|
||||
|
||||
index = new_index;
|
||||
t = tokens[index];
|
||||
|
||||
if (!(t.type == token_type::syntax && t.value == ":")) {
|
||||
return {{}, index,
|
||||
format_parse_error("Expected colon after key in object", t)};
|
||||
}
|
||||
index++;
|
||||
t = tokens[index];
|
||||
|
||||
auto [value, new_index1, error1] = parse(tokens, index);
|
||||
if (error1.size()) {
|
||||
return {{}, index, error1};
|
||||
}
|
||||
|
||||
values[key.string.value()] = value;
|
||||
index = new_index1;
|
||||
}
|
||||
return {values, index+1, ""};
|
||||
}
|
||||
|
||||
std::tuple<json::value, int, std::string> parse(std::vector<json::token> tokens, int index) {
|
||||
auto token = tokens[index];
|
||||
switch(token.type) {
|
||||
case token_type::number: {
|
||||
auto n = std::stod(token.value);
|
||||
return {json::value{.number = n, .type = value_type::number}, index+1, ""};
|
||||
}
|
||||
case token_type::boolean:
|
||||
return {json::value{.boolean = token.value == "true", .type = value_type::boolean}, index + 1, ""};
|
||||
case token_type::null:
|
||||
return {json::value{.type = value_type::null}, index+1, ""};
|
||||
case token_type::string:
|
||||
return {json::value{.string = token.value, .type = value_type::string}, index+1, ""};
|
||||
case token_type::syntax: {
|
||||
if (token.value == "[") {
|
||||
auto [array, new_index, error] = parse_array(tokens, index + 1);
|
||||
return {json::value{.array = array, .type = value_type::array}, new_index, error};
|
||||
}
|
||||
if (token.value == "{") {
|
||||
auto [object, new_index, error] = parse_object(tokens, index + 1);
|
||||
return {json::value{.object = std::optional(object), .type = value_type::object}, new_index, error};
|
||||
}
|
||||
}
|
||||
default:
|
||||
return {{}, index, format_parse_error("Failed to parse", token)};
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<json::value, std::string> parse(std::string source) {
|
||||
auto [tokens, error] = json::lex(source);
|
||||
if (error.size())
|
||||
{
|
||||
return std::make_tuple(JJX::json::value{}, error);
|
||||
}
|
||||
|
||||
auto [ast, _, error1] = json::parse(tokens);
|
||||
return {ast, error1};
|
||||
}
|
||||
|
||||
std::string deparse(json::value v, std::string whitespace) {
|
||||
switch(v.type) {
|
||||
case json::value_type::string:
|
||||
return "\"" + v.string.value() + "\"";
|
||||
case json::value_type::boolean:
|
||||
return (v.boolean.value() ? "true" : "false");
|
||||
case json::value_type::number:
|
||||
return std::to_string(v.number.value());
|
||||
case json::value_type::null:
|
||||
return "null";
|
||||
case json::value_type::array: {
|
||||
std::string s = "[\n";
|
||||
auto a = v.array.value();
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
auto value = a[i];
|
||||
s += whitespace + " " + deparse(value, whitespace + " ");
|
||||
if (i < a.size() - 1) {
|
||||
s += ",";
|
||||
}
|
||||
s += "\n";
|
||||
}
|
||||
return s + whitespace + "]";
|
||||
}
|
||||
case json::value_type::object: {
|
||||
std::string s = "{\n";
|
||||
auto values = v.object.value();
|
||||
auto i = 0;
|
||||
for (auto const &[key, value] : values) {
|
||||
s += whitespace + " " + "\"" + key + "\":" + deparse(value, whitespace + " ");
|
||||
|
||||
if (i < values.size() - 1) {
|
||||
s += ",";
|
||||
}
|
||||
s += "\n";
|
||||
i++;
|
||||
}
|
||||
return s + whitespace + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string_val string(const std::string &text) {
|
||||
string_val out;
|
||||
out.type = value_type::string;
|
||||
out.string = text;
|
||||
return out;
|
||||
}
|
||||
|
||||
number_val number(double input) {
|
||||
number_val out;
|
||||
out.type = value_type::number;
|
||||
out.number = input;
|
||||
return out;
|
||||
}
|
||||
|
||||
boolean_val boolean(bool input) {
|
||||
boolean_val out;
|
||||
out.type = value_type::boolean;
|
||||
out.number = input;
|
||||
return out;
|
||||
}
|
||||
|
||||
array_val array(std::vector<value> input)
|
||||
{
|
||||
array_val arr;
|
||||
arr.type = value_type::array;
|
||||
arr.array = std::vector<value>();
|
||||
|
||||
for (auto& elem: input)
|
||||
{
|
||||
arr.array->push_back(elem);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
array_val array()
|
||||
{
|
||||
array_val out;
|
||||
out.type = value_type::array;
|
||||
out.array = std::vector<value>();
|
||||
return out;
|
||||
}
|
||||
|
||||
object_val object(std::map<std::string, value> input)
|
||||
{
|
||||
object_val out;
|
||||
out.type = value_type::object;
|
||||
out.object = input;
|
||||
return out;
|
||||
}
|
||||
|
||||
object_val object()
|
||||
{
|
||||
object_val out;
|
||||
out.type = value_type::object;
|
||||
out.object = std::map<std::string, value>();
|
||||
return out;
|
||||
}
|
||||
}
|
646
src/json.cpp
Normal file
646
src/json.cpp
Normal file
@@ -0,0 +1,646 @@
|
||||
#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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
std::string format_error(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 << "^";
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
|
||||
|
||||
json::value& value::operator=(double in)
|
||||
{
|
||||
type = value_type::number;
|
||||
number = in;
|
||||
return *this;
|
||||
}
|
||||
|
||||
json::value& value::operator=(const std::string& in)
|
||||
{
|
||||
type = value_type::string;
|
||||
string = in;
|
||||
return *this;
|
||||
}
|
||||
|
||||
json::value& value::operator=(bool in)
|
||||
{
|
||||
type = value_type::boolean;
|
||||
boolean = in;
|
||||
return *this;
|
||||
}
|
||||
|
||||
value& value::operator=(const std::vector<value>& in)
|
||||
{
|
||||
type = value_type::array;
|
||||
array = in;
|
||||
return *this;
|
||||
}
|
||||
|
||||
value& value::operator=(const std::map<std::string, value>& in)
|
||||
{
|
||||
type = value_type::object;
|
||||
object = in;
|
||||
return *this;
|
||||
}
|
||||
|
||||
value::operator double() const { return number.value(); }
|
||||
|
||||
value::operator std::string() const { return string.value(); }
|
||||
|
||||
value::operator bool() const { return boolean.value(); }
|
||||
|
||||
value::operator std::vector<value>() const { return this->value::array.value(); }
|
||||
|
||||
value::operator std::map<std::string, value>() const { return object.value(); }
|
||||
|
||||
struct json::object json::value::as_object_value() const {
|
||||
return json::object(object.value());
|
||||
}
|
||||
|
||||
struct json::array json::value::as_array_value() const {
|
||||
return json::array(array.value());
|
||||
}
|
||||
|
||||
struct json::string json::value::as_string_value() const {
|
||||
return json::string(string.value());
|
||||
}
|
||||
|
||||
struct json::number json::value::as_number_value() const {
|
||||
return json::number(number.value());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
for (auto& [k, v] : object.value())
|
||||
{
|
||||
if (v.type == value_type::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_value();
|
||||
converted.convert_descendants();
|
||||
object.value().emplace(k, converted);
|
||||
}
|
||||
|
||||
|
||||
if (v.type == value_type::string)
|
||||
object.value().emplace(k, v.as_string_value());
|
||||
|
||||
if (v.type == value_type::number)
|
||||
object.value().emplace(k, v.as_number_value());
|
||||
|
||||
if (v.type == value_type::boolean)
|
||||
object.value().emplace(k, v.as_boolean_value());
|
||||
}
|
||||
}
|
||||
|
||||
if (type == value_type::array)
|
||||
{
|
||||
for (int i = 0; i < array.value().size(); ++i)
|
||||
{
|
||||
auto v = array.value()[i];
|
||||
if (v.type == value_type::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_value();
|
||||
converted.convert_descendants();
|
||||
array.value()[i] = converted;
|
||||
} else if (v.type == value_type::string)
|
||||
array.value()[i] = v.as_string_value();
|
||||
else if (v.type == value_type::number)
|
||||
array.value()[i] = v.as_number_value();
|
||||
else if (v.type == value_type::boolean)
|
||||
array.value()[i] = v.as_boolean_value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string::operator std::string()
|
||||
{
|
||||
return this->value::string.value();
|
||||
}
|
||||
|
||||
|
||||
value& object::operator[](const std::string& key)
|
||||
{
|
||||
return this->value::object.value()[key];
|
||||
}
|
||||
|
||||
const value & object::operator[](const std::string &key) const { return value::object->at(key);}
|
||||
|
||||
|
||||
value& array::operator[](int key)
|
||||
{
|
||||
return this->value::array.value()[key];
|
||||
}
|
||||
|
||||
json::value_type array::type_of(int index) {
|
||||
return this->value::array->at(index).type;
|
||||
}
|
||||
|
||||
value &array::operator+=(const value &v) {
|
||||
push_back(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
array::const_iterator array::begin() const { return value::array->cbegin(); }
|
||||
|
||||
array::const_iterator array::end() const { return value::array->cend(); }
|
||||
|
||||
array::const_iterator array::cbegin() const { return value::array->cbegin();}
|
||||
|
||||
array::const_iterator array::cend() const { return value::array->cend();}
|
||||
|
||||
void array::push_back(const value &element) {
|
||||
value::array->push_back(element);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
auto generic_lexers = {lex_syntax, lex_string, lex_number, lex_null, lex_true, lex_false};
|
||||
|
||||
for (int i = 0; i < raw_json.length(); i++) {
|
||||
// Skip past whitespace
|
||||
if (auto new_index = lex_whitespace(raw_json, i); i != new_index) {
|
||||
i = new_index - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto found = false;
|
||||
for (auto lexer: generic_lexers) {
|
||||
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<json::token>{}, error);
|
||||
}
|
||||
|
||||
// Store reference to the original source
|
||||
token.full_source = original_copy;
|
||||
tokens.push_back(token);
|
||||
i = new_index - 1;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
continue;
|
||||
}
|
||||
return std::make_tuple(std::vector<json::token>{}, format_error("Unable to lex", raw_json, i));
|
||||
}
|
||||
return {tokens, ""};
|
||||
}
|
||||
|
||||
// It's very annoying when languages doesn't give you
|
||||
// stringifier methods for enums by default for debugging.
|
||||
// There are ways to do this with reflection but it seems hairy.
|
||||
// There's a better procedure IIRC.
|
||||
std::string token_type_tostring(token_type tok)
|
||||
{
|
||||
switch(tok) {
|
||||
case token_type::string: return "String";
|
||||
case token_type::number: return "Number";
|
||||
case token_type::syntax: return "Syntax";
|
||||
case token_type::boolean: return "Boolean";
|
||||
case token_type::null: return "Null";
|
||||
}
|
||||
}
|
||||
|
||||
std::string format_parse_error(std::string base, json::token token)
|
||||
{
|
||||
std::ostringstream s;
|
||||
s << "Unexpected token '" << token.value << "', type' '"
|
||||
<< token_type_tostring(token.type) << "', index'";
|
||||
s << std::endl << base;
|
||||
return format_error(s.str(), *token.full_source, token.location);
|
||||
}
|
||||
|
||||
std::tuple<std::vector<json::value>, int, std::string> parse_array(std::vector<json::token> tokens, int index) {
|
||||
std::vector<json::value> children = {};
|
||||
while (index < tokens.size()) {
|
||||
auto t = tokens[index];
|
||||
if (t.type == token_type::syntax) {
|
||||
if (t.value == "]") {
|
||||
return {children, index + 1, ""};
|
||||
}
|
||||
if (t.value == ",") {
|
||||
index++;
|
||||
t = tokens[index];
|
||||
} else if (children.size() > 0) {
|
||||
return {{}, index,
|
||||
format_parse_error("Expected comma after element in array", t)};
|
||||
}
|
||||
}
|
||||
|
||||
auto [child, new_index, error] = parse(tokens, index);
|
||||
if (error.size()) { return {{}, index, error}; }
|
||||
children.push_back(child);
|
||||
index = new_index;
|
||||
}
|
||||
return {
|
||||
{}, index,
|
||||
format_parse_error("Unexpected EOF while parsing array", tokens[index])};
|
||||
|
||||
}
|
||||
|
||||
std::tuple<std::map<std::string, value>, int, std::string> parse_object(std::vector<token> tokens, int index) {
|
||||
std::map<std::string, value> values = {};
|
||||
while (index < tokens.size()) {
|
||||
auto t = tokens[index];
|
||||
if (t.type == token_type::syntax) {
|
||||
if (t.value == "}") {
|
||||
return {values, index + 1, ""};
|
||||
}
|
||||
if (t.value == ",") {
|
||||
index++;
|
||||
t = tokens[index];
|
||||
} else if (values.size() > 0) {
|
||||
return {{}, index,
|
||||
format_parse_error("Expected comma after element in object", t)
|
||||
};
|
||||
} else {
|
||||
return {{}, index,
|
||||
format_parse_error("Expected key-value pair or closing brace in object", t)
|
||||
};
|
||||
}
|
||||
}
|
||||
auto [key, new_index, error] = parse(tokens, index);
|
||||
if (!error.empty())
|
||||
{
|
||||
return {{}, index, error};
|
||||
}
|
||||
|
||||
if (key.type != value_type::string) {
|
||||
return {{}, index,
|
||||
format_parse_error("Expected string key in object", t)};
|
||||
}
|
||||
|
||||
index = new_index;
|
||||
t = tokens[index];
|
||||
|
||||
if (!(t.type == token_type::syntax && t.value == ":")) {
|
||||
return {{}, index,
|
||||
format_parse_error("Expected colon after key in object", t)};
|
||||
}
|
||||
index++;
|
||||
t = tokens[index];
|
||||
|
||||
auto [value, new_index1, error1] = parse(tokens, index);
|
||||
if (error1.size()) {
|
||||
return {{}, index, error1};
|
||||
}
|
||||
|
||||
values[key.string.value()] = value;
|
||||
index = new_index1;
|
||||
}
|
||||
return {values, index+1, ""};
|
||||
}
|
||||
|
||||
std::tuple<json::value, int, std::string> parse(std::vector<json::token> tokens, int index) {
|
||||
auto token = tokens[index];
|
||||
switch(token.type) {
|
||||
case token_type::number: {
|
||||
double n = std::stod(token.value);
|
||||
return {json::number(n), index + 1, ""};
|
||||
}
|
||||
case token_type::boolean:
|
||||
return {json::boolean(token.value == "true"), index + 1, ""};
|
||||
case token_type::null:
|
||||
return {json::value(), index+1, ""};
|
||||
case token_type::string:
|
||||
return {json::value(token.value), index+1, ""};
|
||||
case token_type::syntax: {
|
||||
if (token.value == "[") {
|
||||
auto [array, new_index, error] = parse_array(tokens, index + 1);
|
||||
return {json::value(array), new_index, error};
|
||||
}
|
||||
if (token.value == "{") {
|
||||
auto [object, new_index, error] = parse_object(tokens, index + 1);
|
||||
return {json::value(object), new_index, error};
|
||||
}
|
||||
}
|
||||
default:
|
||||
return {json::value(), index, format_parse_error("Failed to parse", token)};
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<json::value, std::string> parse(std::string source) {
|
||||
auto [tokens, error] = json::lex(source);
|
||||
if (error.size())
|
||||
{
|
||||
return std::make_tuple(json::value{}, error);
|
||||
}
|
||||
|
||||
auto [ast, _, error1] = json::parse(tokens);
|
||||
return {ast, error1};
|
||||
}
|
||||
|
||||
std::string deparse(json::value v, std::string whitespace) {
|
||||
switch(v.type) {
|
||||
case json::value_type::string:
|
||||
return "\"" + v.string.value() + "\"";
|
||||
case json::value_type::boolean:
|
||||
return (v.boolean.value() ? "true" : "false");
|
||||
case json::value_type::number:
|
||||
return std::to_string(v.number.value());
|
||||
case json::value_type::null:
|
||||
return "null";
|
||||
case json::value_type::array: {
|
||||
std::string s = "[\n";
|
||||
auto a = v.array.value();
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
auto value = a[i];
|
||||
s += whitespace + " " + deparse(value, whitespace + " ");
|
||||
if (i < a.size() - 1) {
|
||||
s += ",";
|
||||
}
|
||||
s += "\n";
|
||||
}
|
||||
return s + whitespace + "]";
|
||||
}
|
||||
case json::value_type::object: {
|
||||
std::string s = "{\n";
|
||||
auto values = v.object.value();
|
||||
auto i = 0;
|
||||
for (auto const &[key, value] : values) {
|
||||
s += whitespace + " " + "\"" + key + "\":" + deparse(value, whitespace + " ");
|
||||
|
||||
if (i < values.size() - 1) {
|
||||
s += ",";
|
||||
}
|
||||
s += "\n";
|
||||
i++;
|
||||
}
|
||||
return s + whitespace + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value::value(): type(value_type::null) { }
|
||||
|
||||
value::value(double v): type(value_type::number), number(v) {}
|
||||
|
||||
value::value(const std::string &v): string(v), type(value_type::string) {}
|
||||
|
||||
value::value(const std::vector<value> &v): array(v), type(value_type::array) {}
|
||||
|
||||
value::value(const std::map<std::string, value> &v): object(v), type(value_type::object) {}
|
||||
|
||||
value::value(bool v) : boolean(v), type(value_type::boolean) {}
|
||||
|
||||
value::value(int v) : number(v), type(value_type::number) {}
|
||||
|
||||
value::value(float v) : number(v), type(value_type::number) {}
|
||||
|
||||
value &value::at(const std::string &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);
|
||||
// TODO: utilize std::map operator
|
||||
return value::object->at(key);
|
||||
}
|
||||
|
||||
const value &value::operator[](const std::string &key) const {
|
||||
assert(type == value_type::object);
|
||||
// TODO: utilize std::map operator
|
||||
return value::object->at(key);
|
||||
}
|
||||
|
||||
bool value::contains(const std::string &key) {
|
||||
assert(type == value_type::object);
|
||||
return this->value::object->contains(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;
|
||||
out.value::string = text;
|
||||
return out;
|
||||
}
|
||||
|
||||
struct number number(double input) {
|
||||
struct number out;
|
||||
out.type = value_type::number;
|
||||
out.value::number = input;
|
||||
return out;
|
||||
}
|
||||
|
||||
struct boolean boolean(bool input) {
|
||||
struct boolean out;
|
||||
out.type = value_type::boolean;
|
||||
out.number = input;
|
||||
return out;
|
||||
}
|
||||
|
||||
array::array(const std::vector<value>& v)
|
||||
{
|
||||
|
||||
this->type = value_type::array;
|
||||
this->value::array = std::vector<value>();
|
||||
|
||||
for (auto& elem: v)
|
||||
this->value::array->push_back(elem);
|
||||
|
||||
}
|
||||
|
||||
/*struct array array()
|
||||
{
|
||||
struct json::array out;
|
||||
out.type = value_type::array;
|
||||
out.array = std::vector<value>();
|
||||
return out;
|
||||
}
|
||||
|
||||
struct object object(std::map<std::string, value> input)
|
||||
{
|
||||
struct object out;
|
||||
out.type = value_type::object;
|
||||
out.value::object = input;
|
||||
return out;
|
||||
}
|
||||
|
||||
struct object object()
|
||||
{
|
||||
struct object out;
|
||||
out.type = value_type::object;
|
||||
out.value::object = std::map<std::string, value>();
|
||||
return out;
|
||||
}*/
|
||||
|
||||
bool object::contains(const std::string &key) {
|
||||
return this->value::object->contains(key);
|
||||
}
|
||||
|
||||
void object::insert(const std::pair<std::string, value> &kvp) {
|
||||
value::object->insert(kvp);
|
||||
}
|
||||
|
||||
void object::insert(const std::string &key, const value &val) {
|
||||
value::object->insert({key, val});
|
||||
}
|
||||
|
||||
json::object & object::operator+=(const std::pair<std::string, value> &kvp) {
|
||||
insert(kvp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
value &object::at(const std::string &key) {
|
||||
return this->value::object.value()[key];
|
||||
}
|
||||
|
||||
json::value_type object::type_of(const std::string &key) {
|
||||
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
137
src/lexer.cpp
Normal 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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user