28 Commits

Author SHA1 Message Date
92e97ab6fe Making edits and integrating ArgsParser 2025-07-08 11:14:49 -04:00
efd8ba9cd8 Integrate ArgsParser as separate package. Setup json_cli app testbed. 2025-07-03 02:03:11 -04:00
c2f95ad146 Ended up writing an ArgsParser class. 2025-07-02 03:23:54 -04:00
b938bebafe Update README.md 2025-07-01 18:14:34 -04:00
324ca06cf9 Start JSON exception handling 2025-06-24 13:43:03 -04:00
ddaf153340 Add is<T> member functions to json::value. 2025-06-04 01:23:48 -05:00
c2a83ded35 Work in progress refactor. 2025-06-03 13:51:35 -05:00
02cdf697fc Added documentation to json::lexer namespace. 2025-06-02 15:01:24 -05:00
fdda938352 Refactoring more. 2025-05-31 01:35:33 -05:00
933d988cf6 Dropping XML from being part of this package. 2025-05-30 09:48:10 -04:00
e73d31a783 Fixed missing include that wasn't caught previously. 2025-05-29 01:01:04 -05:00
b2d78e5b33 Fix logic error causing number tokens to be parsed as though they had two decimal place indicators (periods). 2025-05-22 18:08:48 -05:00
3357409aa7 Adjusted json::value operator indices. 2025-03-31 23:43:32 -04:00
24a6034564 Adjusted json::value operator indices. 2025-03-31 23:41:39 -04:00
9546b2bcfe Better demo program. 2025-03-27 16:36:52 -04:00
c643eb167f building 2025-03-27 03:06:04 -05:00
07dd98a268 Fixing demo program 2025-03-26 04:40:54 -04:00
6d700fcf97 Working out STL-like support for json arrays and objects. 2025-03-26 00:19:19 -05:00
7bbcb98914 Documentation work 2025-03-25 21:39:08 -05:00
1253861ad1 Partial edits of library refactor 2025-03-24 23:45:16 -04:00
c20c60f934 Make sure required headers are included in JSON.hpp 2025-03-22 15:08:43 -04:00
e183f130a4 Text File Parsing Warcrime mini update 2024-11-04 19:51:40 -06:00
5d078c16ff Text File Parsing Warcrime 2024-11-04 19:20:14 -06:00
77c7f241a5 Working on making this lib more usable by usage-testing. 2024-11-04 17:18:18 -05:00
42fd86bdf1 Working on making this lib more usable by usage-testing. 2024-11-04 16:40:57 -05:00
ddc5d3fee7 Restructure
Make the type that will eventually be returned. The idea is that, You'd want it to be easy to traverse the file with it's descending structure. This will also make it easy to turn it back into json.
2024-11-03 13:52:43 -05:00
9c926efb11 Update json.cpp
Ayyyy we actually make it to the end of a glTF2 now.
2024-11-02 14:55:50 -04:00
da75265fcb lexer update
parse decimals and negatives.
2024-11-02 09:51:22 -04:00
22 changed files with 1829 additions and 174 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
/.idea
/.cache
/.ccls-cache
/compile_commands.json
/cmake-build-debug
/build

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)
@@ -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
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})

107
include/JJX/ParsedFile.h Normal file
View File

@@ -0,0 +1,107 @@
#pragma once
#include <string>
#include <vector>
namespace JJX {
class ParsedValue;
class ParsedObject;
class ParsedString;
class ParsedNumber;
class ParsedArray;
class ParsedBool;
class ParsedNULL;
class ParsedFile;
}
/** This is kind-of slow, But it makes it easy to traverse the file using dynamic_cast.
* If you need bleeding edge speed while parsing you're not doing it right anyway :shrug: - Redacted */
class JJX::ParsedValue {
protected:
std::string key;
public:
[[nodiscard]] std::string GetKey() const;
public:
virtual ~ParsedValue() = default;
};
class JJX::ParsedObject : public ParsedValue {
private:
std::vector<ParsedValue*> values;
protected:
explicit ParsedObject(const std::vector<ParsedValue*>& values);
public:
void EraseChildren();
[[nodiscard]] std::vector<ParsedValue*> GetValue() const;
public:
static ParsedObject* Create(const std::vector<ParsedValue*>& values);
};
class JJX::ParsedString : public ParsedValue {
private:
std::string value;
protected:
ParsedString(const std::string& key, const std::string& value);
public:
[[nodiscard]] std::string GetValue() const;
public:
/** This is a trick borrowed from Java, But in this instance, it's to ensure that they're heap allocated. */
static ParsedString* Create(const std::string& key, const std::string& value);
};
class JJX::ParsedNumber : public ParsedValue {
private:
double value;
protected:
ParsedNumber(const std::string& key, const std::string& value);
ParsedNumber(const std::string& key, double value);
public:
[[nodiscard]] double GetValue() const;
public:
static ParsedNumber* Create(const std::string& key, const std::string& value);
static ParsedNumber* Create(const std::string& key, double value);
};
class JJX::ParsedArray : public ParsedValue {
private:
std::vector<ParsedValue*> values;
protected:
ParsedArray(const std::string& key, const std::vector<ParsedValue*>& values);
public:
void EraseChildren();
[[nodiscard]] std::vector<ParsedValue*> GetValue() const;
public:
static ParsedArray* Create(const std::string& key, const std::vector<ParsedValue*>& values);
};
class JJX::ParsedBool : public ParsedValue {
private:
bool value;
protected:
ParsedBool(const std::string& key, bool value);
public:
[[nodiscard]] bool GetValue() const;
public:
static ParsedBool* Create(const std::string& key, bool value);
};
class JJX::ParsedNULL : public ParsedValue {
protected:
explicit ParsedNULL(const std::string& key);
public:
[[nodiscard]] nullptr_t GetValue() const;
public:
static ParsedNULL* Create(const std::string& key);
};
class JJX::ParsedFile {
private:
std::vector<ParsedValue*> content;
public:
std::vector<ParsedValue*> GetContent();
public:
explicit ParsedFile(const std::vector<ParsedValue*>& content);
~ParsedFile();
};

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

@@ -1,35 +0,0 @@
#pragma once
#include <iostream>
#include <vector>
#include <memory>
#include <optional>
#include <map>
namespace jjx
{
namespace 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;
};
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 = "");
}
namespace xml {}
}

341
include/json.hpp Normal file
View 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
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);
}

218
main.cpp
View File

@@ -1,22 +1,210 @@
#include <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>
int main(int argc, char *argv[]) {
if (argc == 1) {
std::cerr << "Expected JSON input argument to parse" << std::endl;
return 1;
}
#include "AnsiEscapeCodes.hpp"
std::string in{argv[1]};
/// 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);
auto [ast, error] = json::parse(in);
if (error.size()) {
std::cerr << error << std::endl;
return 1;
}
file.seekg(0, std::ios::end);
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::cout << json::deparse(ast);
return 0;
std::string file_content(file_size, '\0');
file.read(&file_content[0], file_size);
file.close();
return file_content;
}
void parse_json_file(const std::string& file_path) {
auto file_content = read_file(file_path);
auto [text, parse_error] = json::parse(file_content);
if (!parse_error.empty())
std::cerr << "Error while parsing json: " << parse_error << std::endl;
std::cout << json::deparse(text) << std::endl;
}
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()
{
}
/// Parses a string of JSON and outputs any syntax errors, if present.
void validate_json_string(const std::string& input) {
}
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;
}
int main(int argc, char* argv[]) {
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());
}

121
samples/cube.glTF Normal file
View File

@@ -0,0 +1,121 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v3.6.28",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"Cube"
}
],
"materials":[
{
"doubleSided":true,
"name":"Material",
"pbrMetallicRoughness":{
"baseColorFactor":[
0.800000011920929,
0.800000011920929,
0.800000011920929,
1
],
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"name":"Cube",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3,
"material":0
}
]
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":24,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":288,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":288,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":576,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":768,
"target":34963
}
],
"buffers":[
{
"byteLength":840,
"uri":"data:application/octet-stream;base64,AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AADAPgAAQD8AAAA+AAAAPwAAwD4AAEA/AAAgPwAAgD8AACA/AAAAAAAAYD8AAIA+AADAPgAAgD8AAAA+AACAPgAAwD4AAAAAAQAOABQAAQAUAAcACgAGABMACgATABcAFQASAAwAFQAMAA8AEAADAAkAEAAJABYABQACAAgABQAIAAsAEQANAAAAEQAAAAQA"
}
]
}

27
samples/menu.json Normal file
View File

@@ -0,0 +1,27 @@
{"menu": {
"header": "SVG Viewer",
"items": [
{"id": "Open"},
{"id": "OpenNew", "label": "Open New"},
null,
{"id": "ZoomIn", "label": "Zoom In"},
{"id": "ZoomOut", "label": "Zoom Out"},
{"id": "OriginalView", "label": "Original View"},
null,
{"id": "Quality"},
{"id": "Pause"},
{"id": "Mute"},
null,
{"id": "Find", "label": "Find..."},
{"id": "FindAgain", "label": "Find Again"},
{"id": "Copy"},
{"id": "CopyAgain", "label": "Copy Again"},
{"id": "CopySVG", "label": "Copy SVG"},
{"id": "ViewSVG", "label": "View SVG"},
{"id": "ViewSource", "label": "View Source"},
{"id": "SaveAs", "label": "Save As"},
null,
{"id": "Help"},
{"id": "About", "label": "About Adobe CVG Viewer..."}
]
}}

16
samples/months.json Normal file
View File

@@ -0,0 +1,16 @@
{
"months": [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
]
}

32
samples/product_info.json Normal file
View File

@@ -0,0 +1,32 @@
{
"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": [
{
"user": "user123",
"rating": 5,
"review_text": "Great product, highly recommended!"
},
{
"user": "user456",
"rating": 4,
"review_text": "Good value for the price."
},
{
"user": "user450",
"rating": 4,
"review_text": "Good value for the price."
}
]
}

55
samples/tiles.json Normal file
View 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
}
]

3
samples/vector3.json Normal file
View File

@@ -0,0 +1,3 @@
{
"position": [0, 0, 0]
}

View File

@@ -4,13 +4,13 @@
"title": "Sample Konfabulator Widget",
"name": "main_window",
"width": 500,
"height": 500
"height": -500.75
},
"image": {
"src": "Images/Sun.png",
"name": "sun1",
"hOffset": 250,
"vOffset": 250,
"hOffset": -250,
"vOffset": 2.50,
"alignment": "center"
},
"text": {

136
src/ParsedFile.cpp Normal file
View File

@@ -0,0 +1,136 @@
#include <JJX/ParsedFile.h>
using namespace JJX;
std::string JJX::ParsedValue::GetKey() const {
return key;
}
JJX::ParsedString::ParsedString(const std::string &key, const std::string& value) {
this->key = key;
this->value = value;
}
std::string JJX::ParsedString::GetValue() const {
return value;
}
ParsedString* ParsedString::Create(const std::string& key, const std::string& value) {
return new ParsedString(key, value);
}
double JJX::ParsedNumber::GetValue() const {
return value;
}
JJX::ParsedNumber::ParsedNumber(const std::string& key, const std::string& value) {
this->key = key;
this->value = std::stod(value);
}
JJX::ParsedNumber::ParsedNumber(const std::string& key, double value) {
this->key = key;
this->value = value;
}
ParsedNumber* ParsedNumber::Create(const std::string& key, const std::string& value) {
return new ParsedNumber(key, value);
}
ParsedNumber* ParsedNumber::Create(const std::string& key, double value) {
return new ParsedNumber(key, value);
}
std::vector<ParsedValue*> JJX::ParsedArray::GetValue() const {
return values;
}
ParsedArray::ParsedArray(const std::string& key, const std::vector<ParsedValue*>& values) {
this->key = key;
this->values = values;
}
void ParsedArray::EraseChildren() {
for (auto& value : values)
if (auto* array = dynamic_cast<ParsedArray*>(value))
array->EraseChildren();
else if (auto* object = dynamic_cast<ParsedObject*>(value))
object->EraseChildren();
else
delete value;
values = {};
}
ParsedArray* ParsedArray::Create(const std::string& key, const std::vector<ParsedValue*>& values) {
return new ParsedArray(key, values);
}
ParsedBool::ParsedBool(const std::string& key, bool value) {
this->key = key;
this->value = value;
}
bool ParsedBool::GetValue() const {
return value;
}
ParsedBool* ParsedBool::Create(const std::string& key, bool value) {
return new ParsedBool(key, value);
}
nullptr_t ParsedNULL::GetValue() const {
return nullptr;
}
ParsedNULL::ParsedNULL(const std::string& key) {
this->key = key;
}
ParsedNULL* ParsedNULL::Create(const std::string& key) {
return new ParsedNULL(key);
}
std::vector<ParsedValue*> ParsedFile::GetContent() {
return content;
}
// kick off the recursive deletion.
ParsedFile::~ParsedFile() {
for (auto& value : content)
if (auto* array = dynamic_cast<ParsedArray*>(value))
array->EraseChildren();
else if (auto* object = dynamic_cast<ParsedObject*>(value))
object->EraseChildren();
else
delete value;
content = {};
}
ParsedFile::ParsedFile(const std::vector<ParsedValue*>& content) {
this->content = content;
}
ParsedObject::ParsedObject(const std::vector<ParsedValue*>& values) {
this->key = "";
this->values = values;
}
ParsedObject* ParsedObject::Create(const std::vector<ParsedValue*>& values) {
return new ParsedObject(values);
}
void ParsedObject::EraseChildren() {
for (auto& value : values)
if (auto* array = dynamic_cast<ParsedArray*>(value))
array->EraseChildren();
else if (auto* object = dynamic_cast<ParsedObject*>(value))
object->EraseChildren();
else
delete value;
values = {};
}
std::vector<ParsedValue*> ParsedObject::GetValue() const {
return values;
}

View File

@@ -1,3 +0,0 @@
//
// Created by josh on 8/19/24.
//

View File

@@ -1,7 +1,23 @@
#include <jjx.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;
@@ -47,108 +63,197 @@ 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::value& value::operator=(double in)
{
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, ""};
type = value_type::number;
number = in;
return *this;
}
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 = "";
// TODO: handle not just integers
while(true) {
if (index == raw_json.length()) {
break;
}
auto c = raw_json[index];
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)
json::value& value::operator=(const std::string& in)
{
return lex_keyword(raw_json, "null", token_type::null, index);
type = value_type::string;
string = in;
return *this;
}
std::tuple<json::token, int, std::string> lex_true(std::string raw_json, int index)
json::value& value::operator=(bool in)
{
return lex_keyword(raw_json, "true", token_type::boolean, index);
type = value_type::boolean;
boolean = in;
return *this;
}
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);
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);
@@ -167,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
@@ -182,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, ""};
}
@@ -261,7 +366,7 @@ namespace jjx::json {
}
}
auto [key, new_index, error] = parse(tokens, index);
if (error.size())
if (!error.empty())
{
return {{}, index, error};
}
@@ -296,39 +401,35 @@ namespace jjx::json {
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, ""};
double n = std::stod(token.value);
return {json::number(n), index + 1, ""};
}
case token_type::boolean:
return {json::value{.boolean = token.value == "true", .type = value_type::boolean}, index + 1, ""};
return {json::boolean(token.value == "true"), index + 1, ""};
case token_type::null:
return {json::value{.type = value_type::null}, index+1, ""};
return {json::value(), index+1, ""};
case token_type::string:
return {json::value{.string = token.value, .type = value_type::string}, index+1, ""};
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 = array, .type = value_type::array}, new_index, error};
return {json::value(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};
return {json::value(object), new_index, error};
}
}
default:
return {{}, index, format_parse_error("Failed to parse", token)};
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(jjx::json::value{}, error);
return std::make_tuple(json::value{}, error);
}
auto [ast, _, error1] = json::parse(tokens);
@@ -375,4 +476,171 @@ namespace jjx::json {
}
}
}
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
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);
}
}

View File

@@ -1 +0,0 @@
#include "../include/jjx.hpp"