├── .clang-format ├── neujson.pc.in ├── .github └── workflows │ ├── registry.json │ └── docker-publish.yml ├── test ├── clzll_test.cc ├── strtod_test.cc ├── itoa_test.cc ├── round_trip.cc └── parse_test.cc ├── benchmark ├── benchmark.h └── hominsu_neujson.cc ├── include └── neujson │ ├── non_copyable.h │ ├── ostream_wrapper.h │ ├── internal │ ├── strtod.h │ ├── cllzl.h │ ├── ieee754.h │ ├── pow10.h │ ├── diy_fp.h │ ├── big_integer.h │ └── itoa.h │ ├── string_write_stream.h │ ├── string_read_stream.h │ ├── exception.h │ ├── file_write_stream.h │ ├── file_read_stream.h │ ├── neujson.h │ ├── istream_wrapper.h │ ├── document.h │ ├── pretty_writer.h │ ├── writer.h │ ├── value.h │ └── reader.h ├── neujsonConfigVersion.cmake.in ├── .gitignore ├── neujsonConfig.cmake.in ├── example ├── create_json.cc ├── parse.cc ├── pretty.cc ├── document.cc ├── file.cc ├── stream_wrapper.cc ├── struct.cc └── sample.h ├── .all-contributorsrc ├── .dockerignore ├── docker ├── docker-bake.hcl └── Dockerfile ├── neujsonUninstall.cmake.in ├── LICENSE ├── cmake └── ExtProjectUtils.cmake ├── CMakeLists.txt ├── README_zh.md └── README.md /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM -------------------------------------------------------------------------------- /neujson.pc.in: -------------------------------------------------------------------------------- 1 | includedir=@INCLUDE_INSTALL_DIR@ 2 | 3 | Name: @PROJECT_NAME@ 4 | Description: JSON parser/generator in C++17 5 | Version: @LIB_VERSION_STRING@ 6 | URL: https://github.com/hominsu/neujson 7 | Cflags: -I${includedir} -------------------------------------------------------------------------------- /.github/workflows/registry.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Docker", 4 | "registry": "", 5 | "push_link": "hominsu", 6 | "id": "DOCKER_ID", 7 | "token": "DOCKER_TOKEN" 8 | }, 9 | { 10 | "name": "Ghcr", 11 | "registry": "ghcr.io", 12 | "push_link": "ghcr.io/hominsu", 13 | "id": "GHCR_ID", 14 | "token": "GITHUB_TOKEN" 15 | } 16 | ] -------------------------------------------------------------------------------- /test/clzll_test.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/7/1. 3 | // 4 | 5 | #include "neujson/internal/cllzl.h" 6 | 7 | #include "gtest/gtest.h" 8 | 9 | using namespace neujson::internal; 10 | 11 | TEST(clzll, normal) { 12 | EXPECT_EQ(clzll(1), 63U); 13 | EXPECT_EQ(clzll(2), 62U); 14 | EXPECT_EQ(clzll(12), 60U); 15 | EXPECT_EQ(clzll(0x0000000080000001UL), 32U); 16 | EXPECT_EQ(clzll(0x8000000000000001UL), 0U); 17 | } 18 | -------------------------------------------------------------------------------- /benchmark/benchmark.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homin Su on 24-5-29. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_BENCHMARK_H 6 | #define NEUJSON_NEUJSON_BENCHMARK_H 7 | 8 | #include 9 | 10 | namespace resource { 11 | 12 | static std::filesystem::path canada = RESOURCES_DIR "/canada.json"; 13 | static std::filesystem::path citm_catalog = RESOURCES_DIR "/citm_catalog.json"; 14 | 15 | } // namespace resource 16 | 17 | #endif // NEUJSON_NEUJSON_BENCHMARK_H 18 | -------------------------------------------------------------------------------- /include/neujson/non_copyable.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/7. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_NON_COPYABLE_H_ 6 | #define NEUJSON_NEUJSON_NON_COPYABLE_H_ 7 | 8 | namespace neujson { 9 | 10 | class NonCopyable { 11 | public: 12 | NonCopyable(const NonCopyable &) = delete; 13 | NonCopyable &operator=(const NonCopyable &) = delete; 14 | 15 | protected: 16 | NonCopyable() = default; 17 | ~NonCopyable() = default; 18 | }; 19 | 20 | } // namespace neujson 21 | 22 | #endif // NEUJSON_NEUJSON_NON_COPYABLE_H_ 23 | -------------------------------------------------------------------------------- /neujsonConfigVersion.cmake.in: -------------------------------------------------------------------------------- 1 | set(PACKAGE_VERSION "@LIB_VERSION_STRING@") 2 | 3 | if (PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) 4 | set(PACKAGE_VERSION_EXACT "true") 5 | endif (PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) 6 | if (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) 7 | set(PACKAGE_VERSION_COMPATIBLE "true") 8 | else (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) 9 | set(PACKAGE_VERSION_UNSUITABLE "true") 10 | endif (NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | .idea 3 | .cache 4 | out 5 | x64 6 | build 7 | cmake-build-*/ 8 | 9 | **/*.sln 10 | **/*.vcxproj 11 | **/*.vcxproj.* 12 | 13 | **/*.json 14 | **/*.DS_Store 15 | 16 | # Ignore all bazel-* symlinks. There is no full list since this can change 17 | # based on the name of the directory bazel is cloned into. 18 | /bazel-* 19 | # Ignore outputs generated during Bazel bootstrapping. 20 | /output/ 21 | # Ignore jekyll build output. 22 | /production 23 | /.sass-cache 24 | # Bazelisk version file 25 | .bazelversion 26 | # User-specific .bazelrc 27 | user.bazelrc 28 | -------------------------------------------------------------------------------- /neujsonConfig.cmake.in: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | set( neujson_SOURCE_DIR "@CONFIG_SOURCE_DIR@") 3 | set( neujson_DIR "@CONFIG_DIR@") 4 | get_filename_component(neujson_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 5 | 6 | set( neujson_INCLUDE_DIR "@neujson_INCLUDE_DIR@" ) 7 | set( neujson_INCLUDE_DIRS "@neujson_INCLUDE_DIR@" ) 8 | message(STATUS "neujson found. Headers: ${neujson_INCLUDE_DIRS}") 9 | 10 | if(NOT TARGET neujson) 11 | add_library(neujson INTERFACE IMPORTED) 12 | set_property(TARGET neujson PROPERTY 13 | INTERFACE_INCLUDE_DIRECTORIES ${neujson_INCLUDE_DIRS}) 14 | endif() -------------------------------------------------------------------------------- /example/create_json.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/15. 3 | // 4 | 5 | #include 6 | 7 | #include "neujson/file_write_stream.h" 8 | #include "neujson/writer.h" 9 | 10 | int main() { 11 | char writeBuffer[65536]; 12 | neujson::FileWriteStream os(stdout, writeBuffer); 13 | neujson::Writer writer(os); 14 | 15 | writer.StartArray(); 16 | writer.Double(std::numeric_limits::infinity()); 17 | writer.Double(std::numeric_limits::quiet_NaN()); 18 | writer.Bool(true); 19 | writer.String(R"({"happy": true, "pi": 3.141})"); 20 | writer.Bool(true); 21 | writer.EndArray(); 22 | 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /example/parse.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/15. 3 | // 4 | 5 | #include 6 | 7 | #include "neujson/file_write_stream.h" 8 | #include "neujson/reader.h" 9 | #include "neujson/string_read_stream.h" 10 | #include "neujson/writer.h" 11 | 12 | int main() { 13 | neujson::StringReadStream in(R"({"key":"value"})"); 14 | 15 | char writeBuffer[65536]; 16 | 17 | neujson::FileWriteStream out(stdout, writeBuffer); 18 | neujson::Writer writer(out); 19 | 20 | if (const auto err = neujson::Reader::Parse(in, writer); 21 | err != neujson::error::OK) { 22 | puts(neujson::ParseErrorStr(err)); 23 | return EXIT_FAILURE; 24 | } 25 | 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /example/pretty.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/15. 3 | // 4 | 5 | #include 6 | 7 | #include "neujson/document.h" 8 | #include "neujson/file_write_stream.h" 9 | #include "neujson/pretty_writer.h" 10 | #include "sample.h" 11 | 12 | int main() { 13 | neujson::Document doc; 14 | if (const auto err = doc.Parse(kSample[1]); err != neujson::error::OK) { 15 | puts(neujson::ParseErrorStr(err)); 16 | return EXIT_FAILURE; 17 | } 18 | 19 | char writeBuffer[65536]; 20 | neujson::FileWriteStream out(stdout, writeBuffer); 21 | neujson::PrettyWriter pretty_writer(out); 22 | pretty_writer.SetIndent(' ', 2); 23 | doc.WriteTo(pretty_writer); 24 | 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md", 4 | "README_zh.md", 5 | "docs/README.md", 6 | "docs/zh-cn/README_zh.md" 7 | ], 8 | "imageSize": 100, 9 | "commit": false, 10 | "contributors": [ 11 | { 12 | "login": "hominsu", 13 | "name": "Homing So", 14 | "avatar_url": "https://avatars.githubusercontent.com/u/14991225?v=4", 15 | "profile": "https://homing.so", 16 | "contributions": [ 17 | "code", 18 | "doc", 19 | "design", 20 | "example", 21 | "infra", 22 | "platform", 23 | "test" 24 | ] 25 | } 26 | ], 27 | "contributorsPerLine": 7, 28 | "projectName": "neujson", 29 | "projectOwner": "hominsu", 30 | "repoType": "github", 31 | "repoHost": "https://github.com", 32 | "skipCi": true 33 | } 34 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/*.*proj.user 11 | **/*.dbmdl 12 | **/*.jfm 13 | **/azds.yaml 14 | **/bin 15 | **/charts 16 | **/compose* 17 | **/Dockerfile* 18 | **/node_modules 19 | **/npm-debug.log 20 | **/obj 21 | **/secrets.dev.yaml 22 | **/values.dev.yaml 23 | README.md 24 | README_zh.md 25 | 26 | **/.idea 27 | **/.github 28 | **/docs 29 | 30 | # Ignore all bazel-* symlinks. There is no full list since this can change 31 | # based on the name of the directory bazel is cloned into. 32 | /bazel-* 33 | # Ignore outputs generated during Bazel bootstrapping. 34 | /output/ 35 | # Ignore jekyll build output. 36 | /production 37 | /.sass-cache 38 | # Bazelisk version file 39 | .bazelversion 40 | # User-specific .bazelrc 41 | user.bazelrc -------------------------------------------------------------------------------- /example/document.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/15. 3 | // 4 | 5 | #include 6 | 7 | #include "neujson/document.h" 8 | #include "neujson/string_write_stream.h" 9 | #include "neujson/writer.h" 10 | #include "sample.h" 11 | 12 | int main() { 13 | // 1. Parse a JSON string into DOM. 14 | neujson::Document doc; 15 | if (const auto err = doc.Parse(kSample[0]); err != neujson::error::OK) { 16 | puts(neujson::ParseErrorStr(err)); 17 | return EXIT_FAILURE; 18 | } 19 | 20 | // 2. Modify it by DOM. 21 | auto &s = doc[0]["Longitude"]; 22 | s.SetDouble(s.GetDouble() + 100.0); 23 | 24 | // 3. Stringify the DOM 25 | neujson::StringWriteStream os; 26 | neujson::Writer writer(os); 27 | doc.WriteTo(writer); 28 | 29 | // Output 30 | fprintf(stdout, "%.*s", static_cast(os.get().length()), os.get().data()); 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /docker/docker-bake.hcl: -------------------------------------------------------------------------------- 1 | variable "REPO" { 2 | default = "hominsu" 3 | } 4 | 5 | variable "AUTHOR_NAME" { 6 | default = "hominsu" 7 | } 8 | 9 | variable "AUTHOR_EMAIL" { 10 | default = "hominsu@foxmail.com" 11 | } 12 | 13 | variable "ALPINE_VERSION" { 14 | default = "latest" 15 | } 16 | 17 | variable "VERSION" { 18 | default = "" 19 | } 20 | 21 | group "default" { 22 | targets = [ 23 | "neujson-alpine", 24 | ] 25 | } 26 | 27 | target "neujson-alpine" { 28 | context = "." 29 | dockerfile = "docker/Dockerfile" 30 | args = { 31 | AUTHOR_NAME = "${AUTHOR_NAME}" 32 | AUTHOR_EMAIL = "${AUTHOR_EMAIL}" 33 | ALPINE_VERSION = "${ALPINE_VERSION}" 34 | VERSION = "${VERSION}" 35 | } 36 | tags = [ 37 | "${REPO}/neujson:latest", 38 | notequal("", VERSION) ? "${REPO}/neujson:${VERSION}" : "", 39 | ] 40 | platforms = ["linux/amd64"] 41 | } -------------------------------------------------------------------------------- /include/neujson/ostream_wrapper.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/7/1. 3 | // 4 | 5 | #ifndef NEUJSON_INCLUDE_NEUJSON_OSTREAM_WRAPPER_H_ 6 | #define NEUJSON_INCLUDE_NEUJSON_OSTREAM_WRAPPER_H_ 7 | 8 | #include 9 | 10 | #include "non_copyable.h" 11 | 12 | namespace neujson { 13 | 14 | template class OStreamWrapper : NonCopyable { 15 | public: 16 | using Ch = typename Stream::char_type; 17 | 18 | private: 19 | Stream &stream_; 20 | 21 | public: 22 | explicit OStreamWrapper(Stream &stream) : stream_(stream) {} 23 | 24 | void put(Ch ch) { stream_.put(ch); } 25 | 26 | void puts(const char *str, std::streamsize length) { 27 | stream_.write(str, length); 28 | } 29 | 30 | void put_sv(std::string_view sv) const { stream_ << sv; } 31 | 32 | void flush() { stream_.flush(); } 33 | }; 34 | 35 | } // namespace neujson 36 | 37 | #endif // NEUJSON_INCLUDE_NEUJSON_OSTREAM_WRAPPER_H_ 38 | -------------------------------------------------------------------------------- /include/neujson/internal/strtod.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/6/30. 3 | // 4 | 5 | #ifndef NEUJSON_INCLUDE_NEUJSON_INTERNAL_STRTOD_H_ 6 | #define NEUJSON_INCLUDE_NEUJSON_INTERNAL_STRTOD_H_ 7 | 8 | #include "diy_fp.h" 9 | #include "ieee754.h" 10 | #include "pow10.h" 11 | 12 | namespace neujson::internal { 13 | 14 | inline double FastPath(const double fraction, const int exponent) { 15 | if (exponent < -308) { 16 | return 0.0; 17 | } else if (exponent >= 0) { 18 | return fraction * Pow10(exponent); 19 | } else { 20 | return fraction * Pow10(-exponent); 21 | } 22 | } 23 | 24 | inline double StrTodNormalPrecision(double d, int p) { 25 | if (p < -308) { 26 | d = FastPath(d, -308); 27 | d = FastPath(d, p + 308); 28 | } else { 29 | d = FastPath(d, p); 30 | } 31 | return d; 32 | } 33 | 34 | } // namespace neujson::internal 35 | 36 | #endif // NEUJSON_INCLUDE_NEUJSON_INTERNAL_STRTOD_H_ 37 | -------------------------------------------------------------------------------- /benchmark/hominsu_neujson.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homin Su on 24-5-29. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "neujson/document.h" 12 | 13 | #include "benchmark.h" 14 | 15 | static void BM_decode_value(benchmark::State &state, 16 | const std::filesystem::path &path) { 17 | auto ifs = std::ifstream(path, std::ifstream::binary); 18 | const std::string torrent(std::istreambuf_iterator{ifs}, 19 | std::istreambuf_iterator{}); 20 | 21 | for (auto _ : state) { 22 | benchmark::DoNotOptimize(neujson::Document().Parse(torrent)); 23 | benchmark::ClobberMemory(); 24 | } 25 | state.SetBytesProcessed(state.iterations() * torrent.size()); 26 | } 27 | 28 | BENCHMARK_CAPTURE(BM_decode_value, "canada", resource::canada); 29 | BENCHMARK_CAPTURE(BM_decode_value, "citm_catalog", resource::citm_catalog); 30 | 31 | BENCHMARK_MAIN(); 32 | -------------------------------------------------------------------------------- /example/file.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/12. 3 | // 4 | 5 | #include 6 | 7 | #include "neujson/document.h" 8 | #include "neujson/file_read_stream.h" 9 | #include "neujson/file_write_stream.h" 10 | #include "neujson/pretty_writer.h" 11 | 12 | int main() { 13 | #if defined(_MSC_VER) 14 | FILE *input; 15 | fopen_s(&input, "../../citm_catalog.json", "r"); 16 | #else 17 | FILE *input = fopen("../../citm_catalog.json", "r"); 18 | #endif 19 | if (input == nullptr) { 20 | exit(EXIT_FAILURE); 21 | } 22 | neujson::fp is(input); 23 | 24 | neujson::Document doc; 25 | const auto err = doc.ParseStream(is); 26 | fclose(input); 27 | 28 | if (err != neujson::error::OK) { 29 | puts(neujson::ParseErrorStr(err)); 30 | return EXIT_FAILURE; 31 | } 32 | 33 | char writeBuffer[65536]; 34 | neujson::FileWriteStream os(stdout, writeBuffer); 35 | neujson::PrettyWriter writer(os); 36 | writer.SetIndent(' ', 2); 37 | doc.WriteTo(writer); 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /neujsonUninstall.cmake.in: -------------------------------------------------------------------------------- 1 | if (NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") 2 | message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") 3 | endif () 4 | 5 | file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) 6 | string(REGEX REPLACE "\n" ";" files "${files}") 7 | foreach (file ${files}) 8 | message(STATUS "Uninstalling $ENV{DESTDIR}${file}") 9 | if (IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 10 | exec_program( 11 | "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" 12 | OUTPUT_VARIABLE rm_out 13 | RETURN_VALUE rm_retval 14 | ) 15 | if (NOT "${rm_retval}" STREQUAL 0) 16 | message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") 17 | endif () 18 | else (IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 19 | message(STATUS "File $ENV{DESTDIR}${file} does not exist.") 20 | endif () 21 | endforeach () 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Homing So 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # build stage 2 | ARG ALPINE_VERSION 3 | FROM alpine:${ALPINE_VERSION} AS builder 4 | # intstall git build-base 5 | RUN apk add --no-cache git build-base cmake 6 | # build app 7 | COPY .. /src 8 | WORKDIR /src 9 | RUN cmake -H. -Bbuild \ 10 | -DCMAKE_BUILD_TYPE=Release \ 11 | -DNEUJSON_BUILD_EXAMPLES=ON \ 12 | -DNEUJSON_BUILD_TESTS=ON \ 13 | -DNEUJSON_ENABLE_INSTRUMENTATION_OPT=OFF && \ 14 | cmake --build ./build --parallel $(nproc) && \ 15 | GTEST_COLOR=TRUE ctest -VV --test-dir ./build/ --output-on-failure && \ 16 | cmake --install ./build 17 | 18 | # final stage 19 | ARG ALPINE_VERSION 20 | FROM alpine:${ALPINE_VERSION} 21 | # image info 22 | ARG AUTHOR_NAME 23 | ARG AUTHOR_EMAIL 24 | ARG VERSION 25 | # label 26 | LABEL author=${AUTHOR_NAME} email=${AUTHOR_EMAIL} alpine_version=${ALPINE_VERSION} version=${VERSION} 27 | # cpoy the app from builder 28 | COPY --from=builder /usr/local/include/neujson /usr/local/include/neujson 29 | COPY --from=builder /usr/local/lib/cmake/neujson /usr/local/lib/cmake/neujson 30 | COPY --from=builder /usr/local/lib/pkgconfig/neujson.pc /usr/local/lib/pkgconfig/neujson.pc 31 | -------------------------------------------------------------------------------- /example/stream_wrapper.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/7/2. 3 | // 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include "neujson/document.h" 10 | #include "neujson/istream_wrapper.h" 11 | #include "neujson/ostream_wrapper.h" 12 | #include "neujson/pretty_writer.h" 13 | #include "sample.h" 14 | 15 | int main() { 16 | std::stringstream iss; 17 | std::stringstream oss; 18 | 19 | iss << kSample[0]; 20 | 21 | // any class derived from std::istream, ex. std::istringstream, 22 | // std::stringstream, std::ifstream, std::fstream 23 | neujson::IStreamWrapper is(iss); 24 | 25 | neujson::Document doc; 26 | if (auto err = doc.ParseStream(is); err != neujson::error::OK) { 27 | puts(neujson::ParseErrorStr(err)); 28 | return EXIT_FAILURE; 29 | } 30 | 31 | // any class derived from std::ostream, ex. std::ostringstream, 32 | // std::stringstream, std::ofstream, std::fstream 33 | neujson::OStreamWrapper os(oss); 34 | neujson::PrettyWriter pretty_writer(os); 35 | pretty_writer.SetIndent(' ', 2); 36 | doc.WriteTo(pretty_writer); 37 | 38 | fprintf(stdout, "%.*s", static_cast(oss.str().length()), 39 | oss.str().data()); 40 | return 0; 41 | } -------------------------------------------------------------------------------- /include/neujson/string_write_stream.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/10. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_STRING_WRITE_STREAM_H_ 6 | #define NEUJSON_NEUJSON_STRING_WRITE_STREAM_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include "neujson.h" 12 | #include "non_copyable.h" 13 | 14 | #if defined(__GNUC__) 15 | NEUJSON_DIAG_PUSH 16 | NEUJSON_DIAG_OFF(effc++) 17 | #endif 18 | 19 | namespace neujson { 20 | 21 | class StringWriteStream : public NonCopyable { 22 | public: 23 | using Ch = char; 24 | 25 | private: 26 | static constexpr std::size_t kInnerBufferSize = 256; 27 | std::vector buffer_; 28 | 29 | public: 30 | StringWriteStream() { buffer_.reserve(kInnerBufferSize); } 31 | 32 | void put(const Ch ch) { buffer_.push_back(ch); } 33 | 34 | void puts(const Ch *str, const std::size_t length) { 35 | buffer_.insert(buffer_.end(), str, str + length); 36 | } 37 | 38 | void put_sv(const std::string_view str) { 39 | buffer_.insert(buffer_.end(), str.begin(), str.end()); 40 | } 41 | 42 | [[nodiscard]] std::string_view get() const { 43 | return {buffer_.data(), buffer_.size()}; 44 | } 45 | 46 | void flush() {} 47 | }; 48 | 49 | } // namespace neujson 50 | 51 | #if defined(__GNUC__) 52 | NEUJSON_DIAG_POP 53 | #endif 54 | 55 | #endif // NEUJSON_NEUJSON_STRING_WRITE_STREAM_H_ 56 | -------------------------------------------------------------------------------- /include/neujson/internal/cllzl.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/13. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_INTERNAL_CLLZL_H_ 6 | #define NEUJSON_NEUJSON_INTERNAL_CLLZL_H_ 7 | 8 | #include 9 | 10 | #include "neujson/neujson.h" 11 | 12 | #if defined(_MSC_VER) && !defined(UNDER_CE) 13 | #include 14 | #if defined(_WIN64) 15 | #pragma intrinsic(_BitScanReverse64) 16 | #else 17 | #pragma intrinsic(_BitScanReverse) 18 | #endif 19 | #endif 20 | 21 | namespace neujson::internal { 22 | 23 | inline uint32_t clzll(uint64_t n) { 24 | NEUJSON_ASSERT(n != 0); 25 | 26 | #if defined(_MSC_VER) && !defined(UNDER_CE) 27 | unsigned long r = 0; 28 | #if defined(_WIN64) 29 | _BitScanReverse64(&r, n); 30 | #else 31 | // scan the high 32 bits. 32 | if (_BitScanReverse(&r, static_cast(n >> 32))) { 33 | return 63 - (r + 32); 34 | } 35 | 36 | // scan the low 32 bits. 37 | _BitScanReverse(&r, static_cast(n & 0xFFFFFFFF)); 38 | #endif // _WIN64 39 | 40 | return 63 - r; 41 | #elif (defined(__GNUC__) && __GNUC__ >= 4) || \ 42 | NEUJSON_HAS_BUILTIN(__builtin_clzll) 43 | // __builtin_clzll wrapper 44 | return static_cast(__builtin_clzll(n)); 45 | #else 46 | // naive version 47 | uint32_t r = 0; 48 | while (!(n & (static_cast(1) << 63))) { 49 | n <<= 1; 50 | ++r; 51 | } 52 | 53 | return r; 54 | #endif // _MSC_VER 55 | } 56 | 57 | } // namespace neujson::internal 58 | 59 | #endif // NEUJSON_NEUJSON_INTERNAL_CLLZL_H_ 60 | -------------------------------------------------------------------------------- /include/neujson/string_read_stream.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/9. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_STRING_READ_STREAM_H_ 6 | #define NEUJSON_NEUJSON_STRING_READ_STREAM_H_ 7 | 8 | #include 9 | 10 | #include "neujson.h" 11 | #include "non_copyable.h" 12 | 13 | namespace neujson { 14 | 15 | class StringReadStream : public NonCopyable { 16 | public: 17 | using Iterator = std::string_view::iterator; 18 | 19 | private: 20 | std::string_view json_; 21 | Iterator iter_; 22 | 23 | public: 24 | explicit StringReadStream(const std::string_view json) 25 | : json_(json), iter_(json_.begin()) {} 26 | 27 | [[nodiscard]] bool hasNext() const { return iter_ != json_.end(); } 28 | 29 | [[nodiscard]] char peek() const { return hasNext() ? *iter_ : '\0'; } 30 | 31 | char next() { 32 | if (hasNext()) { 33 | const char ch = *iter_; 34 | iter_++; 35 | return ch; 36 | } 37 | return '\0'; 38 | } 39 | 40 | std::string_view next(const std::size_t n) { 41 | auto start = iter_; 42 | if (static_cast(std::distance(iter_, json_.end())) >= n) { 43 | std::advance(iter_, n); 44 | } 45 | return {start, iter_}; 46 | } 47 | 48 | void skip(const std::size_t n) { 49 | if (static_cast(std::distance(iter_, json_.end())) >= n) { 50 | std::advance(iter_, n); 51 | } 52 | } 53 | 54 | void assertNext(const char ch) { 55 | (void)ch; 56 | NEUJSON_ASSERT(peek() == ch); 57 | next(); 58 | } 59 | }; 60 | 61 | } // namespace neujson 62 | 63 | #endif // NEUJSON_NEUJSON_STRING_READ_STREAM_H_ 64 | -------------------------------------------------------------------------------- /include/neujson/internal/ieee754.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/6/28. 3 | // 4 | 5 | #ifndef NEUJSON_INCLUDE_NEUJSON_INTERNAL_IEEE754_H_ 6 | #define NEUJSON_INCLUDE_NEUJSON_INTERNAL_IEEE754_H_ 7 | 8 | #include 9 | 10 | #include "neujson/neujson.h" 11 | 12 | namespace neujson::internal { 13 | 14 | class Double { 15 | union { 16 | double d_; 17 | uint64_t u_; 18 | }; 19 | 20 | static constexpr int kFractionSize = 52; 21 | static constexpr int kExponentBias = 0x3FF; 22 | static constexpr int kStartExponent = 1 - kExponentBias; 23 | static constexpr uint64_t kSignMask = 24 | NEUJSON_UINT64_C2(0x80000000, 0x00000000); 25 | static constexpr uint64_t kExponentMask = 26 | NEUJSON_UINT64_C2(0x7FF00000, 0x00000000); 27 | static constexpr uint64_t kFractionMask = 28 | NEUJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); 29 | 30 | public: 31 | Double() = default; 32 | explicit Double(const double d) { d_ = d; } 33 | explicit Double(const uint64_t u) { u_ = u; } 34 | 35 | [[nodiscard]] double Value() const { return d_; } 36 | [[nodiscard]] uint64_t UInt64Value() const { return u_; } 37 | 38 | [[nodiscard]] bool Sign() const { return (u_ & kSignMask) != 0; } 39 | [[nodiscard]] int Exponent() const { 40 | return static_cast(((u_ & kExponentMask) >> kFractionSize) - 41 | kExponentBias); 42 | } 43 | [[nodiscard]] uint64_t Fraction() const { return u_ & kFractionMask; } 44 | 45 | [[nodiscard]] bool IsNan() const { 46 | return (u_ & kExponentMask) == kExponentMask && Fraction() != 0; 47 | } 48 | [[nodiscard]] bool IsInf() const { 49 | return (u_ & kExponentMask) == kExponentMask && Fraction() == 0; 50 | } 51 | [[nodiscard]] bool IsNanOrInf() const { 52 | return (u_ & kExponentMask) == kExponentMask; 53 | } 54 | [[nodiscard]] bool IsNormal() const { 55 | return (u_ & kExponentMask) != 0 || Fraction() == 0; 56 | } 57 | [[nodiscard]] bool IsZero() const { 58 | return (u_ & (kExponentMask | kFractionMask)) == 0; 59 | } 60 | }; 61 | 62 | } // namespace neujson::internal 63 | 64 | #endif // NEUJSON_INCLUDE_NEUJSON_INTERNAL_IEEE754_H_ 65 | -------------------------------------------------------------------------------- /example/struct.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/15. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | #include "neujson/document.h" 9 | #include "neujson/file_write_stream.h" 10 | #include "neujson/pretty_writer.h" 11 | #include "neujson/value.h" 12 | 13 | struct SerialInfo { 14 | std::string serial_port_; 15 | int32_t baud_rate_; 16 | int32_t data_bits_; 17 | int32_t stop_bits_; 18 | int32_t parity_; 19 | bool flow_control_; 20 | bool clocal_; 21 | 22 | explicit SerialInfo(const neujson::Value &_val) { 23 | switch (_val.GetType()) { 24 | case neujson::NEU_OBJECT: 25 | serial_port_ = _val["serial_port"].GetString(); 26 | baud_rate_ = _val["baud_rate"].GetInt32(); 27 | data_bits_ = _val["data_bits"].GetInt32(); 28 | stop_bits_ = _val["stop_bits"].GetInt32(); 29 | parity_ = _val["parity"].GetInt32(); 30 | flow_control_ = _val["flow_control"].GetBool(); 31 | clocal_ = _val["clocal"].GetBool(); 32 | break; 33 | default: 34 | assert(false && "bad type"); 35 | } 36 | } 37 | 38 | [[nodiscard]] neujson::Value toJsonObject() const { 39 | neujson::Value value(neujson::NEU_OBJECT); 40 | value.AddMember("serial_port", serial_port_); 41 | value.AddMember("baud_rate", baud_rate_); 42 | value.AddMember("data_bits", data_bits_); 43 | value.AddMember("stop_bits", stop_bits_); 44 | value.AddMember("parity", parity_); 45 | value.AddMember("flow_control", flow_control_); 46 | value.AddMember("clocal", clocal_); 47 | return value; 48 | } 49 | }; 50 | 51 | int main() { 52 | neujson::Value value(neujson::NEU_OBJECT); 53 | value.AddMember("serial_port", "/dev/cu.usbserial-AB0JHQVJ"); 54 | value.AddMember("baud_rate", 115200); 55 | value.AddMember("data_bits", 8); 56 | value.AddMember("stop_bits", 1); 57 | value.AddMember("parity", 0); 58 | value.AddMember("flow_control", false); 59 | value.AddMember("clocal", true); 60 | 61 | SerialInfo serial_info(value); 62 | 63 | value = serial_info.toJsonObject(); 64 | char writeBuffer[65536]; 65 | neujson::FileWriteStream out(stdout, writeBuffer); 66 | neujson::PrettyWriter pretty_writer(out); 67 | pretty_writer.SetIndent(' ', 2); 68 | value.WriteTo(pretty_writer); 69 | 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /cmake/ExtProjectUtils.cmake: -------------------------------------------------------------------------------- 1 | include(ExternalProject) 2 | include(CMakeParseArguments) 3 | 4 | # 5 | # Function to inject dependency (download from git repo) 6 | # 7 | # Use as ExternalProjectGit( "" "" "" ) 8 | # where 9 | # - url to repository for ex. https://github.com/log4cplus/log4cplus.git 10 | # project name will be regexed from url as latest part in our case log4cplus.git 11 | # - tag - tag you want to use 12 | # - destination - where to install your binaries, for example ${CMAKE_BINARY_DIR}/3rdparty 13 | # 14 | 15 | function(ExtProjectGit repourl tag destination) 16 | 17 | message(STATUS "Get external project from: ${repourl} : ${tag}") 18 | 19 | string(REGEX MATCH "([^\\/]+)[/]([^\\/]+)[.]git" _match_result ${repourl}) 20 | string(REGEX REPLACE "([^\\/]+)[/]([^\\/]+)[.]git" "\\1_\\2.git" _name ${_match_result}) 21 | message(STATUS "_name = ${_name}") 22 | 23 | set(options) 24 | set(oneValueArgs) 25 | set(multiValueArgs CMAKE_ARGS) 26 | cmake_parse_arguments(ExtProjectGit "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 27 | 28 | set(cmake_cli_args -DCMAKE_INSTALL_PREFIX=${destination} 29 | -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}) 30 | if (CMAKE_TOOLCHAIN_FILE) 31 | get_filename_component(_ft_path ${CMAKE_TOOLCHAIN_FILE} ABSOLUTE) 32 | get_filename_component(_cm_rt_opath ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ABSOLUTE) 33 | set(cmake_cli_args ${cmake_cli_args} 34 | -DCMAKE_TOOLCHAIN_FILE=${_ft_path} 35 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${_cm_rt_opath}) 36 | endif () 37 | 38 | foreach (cmake_key ${ExtProjectGit_CMAKE_ARGS}) 39 | set(cmake_cli_args ${cmake_key} ${cmake_cli_args}) 40 | endforeach () 41 | 42 | message(STATUS "ARGS for ExternalProject_Add(${_name}): ${cmake_cli_args}") 43 | message(STATUS "CMAKE_CXX_FLAGS = ${CMAKE_CXX_FLAGS}") 44 | 45 | ExternalProject_Add(${_name} 46 | GIT_REPOSITORY ${repourl} 47 | GIT_TAG ${tag} 48 | CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} ${cmake_cli_args} -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} 49 | PREFIX "${destination}" 50 | INSTALL_DIR "${destination}") 51 | endfunction() -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker Deploy 2 | 3 | on: 4 | push: 5 | tags: [ 'v*.*.*' ] 6 | 7 | jobs: 8 | set-matrix: 9 | runs-on: ubuntu-latest 10 | outputs: 11 | matrix: ${{ steps.set-matrix.outputs.matrix }} 12 | git_tag: ${{ steps.git_info.outputs.tag }} 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v3 17 | 18 | - id: set-matrix 19 | run: | 20 | JSON=$(cat .github/workflows/registry.json | tr '\n' ' ' | sed 's/ //g') 21 | echo "::set-output name=matrix::$JSON" 22 | 23 | - id: git_info 24 | run: | 25 | tag=$(echo ${{ github.ref }} | rev | cut -d/ -f1 | rev) 26 | echo "::set-output name=tag::$tag" 27 | 28 | build: 29 | runs-on: ubuntu-latest 30 | needs: [ set-matrix ] 31 | strategy: 32 | matrix: 33 | registry: ${{fromJSON(needs.set-matrix.outputs.matrix)}} 34 | 35 | env: 36 | id: ${{ secrets[matrix.registry.id] }} 37 | token: ${{ secrets[matrix.registry.token] }} 38 | 39 | permissions: 40 | contents: read 41 | packages: write 42 | 43 | steps: 44 | - name: Checkout repository 45 | uses: actions/checkout@v4 46 | 47 | - name: Log into Registry ${{ matrix.registry.name }} 48 | uses: docker/login-action@v3 49 | with: 50 | registry: ${{ matrix.registry.registry }} 51 | username: ${{ env.id }} 52 | password: ${{ env.token }} 53 | 54 | - name: Set Up QEMU 55 | id: qemu 56 | uses: docker/setup-qemu-action@v3 57 | 58 | - name: Set Up Docker Buildx 59 | id: buildx 60 | uses: docker/setup-buildx-action@v3 61 | with: 62 | install: true 63 | 64 | - name: Build and Push ${{ matrix.registry.name }} 65 | run: | 66 | REPO=${{ matrix.registry.push_link }} VERSION=${{ needs.set-matrix.outputs.git_tag }} docker buildx bake --file docker/docker-bake.hcl --push --set "*.platform=linux/arm64,linux/amd64,linux/s390x,linux/386,linux/arm/v7,linux/arm/v6" 67 | 68 | release: 69 | runs-on: ubuntu-latest 70 | needs: [ build ] 71 | 72 | permissions: 73 | contents: write 74 | packages: write 75 | 76 | steps: 77 | - name: Checkout 78 | uses: actions/checkout@v4 79 | 80 | - name: Release 81 | uses: softprops/action-gh-release@v2 82 | if: startsWith(github.ref, 'refs/tags/') 83 | with: 84 | generate_release_notes: true -------------------------------------------------------------------------------- /include/neujson/exception.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/7. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_EXCEPTION_H_ 6 | #define NEUJSON_NEUJSON_EXCEPTION_H_ 7 | 8 | #include 9 | 10 | #include "neujson.h" 11 | 12 | namespace neujson { 13 | 14 | #define ERR_TABLE(ERR) \ 15 | ERR(OK, "ok") \ 16 | ERR(ROOT_NOT_SINGULAR, "root not singular") \ 17 | ERR(BAD_VALUE, "bad value") \ 18 | ERR(EXPECT_VALUE, "expect value") \ 19 | ERR(NUMBER_TOO_BIG, "number too big") \ 20 | ERR(BAD_STRING_CHAR, "bad character") \ 21 | ERR(BAD_STRING_ESCAPE, "bad escape") \ 22 | ERR(BAD_UNICODE_HEX, "bad unicode hex") \ 23 | ERR(BAD_UNICODE_SURROGATE, "bad unicode surrogate") \ 24 | ERR(MISS_QUOTATION_MARK, "miss quotation mark") \ 25 | ERR(MISS_COMMA_OR_SQUARE_BRACKET, "miss comma or square bracket") \ 26 | ERR(MISS_KEY, "miss key") \ 27 | ERR(MISS_COLON, "miss colon") \ 28 | ERR(MISS_COMMA_OR_CURLY_BRACKET, "miss comma or curly bracket") \ 29 | ERR(USER_STOPPED, "user stopped parse") \ 30 | // 31 | 32 | namespace error { 33 | enum ParseError { 34 | #define ERR_NO(_err, _str) _err, 35 | ERR_TABLE(ERR_NO) 36 | #undef ERR_NO 37 | }; 38 | } // namespace error 39 | 40 | inline const char *ParseErrorStr(const error::ParseError err) { 41 | const static char *err_str_table[] = { 42 | #define ERR_STR(_err, _str) _str, 43 | ERR_TABLE(ERR_STR) 44 | #undef ERR_STR 45 | }; 46 | 47 | NEUJSON_ASSERT(err >= 0 && 48 | err < sizeof(err_str_table) / sizeof(err_str_table[0])); 49 | return err_str_table[err]; 50 | } 51 | 52 | #undef ERR_TABLE 53 | 54 | class Exception : public std::exception { 55 | error::ParseError err_; 56 | 57 | public: 58 | explicit Exception(const error::ParseError err) : err_(err) {} 59 | 60 | [[nodiscard]] const char *what() const noexcept override { 61 | return ParseErrorStr(err_); 62 | } 63 | [[nodiscard]] error::ParseError err() const { return err_; } 64 | }; 65 | 66 | } // namespace neujson 67 | 68 | #endif // NEUJSON_NEUJSON_EXCEPTION_H_ 69 | -------------------------------------------------------------------------------- /test/strtod_test.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/8/17. 3 | // 4 | 5 | #include "neujson/internal/strtod.h" 6 | 7 | #include "gtest/gtest.h" 8 | 9 | #if defined(__clang__) 10 | NEUJSON_DIAG_PUSH 11 | NEUJSON_DIAG_OFF(unreachable-code) 12 | #elif defined(_MSC_VER) 13 | NEUJSON_DIAG_PUSH 14 | NEUJSON_DIAG_OFF(4127) // conditional expression is constant 15 | NEUJSON_DIAG_OFF(4702) // unreachable code 16 | #endif 17 | 18 | using namespace neujson::internal; 19 | 20 | TEST(strtod, check_approximation_case) { 21 | static constexpr int kSignificandSize = 52; 22 | static constexpr int kExponentBias = 0x3FF; 23 | static constexpr uint64_t kExponentMask = 24 | NEUJSON_UINT64_C2(0x7FF00000, 0x00000000); 25 | static constexpr uint64_t kSignificandMask = 26 | NEUJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); 27 | static constexpr uint64_t kHiddenBit = 28 | NEUJSON_UINT64_C2(0x00100000, 0x00000000); 29 | 30 | // http://www.exploringbinary.com/using-integers-to-check-a-floating-point-approximation/ 31 | // Let b = 0x1.465a72e467d88p-149 32 | // = 5741268244528520 x 2^-201 33 | union { 34 | double d; 35 | uint64_t u; 36 | } u{}; 37 | u.u = 0x465a72e467d88 | 38 | ((static_cast(-149 + kExponentBias)) << kSignificandSize); 39 | const double b = u.d; 40 | const uint64_t bInt = (u.u & kSignificandMask) | kHiddenBit; 41 | const int bExp = 42 | static_cast(((u.u & kExponentMask) >> kSignificandSize) - 43 | kExponentBias - kSignificandSize); 44 | EXPECT_DOUBLE_EQ(1.7864e-45, b); 45 | EXPECT_EQ(NEUJSON_UINT64_C2(0x001465a7, 0x2e467d88), bInt); 46 | EXPECT_EQ(-201, bExp); 47 | 48 | // Let d = 17864 x 10-49 49 | constexpr char dInt[] = "17864"; 50 | (void)dInt; 51 | constexpr int dExp = -49; 52 | 53 | // Let h = 2^(bExp-1) 54 | const int hExp = bExp - 1; 55 | EXPECT_EQ(-202, hExp); 56 | 57 | int dS_Exp2 = 0; 58 | int dS_Exp5 = 0; 59 | int bS_Exp2 = 0; 60 | int bS_Exp5 = 0; 61 | int hS_Exp2 = 0; 62 | int hS_Exp5 = 0; 63 | 64 | // Adjust for decimal exponent 65 | if (dExp >= 0) { 66 | dS_Exp2 += dExp; 67 | dS_Exp5 += dExp; 68 | } else { 69 | bS_Exp2 -= dExp; 70 | bS_Exp5 -= dExp; 71 | hS_Exp2 -= dExp; 72 | hS_Exp5 -= dExp; 73 | } 74 | 75 | // Adjust for binary exponent 76 | if (bExp >= 0) 77 | bS_Exp2 += bExp; 78 | else { 79 | dS_Exp2 -= bExp; 80 | hS_Exp2 -= bExp; 81 | } 82 | 83 | // Adjust for half ulp exponent 84 | if (hExp >= 0) 85 | hS_Exp2 += hExp; 86 | else { 87 | dS_Exp2 -= hExp; 88 | bS_Exp2 -= hExp; 89 | } 90 | 91 | // Remove common power of two factor from all three scaled values 92 | const int common_Exp2 = (std::min)(dS_Exp2, (std::min)(bS_Exp2, hS_Exp2)); 93 | dS_Exp2 -= common_Exp2; 94 | bS_Exp2 -= common_Exp2; 95 | hS_Exp2 -= common_Exp2; 96 | 97 | EXPECT_EQ(153, dS_Exp2); 98 | EXPECT_EQ(0, dS_Exp5); 99 | EXPECT_EQ(1, bS_Exp2); 100 | EXPECT_EQ(49, bS_Exp5); 101 | EXPECT_EQ(0, hS_Exp2); 102 | EXPECT_EQ(49, hS_Exp5); 103 | } 104 | 105 | #if defined(_MSC_VER) || defined(__clang__) 106 | NEUJSON_DIAG_POP 107 | #endif 108 | -------------------------------------------------------------------------------- /include/neujson/internal/pow10.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/6/30. 3 | // 4 | 5 | #ifndef NEUJSON_INCLUDE_NEUJSON_INTERNAL_POW10_H_ 6 | #define NEUJSON_INCLUDE_NEUJSON_INTERNAL_POW10_H_ 7 | 8 | #include "neujson/neujson.h" 9 | 10 | namespace neujson::internal { 11 | 12 | inline double Pow10(const int n) { 13 | static constexpr double e[] = { 14 | 1e+0, 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 15 | 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 16 | 1e+18, 1e+19, 1e+20, 1e+21, 1e+22, 1e+23, 1e+24, 1e+25, 1e+26, 17 | 1e+27, 1e+28, 1e+29, 1e+30, 1e+31, 1e+32, 1e+33, 1e+34, 1e+35, 18 | 1e+36, 1e+37, 1e+38, 1e+39, 1e+40, 1e+41, 1e+42, 1e+43, 1e+44, 19 | 1e+45, 1e+46, 1e+47, 1e+48, 1e+49, 1e+50, 1e+51, 1e+52, 1e+53, 20 | 1e+54, 1e+55, 1e+56, 1e+57, 1e+58, 1e+59, 1e+60, 1e+61, 1e+62, 21 | 1e+63, 1e+64, 1e+65, 1e+66, 1e+67, 1e+68, 1e+69, 1e+70, 1e+71, 22 | 1e+72, 1e+73, 1e+74, 1e+75, 1e+76, 1e+77, 1e+78, 1e+79, 1e+80, 23 | 1e+81, 1e+82, 1e+83, 1e+84, 1e+85, 1e+86, 1e+87, 1e+88, 1e+89, 24 | 1e+90, 1e+91, 1e+92, 1e+93, 1e+94, 1e+95, 1e+96, 1e+97, 1e+98, 25 | 1e+99, 1e+100, 1e+101, 1e+102, 1e+103, 1e+104, 1e+105, 1e+106, 1e+107, 26 | 1e+108, 1e+109, 1e+110, 1e+111, 1e+112, 1e+113, 1e+114, 1e+115, 1e+116, 27 | 1e+117, 1e+118, 1e+119, 1e+120, 1e+121, 1e+122, 1e+123, 1e+124, 1e+125, 28 | 1e+126, 1e+127, 1e+128, 1e+129, 1e+130, 1e+131, 1e+132, 1e+133, 1e+134, 29 | 1e+135, 1e+136, 1e+137, 1e+138, 1e+139, 1e+140, 1e+141, 1e+142, 1e+143, 30 | 1e+144, 1e+145, 1e+146, 1e+147, 1e+148, 1e+149, 1e+150, 1e+151, 1e+152, 31 | 1e+153, 1e+154, 1e+155, 1e+156, 1e+157, 1e+158, 1e+159, 1e+160, 1e+161, 32 | 1e+162, 1e+163, 1e+164, 1e+165, 1e+166, 1e+167, 1e+168, 1e+169, 1e+170, 33 | 1e+171, 1e+172, 1e+173, 1e+174, 1e+175, 1e+176, 1e+177, 1e+178, 1e+179, 34 | 1e+180, 1e+181, 1e+182, 1e+183, 1e+184, 1e+185, 1e+186, 1e+187, 1e+188, 35 | 1e+189, 1e+190, 1e+191, 1e+192, 1e+193, 1e+194, 1e+195, 1e+196, 1e+197, 36 | 1e+198, 1e+199, 1e+200, 1e+201, 1e+202, 1e+203, 1e+204, 1e+205, 1e+206, 37 | 1e+207, 1e+208, 1e+209, 1e+210, 1e+211, 1e+212, 1e+213, 1e+214, 1e+215, 38 | 1e+216, 1e+217, 1e+218, 1e+219, 1e+220, 1e+221, 1e+222, 1e+223, 1e+224, 39 | 1e+225, 1e+226, 1e+227, 1e+228, 1e+229, 1e+230, 1e+231, 1e+232, 1e+233, 40 | 1e+234, 1e+235, 1e+236, 1e+237, 1e+238, 1e+239, 1e+240, 1e+241, 1e+242, 41 | 1e+243, 1e+244, 1e+245, 1e+246, 1e+247, 1e+248, 1e+249, 1e+250, 1e+251, 42 | 1e+252, 1e+253, 1e+254, 1e+255, 1e+256, 1e+257, 1e+258, 1e+259, 1e+260, 43 | 1e+261, 1e+262, 1e+263, 1e+264, 1e+265, 1e+266, 1e+267, 1e+268, 1e+269, 44 | 1e+270, 1e+271, 1e+272, 1e+273, 1e+274, 1e+275, 1e+276, 1e+277, 1e+278, 45 | 1e+279, 1e+280, 1e+281, 1e+282, 1e+283, 1e+284, 1e+285, 1e+286, 1e+287, 46 | 1e+288, 1e+289, 1e+290, 1e+291, 1e+292, 1e+293, 1e+294, 1e+295, 1e+296, 47 | 1e+297, 1e+298, 1e+299, 1e+300, 1e+301, 1e+302, 1e+303, 1e+304, 1e+305, 48 | 1e+306, 1e+307, 1e+308}; 49 | 50 | NEUJSON_ASSERT(n >= 0 && n <= 308); 51 | return e[n]; 52 | } 53 | 54 | } // namespace neujson::internal 55 | 56 | #endif // NEUJSON_INCLUDE_NEUJSON_INTERNAL_POW10_H_ 57 | -------------------------------------------------------------------------------- /include/neujson/file_write_stream.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/11. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_FILE_WRITE_STREAM_H_ 6 | #define NEUJSON_NEUJSON_FILE_WRITE_STREAM_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "neujson.h" 14 | #include "non_copyable.h" 15 | 16 | namespace neujson { 17 | 18 | class FileWriteStream : public NonCopyable { 19 | static constexpr std::size_t kInnerBufferSize = 256; 20 | std::FILE *fp_; 21 | char inner_buffer_[kInnerBufferSize]{}; 22 | char *buffer_; 23 | char *buffer_end_; 24 | char *current_; 25 | 26 | public: 27 | explicit FileWriteStream(std::FILE *fp) 28 | : fp_(fp), buffer_(inner_buffer_), 29 | buffer_end_(buffer_ + kInnerBufferSize), current_(buffer_) { 30 | NEUJSON_ASSERT(fp != nullptr && "FILE pointer equal zero"); 31 | } 32 | 33 | explicit FileWriteStream(std::FILE *fp, char *buffer, 34 | const std::size_t buffer_size) 35 | : fp_(fp), buffer_(buffer), buffer_end_(buffer + buffer_size), 36 | current_(buffer_) { 37 | NEUJSON_ASSERT(fp != nullptr && "FILE pointer equal zero"); 38 | } 39 | 40 | template 41 | explicit FileWriteStream(std::FILE *fp, char (&buffer)[N]) 42 | : fp_(fp), buffer_(buffer), buffer_end_(buffer + N), 43 | current_(buffer_) { 44 | NEUJSON_ASSERT(fp != nullptr && "FILE pointer equal zero"); 45 | } 46 | 47 | ~FileWriteStream() { 48 | if (current_ != buffer_) { 49 | flush(); 50 | } 51 | } 52 | 53 | void put(const char ch) { 54 | if (current_ >= buffer_end_) { 55 | flush(); 56 | } 57 | *current_++ = ch; 58 | } 59 | 60 | void put_n(const char ch, std::size_t n) { 61 | auto avail = static_cast(buffer_end_ - current_); 62 | while (n > avail) { 63 | std::memset(current_, ch, avail); 64 | current_ += avail; 65 | flush(); 66 | n -= avail; 67 | avail = static_cast(buffer_end_ - current_); 68 | } 69 | 70 | if (n > 0) { 71 | std::memset(current_, ch, n); 72 | current_ += n; 73 | } 74 | } 75 | 76 | void puts(const char *str, std::size_t length) { 77 | auto avail = static_cast(buffer_end_ - current_); 78 | std::size_t copy = 0; 79 | 80 | while (length > avail) { 81 | std::memcpy(current_, str + copy, avail); 82 | current_ += avail; 83 | copy += avail; 84 | flush(); 85 | length -= avail; 86 | avail = static_cast(buffer_end_ - current_); 87 | } 88 | 89 | if (length > 0) { 90 | std::memcpy(current_, str + copy, length); 91 | current_ += length; 92 | } 93 | } 94 | 95 | void put_sv(const std::string_view sv) { 96 | auto avail = static_cast(buffer_end_ - current_); 97 | std::size_t length = sv.length(); 98 | std::size_t copy = 0; 99 | 100 | while (length > avail) { 101 | std::memcpy(current_, sv.data() + copy, avail); 102 | current_ += avail; 103 | copy += avail; 104 | flush(); 105 | length -= avail; 106 | avail = static_cast(buffer_end_ - current_); 107 | } 108 | 109 | if (length > 0) { 110 | std::memcpy(current_, sv.data() + copy, length); 111 | current_ += length; 112 | } 113 | } 114 | 115 | void flush() { 116 | if (current_ != buffer_) { 117 | const std::size_t result = std::fwrite( 118 | buffer_, 1, static_cast(current_ - buffer_), fp_); 119 | if (result < static_cast(current_ - buffer_)) { 120 | } 121 | current_ = buffer_; 122 | } 123 | } 124 | }; 125 | 126 | } // namespace neujson 127 | 128 | #endif // NEUJSON_NEUJSON_FILE_WRITE_STREAM_H_ 129 | -------------------------------------------------------------------------------- /test/itoa_test.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/7/1. 3 | // 4 | 5 | #include "neujson/internal/itoa.h" 6 | 7 | #include "gtest/gtest.h" 8 | 9 | #if defined(__GNUC__) && !defined(__clang__) 10 | NEUJSON_DIAG_PUSH 11 | NEUJSON_DIAG_OFF(stringop-overflow) 12 | #endif 13 | 14 | using namespace neujson::internal; 15 | 16 | template struct Traits {}; 17 | 18 | template <> struct Traits { 19 | enum { kBufferSize = 11 }; 20 | enum { kMaxDigit = 10 }; 21 | static uint32_t Negate(const uint32_t x) { return x; } 22 | }; 23 | 24 | template <> struct Traits { 25 | enum { kBufferSize = 12 }; 26 | enum { kMaxDigit = 10 }; 27 | static int32_t Negate(const int32_t x) { return -x; } 28 | }; 29 | 30 | template <> struct Traits { 31 | enum { kBufferSize = 21 }; 32 | enum { kMaxDigit = 20 }; 33 | static uint64_t Negate(const uint64_t x) { return x; } 34 | }; 35 | 36 | template <> struct Traits { 37 | enum { kBufferSize = 22 }; 38 | enum { kMaxDigit = 20 }; 39 | static int64_t Negate(const int64_t x) { return -x; } 40 | }; 41 | 42 | template 43 | static void VerifyValue(T value, void (*f)(T, char *), char *(*g)(T, char *)) { 44 | char buffer1[Traits::kBufferSize]; 45 | char buffer2[Traits::kBufferSize]; 46 | 47 | f(value, buffer1); 48 | *g(value, buffer2) = '\0'; 49 | 50 | EXPECT_STREQ(buffer1, buffer2); 51 | } 52 | 53 | template 54 | static void Verify(void (*f)(T, char *), char *(*g)(T, char *)) { 55 | // Boundary cases 56 | VerifyValue(0, f, g); 57 | VerifyValue((std::numeric_limits::min)(), f, g); 58 | VerifyValue((std::numeric_limits::max)(), f, g); 59 | 60 | // 2^n - 1, 2^n, 10^n - 1, 10^n until overflow 61 | for (int power = 2; power <= 10; power += 8) { 62 | T i = 1, last; 63 | do { 64 | VerifyValue(i - 1, f, g); 65 | VerifyValue(i, f, g); 66 | if ((std::numeric_limits::min)() < 0) { 67 | VerifyValue(Traits::Negate(i), f, g); 68 | VerifyValue(Traits::Negate(i + 1), f, g); 69 | } 70 | last = i; 71 | if (i > static_cast((std::numeric_limits::max)() / 72 | static_cast(power))) 73 | break; 74 | i *= static_cast(power); 75 | } while (last < i); 76 | } 77 | } 78 | 79 | static void u32toa_naive(uint32_t value, char *buffer) { 80 | char temp[10]; 81 | char *p = temp; 82 | do { 83 | *p++ = static_cast(static_cast(value % 10) + '0'); 84 | value /= 10; 85 | } while (value > 0); 86 | 87 | do { 88 | *buffer++ = *--p; 89 | } while (p != temp); 90 | 91 | *buffer = '\0'; 92 | } 93 | 94 | static void i32toa_naive(const int32_t value, char *buffer) { 95 | auto u = static_cast(value); 96 | if (value < 0) { 97 | *buffer++ = '-'; 98 | u = ~u + 1; 99 | } 100 | u32toa_naive(u, buffer); 101 | } 102 | 103 | static void u64toa_naive(uint64_t value, char *buffer) { 104 | char temp[20]; 105 | char *p = temp; 106 | do { 107 | *p++ = static_cast(static_cast(value % 10) + '0'); 108 | value /= 10; 109 | } while (value > 0); 110 | 111 | do { 112 | *buffer++ = *--p; 113 | } while (p != temp); 114 | 115 | *buffer = '\0'; 116 | } 117 | 118 | static void i64toa_naive(const int64_t value, char *buffer) { 119 | auto u = static_cast(value); 120 | if (value < 0) { 121 | *buffer++ = '-'; 122 | u = ~u + 1; 123 | } 124 | u64toa_naive(u, buffer); 125 | } 126 | 127 | TEST(itoa, u32toa) { Verify(u32toa_naive, u32toa); } 128 | 129 | TEST(itoa, i32toa) { Verify(i32toa_naive, i32toa); } 130 | 131 | TEST(itoa, u64toa) { Verify(u64toa_naive, u64toa); } 132 | 133 | TEST(itoa, i64toa) { Verify(i64toa_naive, i64toa); } 134 | 135 | #if defined(__GNUC__) && !defined(__clang__) 136 | NEUJSON_DIAG_POP 137 | #endif 138 | -------------------------------------------------------------------------------- /include/neujson/file_read_stream.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/11. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_FILE_READ_STREAM_H_ 6 | #define NEUJSON_NEUJSON_FILE_READ_STREAM_H_ 7 | 8 | #include 9 | 10 | #include "neujson.h" 11 | #include "non_copyable.h" 12 | 13 | namespace neujson { 14 | 15 | class fp : NonCopyable { 16 | static constexpr std::size_t kInnerBufferSize = 256; 17 | std::FILE *fp_; 18 | char inner_buffer_[kInnerBufferSize]{}; 19 | char *buffer_; 20 | char *current_; 21 | char *buffer_last_; 22 | std::size_t buffer_size_; 23 | std::size_t read_count_; 24 | std::size_t read_total_; 25 | bool eof_; 26 | 27 | public: 28 | explicit fp(FILE *fp) 29 | : fp_(fp), buffer_(inner_buffer_), current_(inner_buffer_), 30 | buffer_last_(nullptr), buffer_size_(kInnerBufferSize), read_count_(0), 31 | read_total_(0), eof_(false) { 32 | NEUJSON_ASSERT(fp_ != nullptr && "file pointer should not be empty"); 33 | read(); 34 | } 35 | 36 | explicit fp(FILE *fp, char *buffer, const std::size_t buffer_size) 37 | : fp_(fp), buffer_(buffer), current_(buffer), buffer_last_(nullptr), 38 | buffer_size_(buffer_size), read_count_(0), read_total_(0), eof_(false) { 39 | NEUJSON_ASSERT(fp_ != nullptr && "file pointer should not be empty"); 40 | NEUJSON_ASSERT(buffer_size_ >= 4 && 41 | "buffer size should be bigger then four"); 42 | read(); 43 | } 44 | 45 | template 46 | explicit fp(FILE *fp, char (&buffer)[N]) 47 | : fp_(fp), buffer_(buffer), current_(buffer), buffer_last_(nullptr), 48 | buffer_size_(N), read_count_(0), read_total_(0), eof_(false) { 49 | NEUJSON_ASSERT(fp_ != nullptr && "file pointer should not be empty"); 50 | NEUJSON_ASSERT(buffer_size_ >= 4 && 51 | "buffer size should be bigger then four"); 52 | read(); 53 | } 54 | 55 | [[nodiscard]] bool hasNext() const { 56 | return !eof_ || (current_ + 1 - !eof_ <= buffer_last_); 57 | } 58 | 59 | [[nodiscard]] char peek() const { return *current_; } 60 | 61 | char next() { 62 | const char ch = *current_; 63 | read(); 64 | return ch; 65 | } 66 | 67 | std::string next(const std::size_t n) { 68 | auto str = std::make_shared(); 69 | auto ret = read(str, n); 70 | return {ret->c_str(), ret->size()}; 71 | } 72 | 73 | void skip(const std::size_t n) { 74 | for (std::size_t i = 0; i < n; ++i) { 75 | if (hasNext()) { 76 | read(); 77 | } else { 78 | break; 79 | } 80 | } 81 | } 82 | 83 | void assertNext(const char ch) { 84 | (void)ch; 85 | NEUJSON_ASSERT(peek() == ch); 86 | read(); 87 | } 88 | 89 | private: 90 | void read() { 91 | if (current_ < buffer_last_) { 92 | ++current_; 93 | } else if (!eof_) { 94 | read_total_ += read_count_; 95 | read_count_ = std::fread(buffer_, 1, buffer_size_, fp_); 96 | buffer_last_ = buffer_ + read_count_ - 1; 97 | current_ = buffer_; 98 | 99 | if (read_count_ < buffer_size_) { 100 | buffer_[read_count_] = '\0'; 101 | ++buffer_last_; 102 | eof_ = true; 103 | } 104 | } 105 | } 106 | 107 | std::shared_ptr &read(std::shared_ptr &str, 108 | const std::size_t n = 1) { 109 | if (current_ + n <= buffer_last_) { 110 | str->append(current_, n); 111 | current_ += n; 112 | return str; 113 | } else if (!eof_) { 114 | const std::size_t remaining = n - (buffer_last_ - current_ + 1); 115 | const std::size_t need_read = n - remaining; 116 | str->append(current_, buffer_last_ - current_ + 1); 117 | 118 | read_total_ += read_count_; 119 | read_count_ = std::fread(buffer_, 1, buffer_size_, fp_); 120 | buffer_last_ = buffer_ + read_count_ - 1; 121 | current_ = buffer_; 122 | 123 | if (read_count_ < buffer_size_) { 124 | buffer_[read_count_] = '\0'; 125 | ++buffer_last_; 126 | eof_ = true; 127 | } 128 | 129 | if (eof_ && need_read > read_count_) { 130 | str->append(current_, read_count_); 131 | return str; 132 | } 133 | 134 | return read(str, need_read); 135 | } 136 | 137 | return str; 138 | } 139 | }; 140 | 141 | } // namespace neujson 142 | 143 | #endif // NEUJSON_NEUJSON_FILE_READ_STREAM_H_ 144 | -------------------------------------------------------------------------------- /include/neujson/internal/diy_fp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/6/30. 3 | // 4 | 5 | #ifndef NEUJSON_INCLUDE_NEUJSON_INTERNAL_DIY_FP_H_ 6 | #define NEUJSON_INCLUDE_NEUJSON_INTERNAL_DIY_FP_H_ 7 | 8 | #include 9 | 10 | #include "cllzl.h" 11 | #include "neujson/neujson.h" 12 | 13 | #if defined(_MSC_VER) && defined(_M_AMD64) && !defined(__INTEL_COMPILER) 14 | #include 15 | #if !defined(_ARM64EC_) 16 | #pragma intrinsic(_umul128) 17 | #else 18 | #pragma comment(lib, "softintrin") 19 | #endif 20 | #endif 21 | 22 | #ifdef __GNUC__ 23 | NEUJSON_DIAG_PUSH 24 | NEUJSON_DIAG_OFF(effc++) 25 | #endif // __GNUC__ 26 | 27 | namespace neujson::internal { 28 | 29 | struct DiyFp { 30 | static constexpr int kDiyFpSize = 64; 31 | static constexpr int kDpFractionSize = 52; 32 | static constexpr int kDpExponentBias = 0x3FF + kDpFractionSize; 33 | static constexpr int kDpMaxExponent = 0x7FF - kDpExponentBias; 34 | static constexpr int kDpMinExponent = -kDpExponentBias; 35 | static constexpr int kDpStartExponent = 1 - kDpExponentBias; 36 | static constexpr uint64_t kDpExponentMask = 37 | NEUJSON_UINT64_C2(0x7FF00000, 0x00000000); 38 | static constexpr uint64_t kDpFractionMask = 39 | NEUJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); 40 | static constexpr uint64_t kDpHiddenBit = 41 | NEUJSON_UINT64_C2(0x00100000, 0x00000000); 42 | 43 | uint64_t f_; 44 | int e_; 45 | 46 | DiyFp() : f_(), e_() {} 47 | DiyFp(const uint64_t fp, const int exp) : f_(fp), e_(exp) {} 48 | 49 | explicit DiyFp(const double d) { 50 | union { 51 | double d; 52 | uint64_t u64; 53 | } const u = {d}; 54 | 55 | const int exponent = 56 | static_cast((u.u64 & kDpExponentMask) >> kDpFractionSize); 57 | const uint64_t fraction = (u.u64 & kDpFractionMask); 58 | if (exponent != 0) { 59 | f_ = fraction + kDpHiddenBit; 60 | e_ = exponent - kDpExponentBias; 61 | } else { 62 | f_ = fraction; 63 | e_ = kDpMinExponent + 1; 64 | } 65 | } 66 | 67 | [[nodiscard]] DiyFp Normalize() const { 68 | int s = static_cast(clzll(f_)); 69 | return {f_ << s, e_ - s}; 70 | } 71 | 72 | [[nodiscard]] double ToDouble() const { 73 | union { 74 | double d; 75 | uint64_t u64; 76 | } u{}; 77 | 78 | NEUJSON_ASSERT(f_ <= kDpHiddenBit + kDpFractionMask); 79 | if (e_ < kDpStartExponent) { 80 | return 0.0; 81 | } 82 | if (e_ >= kDpMaxExponent) { 83 | return std::numeric_limits::infinity(); 84 | } 85 | const uint64_t exponent = 86 | (e_ == kDpStartExponent && (f_ & kDpHiddenBit) == 0) 87 | ? 0 88 | : static_cast(e_ + kDpExponentBias); 89 | u.u64 = ((f_ & kDpFractionMask) | (exponent << kDpFractionSize)); 90 | return u.d; 91 | } 92 | 93 | DiyFp operator-(const DiyFp &diy_fp) const { return {f_ - diy_fp.f_, e_}; } 94 | 95 | DiyFp operator*(const DiyFp &diy_fp) const { 96 | #if defined(_MSC_VER) && defined(_M_AMD64) 97 | uint64_t h; 98 | uint64_t l = _umul128(f_, diy_fp.f_, &h); 99 | if (l & (uint64_t(1) << 63)) { 100 | ++h; 101 | } // rounding 102 | return {h, e_ + diy_fp.e_ + 64}; 103 | #elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && \ 104 | defined(__x86_64__) 105 | __extension__ typedef unsigned __int128 uint128_t; 106 | uint128_t p = 107 | static_cast(f_) * static_cast(diy_fp.f_); 108 | auto h = static_cast(p >> 64); 109 | auto l = static_cast(p); 110 | if (l & (uint64_t(1) << 63)) { 111 | ++h; 112 | } // rounding 113 | return {h, e_ + diy_fp.e_ + 64}; 114 | #else 115 | constexpr uint64_t M32 = 0xFFFFFFFF; 116 | const uint64_t a = f_ >> 32; 117 | const uint64_t b = f_ & M32; 118 | const uint64_t c = diy_fp.f_ >> 32; 119 | const uint64_t d = diy_fp.f_ & M32; 120 | const uint64_t ac = a * c; 121 | const uint64_t bc = b * c; 122 | const uint64_t ad = a * d; 123 | const uint64_t bd = b * d; 124 | uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32); 125 | tmp += 1U << 31; /// mult_round 126 | return {ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e_ + diy_fp.e_ + 64}; 127 | #endif 128 | } 129 | }; 130 | 131 | } // namespace neujson::internal 132 | 133 | #ifdef __GNUC__ 134 | NEUJSON_DIAG_POP 135 | #endif // __GNUC__ 136 | 137 | #endif // NEUJSON_INCLUDE_NEUJSON_INTERNAL_DIY_FP_H_ 138 | -------------------------------------------------------------------------------- /include/neujson/neujson.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/13. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_NEUJSON_H_ 6 | #define NEUJSON_NEUJSON_NEUJSON_H_ 7 | 8 | #if defined(_WIN64) || defined(WIN64) || defined(_WIN32) || defined(WIN32) 9 | #if defined(_WIN64) || defined(WIN64) 10 | #define NEUJSON_ARCH_64 1 11 | #else 12 | #define NEUJSON_ARCH_32 1 13 | #endif 14 | #define NEUJSON_PLATFORM_STRING "windows" 15 | #define NEUJSON_WINDOWS 1 16 | #elif defined(__linux__) 17 | #define NEUJSON_PLATFORM_STRING "linux" 18 | #define NEUJSON_LINUX 1 19 | #ifdef _LP64 20 | #define NEUJSON_ARCH_64 1 21 | #else /* _LP64 */ 22 | #define NEUJSON_ARCH_32 1 23 | #endif /* _LP64 */ 24 | #elif defined(__APPLE__) 25 | #define NEUJSON_PLATFORM_STRING "osx" 26 | #define NEUJSON_APPLE 1 27 | #ifdef _LP64 28 | #define NEUJSON_ARCH_64 1 29 | #else /* _LP64 */ 30 | #define NEUJSON_ARCH_32 1 31 | #endif /* _LP64 */ 32 | #endif 33 | 34 | #ifndef NEUJSON_WINDOWS 35 | #define NEUJSON_WINDOWS 0 36 | #endif 37 | #ifndef NEUJSON_LINUX 38 | #define NEUJSON_LINUX 0 39 | #endif 40 | #ifndef NEUJSON_APPLE 41 | #define NEUJSON_APPLE 0 42 | #endif 43 | 44 | #if defined(__has_builtin) 45 | #define NEUJSON_HAS_BUILTIN(x) __has_builtin(x) 46 | #else 47 | #define NEUJSON_HAS_BUILTIN(x) 0 48 | #endif 49 | 50 | #ifndef NEUJSON_ASSERT 51 | #include 52 | #define NEUJSON_ASSERT(x) assert(x) 53 | #endif // NEUJSON_ASSERT 54 | 55 | /** 56 | * @brief const array length 57 | */ 58 | #ifndef NEUJSON_LENGTH 59 | #define NEUJSON_LENGTH(CONST_ARRAY) \ 60 | (sizeof(CONST_ARRAY) / sizeof(CONST_ARRAY[0])) 61 | #endif // NEUJSON_LENGTH 62 | 63 | /** 64 | * @brief const string length 65 | */ 66 | #ifndef NEUJSON_STR_LENGTH 67 | #if defined(_MSC_VER) 68 | #define NEUJSON_STR_LENGTH(CONST_STR) _countof(CONST_STR) 69 | #else 70 | #define NEUJSON_STR_LENGTH(CONST_STR) (sizeof(CONST_STR) / sizeof(CONST_STR[0])) 71 | #endif 72 | #endif // NEUJSON_STR_LENGTH 73 | 74 | // stringification 75 | #define NEUJSON_STRINGIFY(X) NEUJSON_DO_STRINGIFY(X) 76 | #define NEUJSON_DO_STRINGIFY(X) #X 77 | 78 | // concatenation 79 | #define NEUJSON_JOIN(X, Y) NEUJSON_DO_JOIN(X, Y) 80 | #define NEUJSON_DO_JOIN(X, Y) X##Y 81 | 82 | /** 83 | * @brief adopted from Boost 84 | */ 85 | #define NEUJSON_VERSION_CODE(x, y, z) (((x) * 100000) + ((y) * 100) + (z)) 86 | 87 | /** 88 | * @brief gnuc version 89 | */ 90 | #if defined(__GNUC__) 91 | #define NEUJSON_GNUC \ 92 | NEUJSON_VERSION_CODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) 93 | #endif 94 | 95 | #if defined(__clang__) || \ 96 | (defined(NEUJSON_GNUC) && NEUJSON_GNUC >= NEUJSON_VERSION_CODE(4, 2, 0)) 97 | 98 | #define NEUJSON_PRAGMA(x) _Pragma(NEUJSON_STRINGIFY(x)) 99 | #if defined(__clang__) 100 | #define NEUJSON_DIAG_PRAGMA(x) NEUJSON_PRAGMA(clang diagnostic x) 101 | #else 102 | #define NEUJSON_DIAG_PRAGMA(x) NEUJSON_PRAGMA(GCC diagnostic x) 103 | #endif 104 | #define NEUJSON_DIAG_OFF(x) \ 105 | NEUJSON_DIAG_PRAGMA(ignored NEUJSON_STRINGIFY(NEUJSON_JOIN(-W, x))) 106 | 107 | // push/pop support in Clang and GCC>=4.6 108 | #if defined(__clang__) || \ 109 | (defined(NEUJSON_GNUC) && NEUJSON_GNUC >= NEUJSON_VERSION_CODE(4, 6, 0)) 110 | #define NEUJSON_DIAG_PUSH NEUJSON_DIAG_PRAGMA(push) 111 | #define NEUJSON_DIAG_POP NEUJSON_DIAG_PRAGMA(pop) 112 | #else // GCC >= 4.2, < 4.6 113 | #define NEUJSON_DIAG_PUSH /* ignored */ 114 | #define NEUJSON_DIAG_POP /* ignored */ 115 | #endif 116 | 117 | #elif defined(_MSC_VER) 118 | 119 | // pragma (MSVC specific) 120 | #define NEUJSON_PRAGMA(x) __pragma(x) 121 | #define NEUJSON_DIAG_PRAGMA(x) NEUJSON_PRAGMA(warning(x)) 122 | 123 | #define NEUJSON_DIAG_OFF(x) NEUJSON_DIAG_PRAGMA(disable : x) 124 | #define NEUJSON_DIAG_PUSH NEUJSON_DIAG_PRAGMA(push) 125 | #define NEUJSON_DIAG_POP NEUJSON_DIAG_PRAGMA(pop) 126 | 127 | #else 128 | 129 | #define NEUJSON_DIAG_OFF(x) /* ignored */ 130 | #define NEUJSON_DIAG_PUSH /* ignored */ 131 | #define NEUJSON_DIAG_POP /* ignored */ 132 | 133 | #endif 134 | 135 | /* 136 | * @brief Avoid compiler warnings 137 | */ 138 | #ifndef NEUJSON_UINT64_C2 139 | #define NEUJSON_UINT64_C2(high32, low32) \ 140 | ((static_cast(high32) << 32) | static_cast(low32)) 141 | #endif 142 | 143 | #endif // NEUJSON_NEUJSON_NEUJSON_H_ 144 | -------------------------------------------------------------------------------- /include/neujson/istream_wrapper.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/7/1. 3 | // 4 | 5 | #ifndef NEUJSON_INCLUDE_NEUJSON_ISTREAM_WRAPPER_H_ 6 | #define NEUJSON_INCLUDE_NEUJSON_ISTREAM_WRAPPER_H_ 7 | 8 | #include 9 | 10 | #include "non_copyable.h" 11 | 12 | namespace neujson { 13 | 14 | namespace required { 15 | 16 | template 17 | concept StreamCharTypeIsChar = 18 | requires { std::same_as; }; 19 | 20 | } // namespace required 21 | 22 | template 23 | class IStreamWrapper : NonCopyable { 24 | static constexpr std::size_t kInnerBufferSize = 256; 25 | Stream &stream_; 26 | char inner_buffer_[kInnerBufferSize]{}; 27 | char *buffer_; 28 | char *current_; 29 | char *buffer_last_; 30 | std::size_t buffer_size_; 31 | std::size_t read_count_; 32 | std::size_t read_total_; 33 | bool eof_; 34 | 35 | public: 36 | explicit IStreamWrapper(Stream &stream) 37 | : stream_(stream), buffer_(inner_buffer_), current_(inner_buffer_), 38 | buffer_last_(nullptr), buffer_size_(kInnerBufferSize), read_count_(0), 39 | read_total_(0), eof_(false) { 40 | read(); 41 | } 42 | 43 | IStreamWrapper(Stream &stream, char *buffer, const std::size_t buffer_size) 44 | : stream_(stream), buffer_(buffer), current_(buffer_), 45 | buffer_last_(nullptr), buffer_size_(buffer_size), read_count_(0), 46 | read_total_(0), eof_(false) { 47 | NEUJSON_ASSERT(buffer_size_ >= 4 && 48 | "buffer size should be bigger then four"); 49 | read(); 50 | } 51 | 52 | template 53 | IStreamWrapper(Stream &stream, char (&buffer)[N]) 54 | : stream_(stream), buffer_(buffer), current_(buffer_), 55 | buffer_last_(nullptr), buffer_size_(N), read_count_(0), read_total_(0), 56 | eof_(false) { 57 | NEUJSON_ASSERT(buffer_size_ >= 4 && 58 | "buffer size should be bigger then four"); 59 | read(); 60 | } 61 | 62 | [[nodiscard]] bool hasNext() const { 63 | return !eof_ || (current_ + 1 - !eof_ <= buffer_last_); 64 | } 65 | 66 | [[nodiscard]] char peek() const { return *current_; } 67 | 68 | char next() { 69 | const char ch = *current_; 70 | read(); 71 | return ch; 72 | } 73 | 74 | std::string next(const std::size_t n) { 75 | auto str = std::make_shared(); 76 | auto ret = read(str, n); 77 | return {ret->c_str(), ret->size()}; 78 | } 79 | 80 | void skip(const std::size_t n) { 81 | for (std::size_t i = 0; i < n; ++i) { 82 | if (hasNext()) { 83 | read(); 84 | } else { 85 | break; 86 | } 87 | } 88 | } 89 | 90 | void assertNext(const char ch) { 91 | (void)ch; 92 | NEUJSON_ASSERT(peek() == ch); 93 | read(); 94 | } 95 | 96 | private: 97 | void read() { 98 | if (current_ < buffer_last_) { 99 | ++current_; 100 | } else if (!eof_) { 101 | read_total_ += read_count_; 102 | 103 | // if no eof 104 | buffer_last_ = buffer_ + buffer_size_ - 1; 105 | current_ = buffer_; 106 | 107 | // eof 108 | if (!stream_.read(buffer_, static_cast(buffer_size_))) { 109 | read_count_ = static_cast(stream_.gcount()); 110 | *(buffer_last_ = buffer_ + read_count_) = '\0'; 111 | eof_ = true; 112 | } 113 | } 114 | } 115 | 116 | std::shared_ptr &read(std::shared_ptr &str, 117 | const std::size_t n = 1) { 118 | if (current_ + n <= buffer_last_) { 119 | str->append(current_, n); 120 | current_ += n; 121 | return str; 122 | } else if (!eof_) { 123 | const std::size_t remaining = n - (buffer_last_ - current_ + 1); 124 | const std::size_t need_read = n - remaining; 125 | str->append(current_, remaining); 126 | 127 | read_total_ += read_count_; 128 | 129 | // if no eof 130 | buffer_last_ = buffer_ + buffer_size_ - 1; 131 | current_ = buffer_; 132 | 133 | // eof 134 | if (!stream_.read(buffer_, static_cast(buffer_size_))) { 135 | read_count_ = static_cast(stream_.gcount()); 136 | *(buffer_last_ = buffer_ + read_count_) = '\0'; 137 | eof_ = true; 138 | } 139 | 140 | if (eof_ && need_read > read_count_) { 141 | str->append(current_, read_count_); 142 | return str; 143 | } 144 | 145 | return read(str, need_read); 146 | } 147 | 148 | return str; 149 | } 150 | }; 151 | 152 | } // namespace neujson 153 | 154 | #endif // NEUJSON_INCLUDE_NEUJSON_ISTREAM_WRAPPER_H_ 155 | -------------------------------------------------------------------------------- /example/sample.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/11. 3 | // 4 | 5 | #ifndef NEUJSON_EXAMPLE_SAMPLE_H_ 6 | #define NEUJSON_EXAMPLE_SAMPLE_H_ 7 | 8 | #include 9 | 10 | constexpr std::string_view kSample[] = { 11 | R"( 12 | [ 13 | { 14 | "precision": "zip", 15 | "Latitude": 37.766800000000003, 16 | "Longitude": -122.3959, 17 | "Address": "", 18 | "City": "SAN FRANCISCO", 19 | "State": "CA", 20 | "Zip": "94107", 21 | "Country": "US" 22 | }, 23 | { 24 | "precision": "zip", 25 | "Latitude": 37.371991000000001, 26 | "Longitude": -122.02602, 27 | "Address": "", 28 | "City": "SUNNYVALE", 29 | "State": "CA", 30 | "Zip": "94085", 31 | "Country": "US" 32 | } 33 | ] 34 | )", 35 | R"( 36 | { 37 | "web-app": { 38 | "servlet": [ 39 | { 40 | "servlet-name": "cofaxCDS", 41 | "servlet-class": "org.cofax.cds.CDSServlet", 42 | "init-param": { 43 | "configGlossary:installationAt": "Philadelphia, PA", 44 | "configGlossary:adminEmail": "ksm@pobox.com", 45 | "configGlossary:poweredBy": "Cofax", 46 | "configGlossary:poweredByIcon": "/images/cofax.gif", 47 | "configGlossary:staticPath": "/content/static", 48 | "templateProcessorClass": "org.cofax.WysiwygTemplate", 49 | "templateLoaderClass": "org.cofax.FilesTemplateLoader", 50 | "templatePath": "templates", 51 | "templateOverridePath": "", 52 | "defaultListTemplate": "listTemplate.htm", 53 | "defaultFileTemplate": "articleTemplate.htm", 54 | "useJSP": false, 55 | "jspListTemplate": "listTemplate.jsp", 56 | "jspFileTemplate": "articleTemplate.jsp", 57 | "cachePackageTagsTrack": 200, 58 | "cachePackageTagsStore": 200, 59 | "cachePackageTagsRefresh": 60, 60 | "cacheTemplatesTrack": 100, 61 | "cacheTemplatesStore": 50, 62 | "cacheTemplatesRefresh": 15, 63 | "cachePagesTrack": 200, 64 | "cachePagesStore": 100, 65 | "cachePagesRefresh": 10, 66 | "cachePagesDirtyRead": 10, 67 | "searchEngineListTemplate": "forSearchEnginesList.htm", 68 | "searchEngineFileTemplate": "forSearchEngines.htm", 69 | "searchEngineRobotsDb": "WEB-INF/robots.db", 70 | "useDataStore": true, 71 | "dataStoreClass": "org.cofax.SqlDataStore", 72 | "redirectionClass": "org.cofax.SqlRedirection", 73 | "dataStoreName": "cofax", 74 | "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", 75 | "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", 76 | "dataStoreUser": "sa", 77 | "dataStorePassword": "dataStoreTestQuery", 78 | "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", 79 | "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", 80 | "dataStoreInitConns": 10, 81 | "dataStoreMaxConns": 100, 82 | "dataStoreConnUsageLimit": 100, 83 | "dataStoreLogLevel": "debug", 84 | "maxUrlLength": 500 85 | } 86 | }, 87 | { 88 | "servlet-name": "cofaxEmail", 89 | "servlet-class": "org.cofax.cds.EmailServlet", 90 | "init-param": { 91 | "mailHost": "mail1", 92 | "mailHostOverride": "mail2" 93 | } 94 | }, 95 | { 96 | "servlet-name": "cofaxAdmin", 97 | "servlet-class": "org.cofax.cds.AdminServlet" 98 | }, 99 | { 100 | "servlet-name": "fileServlet", 101 | "servlet-class": "org.cofax.cds.FileServlet" 102 | }, 103 | { 104 | "servlet-name": "cofaxTools", 105 | "servlet-class": "org.cofax.cms.CofaxToolsServlet", 106 | "init-param": { 107 | "templatePath": "toolstemplates/", 108 | "log": 1, 109 | "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", 110 | "logMaxSize": "", 111 | "dataLog": 1, 112 | "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", 113 | "dataLogMaxSize": "", 114 | "removePageCache": "/content/admin/remove?cache=pages&id=", 115 | "removeTemplateCache": "/content/admin/remove?cache=templates&id=", 116 | "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", 117 | "lookInContext": 1, 118 | "adminGroupID": 4, 119 | "betaServer": true 120 | } 121 | } 122 | ], 123 | "servlet-mapping": { 124 | "cofaxCDS": "/", 125 | "cofaxEmail": "/cofaxutil/aemail/*", 126 | "cofaxAdmin": "/admin/*", 127 | "fileServlet": "/static/*", 128 | "cofaxTools": "/tools/*" 129 | }, 130 | "taglib": { 131 | "taglib-uri": "cofax.tld", 132 | "taglib-location": "/WEB-INF/tlds/cofax.tld" 133 | } 134 | } 135 | } 136 | )"}; 137 | 138 | #endif // NEUJSON_EXAMPLE_SAMPLE_H_ 139 | -------------------------------------------------------------------------------- /include/neujson/document.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/9. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_DOCUMENT_H_ 6 | #define NEUJSON_NEUJSON_DOCUMENT_H_ 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "exception.h" 15 | #include "internal/ieee754.h" 16 | #include "neujson.h" 17 | #include "reader.h" 18 | #include "string_read_stream.h" 19 | #include "value.h" 20 | 21 | #ifdef __GNUC__ 22 | NEUJSON_DIAG_PUSH 23 | NEUJSON_DIAG_OFF(effc++) 24 | #endif // __GNUC__ 25 | 26 | namespace neujson { 27 | 28 | class Document : public Value { 29 | struct Level { 30 | Value *value; 31 | int value_count; 32 | 33 | explicit Level(Value *value) : value(value), value_count(0) {} 34 | 35 | [[nodiscard]] Type type() const { return value->GetType(); } 36 | [[nodiscard]] Value *lastValue() const; 37 | }; 38 | 39 | std::vector stack_; 40 | Value key_; 41 | bool see_value_ = false; 42 | 43 | public: 44 | error::ParseError Parse(const char *json, size_t len); 45 | error::ParseError Parse(std::string_view json); 46 | 47 | template 48 | error::ParseError ParseStream(ReadStream &rs); 49 | 50 | // handler 51 | bool Null(); 52 | bool Bool(bool b); 53 | bool Int32(int32_t i32); 54 | bool Int64(int64_t i64); 55 | bool Double(internal::Double d); 56 | bool String(std::string_view str); 57 | bool Key(std::string_view str); 58 | bool StartObject(); 59 | bool EndObject(); 60 | bool StartArray(); 61 | bool EndArray(); 62 | 63 | private: 64 | Value *AddValue(Value &&value); 65 | }; 66 | 67 | inline Value *Document::Level::lastValue() const { 68 | if (type() == NEU_ARRAY) { 69 | return &std::get(value->data_)->back(); 70 | } else { 71 | return &std::get(value->data_)->back().value_; 72 | } 73 | } 74 | 75 | inline error::ParseError Document::Parse(const char *json, const size_t len) { 76 | return Parse(std::string_view(json, len)); 77 | } 78 | 79 | inline error::ParseError Document::Parse(const std::string_view json) { 80 | StringReadStream string_read_stream(json); 81 | return ParseStream(string_read_stream); 82 | } 83 | 84 | template 85 | error::ParseError Document::ParseStream(ReadStream &rs) { 86 | return Reader::Parse(rs, *this); 87 | } 88 | 89 | inline bool Document::Null() { 90 | AddValue(Value(NEU_NULL)); 91 | return true; 92 | } 93 | 94 | inline bool Document::Bool(const bool b) { 95 | AddValue(Value(b)); 96 | return true; 97 | } 98 | 99 | inline bool Document::Int32(const int32_t i32) { 100 | AddValue(Value(i32)); 101 | return true; 102 | } 103 | 104 | inline bool Document::Int64(const int64_t i64) { 105 | AddValue(Value(i64)); 106 | return true; 107 | } 108 | 109 | inline bool Document::Double(const internal::Double d) { 110 | AddValue(Value(d)); 111 | return true; 112 | } 113 | 114 | inline bool Document::String(const std::string_view str) { 115 | AddValue(Value(str)); 116 | return true; 117 | } 118 | 119 | inline bool Document::Key(const std::string_view str) { 120 | AddValue(Value(str)); 121 | return true; 122 | } 123 | 124 | inline bool Document::StartObject() { 125 | auto value = AddValue(Value(NEU_OBJECT)); 126 | stack_.emplace_back(value); 127 | return true; 128 | } 129 | 130 | inline bool Document::EndObject() { 131 | NEUJSON_ASSERT(!stack_.empty()); 132 | NEUJSON_ASSERT(stack_.back().type() == NEU_OBJECT); 133 | stack_.pop_back(); 134 | return true; 135 | } 136 | 137 | inline bool Document::StartArray() { 138 | auto value = AddValue(Value(NEU_ARRAY)); 139 | stack_.emplace_back(value); 140 | return true; 141 | } 142 | 143 | inline bool Document::EndArray() { 144 | NEUJSON_ASSERT(!stack_.empty()); 145 | NEUJSON_ASSERT(stack_.back().type() == NEU_ARRAY); 146 | stack_.pop_back(); 147 | return true; 148 | } 149 | 150 | inline Value *Document::AddValue(Value &&value) { 151 | Type type = value.GetType(); 152 | (void)type; 153 | if (see_value_) { 154 | NEUJSON_ASSERT(!stack_.empty() && "root not singular"); 155 | } else { 156 | NEUJSON_ASSERT(type_ == NEU_NULL); 157 | see_value_ = true; 158 | type_ = value.type_; 159 | 160 | switch (type) { 161 | case NEU_NULL: 162 | break; 163 | case NEU_BOOL: 164 | data_ = std::get(value.data_); 165 | break; 166 | case NEU_INT32: 167 | data_ = std::get(value.data_); 168 | break; 169 | case NEU_INT64: 170 | data_ = std::get(value.data_); 171 | break; 172 | case NEU_DOUBLE: 173 | data_ = std::get(value.data_); 174 | break; 175 | case NEU_STRING: 176 | data_ = std::get(value.data_); 177 | std::get(value.data_) = nullptr; 178 | break; 179 | case NEU_ARRAY: 180 | data_ = std::get(value.data_); 181 | std::get(value.data_) = nullptr; 182 | break; 183 | case NEU_OBJECT: 184 | data_ = std::get(value.data_); 185 | std::get(value.data_) = nullptr; 186 | break; 187 | default: 188 | break; 189 | } 190 | value.type_ = NEU_NULL; 191 | return this; 192 | } 193 | 194 | auto &top = stack_.back(); 195 | if (top.type() == NEU_ARRAY) { 196 | top.value->AddValue(std::move(value)); 197 | top.value_count++; 198 | return top.lastValue(); 199 | } else { 200 | NEUJSON_ASSERT(top.type() == NEU_OBJECT); 201 | if (top.value_count % 2 == 0) { 202 | NEUJSON_ASSERT(type == NEU_STRING && "miss quotation mark"); 203 | key_ = std::move(value); 204 | top.value_count++; 205 | return &key_; 206 | } else { 207 | top.value->AddMember(std::move(key_), std::move(value)); 208 | top.value_count++; 209 | return top.lastValue(); 210 | } 211 | } 212 | } 213 | 214 | } // namespace neujson 215 | 216 | #ifdef __GNUC__ 217 | NEUJSON_DIAG_POP 218 | #endif // __GNUC__ 219 | 220 | #endif // NEUJSON_NEUJSON_DOCUMENT_H_ 221 | -------------------------------------------------------------------------------- /include/neujson/pretty_writer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/11. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_PRETTY_WRITER_H_ 6 | #define NEUJSON_NEUJSON_PRETTY_WRITER_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "internal/ieee754.h" 15 | #include "neujson.h" 16 | #include "writer.h" 17 | 18 | namespace neujson { 19 | 20 | namespace required::pretty_write_stream { 21 | namespace details { 22 | 23 | template 24 | concept HasPutSV = requires(WriteStream os, const std::string_view sv) { 25 | { os.put_sv(sv) } -> std::same_as; 26 | }; 27 | 28 | } // namespace details 29 | 30 | template 31 | concept HasAllRequiredFunctions = 32 | write_stream::HasAllRequiredFunctions && details::HasPutSV; 33 | 34 | } // namespace required::pretty_write_stream 35 | 36 | enum PrettyFormatOptions { 37 | kFormatDefault = 0, //!< Default pretty formatting. 38 | kFormatSingleLineArray = 1 //!< Format arrays on a single line. 39 | }; 40 | 41 | template 42 | class PrettyWriter : public Writer { 43 | public: 44 | using Base = Writer; 45 | 46 | protected: 47 | PrettyFormatOptions format_options_; 48 | 49 | private: 50 | std::string indent_; 51 | std::string_view indent_sv_; 52 | 53 | public: 54 | explicit PrettyWriter(WriteStream &os) 55 | : Base(os), format_options_(kFormatDefault) { 56 | InitIndent(' ', 4); 57 | } 58 | 59 | PrettyWriter &SetIndent(const char indent_char, 60 | const std::size_t indent_char_count) { 61 | NEUJSON_ASSERT(indent_char == ' ' || indent_char == '\t' || 62 | indent_char == '\n' || indent_char == '\r'); 63 | InitIndent(indent_char, indent_char_count); 64 | return *this; 65 | } 66 | 67 | PrettyWriter &SetFormatOptions(const PrettyFormatOptions format_options) { 68 | format_options_ = format_options; 69 | return *this; 70 | } 71 | 72 | bool Null() override { 73 | PrettyPrefix(NEU_NULL); 74 | return Base::EndValue(Base::WriteNull()); 75 | } 76 | 77 | bool Bool(const bool b) override { 78 | PrettyPrefix(NEU_BOOL); 79 | return Base::EndValue(Base::WriteBool(b)); 80 | } 81 | 82 | bool Int32(const int32_t i32) override { 83 | PrettyPrefix(NEU_INT32); 84 | return Base::EndValue(Base::WriteInt32(i32)); 85 | } 86 | 87 | bool Int64(const int64_t i64) override { 88 | PrettyPrefix(NEU_INT64); 89 | return Base::EndValue(Base::WriteInt64(i64)); 90 | } 91 | 92 | bool Double(const double d) override { 93 | PrettyPrefix(NEU_DOUBLE); 94 | return Base::EndValue(Base::WriteDouble(internal::Double(d))); 95 | } 96 | 97 | bool Double(const internal::Double d) override { 98 | PrettyPrefix(NEU_DOUBLE); 99 | return Base::EndValue(Base::WriteDouble(d)); 100 | } 101 | 102 | bool String(const std::string_view str) override { 103 | PrettyPrefix(NEU_STRING); 104 | return Base::EndValue(Base::WriteString(str)); 105 | } 106 | 107 | bool Key(const std::string_view str) override { 108 | PrettyPrefix(NEU_STRING); 109 | return Base::EndValue(Base::WriteKey(str)); 110 | } 111 | 112 | bool StartObject() override { 113 | PrettyPrefix(NEU_OBJECT); 114 | Base::stack_.emplace_back(false); 115 | return Base::EndValue(Base::WriteStartObject()); 116 | } 117 | 118 | bool EndObject() override { 119 | NEUJSON_ASSERT(!Base::stack_.empty()); 120 | NEUJSON_ASSERT(!Base::stack_.back().in_array_flag_); 121 | NEUJSON_ASSERT(0 == Base::stack_.back().value_count_ % 2); 122 | const bool empty = Base::stack_.back().value_count_ == 0; 123 | Base::stack_.pop_back(); 124 | 125 | if (!empty) { 126 | Base::os_.put('\n'); 127 | WriteIndent(); 128 | } 129 | 130 | auto ret = Base::EndValue(Base::WriteEndObject()); 131 | (void)ret; 132 | NEUJSON_ASSERT(ret == true); 133 | if (Base::stack_.empty()) { 134 | Base::Flush(); 135 | } 136 | return true; 137 | } 138 | 139 | bool StartArray() override { 140 | PrettyPrefix(NEU_ARRAY); 141 | Base::stack_.emplace_back(true); 142 | return Base::EndValue(Base::WriteStartArray()); 143 | } 144 | 145 | bool EndArray() override { 146 | NEUJSON_ASSERT(!Base::stack_.empty()); 147 | NEUJSON_ASSERT(Base::stack_.back().in_array_flag_); 148 | bool empty = Base::stack_.back().value_count_ == 0; 149 | Base::stack_.pop_back(); 150 | 151 | if (!empty && !(format_options_ & kFormatSingleLineArray)) { 152 | Base::os_.put('\n'); 153 | WriteIndent(); 154 | } 155 | 156 | auto ret = Base::EndValue(Base::WriteEndArray()); 157 | (void)ret; 158 | NEUJSON_ASSERT(ret == true); 159 | if (Base::stack_.empty()) { 160 | Base::Flush(); 161 | } 162 | return true; 163 | } 164 | 165 | protected: 166 | void PrettyPrefix(Type type); 167 | 168 | private: 169 | void InitIndent(char indent_char, std::size_t indent_char_count); 170 | void WriteIndent(); 171 | }; 172 | 173 | template 174 | void PrettyWriter::PrettyPrefix(const Type type) { 175 | (void)type; 176 | if (!Base::stack_.empty()) { 177 | auto &level = Base::stack_.back(); 178 | 179 | if (level.in_array_flag_) { 180 | if (level.value_count_ > 0) { 181 | Base::os_.put(','); 182 | if (format_options_ & kFormatSingleLineArray) { 183 | Base::os_.put(' '); 184 | } 185 | } 186 | if (!(format_options_ & kFormatSingleLineArray)) { 187 | Base::os_.put('\n'); 188 | WriteIndent(); 189 | } 190 | } else { 191 | if (level.value_count_ > 0) { 192 | if (level.value_count_ % 2 == 0) { 193 | Base::os_.put(','); 194 | Base::os_.put('\n'); 195 | } else { 196 | Base::os_.put(':'); 197 | Base::os_.put(' '); 198 | } 199 | } else { 200 | Base::os_.put('\n'); 201 | } 202 | 203 | if (level.value_count_ % 2 == 0) { 204 | WriteIndent(); 205 | } 206 | } 207 | if (!level.in_array_flag_ && level.value_count_ % 2 == 0) { 208 | NEUJSON_ASSERT(type == NEU_STRING && "miss quotation mark"); 209 | } 210 | ++level.value_count_; 211 | } else { 212 | NEUJSON_ASSERT(!Base::has_root_ && "root not singular"); 213 | Base::has_root_ = true; 214 | } 215 | } 216 | 217 | template 218 | void PrettyWriter::InitIndent( 219 | const char indent_char, const std::size_t indent_char_count) { 220 | indent_.clear(); 221 | for (std::size_t i = 0; i < indent_char_count; ++i) { 222 | indent_.push_back(indent_char); 223 | } 224 | indent_sv_ = std::string_view(indent_); 225 | } 226 | 227 | template 228 | void PrettyWriter::WriteIndent() { 229 | const std::size_t count = Base::stack_.size(); 230 | for (std::size_t i = 0; i < count; ++i) { 231 | Base::os_.put_sv(indent_sv_); 232 | } 233 | } 234 | 235 | } // namespace neujson 236 | 237 | #endif // NEUJSON_NEUJSON_PRETTY_WRITER_H_ 238 | -------------------------------------------------------------------------------- /test/round_trip.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/7/16. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | #include "neujson/document.h" 9 | #include "neujson/exception.h" 10 | #include "neujson/string_write_stream.h" 11 | #include "neujson/value.h" 12 | #include "neujson/writer.h" 13 | 14 | #include "gtest/gtest.h" 15 | 16 | #define TEST_ROUNDTRIP_BOOL(_json) \ 17 | do { \ 18 | std::string_view ssi((_json)); \ 19 | neujson::Document doc; \ 20 | EXPECT_EQ(neujson::error::ParseError::OK, doc.Parse(ssi)); \ 21 | EXPECT_EQ(neujson::NEU_BOOL, doc.GetType()); \ 22 | neujson::StringWriteStream oss; \ 23 | neujson::Writer writer(oss); \ 24 | doc.WriteTo(writer); \ 25 | EXPECT_STREQ((_json), std::string(oss.get()).c_str()); \ 26 | } while (0) // 27 | 28 | TEST(round_trip, bool) { 29 | TEST_ROUNDTRIP_BOOL("true"); 30 | TEST_ROUNDTRIP_BOOL("false"); 31 | } 32 | 33 | #define TEST_ROUNDTRIP_INT32(_json) \ 34 | do { \ 35 | std::string_view ssi((_json)); \ 36 | neujson::Document doc; \ 37 | EXPECT_EQ(neujson::error::ParseError::OK, doc.Parse(ssi)); \ 38 | EXPECT_EQ(neujson::NEU_INT32, doc.GetType()); \ 39 | neujson::StringWriteStream oss; \ 40 | neujson::Writer writer(oss); \ 41 | doc.WriteTo(writer); \ 42 | EXPECT_STREQ((_json), std::string(oss.get()).c_str()); \ 43 | } while (0) // 44 | 45 | TEST(round_trip, int32) { 46 | TEST_ROUNDTRIP_INT32("0"); 47 | TEST_ROUNDTRIP_INT32("1234567890"); 48 | TEST_ROUNDTRIP_INT32("-1234567890"); 49 | TEST_ROUNDTRIP_INT32("2147483647"); 50 | TEST_ROUNDTRIP_INT32("-2147483648"); 51 | } 52 | 53 | #define TEST_ROUNDTRIP_INT64(_json) \ 54 | do { \ 55 | std::string_view ssi((_json)); \ 56 | neujson::Document doc; \ 57 | EXPECT_EQ(neujson::error::ParseError::OK, doc.Parse(ssi)); \ 58 | EXPECT_TRUE(neujson::NEU_INT64 == doc.GetType() || \ 59 | neujson::NEU_INT32 == doc.GetType()); \ 60 | neujson::StringWriteStream oss; \ 61 | neujson::Writer writer(oss); \ 62 | doc.WriteTo(writer); \ 63 | EXPECT_STREQ((_json), std::string(oss.get()).c_str()); \ 64 | } while (0) // 65 | 66 | TEST(round_trip, int64) { 67 | TEST_ROUNDTRIP_INT64("0"); 68 | TEST_ROUNDTRIP_INT64("12345678901234"); 69 | TEST_ROUNDTRIP_INT64("-12345678901234"); 70 | TEST_ROUNDTRIP_INT64("9223372036854775807"); 71 | TEST_ROUNDTRIP_INT64("-9223372036854775808"); 72 | } 73 | 74 | #define TEST_ROUNDTRIP_DOUBLE(_json) \ 75 | do { \ 76 | std::string_view ssi((_json)); \ 77 | neujson::Document doc; \ 78 | EXPECT_EQ(neujson::error::ParseError::OK, doc.Parse(ssi)); \ 79 | EXPECT_EQ(neujson::NEU_DOUBLE, doc.GetType()); \ 80 | neujson::StringWriteStream oss; \ 81 | neujson::Writer writer(oss); \ 82 | doc.WriteTo(writer); \ 83 | EXPECT_STREQ((_json), std::string(oss.get()).c_str()); \ 84 | } while (0) // 85 | 86 | TEST(round_trip, double) { 87 | TEST_ROUNDTRIP_DOUBLE("1.5"); 88 | TEST_ROUNDTRIP_DOUBLE("-1.5"); 89 | TEST_ROUNDTRIP_DOUBLE("3.25"); 90 | TEST_ROUNDTRIP_DOUBLE("1e+20"); 91 | TEST_ROUNDTRIP_DOUBLE("1.234e+20"); 92 | TEST_ROUNDTRIP_DOUBLE("1.234e-20"); 93 | } 94 | 95 | #define TEST_ROUNDTRIP_STRING(_expect, _json) \ 96 | do { \ 97 | std::string_view ssi((_json)); \ 98 | neujson::Document doc; \ 99 | EXPECT_EQ(neujson::error::ParseError::OK, doc.Parse(ssi)); \ 100 | EXPECT_EQ(neujson::NEU_STRING, doc.GetType()); \ 101 | neujson::StringWriteStream oss; \ 102 | neujson::Writer writer(oss); \ 103 | doc.WriteTo(writer); \ 104 | EXPECT_STREQ((_expect), std::string(oss.get()).c_str()); \ 105 | } while (0) // 106 | 107 | TEST(round_trip, string) { 108 | TEST_ROUNDTRIP_STRING("\"\"", R"("")"); 109 | TEST_ROUNDTRIP_STRING("\"Hello\"", R"("Hello")"); 110 | TEST_ROUNDTRIP_STRING("\"Hello\\nWorld\"", R"("Hello\nWorld")"); 111 | TEST_ROUNDTRIP_STRING("\"\\\" \\\\ / \\b \\f \\n \\r \\t\"", 112 | R"("\" \\ / \b \f \n \r \t")"); 113 | TEST_ROUNDTRIP_STRING("\"Hello\\u0000World\"", R"("Hello\u0000World")"); 114 | } 115 | 116 | #define TEST_ROUNDTRIP_ARRAY(_json) \ 117 | do { \ 118 | std::string_view ssi((_json)); \ 119 | neujson::Document doc; \ 120 | EXPECT_EQ(neujson::error::ParseError::OK, doc.Parse(ssi)); \ 121 | EXPECT_EQ(neujson::NEU_ARRAY, doc.GetType()); \ 122 | neujson::StringWriteStream oss; \ 123 | neujson::Writer writer(oss); \ 124 | doc.WriteTo(writer); \ 125 | EXPECT_STREQ((_json), std::string(oss.get()).c_str()); \ 126 | } while (0) // 127 | 128 | TEST(round_trip, array) { 129 | TEST_ROUNDTRIP_ARRAY("[]"); 130 | TEST_ROUNDTRIP_ARRAY(R"([null,false,true,123,"abc",[1,2,3]])"); 131 | TEST_ROUNDTRIP_ARRAY(R"([[],[0],[0,1],[0,1,2]])"); 132 | } 133 | 134 | #define TEST_ROUNDTRIP_OBJECT(_json) \ 135 | do { \ 136 | std::string_view ssi((_json)); \ 137 | neujson::Document doc; \ 138 | EXPECT_EQ(neujson::error::ParseError::OK, doc.Parse(ssi)); \ 139 | EXPECT_EQ(neujson::NEU_OBJECT, doc.GetType()); \ 140 | neujson::StringWriteStream oss; \ 141 | neujson::Writer writer(oss); \ 142 | doc.WriteTo(writer); \ 143 | EXPECT_STREQ((_json), std::string(oss.get()).c_str()); \ 144 | } while (0) // 145 | 146 | TEST(round_trip, object) { 147 | TEST_ROUNDTRIP_OBJECT("{}"); 148 | TEST_ROUNDTRIP_OBJECT( 149 | R"({"n":null,"f":false,"t":true,"i":123,"s":"abc","a":[1,2,3],"o":{"1":1,"2":2,"3":3}})"); 150 | } 151 | -------------------------------------------------------------------------------- /include/neujson/internal/big_integer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/6/30. 3 | // 4 | 5 | #ifndef NEUJSON_INCLUDE_NEUJSON_INTERNAL_BIG_INTEGER_H_ 6 | #define NEUJSON_INCLUDE_NEUJSON_INTERNAL_BIG_INTEGER_H_ 7 | 8 | #include 9 | 10 | #include "neujson/neujson.h" 11 | 12 | #if defined(_MSC_VER) && defined(_M_AMD64) && !defined(__INTEL_COMPILER) 13 | #include 14 | #if !defined(_ARM64EC_) 15 | #pragma intrinsic(_umul128) 16 | #else 17 | #pragma comment(lib, "softintrin") 18 | #endif 19 | #endif 20 | 21 | namespace neujson::internal { 22 | 23 | class BigInteger { 24 | public: 25 | using Type = uint64_t; 26 | 27 | private: 28 | static const std::size_t kBitCount = 3328; // 64bit * 54 > 10^1000 29 | static const std::size_t kCapacity = kBitCount / sizeof(Type); 30 | static const std::size_t kTypeBit = sizeof(Type) * 8; 31 | 32 | Type digits_[kCapacity]{}; 33 | std::size_t count_; 34 | 35 | public: 36 | explicit BigInteger(uint64_t u) : count_(1) { digits_[0] = u; } 37 | 38 | BigInteger(const BigInteger &big_integer) : count_(big_integer.count_) { 39 | std::memcpy(digits_, big_integer.digits_, count_ * sizeof(Type)); 40 | } 41 | 42 | template 43 | BigInteger(const Ch *decimals, std::size_t length) : count_(1) { 44 | NEUJSON_ASSERT(length > 0); 45 | digits_[0] = 0; 46 | std::size_t i = 0; 47 | const std::size_t kMaxDigitPerIter = 19; // 2^64 > 10^19 48 | while (length > 19) { 49 | AppendDecimal64(decimals + i, decimals + i + kMaxDigitPerIter); 50 | length -= kMaxDigitPerIter; 51 | i += kMaxDigitPerIter; 52 | } 53 | 54 | // other( < kMaxDigitPerIter ) 55 | if (length > 0) { 56 | AppendDecimal64(decimals + i, decimals + i + length); 57 | } 58 | } 59 | 60 | BigInteger &operator=(const BigInteger &big_integer) { 61 | if (this != &big_integer) { 62 | count_ = big_integer.count_; 63 | std::memcpy(digits_, big_integer.digits_, count_ * sizeof(Type)); 64 | } 65 | return *this; 66 | } 67 | 68 | BigInteger &operator=(uint64_t u64) { 69 | digits_[0] = u64; 70 | count_ = 1; 71 | return *this; 72 | } 73 | 74 | bool operator==(const BigInteger *big_integer) const { 75 | return count_ == big_integer->count_ && 76 | std::memcmp(digits_, big_integer->digits_, count_ * sizeof(Type)) == 77 | 0; 78 | } 79 | 80 | bool operator==(const Type &type) const { 81 | return count_ == 1 & digits_[0] == type; 82 | } 83 | 84 | BigInteger &operator<<=(std::size_t shift) { 85 | if (IsZero() || shift == 0) { 86 | return *this; 87 | } 88 | 89 | std::size_t offset = shift / kTypeBit; 90 | std::size_t inner_shift = shift & kTypeBit; 91 | NEUJSON_ASSERT(count_ + offset <= kCapacity); 92 | 93 | if (inner_shift == 0) { 94 | std::memmove(digits_ + offset, digits_, count_ * sizeof(Type)); 95 | count_ += offset; 96 | } else { 97 | digits_[count_] = 0; 98 | for (std::size_t i = count_; i > 0; --i) { 99 | digits_[i + offset] = (digits_[i] << inner_shift) | 100 | (digits_[i - 1] >> (kTypeBit - inner_shift)); 101 | } 102 | digits_[offset] = digits_[0] << inner_shift; 103 | count_ += offset; 104 | if (digits_[count_]) { 105 | ++count_; 106 | } 107 | } 108 | 109 | std::memset(digits_, 0, offset * sizeof(Type)); 110 | 111 | return *this; 112 | } 113 | 114 | BigInteger &operator+=(uint64_t u64) { 115 | Type backup = digits_[0]; 116 | digits_[0] += u64; 117 | for (std::size_t i = 0; i < count_ - 1; ++i) { 118 | if (digits_[i] >= backup) { 119 | return *this; 120 | } // no carry 121 | backup = digits_[i + 1]; 122 | digits_[i + 1] += 1; 123 | } 124 | 125 | if (digits_[count_ - 1] < backup) { 126 | PushBack(1); 127 | } // last carry 128 | 129 | return *this; 130 | } 131 | 132 | BigInteger &operator*=(uint64_t u64) { 133 | if (u64 == 0) { 134 | return *this = 0; 135 | } 136 | if (u64 == 1) { 137 | return *this; 138 | } 139 | if (*this == 1) { 140 | return *this = u64; 141 | } 142 | 143 | uint64_t carry = 0; 144 | for (std::size_t i = 0; i < count_; ++i) { 145 | uint64_t h; 146 | digits_[i] = MulAdd64(digits_[i], u64, carry, &h); 147 | carry = h; 148 | } 149 | 150 | if (carry > 0) { 151 | PushBack(carry); 152 | } 153 | 154 | return *this; 155 | } 156 | 157 | BigInteger &operator*=(uint32_t u32) { 158 | if (u32 == 0) { 159 | return *this = 0; 160 | } 161 | if (u32 == 1) { 162 | return *this; 163 | } 164 | if (*this == 1) { 165 | return *this = u32; 166 | } 167 | 168 | uint64_t carry = 0; 169 | for (std::size_t i = 0; i < count_; ++i) { 170 | const uint64_t h = digits_[i] >> 32; 171 | const uint64_t l = digits_[i] & 0xFFFFFFFF; 172 | const uint64_t uh = u32 * h; 173 | const uint64_t ul = u32 * l; 174 | const uint64_t p0 = ul + carry; 175 | const uint64_t p1 = uh + (p0 >> 32); // add carry 176 | digits_[i] = (p0 & 0xFFFFFFFF) | (p1 << 32); 177 | carry = p1 >> 32; 178 | } 179 | 180 | if (carry > 0) { 181 | PushBack(carry); 182 | } 183 | 184 | return *this; 185 | } 186 | 187 | BigInteger &MultiplyPow5(unsigned exponent) { 188 | static const uint32_t kPow5[12] = { 189 | // use static pre-cal table to speed up the cal 190 | 5, 191 | 5 * 5, 192 | 5 * 5 * 5, 193 | 5 * 5 * 5 * 5, 194 | 5 * 5 * 5 * 5 * 5, 195 | 5 * 5 * 5 * 5 * 5 * 5, 196 | 5 * 5 * 5 * 5 * 5 * 5 * 5, 197 | 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, 198 | 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, 199 | 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, 200 | 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, 201 | 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5}; 202 | 203 | if (exponent == 0) { 204 | return *this; 205 | } 206 | for (; exponent >= 27; exponent -= 27) { 207 | *this *= NEUJSON_UINT64_C2(0x6765C793, 0xFA10079D); 208 | } // 5^27 209 | for (; exponent >= 13; exponent -= 13) { 210 | *this *= static_cast(1220703125U); 211 | } // 5^13 212 | if (exponent > 0) { 213 | *this *= kPow5[exponent - 1]; 214 | } 215 | return *this; 216 | } 217 | 218 | [[nodiscard]] bool IsZero() const { return count_ == 1 & digits_[0] == 1; } 219 | 220 | private: 221 | void PushBack(Type digit) { 222 | NEUJSON_ASSERT(count_ < kCapacity); 223 | digits_[count_++] = digit; 224 | } 225 | 226 | template void AppendDecimal64(const Ch *begin, const Ch *end) { 227 | uint64_t u = ParseUint64(begin, end); 228 | if (IsZero()) { 229 | *this = u; 230 | } else { 231 | auto exponent = static_cast(end - begin); 232 | (MultiplyPow5(exponent) <<= exponent) += u; // *this * 10^exp + u 233 | } 234 | } 235 | 236 | template 237 | static uint64_t ParseUint64(const Ch *begin, const Ch *end) { 238 | uint64_t ret = 0; 239 | for (const Ch *p = begin; p < end; ++p) { 240 | NEUJSON_ASSERT(*p >= Ch('0') && *p <= Ch('9')); 241 | ret = ret * 10U + static_cast(*p - Ch('0')); 242 | } 243 | return ret; 244 | } 245 | 246 | // a * b + k 247 | static uint64_t MulAdd64(uint64_t a, uint64_t b, uint64_t k, 248 | uint64_t *out_high) { 249 | #if defined(_MSC_VER) && defined(_M_AMD64) 250 | uint64_t low = _umul128(_a, _b, &_out_high) + _k; 251 | if (low < _k) { 252 | *(_out_high)++; 253 | } // with carry 254 | return low; 255 | #elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && \ 256 | defined(__x86_64__) 257 | __extension__ typedef unsigned __int128 uint128_t; 258 | uint128_t p = static_cast(_a) * static_cast(_b); 259 | p += _k; 260 | *_out_high = static_cast(p >> 64); 261 | return static_cast(p); 262 | #else 263 | const uint64_t al = a & 0xFFFFFFFF, ah = a >> 32, bl = b & 0xFFFFFFFF, 264 | bh = b >> 32; 265 | uint64_t x0 = al * bl, x1 = al * bh, x2 = ah * bl, x3 = ah * bh; 266 | x1 += (x0 >> 32); 267 | x1 += x2; 268 | if (x1 < x2) { 269 | x3 += (static_cast(1) << 32); 270 | } 271 | uint64_t l = (x1 << 32) + (x0 & 0xFFFFFFFF); 272 | uint64_t h = x3 + (x1 >> 32); 273 | 274 | l += k; 275 | if (l < k) { 276 | ++h; 277 | } 278 | *out_high = h; 279 | return l; 280 | #endif 281 | } 282 | }; 283 | 284 | } // namespace neujson::internal 285 | 286 | #endif // NEUJSON_INCLUDE_NEUJSON_INTERNAL_BIG_INTEGER_H_ 287 | -------------------------------------------------------------------------------- /include/neujson/writer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/10. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_WRITER_H_ 6 | #define NEUJSON_NEUJSON_WRITER_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "internal/ieee754.h" 15 | #include "internal/itoa.h" 16 | #include "neujson.h" 17 | #include "non_copyable.h" 18 | #include "value.h" 19 | 20 | #if defined(__GNUC__) 21 | NEUJSON_DIAG_PUSH 22 | NEUJSON_DIAG_OFF(effc++) 23 | #endif 24 | 25 | namespace neujson { 26 | 27 | namespace required::write_stream { 28 | namespace details { 29 | 30 | template 31 | concept HasPut = requires(WriteStream os, char ch) { 32 | { os.put(ch) } -> std::same_as; 33 | }; 34 | 35 | template 36 | concept HasPuts = 37 | requires(WriteStream os, const char *str, std::size_t length) { 38 | { os.puts(str, length) } -> std::same_as; 39 | }; 40 | 41 | template 42 | concept HasFlush = requires(WriteStream os) { 43 | { os.flush() } -> std::same_as; 44 | }; 45 | 46 | } // namespace details 47 | 48 | template 49 | concept HasAllRequiredFunctions = 50 | details::HasPut && details::HasPuts && details::HasFlush; 51 | 52 | } // namespace required::write_stream 53 | 54 | template 55 | class Writer : NonCopyable { 56 | struct Level { 57 | bool in_array_flag_; // in array or object 58 | int value_count_; 59 | 60 | explicit Level(const bool in_array_flag) 61 | : in_array_flag_(in_array_flag), value_count_(0) {} 62 | }; 63 | 64 | protected: 65 | std::vector stack_; 66 | WriteStream &os_; 67 | bool has_root_; 68 | 69 | public: 70 | explicit Writer(WriteStream &os) : os_(os), has_root_(false) {} 71 | virtual ~Writer() = default; 72 | 73 | virtual bool Null() { 74 | Prefix(NEU_NULL); 75 | return EndValue(WriteNull()); 76 | } 77 | 78 | virtual bool Bool(const bool b) { 79 | Prefix(NEU_BOOL); 80 | return EndValue(WriteBool(b)); 81 | } 82 | 83 | virtual bool Int32(const int32_t i32) { 84 | Prefix(NEU_INT32); 85 | return EndValue(WriteInt32(i32)); 86 | } 87 | 88 | virtual bool Int64(const int64_t i64) { 89 | Prefix(NEU_INT64); 90 | return EndValue(WriteInt64(i64)); 91 | } 92 | 93 | virtual bool Double(const double d) { 94 | Prefix(NEU_DOUBLE); 95 | return EndValue(WriteDouble(internal::Double(d))); 96 | } 97 | 98 | virtual bool Double(const internal::Double d) { 99 | Prefix(NEU_DOUBLE); 100 | return EndValue(WriteDouble(d)); 101 | } 102 | 103 | virtual bool String(const std::string_view str) { 104 | Prefix(NEU_STRING); 105 | return EndValue(WriteString(str)); 106 | } 107 | 108 | virtual bool Key(const std::string_view str) { 109 | Prefix(NEU_STRING); 110 | return EndValue(WriteKey(str)); 111 | } 112 | 113 | virtual bool StartObject() { 114 | Prefix(NEU_OBJECT); 115 | stack_.emplace_back(false); 116 | return EndValue(WriteStartObject()); 117 | } 118 | 119 | virtual bool EndObject() { 120 | NEUJSON_ASSERT(!stack_.empty()); 121 | NEUJSON_ASSERT(!stack_.back().in_array_flag_); 122 | stack_.pop_back(); 123 | return EndValue(WriteEndObject()); 124 | } 125 | 126 | virtual bool StartArray() { 127 | Prefix(NEU_ARRAY); 128 | stack_.emplace_back(true); 129 | return EndValue(WriteStartArray()); 130 | } 131 | 132 | virtual bool EndArray() { 133 | NEUJSON_ASSERT(!stack_.empty()); 134 | NEUJSON_ASSERT(stack_.back().in_array_flag_); 135 | stack_.pop_back(); 136 | return EndValue(WriteEndArray()); 137 | } 138 | 139 | protected: 140 | void Prefix(Type type); 141 | 142 | bool WriteNull(); 143 | bool WriteBool(bool b); 144 | bool WriteInt32(int32_t i32); 145 | bool WriteInt64(int64_t i64); 146 | bool WriteDouble(internal::Double d); 147 | bool WriteString(std::string_view str); 148 | bool WriteKey(std::string_view str); 149 | bool WriteStartObject(); 150 | bool WriteEndObject(); 151 | bool WriteStartArray(); 152 | bool WriteEndArray(); 153 | bool EndValue(bool ret); 154 | void Flush() { os_.flush(); } 155 | }; 156 | 157 | template 158 | void Writer::Prefix(const Type type) { 159 | (void)type; 160 | if (!stack_.empty()) { 161 | auto &level = stack_.back(); 162 | if (level.value_count_ > 0) { 163 | if (level.in_array_flag_) { 164 | os_.put(','); 165 | } else { 166 | os_.put((level.value_count_ % 2 == 0) ? ',' : ':'); 167 | } 168 | } 169 | 170 | if (!level.in_array_flag_ && level.value_count_ % 2 == 0) { 171 | NEUJSON_ASSERT(type == NEU_STRING && "miss quotation mark"); 172 | } 173 | ++level.value_count_; 174 | } else { 175 | NEUJSON_ASSERT(!has_root_ && "root not singular"); 176 | has_root_ = true; 177 | } 178 | } 179 | 180 | template 181 | bool Writer::WriteNull() { 182 | os_.puts("null", 4); 183 | return true; 184 | } 185 | 186 | template 187 | bool Writer::WriteBool(const bool b) { 188 | os_.puts(b ? "true" : "false", b ? 4 : 5); 189 | return true; 190 | } 191 | 192 | template 193 | bool Writer::WriteInt32(const int32_t i32) { 194 | char buf[16]{}; 195 | auto size = static_cast(internal::i32toa(i32, buf) - buf); 196 | os_.puts(buf, size); 197 | return true; 198 | } 199 | 200 | template 201 | bool Writer::WriteInt64(const int64_t i64) { 202 | char buf[32]{}; 203 | auto size = static_cast(internal::i64toa(i64, buf) - buf); 204 | os_.puts(buf, size); 205 | return true; 206 | } 207 | 208 | template 209 | bool Writer::WriteDouble(const internal::Double d) { 210 | char buf[32]; 211 | std::size_t size = 0; 212 | if (d.IsInf()) { 213 | #if defined(_MSC_VER) 214 | strcpy_s(buf, "Infinity"); 215 | #else 216 | strcpy(buf, "Infinity"); 217 | #endif 218 | size += 8; 219 | } else if (d.IsNan()) { 220 | #if defined(_MSC_VER) 221 | strcpy_s(buf, "NaN"); 222 | #else 223 | strcpy(buf, "NaN"); 224 | #endif 225 | size += 3; 226 | } else { 227 | const int n = snprintf(buf, sizeof(buf), "%.17g", d.Value()); 228 | // type information loss if ".0" not added 229 | // "1.0" -> double 1 -> "1" 230 | NEUJSON_ASSERT(n > 0 && n < 32); 231 | size += static_cast(n); 232 | if (std::find_if_not(buf, buf + n, isdigit) == buf + n) { 233 | #if defined(_MSC_VER) 234 | strcat_s(buf, ".0"); 235 | #else 236 | strcat(buf, ".0"); 237 | #endif 238 | size += 2; 239 | } 240 | } 241 | 242 | os_.puts(buf, size); 243 | return true; 244 | } 245 | 246 | template 247 | bool Writer::WriteString(const std::string_view str) { 248 | os_.put('"'); 249 | for (auto c : str) { 250 | switch (const auto u = static_cast(c); u) { 251 | case '\"': 252 | os_.puts("\\\"", 2); 253 | break; 254 | case '\b': 255 | os_.puts("\\b", 2); 256 | break; 257 | case '\f': 258 | os_.puts("\\f", 2); 259 | break; 260 | case '\n': 261 | os_.puts("\\n", 2); 262 | break; 263 | case '\r': 264 | os_.puts("\\r", 2); 265 | break; 266 | case '\t': 267 | os_.puts("\\t", 2); 268 | break; 269 | case '\\': 270 | os_.puts("\\\\", 2); 271 | break; 272 | default: 273 | if (u < 0x20) { 274 | char buf[7]; 275 | snprintf(buf, 7, "\\u%04X", u); 276 | os_.puts(buf, 6); 277 | } else { 278 | os_.put(c); 279 | } 280 | } 281 | } 282 | os_.put('"'); 283 | return true; 284 | } 285 | 286 | template 287 | bool Writer::WriteKey(const std::string_view str) { 288 | WriteString(str); 289 | return true; 290 | } 291 | 292 | template 293 | bool Writer::WriteStartObject() { 294 | os_.put('{'); 295 | return true; 296 | } 297 | 298 | template 299 | bool Writer::WriteEndObject() { 300 | os_.put('}'); 301 | return true; 302 | } 303 | 304 | template 305 | bool Writer::WriteStartArray() { 306 | os_.put('['); 307 | return true; 308 | } 309 | 310 | template 311 | bool Writer::WriteEndArray() { 312 | os_.put(']'); 313 | return true; 314 | } 315 | 316 | template 317 | bool Writer::EndValue(const bool ret) { 318 | // end of json text 319 | if (stack_.empty()) { 320 | Flush(); 321 | } 322 | return ret; 323 | } 324 | 325 | } // namespace neujson 326 | 327 | #if defined(__GNUC__) 328 | NEUJSON_DIAG_POP 329 | #endif 330 | 331 | #endif // NEUJSON_NEUJSON_WRITER_H_ 332 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) 2 | if (POLICY CMP0025) 3 | # detect Apple's Clang 4 | cmake_policy(SET CMP0025 NEW) 5 | endif () 6 | if (POLICY CMP0054) 7 | cmake_policy(SET CMP0054 NEW) 8 | endif () 9 | 10 | set(LIB_MAJOR_VERSION "1") 11 | set(LIB_MINOR_VERSION "3") 12 | set(LIB_PATCH_VERSION "0") 13 | set(LIB_VERSION_STRING "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_PATCH_VERSION}") 14 | 15 | if (CMAKE_VERSION VERSION_LESS 3.0) 16 | PROJECT(neujson CXX) 17 | else () 18 | cmake_policy(SET CMP0048 NEW) 19 | PROJECT(neujson VERSION "${LIB_VERSION_STRING}" LANGUAGES C CXX) 20 | endif () 21 | 22 | set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) 23 | get_filename_component(DEPS_ROOT "${PROJECT_BINARY_DIR}/deps" ABSOLUTE) 24 | include(ExtProjectUtils) 25 | 26 | # compile in release with debug info mode by default 27 | if (NOT CMAKE_BUILD_TYPE) 28 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) 29 | endif () 30 | 31 | option(NEUJSON_BUILD_BENCHMARKS "Build neujson benchmarks." OFF) 32 | option(NEUJSON_BUILD_EXAMPLES "Build neujson examples." OFF) 33 | option(NEUJSON_BUILD_TESTS "Build neujson unittests." OFF) 34 | 35 | option(NEUJSON_ENABLE_INSTRUMENTATION_OPT "Build neujson with -march or -mcpu options" OFF) 36 | 37 | set(CMAKE_CXX_STANDARD 20) 38 | set(CMAKE_CXX_STANDARD_REQUIRED TRUE) 39 | 40 | message("") 41 | message("Operation system is ${CMAKE_SYSTEM}") 42 | message("Current compiler: ${CMAKE_CXX_COMPILER_ID}") 43 | message("Current compiler version: ${CMAKE_CXX_COMPILER_VERSION}") 44 | message("Current compiler directory: ${CMAKE_CXX_COMPILER}") 45 | 46 | find_program(CCACHE_FOUND ccache) 47 | if (CCACHE_FOUND) 48 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) 49 | set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) 50 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 51 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Qunused-arguments -fcolor-diagnostics") 52 | endif () 53 | endif (CCACHE_FOUND) 54 | 55 | if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 56 | if (NEUJSON_ENABLE_INSTRUMENTATION_OPT AND NOT CMAKE_CROSSCOMPILING) 57 | if (CMAKE_SYSTEM_PROCESSOR STREQUAL "powerpc" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64le") 58 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=native") 59 | else () 60 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") 61 | endif () 62 | endif () 63 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror") 64 | set(EXTRA_CXX_FLAGS -Weffc++ -Wswitch-default -Wfloat-equal -Wconversion -Wsign-conversion) 65 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 66 | if (NOT CMAKE_CROSSCOMPILING) 67 | if (CMAKE_SYSTEM_PROCESSOR STREQUAL "powerpc" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64le") 68 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=native") 69 | elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64" AND CMAKE_SYSTEM_NAME MATCHES "Darwin") 70 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=apple-m1") 71 | else () 72 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") 73 | endif () 74 | endif () 75 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror -Wno-missing-field-initializers") 76 | set(EXTRA_CXX_FLAGS -Weffc++ -Wswitch-default -Wfloat-equal -Wconversion -Wimplicit-fallthrough) 77 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 78 | add_definitions(-D_CRT_SECURE_NO_WARNINGS=1) 79 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") 80 | # Always compile with /WX 81 | if (CMAKE_CXX_FLAGS MATCHES "/WX-") 82 | string(REGEX REPLACE "/WX-" "/WX" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 83 | else () 84 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX") 85 | endif () 86 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "XL") 87 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -qarch=auto") 88 | endif () 89 | 90 | 91 | include_directories(${PROJECT_SOURCE_DIR}/include) 92 | 93 | # add extra search paths for libraries and includes 94 | set(INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH "The directory the headers are installed in") 95 | set(LIB_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE STRING "Directory where lib will install") 96 | 97 | # benchmarks 98 | if (NEUJSON_BUILD_BENCHMARKS) 99 | ExtProjectGit("https://github.com/google/benchmark.git" "v1.8.4" ${DEPS_ROOT} CMAKE_ARGS "-DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON" "${_OPT_CMAKE_ARGS}") 100 | add_library(google-benchmark IMPORTED STATIC GLOBAL) 101 | add_dependencies(google-benchmark "google_benchmark.git") 102 | set_target_properties(google-benchmark PROPERTIES 103 | IMPORTED_LOCATION ${DEPS_ROOT}/lib/libbenchmark.a 104 | ) 105 | 106 | enable_testing() 107 | file(GLOB BENCHMARK_SRC_FILES ${PROJECT_SOURCE_DIR}/benchmark/*.cc) 108 | foreach (_benchmark_file ${BENCHMARK_SRC_FILES}) 109 | get_filename_component(_benchmark_name ${_benchmark_file} NAME_WE) 110 | add_executable(${_benchmark_name} ${_benchmark_file}) 111 | add_dependencies(${_benchmark_name} google-benchmark) 112 | target_compile_definitions(${_benchmark_name} PRIVATE RESOURCES_DIR=\"${PROJECT_SOURCE_DIR}/benchmark/resources\") 113 | target_include_directories(${_benchmark_name} PRIVATE "${DEPS_ROOT}/include") 114 | target_link_libraries(${_benchmark_name} PRIVATE google-benchmark) 115 | endforeach () 116 | endif () 117 | 118 | # example 119 | if (NEUJSON_BUILD_EXAMPLES) 120 | file(GLOB EXAMPLE_SRC_FILES ${PROJECT_SOURCE_DIR}/example/*.cc) 121 | foreach (_example_file ${EXAMPLE_SRC_FILES}) 122 | get_filename_component(_example_name ${_example_file} NAME_WE) 123 | add_executable(${_example_name} ${_example_file}) 124 | endforeach () 125 | endif () 126 | 127 | if (NEUJSON_BUILD_TESTS) 128 | if (MSVC11) 129 | # required for VS2012 due to missing support for variadic templates 130 | add_definitions(-D_VARIADIC_MAX=10) 131 | endif () 132 | ExtProjectGit("https://github.com/google/googletest.git" "v1.13.0" ${DEPS_ROOT} CMAKE_ARGS "${_OPT_CMAKE_ARGS}") 133 | enable_testing() 134 | file(GLOB TEST_SRC_FILES ${PROJECT_SOURCE_DIR}/test/*.cc) 135 | foreach (_test_file ${TEST_SRC_FILES}) 136 | get_filename_component(_test_name ${_test_file} NAME_WE) 137 | add_executable(${_test_name} ${_test_file}) 138 | add_dependencies(${_test_name} "google_googletest.git") 139 | target_include_directories(${_test_name} PRIVATE "${DEPS_ROOT}/include") 140 | target_link_directories(${_test_name} PRIVATE "${DEPS_ROOT}/lib") 141 | target_link_libraries(${_test_name} gtest gtest_main ${CMAKE_THREAD_LIBS_INIT}) 142 | add_test(${_test_name} ${_test_name}) 143 | set_tests_properties(${_test_name} PROPERTIES TIMEOUT 5) 144 | endforeach () 145 | endif () 146 | 147 | # header 148 | install(DIRECTORY include/${PROJECT_NAME} 149 | DESTINATION "${INCLUDE_INSTALL_DIR}" 150 | COMPONENT dev) 151 | 152 | # cmake 153 | if (UNIX OR CYGWIN) 154 | set(_CMAKE_INSTALL_DIR "${LIB_INSTALL_DIR}/cmake/${PROJECT_NAME}") 155 | elseif (WIN32) 156 | set(_CMAKE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/cmake") 157 | endif () 158 | set(CMAKE_INSTALL_DIR "${_CMAKE_INSTALL_DIR}" CACHE PATH "The directory cmake files are installed in") 159 | 160 | export(PACKAGE ${PROJECT_NAME}) 161 | 162 | set(CONFIG_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) 163 | set(CONFIG_DIR ${CMAKE_CURRENT_BINARY_DIR}) 164 | set(${PROJECT_NAME}_INCLUDE_DIR "\${${PROJECT_NAME}_SOURCE_DIR}/include") 165 | 166 | configure_file( 167 | "${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in" 168 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 169 | @ONLY) 170 | configure_file( 171 | "${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}ConfigVersion.cmake.in" 172 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" 173 | @ONLY) 174 | 175 | set(CMAKECONFIG_INSTALL_DIR ${LIB_INSTALL_DIR}/cmake/${PROJECT_NAME}) 176 | file(RELATIVE_PATH REL_INCLUDE_DIR 177 | "${CMAKECONFIG_INSTALL_DIR}" 178 | "${CMAKE_INSTALL_PREFIX}/include") 179 | set(${PROJECT_NAME}_INCLUDE_DIR "\${${PROJECT_NAME}_CMAKE_DIR}/${REL_INCLUDE_DIR}") 180 | set(CONFIG_SOURCE_DIR) 181 | set(CONFIG_DIR) 182 | configure_file( 183 | ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in 184 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake 185 | @ONLY) 186 | 187 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 188 | DESTINATION ${CMAKECONFIG_INSTALL_DIR}) 189 | 190 | if (CMAKE_INSTALL_DIR) 191 | install(FILES 192 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake 193 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake 194 | DESTINATION "${CMAKE_INSTALL_DIR}" 195 | COMPONENT dev) 196 | endif () 197 | 198 | # pkg-config 199 | if (UNIX OR CYGWIN) 200 | configure_file(${PROJECT_SOURCE_DIR}/${PROJECT_NAME}.pc.in 201 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc 202 | @ONLY) 203 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc 204 | DESTINATION "${LIB_INSTALL_DIR}/pkgconfig" 205 | COMPONENT pkgconfig) 206 | endif () 207 | 208 | # uninstall target 209 | if (NOT TARGET uninstall) 210 | configure_file( 211 | "${PROJECT_SOURCE_DIR}/${PROJECT_NAME}Uninstall.cmake.in" 212 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Uninstall.cmake" 213 | IMMEDIATE @ONLY) 214 | add_custom_target(uninstall 215 | COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Uninstall.cmake) 216 | endif () 217 | 218 | message("The directory the headers are installed in: ${CMAKE_INSTALL_PREFIX}") 219 | message("Directory where lib will install: ${LIB_INSTALL_DIR}") 220 | message("") 221 | -------------------------------------------------------------------------------- /include/neujson/internal/itoa.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/11. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_INTERNAL_ITOA_H_ 6 | #define NEUJSON_NEUJSON_INTERNAL_ITOA_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include "neujson/neujson.h" 12 | 13 | #if defined(__clang__) 14 | NEUJSON_DIAG_PUSH 15 | NEUJSON_DIAG_OFF(unreachable-code) 16 | #elif defined(_MSC_VER) 17 | NEUJSON_DIAG_PUSH 18 | NEUJSON_DIAG_OFF(4127) // conditional expression is constant 19 | NEUJSON_DIAG_OFF(4702) // unreachable code 20 | #endif 21 | 22 | namespace neujson::internal { 23 | 24 | constexpr char kDigitsLut[200] = { 25 | '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', 26 | '7', '0', '8', '0', '9', '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', 27 | '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', '2', '0', '2', '1', '2', 28 | '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', 29 | '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', '3', '5', '3', '6', '3', 30 | '7', '3', '8', '3', '9', '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', 31 | '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', '5', '0', '5', '1', '5', 32 | '2', '5', '3', '5', '4', '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', 33 | '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', '6', '5', '6', '6', '6', 34 | '7', '6', '8', '6', '9', '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', 35 | '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', '8', '0', '8', '1', '8', 36 | '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', 37 | '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9', 38 | '7', '9', '8', '9', '9'}; 39 | 40 | inline char *u32toa(uint32_t value, char *buffer) { 41 | NEUJSON_ASSERT(buffer != nullptr); 42 | 43 | if (value < 10000) { 44 | const uint32_t d1 = (value / 100) << 1; 45 | const uint32_t d2 = (value % 100) << 1; 46 | 47 | if (value >= 1000) 48 | *buffer++ = kDigitsLut[d1]; 49 | if (value >= 100) 50 | *buffer++ = kDigitsLut[d1 + 1]; 51 | if (value >= 10) 52 | *buffer++ = kDigitsLut[d2]; 53 | *buffer++ = kDigitsLut[d2 + 1]; 54 | } else if (value < 100000000) { 55 | // value = bbbbcccc 56 | const uint32_t b = value / 10000; 57 | const uint32_t c = value % 10000; 58 | 59 | const uint32_t d1 = (b / 100) << 1; 60 | const uint32_t d2 = (b % 100) << 1; 61 | 62 | const uint32_t d3 = (c / 100) << 1; 63 | const uint32_t d4 = (c % 100) << 1; 64 | 65 | if (value >= 10000000) 66 | *buffer++ = kDigitsLut[d1]; 67 | if (value >= 1000000) 68 | *buffer++ = kDigitsLut[d1 + 1]; 69 | if (value >= 100000) 70 | *buffer++ = kDigitsLut[d2]; 71 | *buffer++ = kDigitsLut[d2 + 1]; 72 | 73 | *buffer++ = kDigitsLut[d3]; 74 | *buffer++ = kDigitsLut[d3 + 1]; 75 | *buffer++ = kDigitsLut[d4]; 76 | *buffer++ = kDigitsLut[d4 + 1]; 77 | } else { 78 | // value = aabbbbcccc in decimal 79 | 80 | const uint32_t a = value / 100000000; // 1 to 42 81 | value %= 100000000; 82 | 83 | if (a >= 10) { 84 | const unsigned i = a << 1; 85 | *buffer++ = kDigitsLut[i]; 86 | *buffer++ = kDigitsLut[i + 1]; 87 | } else 88 | *buffer++ = static_cast('0' + static_cast(a)); 89 | 90 | const uint32_t b = value / 10000; // 0 to 9999 91 | const uint32_t c = value % 10000; // 0 to 9999 92 | 93 | const uint32_t d1 = (b / 100) << 1; 94 | const uint32_t d2 = (b % 100) << 1; 95 | 96 | const uint32_t d3 = (c / 100) << 1; 97 | const uint32_t d4 = (c % 100) << 1; 98 | 99 | *buffer++ = kDigitsLut[d1]; 100 | *buffer++ = kDigitsLut[d1 + 1]; 101 | *buffer++ = kDigitsLut[d2]; 102 | *buffer++ = kDigitsLut[d2 + 1]; 103 | *buffer++ = kDigitsLut[d3]; 104 | *buffer++ = kDigitsLut[d3 + 1]; 105 | *buffer++ = kDigitsLut[d4]; 106 | *buffer++ = kDigitsLut[d4 + 1]; 107 | } 108 | return buffer; 109 | } 110 | 111 | inline char *i32toa(int32_t value, char *buffer) { 112 | NEUJSON_ASSERT(buffer != nullptr); 113 | auto u = static_cast(value); 114 | if (value < 0) { 115 | *buffer++ = '-'; 116 | u = ~u + 1; 117 | } 118 | 119 | return u32toa(u, buffer); 120 | } 121 | 122 | inline char *u64toa(uint64_t value, char *buffer) { 123 | NEUJSON_ASSERT(buffer != nullptr); 124 | constexpr uint64_t kTen8 = 100000000; 125 | constexpr uint64_t kTen9 = kTen8 * 10; 126 | constexpr uint64_t kTen10 = kTen8 * 100; 127 | constexpr uint64_t kTen11 = kTen8 * 1000; 128 | constexpr uint64_t kTen12 = kTen8 * 10000; 129 | constexpr uint64_t kTen13 = kTen8 * 100000; 130 | constexpr uint64_t kTen14 = kTen8 * 1000000; 131 | constexpr uint64_t kTen15 = kTen8 * 10000000; 132 | constexpr uint64_t kTen16 = kTen8 * kTen8; 133 | 134 | if (value < kTen8) { 135 | auto v = static_cast(value); 136 | if (v < 10000) { 137 | const uint32_t d1 = (v / 100) << 1; 138 | const uint32_t d2 = (v % 100) << 1; 139 | 140 | if (v >= 1000) { 141 | *buffer++ = kDigitsLut[d1]; 142 | } 143 | if (v >= 100) { 144 | *buffer++ = kDigitsLut[d1 + 1]; 145 | } 146 | if (v >= 10) { 147 | *buffer++ = kDigitsLut[d2]; 148 | } 149 | *buffer++ = kDigitsLut[d2 + 1]; 150 | } else { 151 | // value = bbbbcccc 152 | const uint32_t b = v / 10000; 153 | const uint32_t c = v % 10000; 154 | 155 | const uint32_t d1 = (b / 100) << 1; 156 | const uint32_t d2 = (b % 100) << 1; 157 | 158 | const uint32_t d3 = (c / 100) << 1; 159 | const uint32_t d4 = (c % 100) << 1; 160 | 161 | if (value >= 10000000) { 162 | *buffer++ = kDigitsLut[d1]; 163 | } 164 | if (value >= 1000000) { 165 | *buffer++ = kDigitsLut[d1 + 1]; 166 | } 167 | if (value >= 100000) { 168 | *buffer++ = kDigitsLut[d2]; 169 | } 170 | *buffer++ = kDigitsLut[d2 + 1]; 171 | 172 | *buffer++ = kDigitsLut[d3]; 173 | *buffer++ = kDigitsLut[d3 + 1]; 174 | *buffer++ = kDigitsLut[d4]; 175 | *buffer++ = kDigitsLut[d4 + 1]; 176 | } 177 | } else if (value < kTen16) { 178 | const auto v0 = static_cast(value / kTen8); 179 | const auto v1 = static_cast(value % kTen8); 180 | 181 | const uint32_t b0 = v0 / 10000; 182 | const uint32_t c0 = v0 % 10000; 183 | 184 | const uint32_t d1 = (b0 / 100) << 1; 185 | const uint32_t d2 = (b0 % 100) << 1; 186 | 187 | const uint32_t d3 = (c0 / 100) << 1; 188 | const uint32_t d4 = (c0 % 100) << 1; 189 | 190 | const uint32_t b1 = v1 / 10000; 191 | const uint32_t c1 = v1 % 10000; 192 | 193 | const uint32_t d5 = (b1 / 100) << 1; 194 | const uint32_t d6 = (b1 % 100) << 1; 195 | 196 | const uint32_t d7 = (c1 / 100) << 1; 197 | const uint32_t d8 = (c1 % 100) << 1; 198 | 199 | if (value >= kTen15) { 200 | *buffer++ = kDigitsLut[d1]; 201 | } 202 | if (value >= kTen14) { 203 | *buffer++ = kDigitsLut[d1 + 1]; 204 | } 205 | if (value >= kTen13) { 206 | *buffer++ = kDigitsLut[d2]; 207 | } 208 | if (value >= kTen12) { 209 | *buffer++ = kDigitsLut[d2 + 1]; 210 | } 211 | if (value >= kTen11) { 212 | *buffer++ = kDigitsLut[d3]; 213 | } 214 | if (value >= kTen10) { 215 | *buffer++ = kDigitsLut[d3 + 1]; 216 | } 217 | if (value >= kTen9) { 218 | *buffer++ = kDigitsLut[d4]; 219 | } 220 | 221 | *buffer++ = kDigitsLut[d4 + 1]; 222 | *buffer++ = kDigitsLut[d5]; 223 | *buffer++ = kDigitsLut[d5 + 1]; 224 | *buffer++ = kDigitsLut[d6]; 225 | *buffer++ = kDigitsLut[d6 + 1]; 226 | *buffer++ = kDigitsLut[d7]; 227 | *buffer++ = kDigitsLut[d7 + 1]; 228 | *buffer++ = kDigitsLut[d8]; 229 | *buffer++ = kDigitsLut[d8 + 1]; 230 | } else { 231 | const auto a = static_cast(value / kTen16); // 1 to 1844 232 | value %= kTen16; 233 | 234 | if (a < 10) { 235 | *buffer++ = static_cast('0' + static_cast(a)); 236 | } else if (a < 100) { 237 | const uint32_t i = a << 1; 238 | *buffer++ = kDigitsLut[i]; 239 | *buffer++ = kDigitsLut[i + 1]; 240 | } else if (a < 1000) { 241 | *buffer++ = static_cast('0' + static_cast(a / 100)); 242 | 243 | const uint32_t i = (a % 100) << 1; 244 | *buffer++ = kDigitsLut[i]; 245 | *buffer++ = kDigitsLut[i + 1]; 246 | } else { 247 | const uint32_t i = (a / 100) << 1; 248 | const uint32_t j = (a % 100) << 1; 249 | *buffer++ = kDigitsLut[i]; 250 | *buffer++ = kDigitsLut[i + 1]; 251 | *buffer++ = kDigitsLut[j]; 252 | *buffer++ = kDigitsLut[j + 1]; 253 | } 254 | 255 | const auto v0 = static_cast(value / kTen8); 256 | const auto v1 = static_cast(value % kTen8); 257 | 258 | const uint32_t b0 = v0 / 10000; 259 | const uint32_t c0 = v0 % 10000; 260 | 261 | const uint32_t d1 = (b0 / 100) << 1; 262 | const uint32_t d2 = (b0 % 100) << 1; 263 | 264 | const uint32_t d3 = (c0 / 100) << 1; 265 | const uint32_t d4 = (c0 % 100) << 1; 266 | 267 | const uint32_t b1 = v1 / 10000; 268 | const uint32_t c1 = v1 % 10000; 269 | 270 | const uint32_t d5 = (b1 / 100) << 1; 271 | const uint32_t d6 = (b1 % 100) << 1; 272 | 273 | const uint32_t d7 = (c1 / 100) << 1; 274 | const uint32_t d8 = (c1 % 100) << 1; 275 | 276 | *buffer++ = kDigitsLut[d1]; 277 | *buffer++ = kDigitsLut[d1 + 1]; 278 | *buffer++ = kDigitsLut[d2]; 279 | *buffer++ = kDigitsLut[d2 + 1]; 280 | *buffer++ = kDigitsLut[d3]; 281 | *buffer++ = kDigitsLut[d3 + 1]; 282 | *buffer++ = kDigitsLut[d4]; 283 | *buffer++ = kDigitsLut[d4 + 1]; 284 | *buffer++ = kDigitsLut[d5]; 285 | *buffer++ = kDigitsLut[d5 + 1]; 286 | *buffer++ = kDigitsLut[d6]; 287 | *buffer++ = kDigitsLut[d6 + 1]; 288 | *buffer++ = kDigitsLut[d7]; 289 | *buffer++ = kDigitsLut[d7 + 1]; 290 | *buffer++ = kDigitsLut[d8]; 291 | *buffer++ = kDigitsLut[d8 + 1]; 292 | } 293 | 294 | return buffer; 295 | } 296 | 297 | inline char *i64toa(int64_t value, char *buffer) { 298 | NEUJSON_ASSERT(buffer != nullptr); 299 | auto u = static_cast(value); 300 | if (value < 0) { 301 | *buffer++ = '-'; 302 | u = ~u + 1; 303 | } 304 | 305 | return u64toa(u, buffer); 306 | } 307 | 308 | } // namespace neujson::internal 309 | 310 | #if defined(_MSC_VER) || defined(__clang__) 311 | NEUJSON_DIAG_POP 312 | #endif 313 | 314 | #endif // NEUJSON_NEUJSON_INTERNAL_ITOA_H_ 315 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |

5 | Contributors 6 | Forks 7 | Stargazers 8 | Issues 9 | License 10 | Deploy 11 |

12 | 13 | 14 | 15 |
16 |
17 | 20 | 21 |

neujson

22 | 23 |

24 | JSON parser/generator in C++20 25 |
26 | Explore the docs » 27 |
28 |
29 | View Demo 30 | · 31 | Report Bug 32 | · 33 | Request Feature 34 |

35 |
36 | 37 | Translations: [English](README.md) | [简体中文](README_zh.md) 38 | 39 | ## 简介 40 | 41 | neujson 是一个 C++ 的 JSON 解析器及生成器。它同时支持 SAX 和 DOM 风格的 API。 42 | 43 | ## 安装 44 | 45 | ### 克隆 neujson 的源代码 46 | 47 | ```bash 48 | git clone https://github.com/hominsu/neujson.git 49 | ``` 50 | 51 | ### 编译和安装 52 | 53 | 下面的命令将在本地构建和安装 neujson: 54 | 55 | ```bash 56 | cd neujson 57 | mkdir -p cmake/build 58 | pushd cmake/build 59 | cmake ../.. 60 | make -j 61 | make install 62 | popd 63 | ``` 64 | 65 | 如果你没有安装目录的权限,请使用 `sudo`: 66 | 67 | ```bash 68 | sudo make install 69 | ``` 70 | 71 | #### 同时构建示例程序 72 | 73 | 示例的源代码已经包含在 `neujson` 的源代码中,你已经在上一步中克隆了源代码,只需要额外添加一个的 `cmake` 选项: 74 | 75 | ```bash 76 | cmake -DNEUJSON_BUILD_EXAMPLES=ON ../.. 77 | ``` 78 | 79 | #### 同时构建基准测试 80 | 81 | 构建基准测试,需要使用 `git submodule` 拉取第三方依赖,并检出到相应的分支,然后添加基准测试的 `cmake` 选项: 82 | 83 | ```bash 84 | git submodule update --init --recursive 85 | pushd cmake/build 86 | cmake -DNEUJSON_BUILD_BENCHMARK=ON ../.. 87 | ... 88 | ``` 89 | 90 | ## 卸载 91 | 92 | 使用一下命令可以卸载 neujson: 93 | 94 | ```bash 95 | pushd cmake/build 96 | make uninstall 97 | popd 98 | ``` 99 | 100 | 如果你没有安装目录的权限,请使用 `sudo`: 101 | 102 | ```bash 103 | sudo make uninstall 104 | ``` 105 | 106 | ## 用法一目了然 107 | 108 | 此简单例子解析一个 JSON 字符串至一个 document (DOM),对 DOM 作出简单修改,最终把 DOM 转换(stringify)至 JSON 字符串。 109 | 110 | ```c++ 111 | #include 112 | 113 | #include "neujson/document.h" 114 | #include "neujson/string_write_stream.h" 115 | #include "neujson/writer.h" 116 | #include "sample.h" 117 | 118 | int main() { 119 | // 1. Parse a JSON string into DOM. 120 | neujson::Document doc; 121 | if (const auto err = doc.Parse(kSample[0]); err != neujson::error::OK) { 122 | puts(neujson::ParseErrorStr(err)); 123 | return EXIT_FAILURE; 124 | } 125 | 126 | // 2. Modify it by DOM. 127 | auto &s = doc[0]["Longitude"]; 128 | s.SetDouble(s.GetDouble() + 100.0); 129 | 130 | // 3. Stringify the DOM 131 | neujson::StringWriteStream os; 132 | neujson::Writer writer(os); 133 | doc.WriteTo(writer); 134 | 135 | // Output 136 | fprintf(stdout, "%.*s", static_cast(os.get().length()), os.get().data()); 137 | return 0; 138 | } 139 | ``` 140 | 141 | 输出: 142 | 143 | ```json 144 | [{"precision":"zip","Latitude":37.766800000000003,"Longitude":-22.395899999999997,"Address":"","City":"SAN FRANCISCO","State":"CA","Zip":"94107","Country":"US"},{"precision":"zip","Latitude":37.371991000000001,"Longitude":-122.02602,"Address":"","City":"SUNNYVALE","State":"CA","Zip":"94085","Country":"US"}] 145 | ``` 146 | 147 | ## 基准性能测试 148 | 149 | 基准性能测试基于 google benchmark 进行 150 | 151 | ### JSON data 152 | 153 | | JSON file | Size | Description | 154 | | :----------------------------------------------------------: | :----: | :----------------------------------------------------------: | 155 | | `canada.json`[source](https://github.com/mloskot/json_benchmark/blob/master/data/canada.json) | 2199KB | 加拿大边境轮廓,格式为: [GeoJSON](http://geojson.org/);包含大量的实数。 | 156 | | `citm_catalog.json`[source](https://github.com/RichardHightower/json-parsers-benchmark/blob/master/data/citm_catalog.json) | 1737KB | 一个带有缩进的大型基准测试文件,用于多个Java JSON解析器基准测试。 | 157 | 158 | ### Sample Results 159 | 160 | 下面是使用 MacBook Air (M1, 2020) 和 Apple clang 13.1.6 得到的结果 161 | 162 | ```bash 163 | Run on (8 X 24.1212 MHz CPU s) 164 | CPU Caches: 165 | L1 Data 64 KiB (x8) 166 | L1 Instruction 128 KiB (x8) 167 | L2 Unified 4096 KiB (x2) 168 | Load Average: 2.04, 1.78, 1.74 169 | -------------------------------------------------------------------------------------------------------- 170 | Benchmark Time CPU Iterations 171 | -------------------------------------------------------------------------------------------------------- 172 | BM_neujson_read_parse/citm_catalog.json 7.04 ms 7.04 ms 72 173 | BM_nlohmann_read_parse/citm_catalog.json 10.6 ms 10.6 ms 66 174 | BM_rapidjson_read_parse/citm_catalog.json 2.96 ms 2.96 ms 236 175 | BM_neujson_read_parse_write_file/citm_catalog.json 7.92 ms 7.92 ms 88 176 | BM_nlohmann_read_parse_write_file/citm_catalog.json 12.5 ms 12.5 ms 56 177 | BM_rapidjson_read_parse_write_file/citm_catalog.json 4.10 ms 4.10 ms 170 178 | BM_neujson_read_parse_write_string/citm_catalog.json 8.03 ms 8.03 ms 87 179 | BM_nlohmann_read_parse_write_string/citm_catalog.json 12.7 ms 12.7 ms 55 180 | BM_rapidjson_read_parse_write_string/citm_catalog.json 3.90 ms 3.90 ms 180 181 | BM_neujson_read_parse_pretty_write_file/citm_catalog.json 8.84 ms 8.84 ms 79 182 | BM_nlohmann_read_parse_pretty_write_file/citm_catalog.json 13.3 ms 13.3 ms 53 183 | BM_rapidjson_read_parse_pretty_write_file/citm_catalog.json 4.56 ms 4.55 ms 154 184 | BM_neujson_read_parse_pretty_write_string/citm_catalog.json 9.44 ms 9.44 ms 72 185 | BM_nlohmann_read_parse_pretty_write_string/citm_catalog.json 14.2 ms 14.2 ms 50 186 | BM_rapidjson_read_parse_pretty_write_string/citm_catalog.json 4.19 ms 4.19 ms 167 187 | BM_neujson_read_parse/canada.json 31.6 ms 31.6 ms 22 188 | BM_nlohmann_read_parse/canada.json 39.1 ms 39.1 ms 18 189 | BM_rapidjson_read_parse/canada.json 3.38 ms 3.38 ms 207 190 | BM_neujson_read_parse_write_file/canada.json 68.2 ms 68.2 ms 10 191 | BM_nlohmann_read_parse_write_file/canada.json 47.6 ms 47.6 ms 15 192 | BM_rapidjson_read_parse_write_file/canada.json 12.5 ms 12.5 ms 55 193 | BM_neujson_read_parse_write_string/canada.json 69.4 ms 69.4 ms 10 194 | BM_nlohmann_read_parse_write_string/canada.json 48.5 ms 48.5 ms 14 195 | BM_rapidjson_read_parse_write_string/canada.json 10.7 ms 10.7 ms 63 196 | BM_neujson_read_parse_pretty_write_file/canada.json 72.3 ms 72.3 ms 10 197 | BM_nlohmann_read_parse_pretty_write_file/canada.json 51.2 ms 51.2 ms 14 198 | BM_rapidjson_read_parse_pretty_write_file/canada.json 13.7 ms 13.7 ms 51 199 | BM_neujson_read_parse_pretty_write_string/canada.json 75.9 ms 75.9 ms 9 200 | BM_nlohmann_read_parse_pretty_write_string/canada.json 55.0 ms 55.0 ms 13 201 | BM_rapidjson_read_parse_pretty_write_string/canada.json 12.4 ms 12.4 ms 56 202 | ``` 203 | 204 | 下面是使用 i5-9500 以及 CentOS-8-Stream 和 gcc 8.5.0 (Red Hat 8.5.0-10) 得到的结果 205 | 206 | ```bash 207 | Run on (6 X 4166.48 MHz CPU s) 208 | CPU Caches: 209 | L1 Data 32 KiB (x6) 210 | L1 Instruction 32 KiB (x6) 211 | L2 Unified 256 KiB (x6) 212 | L3 Unified 9216 KiB (x1) 213 | Load Average: 0.80, 0.52, 0.45 214 | -------------------------------------------------------------------------------------------------------- 215 | Benchmark Time CPU Iterations 216 | -------------------------------------------------------------------------------------------------------- 217 | BM_neujson_read_parse/citm_catalog.json 8.59 ms 8.58 ms 74 218 | BM_nlohmann_read_parse/citm_catalog.json 14.7 ms 14.6 ms 48 219 | BM_rapidjson_read_parse/citm_catalog.json 2.38 ms 2.37 ms 293 220 | BM_neujson_read_parse_write_file/citm_catalog.json 10.1 ms 10.1 ms 70 221 | BM_nlohmann_read_parse_write_file/citm_catalog.json 17.5 ms 17.5 ms 40 222 | BM_rapidjson_read_parse_write_file/citm_catalog.json 3.39 ms 3.39 ms 206 223 | BM_neujson_read_parse_write_string/citm_catalog.json 10.9 ms 10.9 ms 65 224 | BM_nlohmann_read_parse_write_string/citm_catalog.json 17.5 ms 17.5 ms 40 225 | BM_rapidjson_read_parse_write_string/citm_catalog.json 3.20 ms 3.19 ms 218 226 | BM_neujson_read_parse_pretty_write_file/citm_catalog.json 11.3 ms 11.3 ms 60 227 | BM_nlohmann_read_parse_pretty_write_file/citm_catalog.json 18.8 ms 18.7 ms 38 228 | BM_rapidjson_read_parse_pretty_write_file/citm_catalog.json 3.70 ms 3.69 ms 189 229 | BM_neujson_read_parse_pretty_write_string/citm_catalog.json 14.5 ms 14.5 ms 49 230 | BM_nlohmann_read_parse_pretty_write_string/citm_catalog.json 18.5 ms 18.5 ms 38 231 | BM_rapidjson_read_parse_pretty_write_string/citm_catalog.json 3.57 ms 3.57 ms 196 232 | BM_neujson_read_parse/canada.json 27.8 ms 27.7 ms 25 233 | BM_nlohmann_read_parse/canada.json 41.8 ms 41.8 ms 17 234 | BM_rapidjson_read_parse/canada.json 4.59 ms 4.58 ms 152 235 | BM_neujson_read_parse_write_file/canada.json 100 ms 100 ms 7 236 | BM_nlohmann_read_parse_write_file/canada.json 53.5 ms 53.4 ms 13 237 | BM_rapidjson_read_parse_write_file/canada.json 13.6 ms 13.6 ms 51 238 | BM_neujson_read_parse_write_string/canada.json 106 ms 106 ms 7 239 | BM_nlohmann_read_parse_write_string/canada.json 53.3 ms 53.3 ms 13 240 | BM_rapidjson_read_parse_write_string/canada.json 11.9 ms 11.9 ms 58 241 | BM_neujson_read_parse_pretty_write_file/canada.json 106 ms 106 ms 7 242 | BM_nlohmann_read_parse_pretty_write_file/canada.json 58.6 ms 58.6 ms 12 243 | BM_rapidjson_read_parse_pretty_write_file/canada.json 14.4 ms 14.4 ms 49 244 | BM_neujson_read_parse_pretty_write_string/canada.json 119 ms 119 ms 6 245 | BM_nlohmann_read_parse_pretty_write_string/canada.json 64.9 ms 64.8 ms 11 246 | BM_rapidjson_read_parse_pretty_write_string/canada.json 12.8 ms 12.8 ms 54 247 | ``` 248 | 249 | ## 参考 250 | 251 | [RapidJSON](https://github.com/Tencent/rapidjson): A fast JSON parser/generator for C++ with both SAX/DOM style API 252 | 253 | ## 鸣谢 254 | 255 | ![JetBrains Logo (Main) logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg) 256 | 257 | 特别感谢 [JetBrains](https://www.jetbrains.com/) 为本开源项目提供免费的 `All Products Pack` 授权 258 | 259 | ## Contributors ✨ 260 | 261 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 |

Homing So

💻 📖 🎨 💡 🚇 📦 ⚠️
271 | 272 | 273 | 274 | 275 | 276 | 277 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 278 | -------------------------------------------------------------------------------- /include/neujson/value.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/5. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_VALUE_H_ 6 | #define NEUJSON_NEUJSON_VALUE_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "internal/ieee754.h" 21 | #include "neujson.h" 22 | 23 | namespace neujson { 24 | 25 | namespace required::handler { 26 | namespace details { 27 | 28 | template 29 | concept HasNull = requires(Handler handler) { 30 | { handler.Null() } -> std::same_as; 31 | }; 32 | 33 | template 34 | concept HasBool = requires(Handler handler, bool b) { 35 | { handler.Bool(b) } -> std::same_as; 36 | }; 37 | 38 | template 39 | concept HasInt32 = requires(Handler handler, int32_t i32) { 40 | { handler.Int32(i32) } -> std::same_as; 41 | }; 42 | 43 | template 44 | concept HasInt64 = requires(Handler handler, int64_t i64) { 45 | { handler.Int64(i64) } -> std::same_as; 46 | }; 47 | 48 | template 49 | concept HasDouble = requires(Handler handler, internal::Double d) { 50 | { handler.Double(d) } -> std::same_as; 51 | }; 52 | 53 | template 54 | concept HasString = requires(Handler handler, std::string_view sv) { 55 | { handler.String(sv) } -> std::same_as; 56 | }; 57 | 58 | template 59 | concept HasKey = requires(Handler handler, std::string_view sv) { 60 | { handler.Key(sv) } -> std::same_as; 61 | }; 62 | 63 | template 64 | concept HasStartObject = requires(Handler handler) { 65 | { handler.StartObject() } -> std::same_as; 66 | }; 67 | 68 | template 69 | concept HasEndObject = requires(Handler handler) { 70 | { handler.EndObject() } -> std::same_as; 71 | }; 72 | 73 | template 74 | concept HasStartArray = requires(Handler handler) { 75 | { handler.StartArray() } -> std::same_as; 76 | }; 77 | 78 | template 79 | concept HasEndArray = requires(Handler handler) { 80 | { handler.EndArray() } -> std::same_as; 81 | }; 82 | 83 | } // namespace details 84 | 85 | template 86 | concept HasAllRequiredFunctions = 87 | details::HasNull && details::HasBool && details::HasInt32 && 88 | details::HasInt64 && details::HasDouble && details::HasString && 89 | details::HasKey && details::HasStartObject && 90 | details::HasEndObject && details::HasStartArray && 91 | details::HasEndArray; 92 | 93 | } // namespace required::handler 94 | 95 | #undef SUFFIX 96 | #define VALUE(NEU) \ 97 | NEU(NULL, std::monostate) SUFFIX NEU(BOOL, bool) SUFFIX NEU(INT32, int32_t) \ 98 | SUFFIX NEU(INT64, int64_t) \ 99 | SUFFIX NEU(DOUBLE, internal::Double) \ 100 | SUFFIX NEU(STRING, std::shared_ptr>) \ 101 | SUFFIX NEU(ARRAY, std::shared_ptr>) \ 102 | SUFFIX NEU(OBJECT, std::shared_ptr>) // 103 | 104 | enum Type { 105 | #define VALUE_NAME(_name, _type) NEU_##_name 106 | #define SUFFIX , 107 | VALUE(VALUE_NAME) 108 | #undef SUFFIX 109 | #undef VALUE_NAME 110 | }; 111 | 112 | class Value; 113 | struct Member; 114 | 115 | using Data = std::variant< 116 | #define VALUE_TYPE(_name, _type) _type 117 | #define SUFFIX , 118 | VALUE(VALUE_TYPE) 119 | #undef SUFFIX 120 | #undef VALUE_TYPE 121 | >; 122 | 123 | class Document; 124 | 125 | class Value { 126 | public: 127 | using MemberIterator = std::vector::iterator; 128 | using ConstMemberIterator = std::vector::const_iterator; 129 | 130 | #define VALUE_TYPE(_name, _type) using NEU_##_name##_TYPE = _type; 131 | #define SUFFIX 132 | VALUE(VALUE_TYPE) 133 | #undef SUFFIX 134 | #undef VALUE_TYPE 135 | 136 | private: 137 | friend class Document; 138 | 139 | using String = std::vector; 140 | using Array = std::vector; 141 | using Object = std::vector; 142 | 143 | Type type_; 144 | Data data_; 145 | 146 | public: 147 | explicit Value(Type type = NEU_NULL); 148 | explicit Value(bool b) : type_(NEU_BOOL), data_(b) {}; 149 | explicit Value(int32_t i32) : type_(NEU_INT32), data_(i32) {}; 150 | explicit Value(int64_t i64) : type_(NEU_INT64), data_(i64) {}; 151 | explicit Value(double d) : type_(NEU_DOUBLE), data_(internal::Double(d)) {}; 152 | explicit Value(internal::Double d) : type_(NEU_DOUBLE), data_(d) {}; 153 | explicit Value(const char *s) 154 | : type_(NEU_STRING), data_(std::make_shared(s, s + strlen(s))) {}; 155 | explicit Value(std::string_view s) 156 | : type_(NEU_STRING), 157 | data_(std::make_shared(s.begin(), s.end())) {}; 158 | Value(const char *s, const std::size_t len) 159 | : Value(std::string_view(s, len)) {}; 160 | Value(const Value &val) = default; 161 | Value(Value &&val) noexcept 162 | : type_(val.type_), data_(std::move(val.data_)) {}; 163 | ~Value() = default; 164 | 165 | [[nodiscard]] Type GetType() const { return type_; }; 166 | [[nodiscard]] std::size_t GetSize() const; 167 | 168 | [[nodiscard]] bool IsNull() const { return type_ == NEU_NULL; } 169 | [[nodiscard]] bool IsBool() const { return type_ == NEU_BOOL; } 170 | [[nodiscard]] bool IsInt32() const { return type_ == NEU_INT32; } 171 | [[nodiscard]] bool IsInt64() const { 172 | return type_ == NEU_INT64 || type_ == NEU_INT32; 173 | } 174 | [[nodiscard]] bool IsDouble() const { return type_ == NEU_DOUBLE; } 175 | [[nodiscard]] bool IsString() const { return type_ == NEU_STRING; } 176 | [[nodiscard]] bool IsArray() const { return type_ == NEU_ARRAY; } 177 | [[nodiscard]] bool IsObject() const { return type_ == NEU_OBJECT; } 178 | 179 | // getter 180 | //@formatter:off 181 | [[nodiscard]] bool GetBool() const { 182 | NEUJSON_ASSERT(type_ == NEU_BOOL); 183 | return std::get(data_); 184 | } 185 | [[nodiscard]] int32_t GetInt32() const { 186 | NEUJSON_ASSERT(type_ == NEU_INT32); 187 | return std::get(data_); 188 | } 189 | 190 | [[nodiscard]] int64_t GetInt64() const { 191 | NEUJSON_ASSERT(type_ == NEU_INT64 || type_ == NEU_INT32); 192 | return type_ == NEU_INT64 ? std::get(data_) 193 | : std::get(data_); 194 | } 195 | 196 | [[nodiscard]] double GetDouble() const { 197 | NEUJSON_ASSERT(type_ == NEU_DOUBLE); 198 | return std::get(data_).Value(); 199 | } 200 | 201 | [[nodiscard]] std::string_view GetStringView() const { 202 | NEUJSON_ASSERT(type_ == NEU_STRING); 203 | auto s_ptr = std::get(data_); 204 | return {s_ptr->data(), s_ptr->size()}; 205 | } 206 | 207 | [[nodiscard]] std::string GetString() const { 208 | return std::string(GetStringView()); 209 | } 210 | [[nodiscard]] const auto &GetArray() const { 211 | NEUJSON_ASSERT(type_ == NEU_ARRAY); 212 | return std::get(data_); 213 | } 214 | [[nodiscard]] const auto &GetObject() const { 215 | NEUJSON_ASSERT(type_ == NEU_OBJECT); 216 | return std::get(data_); 217 | } 218 | //@formatter:on 219 | 220 | // setter 221 | Value &SetNull() { 222 | this->~Value(); 223 | return *new (this) Value(NEU_NULL); 224 | } 225 | 226 | Value &SetBool(bool _b) { 227 | this->~Value(); 228 | return *new (this) Value(_b); 229 | } 230 | 231 | Value &SetInt32(int32_t _i32) { 232 | this->~Value(); 233 | return *new (this) Value(_i32); 234 | } 235 | 236 | Value &SetInt64(int64_t _i64) { 237 | this->~Value(); 238 | return *new (this) Value(_i64); 239 | } 240 | 241 | Value &SetDouble(double _d) { 242 | this->~Value(); 243 | return *new (this) Value(_d); 244 | } 245 | 246 | Value &SetString(std::string_view _s) { 247 | this->~Value(); 248 | return *new (this) Value(_s); 249 | } 250 | 251 | Value &SetArray() { 252 | this->~Value(); 253 | return *new (this) Value(NEU_ARRAY); 254 | } 255 | 256 | Value &SetObject() { 257 | this->~Value(); 258 | return *new (this) Value(NEU_OBJECT); 259 | } 260 | 261 | Value &operator=(const Value &val); 262 | Value &operator=(Value &&val) noexcept; 263 | 264 | Value &operator[](size_t index); 265 | const Value &operator[](size_t index) const; 266 | Value &operator[](std::string_view key); 267 | const Value &operator[](std::string_view key) const; 268 | 269 | template Value &AddValue(T &&value); 270 | 271 | MemberIterator MemberBegin(); 272 | MemberIterator MemberEnd(); 273 | MemberIterator FindMember(std::string_view key); 274 | 275 | [[nodiscard]] ConstMemberIterator MemberBegin() const; 276 | [[nodiscard]] ConstMemberIterator MemberEnd() const; 277 | [[nodiscard]] ConstMemberIterator FindMember(std::string_view key) const; 278 | 279 | template Value &AddMember(const char *key, T &&value); 280 | Value &AddMember(Value &&key, Value &&value); 281 | 282 | template 283 | bool WriteTo(Handler &handler) const; 284 | }; 285 | 286 | #undef VALUE 287 | 288 | struct Member { 289 | Member(Value &&key, Value &&value) 290 | : key_(std::move(key)), value_(std::move(value)) {} 291 | Member(std::string_view key, Value &&value) 292 | : key_(key), value_(std::move(value)) {} 293 | 294 | Value key_; 295 | Value value_; 296 | }; 297 | 298 | inline Value::Value(Type type) : type_(type) { 299 | switch (type_) { 300 | case NEU_NULL: 301 | case NEU_BOOL: 302 | case NEU_INT32: 303 | case NEU_INT64: 304 | case NEU_DOUBLE: 305 | break; 306 | case NEU_STRING: 307 | data_ = std::make_shared(); 308 | break; 309 | case NEU_ARRAY: 310 | data_ = std::make_shared(); 311 | break; 312 | case NEU_OBJECT: 313 | data_ = std::make_shared(); 314 | break; 315 | default: 316 | NEUJSON_ASSERT(false && "bad value GetType"); 317 | } 318 | } 319 | 320 | inline std::size_t Value::GetSize() const { 321 | switch (type_) { 322 | case NEU_ARRAY: 323 | return std::get(data_)->size(); 324 | case NEU_OBJECT: 325 | return std::get(data_)->size(); 326 | default: 327 | return 1; 328 | } 329 | } 330 | 331 | inline Value &Value::operator=(const Value &val) { 332 | NEUJSON_ASSERT(this != &val); 333 | type_ = val.type_; 334 | data_ = val.data_; 335 | return *this; 336 | } 337 | 338 | inline Value &Value::operator=(Value &&val) noexcept { 339 | NEUJSON_ASSERT(this != &val); 340 | type_ = val.type_; 341 | data_ = std::move(val.data_); 342 | val.type_ = NEU_NULL; 343 | return *this; 344 | } 345 | 346 | inline Value &Value::operator[](const std::size_t index) { 347 | NEUJSON_ASSERT(type_ == NEU_ARRAY); 348 | return std::get(data_)->at(index); 349 | } 350 | 351 | inline const Value &Value::operator[](const std::size_t index) const { 352 | NEUJSON_ASSERT(type_ == NEU_ARRAY); 353 | return std::get(data_)->at(index); 354 | } 355 | 356 | inline Value &Value::operator[](const std::string_view key) { 357 | NEUJSON_ASSERT(type_ == NEU_OBJECT); 358 | auto it = FindMember(key); 359 | if (it != std::get(data_)->end()) { 360 | return it->value_; 361 | } 362 | 363 | NEUJSON_ASSERT(false && "value no found"); 364 | static Value fake(NEU_NULL); 365 | return fake; 366 | } 367 | 368 | inline const Value &Value::operator[](const std::string_view key) const { 369 | NEUJSON_ASSERT(type_ == NEU_OBJECT); 370 | return const_cast(*this)[key]; 371 | } 372 | 373 | template Value &Value::AddValue(T &&value) { 374 | NEUJSON_ASSERT(type_ == NEU_ARRAY); 375 | auto a_ptr = std::get(data_); 376 | a_ptr->emplace_back(std::forward(value)); 377 | return a_ptr->back(); 378 | } 379 | 380 | inline Value::MemberIterator Value::MemberBegin() { 381 | NEUJSON_ASSERT(type_ == NEU_OBJECT); 382 | return std::get(data_)->begin(); 383 | } 384 | 385 | inline Value::MemberIterator Value::MemberEnd() { 386 | NEUJSON_ASSERT(type_ == NEU_OBJECT); 387 | return std::get(data_)->end(); 388 | } 389 | 390 | inline Value::MemberIterator Value::FindMember(const std::string_view key) { 391 | NEUJSON_ASSERT(type_ == NEU_OBJECT); 392 | return std::ranges::find_if(*std::get(data_), 393 | [key](const Member &_m) -> bool { 394 | return _m.key_.GetStringView() == key; 395 | }); 396 | } 397 | 398 | inline Value::ConstMemberIterator Value::MemberBegin() const { 399 | NEUJSON_ASSERT(type_ == NEU_OBJECT); 400 | return const_cast(*this).MemberBegin(); 401 | } 402 | 403 | inline Value::ConstMemberIterator Value::MemberEnd() const { 404 | NEUJSON_ASSERT(type_ == NEU_OBJECT); 405 | return const_cast(*this).MemberEnd(); 406 | } 407 | 408 | inline Value::ConstMemberIterator 409 | Value::FindMember(const std::string_view key) const { 410 | NEUJSON_ASSERT(type_ == NEU_OBJECT); 411 | return const_cast(*this).FindMember(key); 412 | } 413 | 414 | template Value &Value::AddMember(const char *key, T &&value) { 415 | return AddMember(Value(key), Value(std::forward(value))); 416 | } 417 | 418 | inline Value &Value::AddMember(Value &&key, Value &&value) { 419 | NEUJSON_ASSERT(type_ == NEU_OBJECT); 420 | NEUJSON_ASSERT(key.type_ == NEU_STRING); 421 | NEUJSON_ASSERT(FindMember(key.GetStringView()) == MemberEnd()); 422 | auto o_ptr = std::get(data_); 423 | o_ptr->emplace_back(std::move(key), std::move(value)); 424 | return o_ptr->back().value_; 425 | } 426 | 427 | #define CALL_HANDLER(_expr) \ 428 | do { \ 429 | if (!(_expr)) { \ 430 | return false; \ 431 | } \ 432 | } while (false) 433 | 434 | template 435 | bool Value::WriteTo(Handler &handler) const { 436 | switch (type_) { 437 | case NEU_NULL: 438 | CALL_HANDLER(handler.Null()); 439 | break; 440 | case NEU_BOOL: 441 | CALL_HANDLER(handler.Bool(std::get(data_))); 442 | break; 443 | case NEU_INT32: 444 | CALL_HANDLER(handler.Int32(std::get(data_))); 445 | break; 446 | case NEU_INT64: 447 | CALL_HANDLER(handler.Int64(std::get(data_))); 448 | break; 449 | case NEU_DOUBLE: 450 | CALL_HANDLER(handler.Double(std::get(data_))); 451 | break; 452 | case NEU_STRING: 453 | CALL_HANDLER(handler.String(GetStringView())); 454 | break; 455 | case NEU_ARRAY: 456 | CALL_HANDLER(handler.StartArray()); 457 | for (auto &val : *GetArray()) { 458 | CALL_HANDLER(val.WriteTo(handler)); 459 | } 460 | CALL_HANDLER(handler.EndArray()); 461 | break; 462 | case NEU_OBJECT: 463 | CALL_HANDLER(handler.StartObject()); 464 | for (auto &mem : *GetObject()) { 465 | handler.Key(mem.key_.GetStringView()); 466 | CALL_HANDLER(mem.value_.WriteTo(handler)); 467 | } 468 | CALL_HANDLER(handler.EndObject()); 469 | break; 470 | default: 471 | NEUJSON_ASSERT(false && "bad type"); 472 | } 473 | return true; 474 | } 475 | 476 | #undef CALL_HANDLER 477 | 478 | } // namespace neujson 479 | 480 | #endif // NEUJSON_NEUJSON_VALUE_H_ 481 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |

5 | Contributors 6 | Forks 7 | Stargazers 8 | Issues 9 | License 10 | Deploy 11 |

12 | 13 | 14 | 15 |
16 |
17 | 20 | 21 |

neujson

22 | 23 |

24 | A header-only JSON parser/generator in C++20. It supports both SAX and DOM style API. 25 |
26 | Explore the docs » 27 |
28 |
29 | View Demo 30 | · 31 | Report Bug 32 | · 33 | Request Feature 34 |

35 |
36 | 37 | ##### Translate to: [简体中文](README_zh.md) 38 | 39 | ## Features 40 | 41 | - **Simple, Fast**. neujson contains only header files, does not rely on Boost, and takes full advantage of the template's features. You can "assemble" neujson's Handler at will. 42 | - **API simplicity**. neujson supports both DOM and SAX-style API, where SAX allows custom handlers for streaming processing. 43 | - **Unicode friendly**. neujson fully supports UTF-8 codec and can parse utF-8 characters such as '\u0000'. 44 | - **Multiple input/output streams**. neujson has built-in string input/output streams and file input/output streams, and make full use of memory buffers to improve read and write speed. 45 | - **STD Streams Wrapper**. neujson provides official wrappers for std::istream and std::ostream, can be combined with neujson's built-in input/output streams. 46 | - **Performant**. neujson internally implements standards such as IEEE754 and uses high-performance algorithms built into different compilers. neujson also plans to implement Grisu2 algorithm and use SIMD instructions to speed up conversion between floating point numbers and strings. 47 | 48 | ## Examples 49 | 50 | ### Usage at a glance 51 | 52 | This simple example parses a JSON string into a document (DOM), make a simple modification of the DOM, and finally stringify the DOM to a JSON string. 53 | 54 | ```c++ 55 | #include 56 | 57 | #include "neujson/document.h" 58 | #include "neujson/string_write_stream.h" 59 | #include "neujson/writer.h" 60 | #include "sample.h" 61 | 62 | int main() { 63 | // 1. Parse a JSON string into DOM. 64 | neujson::Document doc; 65 | if (const auto err = doc.Parse(kSample[0]); err != neujson::error::OK) { 66 | puts(neujson::ParseErrorStr(err)); 67 | return EXIT_FAILURE; 68 | } 69 | 70 | // 2. Modify it by DOM. 71 | auto &s = doc[0]["Longitude"]; 72 | s.SetDouble(s.GetDouble() + 100.0); 73 | 74 | // 3. Stringify the DOM 75 | neujson::StringWriteStream os; 76 | neujson::Writer writer(os); 77 | doc.WriteTo(writer); 78 | 79 | // Output 80 | fprintf(stdout, "%.*s", static_cast(os.get().length()), os.get().data()); 81 | return 0; 82 | } 83 | ``` 84 | 85 | Output: 86 | 87 | ```json 88 | [{"precision":"zip","Latitude":37.766800000000003,"Longitude":-22.395899999999997,"Address":"","City":"SAN FRANCISCO","State":"CA","Zip":"94107","Country":"US"},{"precision":"zip","Latitude":37.371991000000001,"Longitude":-122.02602,"Address":"","City":"SUNNYVALE","State":"CA","Zip":"94085","Country":"US"}] 89 | ``` 90 | 91 | ## Building 92 | 93 | ### clone the neujson repo 94 | 95 | ```bash 96 | git clone https://github.com/hominsu/neujson.git 97 | ``` 98 | 99 | ## Building 100 | 101 | This project requires C++17. This library uses following projects: 102 | 103 | When building tests: 104 | 105 | - [google/googletest](https://github.com/google/googletest) 106 | 107 | When building examples: 108 | 109 | - [neujson](https://github.com/hominsu/neujson) 110 | 111 | All dependencies are automatically retrieved from github during building, and you do not need to configure them. 112 | 113 | With th CMake build types, you can control whether examples and tests are built. 114 | 115 | ```bash 116 | cmake -H. -Bbuild \ 117 | -DCMAKE_BUILD_TYPE=Release \ 118 | -DCMAKE_INSTALL_PREFIX=/Users/hominsu/utils/install \ 119 | -DNEUJSON_BUILD_EXAMPLES=ON \ 120 | -DNEUJSON_BUILD_TESTS=ON 121 | cmake --build ./build --parallel $(nproc) 122 | ctest -VV --test-dir ./build/ --output-on-failure 123 | cmake --install ./build 124 | ``` 125 | 126 | Or just installed as a CMake package. 127 | 128 | ```bash 129 | cmake -H. -Bbuild \ 130 | -DNEUJSON_BUILD_EXAMPLES=OFF \ 131 | -DNEUJSON_BUILD_TESTS=OFF 132 | cmake --install ./build 133 | ``` 134 | 135 | Uinstall 136 | 137 | ```bash 138 | cd build 139 | make uninstall 140 | ``` 141 | 142 | ## Integration 143 | 144 | Located with `find_package` in CMake. 145 | 146 | ```cmake 147 | find_package(neujson REQUIRED) 148 | target_include_directories(foo PUBLIC ${neujson_INCLUDE_DIRS}) 149 | ``` 150 | 151 | ## Benchmark 152 | 153 | To build with benchmark,use `git submodule` to fetch all the data from that third party project and check out the appropriate commit first, then add the benchmark CMake option: 154 | 155 | ```bash 156 | git submodule update --init --recursive 157 | pushd cmake/build 158 | cmake -DNEUJSON_BUILD_BENCHMARK=ON ../.. 159 | ... 160 | ``` 161 | 162 | The benchmark is base on the google benchmark 163 | 164 | ### JSON data 165 | 166 | | JSON file | Size | Description | 167 | | :----------------------------------------------------------: | :----: | :----------------------------------------------------------: | 168 | | `canada.json`[source](https://github.com/mloskot/json_benchmark/blob/master/data/canada.json) | 2199KB | Contour of Canada border in [GeoJSON](http://geojson.org/) format. Contains a lot of real numbers. | 169 | | `citm_catalog.json`[source](https://github.com/RichardHightower/json-parsers-benchmark/blob/master/data/citm_catalog.json) | 1737KB | A big benchmark file with indentation used in several Java JSON parser benchmarks. | 170 | 171 | ### Sample Results 172 | 173 | The followings are some snapshots from the results of MacBook Air (M1, 2020) with Apple clang 13.1.6 174 | 175 | ```bash 176 | Run on (8 X 24.1212 MHz CPU s) 177 | CPU Caches: 178 | L1 Data 64 KiB (x8) 179 | L1 Instruction 128 KiB (x8) 180 | L2 Unified 4096 KiB (x2) 181 | Load Average: 2.04, 1.78, 1.74 182 | -------------------------------------------------------------------------------------------------------- 183 | Benchmark Time CPU Iterations 184 | -------------------------------------------------------------------------------------------------------- 185 | BM_neujson_read_parse/citm_catalog.json 7.04 ms 7.04 ms 72 186 | BM_nlohmann_read_parse/citm_catalog.json 10.6 ms 10.6 ms 66 187 | BM_rapidjson_read_parse/citm_catalog.json 2.96 ms 2.96 ms 236 188 | BM_neujson_read_parse_write_file/citm_catalog.json 7.92 ms 7.92 ms 88 189 | BM_nlohmann_read_parse_write_file/citm_catalog.json 12.5 ms 12.5 ms 56 190 | BM_rapidjson_read_parse_write_file/citm_catalog.json 4.10 ms 4.10 ms 170 191 | BM_neujson_read_parse_write_string/citm_catalog.json 8.03 ms 8.03 ms 87 192 | BM_nlohmann_read_parse_write_string/citm_catalog.json 12.7 ms 12.7 ms 55 193 | BM_rapidjson_read_parse_write_string/citm_catalog.json 3.90 ms 3.90 ms 180 194 | BM_neujson_read_parse_pretty_write_file/citm_catalog.json 8.84 ms 8.84 ms 79 195 | BM_nlohmann_read_parse_pretty_write_file/citm_catalog.json 13.3 ms 13.3 ms 53 196 | BM_rapidjson_read_parse_pretty_write_file/citm_catalog.json 4.56 ms 4.55 ms 154 197 | BM_neujson_read_parse_pretty_write_string/citm_catalog.json 9.44 ms 9.44 ms 72 198 | BM_nlohmann_read_parse_pretty_write_string/citm_catalog.json 14.2 ms 14.2 ms 50 199 | BM_rapidjson_read_parse_pretty_write_string/citm_catalog.json 4.19 ms 4.19 ms 167 200 | BM_neujson_read_parse/canada.json 31.6 ms 31.6 ms 22 201 | BM_nlohmann_read_parse/canada.json 39.1 ms 39.1 ms 18 202 | BM_rapidjson_read_parse/canada.json 3.38 ms 3.38 ms 207 203 | BM_neujson_read_parse_write_file/canada.json 68.2 ms 68.2 ms 10 204 | BM_nlohmann_read_parse_write_file/canada.json 47.6 ms 47.6 ms 15 205 | BM_rapidjson_read_parse_write_file/canada.json 12.5 ms 12.5 ms 55 206 | BM_neujson_read_parse_write_string/canada.json 69.4 ms 69.4 ms 10 207 | BM_nlohmann_read_parse_write_string/canada.json 48.5 ms 48.5 ms 14 208 | BM_rapidjson_read_parse_write_string/canada.json 10.7 ms 10.7 ms 63 209 | BM_neujson_read_parse_pretty_write_file/canada.json 72.3 ms 72.3 ms 10 210 | BM_nlohmann_read_parse_pretty_write_file/canada.json 51.2 ms 51.2 ms 14 211 | BM_rapidjson_read_parse_pretty_write_file/canada.json 13.7 ms 13.7 ms 51 212 | BM_neujson_read_parse_pretty_write_string/canada.json 75.9 ms 75.9 ms 9 213 | BM_nlohmann_read_parse_pretty_write_string/canada.json 55.0 ms 55.0 ms 13 214 | BM_rapidjson_read_parse_pretty_write_string/canada.json 12.4 ms 12.4 ms 56 215 | ``` 216 | 217 | The followings are some snapshots from the results of i5-9500 with gcc 8.5.0 (Red Hat 8.5.0-10) in CentOS-8-Stream 218 | 219 | ```bash 220 | Run on (6 X 4166.48 MHz CPU s) 221 | CPU Caches: 222 | L1 Data 32 KiB (x6) 223 | L1 Instruction 32 KiB (x6) 224 | L2 Unified 256 KiB (x6) 225 | L3 Unified 9216 KiB (x1) 226 | Load Average: 0.80, 0.52, 0.45 227 | -------------------------------------------------------------------------------------------------------- 228 | Benchmark Time CPU Iterations 229 | -------------------------------------------------------------------------------------------------------- 230 | BM_neujson_read_parse/citm_catalog.json 8.59 ms 8.58 ms 74 231 | BM_nlohmann_read_parse/citm_catalog.json 14.7 ms 14.6 ms 48 232 | BM_rapidjson_read_parse/citm_catalog.json 2.38 ms 2.37 ms 293 233 | BM_neujson_read_parse_write_file/citm_catalog.json 10.1 ms 10.1 ms 70 234 | BM_nlohmann_read_parse_write_file/citm_catalog.json 17.5 ms 17.5 ms 40 235 | BM_rapidjson_read_parse_write_file/citm_catalog.json 3.39 ms 3.39 ms 206 236 | BM_neujson_read_parse_write_string/citm_catalog.json 10.9 ms 10.9 ms 65 237 | BM_nlohmann_read_parse_write_string/citm_catalog.json 17.5 ms 17.5 ms 40 238 | BM_rapidjson_read_parse_write_string/citm_catalog.json 3.20 ms 3.19 ms 218 239 | BM_neujson_read_parse_pretty_write_file/citm_catalog.json 11.3 ms 11.3 ms 60 240 | BM_nlohmann_read_parse_pretty_write_file/citm_catalog.json 18.8 ms 18.7 ms 38 241 | BM_rapidjson_read_parse_pretty_write_file/citm_catalog.json 3.70 ms 3.69 ms 189 242 | BM_neujson_read_parse_pretty_write_string/citm_catalog.json 14.5 ms 14.5 ms 49 243 | BM_nlohmann_read_parse_pretty_write_string/citm_catalog.json 18.5 ms 18.5 ms 38 244 | BM_rapidjson_read_parse_pretty_write_string/citm_catalog.json 3.57 ms 3.57 ms 196 245 | BM_neujson_read_parse/canada.json 27.8 ms 27.7 ms 25 246 | BM_nlohmann_read_parse/canada.json 41.8 ms 41.8 ms 17 247 | BM_rapidjson_read_parse/canada.json 4.59 ms 4.58 ms 152 248 | BM_neujson_read_parse_write_file/canada.json 100 ms 100 ms 7 249 | BM_nlohmann_read_parse_write_file/canada.json 53.5 ms 53.4 ms 13 250 | BM_rapidjson_read_parse_write_file/canada.json 13.6 ms 13.6 ms 51 251 | BM_neujson_read_parse_write_string/canada.json 106 ms 106 ms 7 252 | BM_nlohmann_read_parse_write_string/canada.json 53.3 ms 53.3 ms 13 253 | BM_rapidjson_read_parse_write_string/canada.json 11.9 ms 11.9 ms 58 254 | BM_neujson_read_parse_pretty_write_file/canada.json 106 ms 106 ms 7 255 | BM_nlohmann_read_parse_pretty_write_file/canada.json 58.6 ms 58.6 ms 12 256 | BM_rapidjson_read_parse_pretty_write_file/canada.json 14.4 ms 14.4 ms 49 257 | BM_neujson_read_parse_pretty_write_string/canada.json 119 ms 119 ms 6 258 | BM_nlohmann_read_parse_pretty_write_string/canada.json 64.9 ms 64.8 ms 11 259 | BM_rapidjson_read_parse_pretty_write_string/canada.json 12.8 ms 12.8 ms 54 260 | ``` 261 | 262 | ## Reference 263 | 264 | [RapidJSON](https://github.com/Tencent/rapidjson): A fast JSON parser/generator for C++ with both SAX/DOM style API 265 | 266 | ## Acknowledgment 267 | 268 | ![JetBrains Logo (Main) logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg) 269 | 270 | Special thanks to [JetBrains](https://www.jetbrains.com/) for licensing free `All Products Pack` for this open source projects 271 | 272 | ## Contributors ✨ 273 | 274 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 |

Homing So

💻 📖 🎨 💡 🚇 📦 ⚠️
284 | 285 | 286 | 287 | 288 | 289 | 290 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 291 | 292 | ## License 293 | 294 | Distributed under the MIT license. See `LICENSE` for more information. 295 | -------------------------------------------------------------------------------- /test/parse_test.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/7/5. 3 | // 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "neujson/document.h" 12 | #include "neujson/exception.h" 13 | #include "neujson/internal/ieee754.h" 14 | #include "neujson/non_copyable.h" 15 | #include "neujson/reader.h" 16 | #include "neujson/string_read_stream.h" 17 | #include "neujson/string_write_stream.h" 18 | #include "neujson/value.h" 19 | #include "neujson/writer.h" 20 | 21 | #include "gtest/gtest.h" 22 | 23 | #if defined(_MSC_VER) && !defined(__clang__) 24 | NEUJSON_DIAG_PUSH 25 | #elif defined(__GNUC__) 26 | NEUJSON_DIAG_PUSH 27 | NEUJSON_DIAG_OFF(effc++) 28 | #endif 29 | 30 | class TestHandler : neujson::NonCopyable { 31 | neujson::Value value_; 32 | neujson::Type value_type_ = neujson::NEU_NULL; 33 | 34 | neujson::Type test_error_type_ = neujson::NEU_BOOL; 35 | bool test_error_ = false; 36 | 37 | public: 38 | bool Null() { 39 | addValue(neujson::Value(neujson::NEU_NULL)); 40 | return true; 41 | } 42 | 43 | bool Bool(const bool b) { 44 | addValue(neujson::Value(b)); 45 | return true; 46 | } 47 | 48 | bool Int32(const int32_t i32) { 49 | addValue(neujson::Value(i32)); 50 | return true; 51 | } 52 | 53 | bool Int64(const int64_t i64) { 54 | addValue(neujson::Value(i64)); 55 | return true; 56 | } 57 | 58 | bool Double(const neujson::internal::Double d) { 59 | addValue(neujson::Value(d)); 60 | return true; 61 | } 62 | 63 | bool String(const std::string_view str) { 64 | addValue(neujson::Value(str)); 65 | return true; 66 | } 67 | 68 | bool Key(const std::string_view str) { 69 | addValue(neujson::Value(str)); 70 | return true; 71 | } 72 | 73 | bool StartObject() { 74 | addValue(neujson::Value(neujson::NEU_OBJECT)); 75 | return true; 76 | } 77 | 78 | bool EndObject() { 79 | addValue(neujson::Value(neujson::NEU_OBJECT)); 80 | return true; 81 | } 82 | 83 | bool StartArray() { 84 | addValue(neujson::Value(neujson::NEU_ARRAY)); 85 | return true; 86 | } 87 | 88 | bool EndArray() { 89 | addValue(neujson::Value(neujson::NEU_ARRAY)); 90 | return true; 91 | } 92 | 93 | [[nodiscard]] neujson::Type type() const { return value_type_; } 94 | [[nodiscard]] neujson::Value value() const { return value_; } 95 | 96 | void set_test_error(const bool test_error) { test_error_ = test_error; } 97 | void set_test_error_type(const neujson::Type type) { 98 | test_error_type_ = type; 99 | } 100 | 101 | private: 102 | void addValue(neujson::Value &&value) { 103 | if (!test_error_) { 104 | value_type_ = value.GetType(); 105 | } 106 | value_ = std::move(value); 107 | } 108 | }; 109 | 110 | TEST(parse, null) { 111 | neujson::StringWriteStream write_stream; 112 | neujson::Writer writer(write_stream); 113 | 114 | writer.Null(); 115 | 116 | neujson::StringReadStream read_stream(write_stream.get()); 117 | TestHandler test_handler; 118 | 119 | EXPECT_EQ(neujson::error::ParseError::OK, 120 | neujson::Reader::Parse(read_stream, test_handler)); 121 | EXPECT_EQ(neujson::NEU_NULL, test_handler.type()); 122 | } 123 | 124 | #define TEST_BOOL(_expect, _json) \ 125 | do { \ 126 | std::string_view ss((_json)); \ 127 | neujson::StringReadStream read_stream(ss); \ 128 | TestHandler test_handler; \ 129 | EXPECT_EQ(neujson::error::ParseError::OK, \ 130 | neujson::Reader::Parse(read_stream, test_handler)); \ 131 | EXPECT_EQ(neujson::NEU_BOOL, test_handler.type()); \ 132 | EXPECT_EQ((_expect), test_handler.value().GetBool()); \ 133 | } while (0) // 134 | 135 | TEST(parse, bool) { 136 | TEST_BOOL(true, "true"); 137 | TEST_BOOL(false, "false"); 138 | } 139 | 140 | #define TEST_INT32(_expect, _json) \ 141 | do { \ 142 | std::string_view ss((_json)); \ 143 | neujson::StringReadStream read_stream(ss); \ 144 | TestHandler test_handler; \ 145 | EXPECT_EQ(neujson::error::ParseError::OK, \ 146 | neujson::Reader::Parse(read_stream, test_handler)); \ 147 | EXPECT_EQ(neujson::NEU_INT32, test_handler.type()); \ 148 | EXPECT_EQ((_expect), test_handler.value().GetInt32()); \ 149 | } while (0) // 150 | 151 | TEST(parse, int32) { 152 | TEST_INT32(0, "0"); 153 | TEST_INT32(1234567890, "1234567890"); 154 | TEST_INT32(-1234567890, "-1234567890"); 155 | TEST_INT32(INT32_MAX, "2147483647"); 156 | TEST_INT32(INT32_MIN, "-2147483648"); 157 | } 158 | 159 | #define TEST_INT64(_expect, _json) \ 160 | do { \ 161 | std::string_view ss((_json)); \ 162 | neujson::StringReadStream read_stream(ss); \ 163 | TestHandler test_handler; \ 164 | EXPECT_EQ(neujson::error::ParseError::OK, \ 165 | neujson::Reader::Parse(read_stream, test_handler)); \ 166 | EXPECT_TRUE(neujson::NEU_INT64 == test_handler.type() || \ 167 | neujson::NEU_INT32 == test_handler.type()); \ 168 | EXPECT_EQ((_expect), test_handler.value().GetInt64()); \ 169 | } while (0) // 170 | 171 | TEST(parse, int64) { 172 | TEST_INT64(0, "0"); 173 | TEST_INT64(12345678901234, "12345678901234"); 174 | TEST_INT64(-12345678901234, "-12345678901234"); 175 | TEST_INT64(INT64_MAX, "9223372036854775807"); 176 | TEST_INT64(INT64_MIN, "-9223372036854775808"); 177 | } 178 | 179 | #define TEST_DOUBLE(_expect, _json) \ 180 | do { \ 181 | std::string_view ss((_json)); \ 182 | neujson::StringReadStream read_stream(ss); \ 183 | TestHandler test_handler; \ 184 | EXPECT_EQ(neujson::error::ParseError::OK, \ 185 | neujson::Reader::Parse(read_stream, test_handler)); \ 186 | EXPECT_EQ(neujson::NEU_DOUBLE, test_handler.type()); \ 187 | EXPECT_DOUBLE_EQ((_expect), test_handler.value().GetDouble()); \ 188 | } while (0) // 189 | 190 | TEST(parse, double) { 191 | TEST_DOUBLE(0.0, "0.0"); 192 | TEST_DOUBLE(0.0, "-0.0"); 193 | TEST_DOUBLE(1.0, "1.0"); 194 | TEST_DOUBLE(-1.0, "-1.0"); 195 | TEST_DOUBLE(1.5, "1.5"); 196 | TEST_DOUBLE(-1.5, "-1.5"); 197 | TEST_DOUBLE(3.1416, "3.1416"); 198 | TEST_DOUBLE(1E10, "1E10"); 199 | TEST_DOUBLE(1e10, "1e10"); 200 | TEST_DOUBLE(1E+10, "1E+10"); 201 | TEST_DOUBLE(1E-10, "1E-10"); 202 | TEST_DOUBLE(-1E10, "-1E10"); 203 | TEST_DOUBLE(-1e10, "-1e10"); 204 | TEST_DOUBLE(-1E+10, "-1E+10"); 205 | TEST_DOUBLE(-1E-10, "-1E-10"); 206 | TEST_DOUBLE(1.234E+10, "1.234E+10"); 207 | TEST_DOUBLE(1.234E-10, "1.234E-10"); 208 | 209 | // TEST_DOUBLE(0.0, "1e-10000"); /* must underflow */ 210 | // TEST_DOUBLE(1.0000000000000002, "1.0000000000000002"); /* the smallest 211 | // number > 1 */ TEST_DOUBLE(4.9406564584124654e-324, 212 | // "4.9406564584124654e-324"); /* minimum denormal */ 213 | // TEST_DOUBLE(-4.9406564584124654e-324, "-4.9406564584124654e-324"); 214 | // TEST_DOUBLE(2.2250738585072009e-308, "2.2250738585072009e-308"); /* Max 215 | // subnormal double */ TEST_DOUBLE(-2.2250738585072009e-308, 216 | // "-2.2250738585072009e-308"); TEST_DOUBLE(2.2250738585072014e-308, 217 | // "2.2250738585072014e-308"); /* Min normal positive double */ 218 | // TEST_DOUBLE(-2.2250738585072014e-308, "-2.2250738585072014e-308"); 219 | // TEST_DOUBLE(1.7976931348623157e+308, "1.7976931348623157e+308"); /* Max 220 | // double */ TEST_DOUBLE(-1.7976931348623157e+308, 221 | // "-1.7976931348623157e+308"); 222 | } 223 | 224 | #define TEST_STRING(_expect, _json) \ 225 | do { \ 226 | std::string_view ss((_json)); \ 227 | neujson::StringReadStream read_stream(ss); \ 228 | TestHandler test_handler; \ 229 | EXPECT_EQ(neujson::error::ParseError::OK, \ 230 | neujson::Reader::Parse(read_stream, test_handler)); \ 231 | EXPECT_EQ(neujson::NEU_STRING, test_handler.type()); \ 232 | EXPECT_STREQ((_expect), test_handler.value().GetString().c_str()); \ 233 | } while (0) // 234 | 235 | TEST(parse, string) { 236 | TEST_STRING("", R"("")"); 237 | TEST_STRING("Hello", R"("Hello")"); 238 | TEST_STRING("Hello\nWorld", R"("Hello\nWorld")"); 239 | TEST_STRING("\" \\ / \b \f \n \r \t", R"("\" \\ / \b \f \n \r \t")"); 240 | TEST_STRING("Hello\0World", R"("Hello\u0000World")"); 241 | TEST_STRING("\x24", R"("\u0024")"); /* Dollar sign U+0024 */ 242 | TEST_STRING("\xC2\xA2", R"("\u00A2")"); /* Cents sign U+00A2 */ 243 | TEST_STRING("\xE2\x82\xAC", R"("\u20AC")"); /* Euro sign U+20AC */ 244 | TEST_STRING("\xF0\x9D\x84\x9E", 245 | R"("\uD834\uDD1E")"); /* G clef sign U+1D11E */ 246 | TEST_STRING("\xF0\x9D\x84\x9E", 247 | R"("\ud834\udd1e")"); /* G clef sign U+1D11E */ 248 | } 249 | 250 | TEST(parse, array) { 251 | { 252 | std::string_view ss("[ ]"); 253 | neujson::StringReadStream read_stream(ss); 254 | TestHandler test_handler; 255 | EXPECT_EQ(neujson::error::ParseError::OK, 256 | neujson::Reader::Parse(read_stream, test_handler)); 257 | EXPECT_EQ(neujson::NEU_ARRAY, test_handler.type()); 258 | EXPECT_EQ(0UL, test_handler.value().GetArray()->size()); 259 | } 260 | { 261 | std::string_view ss(R"([ null , false , true , 123 , "abc" ])"); 262 | neujson::Document doc; 263 | EXPECT_EQ(neujson::error::ParseError::OK, doc.Parse(ss)); 264 | EXPECT_EQ(neujson::NEU_ARRAY, doc.GetType()); 265 | EXPECT_EQ(5UL, doc.GetArray()->size()); 266 | EXPECT_EQ(neujson::Type::NEU_NULL, doc.GetArray()->at(0).GetType()); 267 | EXPECT_EQ(neujson::Type::NEU_BOOL, doc.GetArray()->at(1).GetType()); 268 | EXPECT_EQ(neujson::Type::NEU_BOOL, doc.GetArray()->at(2).GetType()); 269 | EXPECT_EQ(neujson::Type::NEU_INT32, doc.GetArray()->at(3).GetType()); 270 | EXPECT_EQ(neujson::Type::NEU_STRING, doc.GetArray()->at(4).GetType()); 271 | EXPECT_EQ(false, doc.GetArray()->at(1).GetBool()); 272 | EXPECT_EQ(true, doc.GetArray()->at(2).GetBool()); 273 | EXPECT_EQ(123, doc.GetArray()->at(3).GetInt32()); 274 | EXPECT_STREQ("abc", doc.GetArray()->at(4).GetString().c_str()); 275 | } 276 | { 277 | std::string_view ss(R"([ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ])"); 278 | neujson::Document doc; 279 | EXPECT_EQ(neujson::error::ParseError::OK, doc.Parse(ss)); 280 | EXPECT_EQ(neujson::NEU_ARRAY, doc.GetType()); 281 | EXPECT_EQ(4UL, doc.GetArray()->size()); 282 | for (std::size_t i = 0; i < 4; i++) { 283 | auto a = doc.GetArray()->at(i); 284 | EXPECT_EQ(neujson::Type::NEU_ARRAY, a.GetType()); 285 | EXPECT_EQ(i, a.GetArray()->size()); 286 | for (std::size_t j = 0; j < i; j++) { 287 | auto e = a.GetArray()->at(j); 288 | EXPECT_EQ(neujson::Type::NEU_INT32, e.GetType()); 289 | EXPECT_EQ(static_cast(j), e.GetInt32()); 290 | } 291 | } 292 | } 293 | } 294 | 295 | TEST(parse, object) { 296 | { 297 | std::string_view ss(" { } "); 298 | neujson::StringReadStream read_stream(ss); 299 | TestHandler test_handler; 300 | EXPECT_EQ(neujson::error::ParseError::OK, 301 | neujson::Reader::Parse(read_stream, test_handler)); 302 | EXPECT_EQ(neujson::NEU_OBJECT, test_handler.type()); 303 | EXPECT_EQ(0UL, test_handler.value().GetObject()->size()); 304 | } 305 | { 306 | std::string_view ss(R"( 307 | { 308 | "n" : null , 309 | "f" : false , 310 | "t" : true , 311 | "i" : 123 , 312 | "s" : "abc", 313 | "a" : [ 1, 2, 3 ], 314 | "o" : { "1" : 1, "2" : 2, "3" : 3 } 315 | } 316 | )"); 317 | neujson::Document doc; 318 | EXPECT_EQ(neujson::error::ParseError::OK, doc.Parse(ss)); 319 | EXPECT_EQ(neujson::NEU_OBJECT, doc.GetType()); 320 | EXPECT_EQ(7UL, doc.GetObject()->size()); 321 | 322 | EXPECT_STREQ("n", doc.GetObject()->at(0).key_.GetString().c_str()); 323 | EXPECT_EQ(neujson::NEU_NULL, doc.GetObject()->at(0).value_.GetType()); 324 | 325 | EXPECT_STREQ("f", doc.GetObject()->at(1).key_.GetString().c_str()); 326 | EXPECT_EQ(neujson::NEU_BOOL, doc.GetObject()->at(1).value_.GetType()); 327 | EXPECT_EQ(false, doc.GetObject()->at(1).value_.GetBool()); 328 | 329 | EXPECT_STREQ("t", doc.GetObject()->at(2).key_.GetString().c_str()); 330 | EXPECT_EQ(neujson::NEU_BOOL, doc.GetObject()->at(2).value_.GetType()); 331 | EXPECT_EQ(true, doc.GetObject()->at(2).value_.GetBool()); 332 | 333 | EXPECT_STREQ("i", doc.GetObject()->at(3).key_.GetString().c_str()); 334 | EXPECT_EQ(neujson::NEU_INT32, doc.GetObject()->at(3).value_.GetType()); 335 | EXPECT_EQ(123, doc.GetObject()->at(3).value_.GetInt32()); 336 | 337 | EXPECT_STREQ("s", doc.GetObject()->at(4).key_.GetString().c_str()); 338 | EXPECT_EQ(neujson::NEU_STRING, doc.GetObject()->at(4).value_.GetType()); 339 | EXPECT_STREQ("abc", doc.GetObject()->at(4).value_.GetString().c_str()); 340 | 341 | EXPECT_STREQ("a", doc.GetObject()->at(5).key_.GetString().c_str()); 342 | EXPECT_EQ(neujson::NEU_ARRAY, doc.GetObject()->at(5).value_.GetType()); 343 | EXPECT_EQ(3UL, doc.GetObject()->at(5).value_.GetArray()->size()); 344 | for (std::size_t i = 0; i < 3; i++) { 345 | auto e = doc.GetObject()->at(5).value_.GetArray(); 346 | EXPECT_EQ(neujson::NEU_INT32, e->at(i).GetType()); 347 | EXPECT_EQ(static_cast(i + 1), e->at(i).GetInt32()); 348 | } 349 | 350 | EXPECT_STREQ("o", doc.GetObject()->at(6).key_.GetString().c_str()); 351 | EXPECT_EQ(neujson::NEU_OBJECT, doc.GetObject()->at(6).value_.GetType()); 352 | EXPECT_EQ(3UL, doc.GetObject()->at(6).value_.GetObject()->size()); 353 | for (std::size_t i = 0; i < 3; i++) { 354 | auto object_key = doc.GetObject()->at(6).value_.GetObject()->at(i).key_; 355 | auto object_value = 356 | doc.GetObject()->at(6).value_.GetObject()->at(i).value_; 357 | char s[2]{}; 358 | s[0] = static_cast('1' + i); 359 | EXPECT_STREQ(s, object_key.GetString().c_str()); 360 | EXPECT_EQ(1UL, object_key.GetString().size()); 361 | EXPECT_EQ(neujson::NEU_INT32, object_value.GetType()); 362 | EXPECT_EQ(static_cast(i + 1), object_value.GetInt32()); 363 | } 364 | } 365 | } 366 | 367 | #define TEST_PARSE_ERROR(_error, _json) \ 368 | do { \ 369 | std::string_view ss((_json)); \ 370 | neujson::StringReadStream read_stream(ss); \ 371 | TestHandler test_handler; \ 372 | EXPECT_EQ((_error), neujson::Reader::Parse(read_stream, test_handler)); \ 373 | } while (0) // 374 | 375 | TEST(parse, expect_value) { 376 | TEST_PARSE_ERROR(neujson::error::EXPECT_VALUE, ""); 377 | TEST_PARSE_ERROR(neujson::error::EXPECT_VALUE, " "); 378 | } 379 | 380 | TEST(parse, bad_value) { 381 | TEST_PARSE_ERROR(neujson::error::BAD_VALUE, "nul"); 382 | TEST_PARSE_ERROR(neujson::error::BAD_VALUE, "?"); 383 | 384 | // invalid number 385 | TEST_PARSE_ERROR(neujson::error::BAD_VALUE, "+0"); 386 | TEST_PARSE_ERROR(neujson::error::BAD_VALUE, "+1"); 387 | TEST_PARSE_ERROR(neujson::error::BAD_VALUE, 388 | ".123"); // at least one digit before '.' 389 | TEST_PARSE_ERROR(neujson::error::BAD_VALUE, 390 | "1."); // at least one digit after '.' 391 | TEST_PARSE_ERROR(neujson::error::BAD_VALUE, "INF"); 392 | TEST_PARSE_ERROR(neujson::error::BAD_VALUE, "inf"); 393 | TEST_PARSE_ERROR(neujson::error::BAD_VALUE, "NAN"); 394 | TEST_PARSE_ERROR(neujson::error::BAD_VALUE, "nan"); 395 | 396 | // invalid value in array 397 | TEST_PARSE_ERROR(neujson::error::BAD_VALUE, "[1,]"); 398 | TEST_PARSE_ERROR(neujson::error::BAD_VALUE, R"(["a", nul])"); 399 | } 400 | 401 | TEST(parse, root_not_singular) { 402 | TEST_PARSE_ERROR(neujson::error::ROOT_NOT_SINGULAR, "null x"); 403 | 404 | // invalid number 405 | TEST_PARSE_ERROR(neujson::error::ROOT_NOT_SINGULAR, 406 | "0123"); // after zero should be '.' , 'E' , 'e' or nothing 407 | TEST_PARSE_ERROR(neujson::error::ROOT_NOT_SINGULAR, "0x0"); 408 | TEST_PARSE_ERROR(neujson::error::ROOT_NOT_SINGULAR, "0x123"); 409 | } 410 | 411 | TEST(parse, number_too_big) { 412 | TEST_PARSE_ERROR(neujson::error::NUMBER_TOO_BIG, "1e309"); 413 | TEST_PARSE_ERROR(neujson::error::NUMBER_TOO_BIG, "-1e309"); 414 | } 415 | 416 | TEST(parse, miss_quotation_mark) { 417 | TEST_PARSE_ERROR(neujson::error::MISS_QUOTATION_MARK, R"(")"); 418 | TEST_PARSE_ERROR(neujson::error::MISS_QUOTATION_MARK, R"("abc)"); 419 | } 420 | 421 | TEST(parse, bad_string_escape) { 422 | TEST_PARSE_ERROR(neujson::error::BAD_STRING_ESCAPE, R"("\v")"); 423 | TEST_PARSE_ERROR(neujson::error::BAD_STRING_ESCAPE, R"("\'")"); 424 | TEST_PARSE_ERROR(neujson::error::BAD_STRING_ESCAPE, R"("\0")"); 425 | TEST_PARSE_ERROR(neujson::error::BAD_STRING_ESCAPE, R"("\x12")"); 426 | } 427 | 428 | TEST(parse, bad_string_char) { 429 | TEST_PARSE_ERROR(neujson::error::BAD_STRING_CHAR, "\"\x01\""); 430 | TEST_PARSE_ERROR(neujson::error::BAD_STRING_CHAR, "\"\x1F\""); 431 | } 432 | 433 | TEST(parse, bad_unicode_hex) { 434 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_HEX, R"("\u")"); 435 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_HEX, R"("\u0")"); 436 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_HEX, R"("\u01")"); 437 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_HEX, R"("\u012")"); 438 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_HEX, R"("\u/000")"); 439 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_HEX, R"("\uG000")"); 440 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_HEX, R"("\u0/00")"); 441 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_HEX, R"("\u0G00")"); 442 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_HEX, R"("\u00/0")"); 443 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_HEX, R"("\u00G0")"); 444 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_HEX, R"("\u000/")"); 445 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_HEX, R"("\u000G")"); 446 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_HEX, R"("\u 123")"); 447 | } 448 | 449 | TEST(parse, bad_unicode_surrogate) { 450 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_SURROGATE, R"("\uD800")"); 451 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_SURROGATE, R"("\uDBFF")"); 452 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_SURROGATE, R"("\uD800\\")"); 453 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_SURROGATE, R"("\uD800\uDBFF")"); 454 | TEST_PARSE_ERROR(neujson::error::BAD_UNICODE_SURROGATE, R"("\uD800\uE000")"); 455 | } 456 | 457 | TEST(parse, miss_comma_or_square_bracket) { 458 | TEST_PARSE_ERROR(neujson::error::MISS_COMMA_OR_SQUARE_BRACKET, "[1"); 459 | TEST_PARSE_ERROR(neujson::error::MISS_COMMA_OR_SQUARE_BRACKET, "[1}"); 460 | TEST_PARSE_ERROR(neujson::error::MISS_COMMA_OR_SQUARE_BRACKET, "[1 2"); 461 | TEST_PARSE_ERROR(neujson::error::MISS_COMMA_OR_SQUARE_BRACKET, "[[]"); 462 | } 463 | 464 | TEST(parse, miss_key) { 465 | TEST_PARSE_ERROR(neujson::error::MISS_KEY, R"({:1,)"); 466 | TEST_PARSE_ERROR(neujson::error::MISS_KEY, R"({1:1,)"); 467 | TEST_PARSE_ERROR(neujson::error::MISS_KEY, R"({true:1,)"); 468 | TEST_PARSE_ERROR(neujson::error::MISS_KEY, R"({false:1,)"); 469 | TEST_PARSE_ERROR(neujson::error::MISS_KEY, R"({null:1,)"); 470 | TEST_PARSE_ERROR(neujson::error::MISS_KEY, R"({[]:1,)"); 471 | TEST_PARSE_ERROR(neujson::error::MISS_KEY, R"({{}:1,)"); 472 | TEST_PARSE_ERROR(neujson::error::MISS_KEY, R"({"a":1,)"); 473 | } 474 | 475 | TEST(parse, miss_colon) { 476 | TEST_PARSE_ERROR(neujson::error::MISS_COLON, R"({"a"})"); 477 | TEST_PARSE_ERROR(neujson::error::MISS_COLON, R"({"a","b"})"); 478 | } 479 | 480 | TEST(parse, miss_comma_or_curly_bracket) { 481 | TEST_PARSE_ERROR(neujson::error::MISS_COMMA_OR_CURLY_BRACKET, R"({"a":1)"); 482 | TEST_PARSE_ERROR(neujson::error::MISS_COMMA_OR_CURLY_BRACKET, R"({"a":1])"); 483 | TEST_PARSE_ERROR(neujson::error::MISS_COMMA_OR_CURLY_BRACKET, 484 | R"({"a":1 "b")"); 485 | TEST_PARSE_ERROR(neujson::error::MISS_COMMA_OR_CURLY_BRACKET, R"({"a":{})"); 486 | } 487 | 488 | #if defined(__GNUC__) || (defined(_MSC_VER) && !defined(__clang__)) 489 | NEUJSON_DIAG_POP 490 | #endif -------------------------------------------------------------------------------- /include/neujson/reader.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Homing So on 2022/3/7. 3 | // 4 | 5 | #ifndef NEUJSON_NEUJSON_READER_H_ 6 | #define NEUJSON_NEUJSON_READER_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "exception.h" 14 | #include "internal/cllzl.h" 15 | #include "non_copyable.h" 16 | #include "value.h" 17 | 18 | #if defined(NEUJSON_SSE42) 19 | #include 20 | #elif defined(NEUJSON_SSE2) 21 | #include 22 | #elif defined(NEUJSON_NEON) 23 | #include 24 | #endif 25 | 26 | #if defined(NEUJSON_SSE42) 27 | #undef NEUJSON_SSE42 28 | #define NEUJSON_SSE42_OFF 29 | #elif defined(NEUJSON_SSE2) 30 | #undef NEUJSON_SSE2 31 | #define NEUJSON_SSE2_OFF 32 | #elif defined(NEUJSON_NEON) 33 | #undef NEUJSON_NEON 34 | #define NEUJSON_NEON_OFF 35 | #endif 36 | 37 | namespace neujson { 38 | 39 | namespace required::read_stream { 40 | namespace details { 41 | 42 | template 43 | concept HasHasNext = requires(ReadStream rs) { 44 | { rs.hasNext() } -> std::same_as; 45 | }; 46 | 47 | template 48 | concept HasPeek = requires(ReadStream rs) { 49 | { rs.peek() } -> std::same_as; 50 | }; 51 | 52 | template 53 | concept HasNext = requires(ReadStream rs) { 54 | { rs.next() } -> std::same_as; 55 | }; 56 | 57 | #if defined(NEUJSON_SSE42) || defined(NEUJSON_SSE2) || defined(NEUJSON_NEON) 58 | template 59 | concept HasSkip = requires(ReadStream rs, std::size_t n) { 60 | { rs.skip(n) } -> std::same_as; 61 | }; 62 | #endif 63 | 64 | template 65 | concept HasAssertNext = requires(ReadStream rs, char ch) { 66 | { rs.assertNext(ch) } -> std::same_as; 67 | }; 68 | 69 | } // namespace details 70 | 71 | template 72 | concept HasAllRequiredFunctions = 73 | details::HasHasNext && details::HasPeek && details::HasNext && 74 | #if defined(NEUJSON_SSE42) || defined(NEUJSON_SSE2) || defined(NEUJSON_NEON) 75 | details::HasSkip && 76 | #endif 77 | details::HasAssertNext; 78 | 79 | } // namespace required::read_stream 80 | 81 | class Reader : NonCopyable { 82 | public: 83 | template 85 | static error::ParseError Parse(ReadStream &rs, Handler &handler); 86 | 87 | private: 88 | template 89 | static unsigned ParseHex4(ReadStream &rs); 90 | 91 | #if defined(NEUJSON_SSE42) 92 | template 93 | static void ParseWhitespaceSSE42(ReadStream &rs); 94 | #elif defined(NEUJSON_SSE2) 95 | template 96 | static void ParseWhitespaceSSE2(ReadStream &rs); 97 | #elif defined(NEUJSON_NEON) 98 | template 99 | static void ParseWhitespaceNEON(ReadStream &rs); 100 | #else 101 | template 102 | static void ParseWhitespaceBasic(ReadStream &rs); 103 | #endif 104 | 105 | template 106 | static void ParseWhitespace(ReadStream &rs); 107 | 108 | template 110 | static void ParseLiteral(ReadStream &rs, Handler &handler, 111 | const char *literal, Type type); 112 | 113 | template 115 | static void ParseNumber(ReadStream &rs, Handler &handler); 116 | 117 | template 119 | static void ParseString(ReadStream &rs, Handler &handler, bool is_key); 120 | 121 | template 123 | static void ParseArray(ReadStream &rs, Handler &handler); 124 | 125 | template 127 | static void ParseObject(ReadStream &rs, Handler &handler); 128 | 129 | template 131 | static void ParseValue(ReadStream &rs, Handler &handler); 132 | 133 | static bool IsDigit(const char ch) { return ch >= '0' && ch <= '9'; } 134 | static bool IsDigit1To9(const char ch) { return ch >= '1' && ch <= '9'; } 135 | static void EncodeUtf8(std::string &buffer, unsigned int u); 136 | }; 137 | 138 | template 140 | error::ParseError Reader::Parse(ReadStream &rs, Handler &handler) { 141 | try { 142 | ParseWhitespace(rs); 143 | ParseValue(rs, handler); 144 | ParseWhitespace(rs); 145 | if (rs.hasNext()) { 146 | throw Exception(error::ROOT_NOT_SINGULAR); 147 | } 148 | return error::OK; 149 | } catch (Exception &e) { 150 | return e.err(); 151 | } 152 | } 153 | 154 | template 155 | unsigned Reader::ParseHex4(ReadStream &rs) { 156 | unsigned int u = 0; 157 | for (int i = 0; i < 4; i++) { 158 | u <<= 4; 159 | if (const char ch = rs.next(); ch >= '0' && ch <= '9') { 160 | u |= static_cast(ch - '0'); 161 | } else if (ch >= 'a' && ch <= 'f') { 162 | u |= static_cast(ch - 'a' + 10); 163 | } else if (ch >= 'A' && ch <= 'F') { 164 | u |= static_cast(ch - 'A' + 10); 165 | } else { 166 | throw Exception(error::BAD_UNICODE_HEX); 167 | } 168 | } 169 | return u; 170 | } 171 | 172 | #if defined(NEUJSON_SSE42) 173 | /** 174 | * @brief Skip whitespace with SSE 4.2 pcmpistrm instruction, testing 16 8-byte 175 | * characters at once. 176 | * @tparam ReadStream 177 | * @param rs 178 | */ 179 | template 180 | void Reader::ParseWhitespaceSSE42(ReadStream &rs) { 181 | char ch = rs.peek(); 182 | if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { 183 | rs.next(); 184 | } else { 185 | return; 186 | } 187 | 188 | // 16-byte align to the next boundary 189 | const char *p = rs.getAddr(); 190 | const char *nextAligned = reinterpret_cast( 191 | (reinterpret_cast(p) + 15) & static_cast(~15)); 192 | while (p != nextAligned) { 193 | if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') { 194 | ++p; 195 | rs.next(); 196 | } else { 197 | return; 198 | } 199 | } 200 | 201 | // The rest of string using SIMD 202 | static const char whitespace[16] = " \n\r\t"; 203 | const __m128i w = 204 | _mm_loadu_si128(reinterpret_cast(&whitespace[0])); 205 | 206 | for (;; p += 16, rs.skip(16)) { 207 | const __m128i s = _mm_load_si128(reinterpret_cast(p)); 208 | const int r = 209 | _mm_cmpistri(w, s, 210 | _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | 211 | _SIDD_LEAST_SIGNIFICANT | _SIDD_NEGATIVE_POLARITY); 212 | // some characters are non-whitespace 213 | if (r != 16) { 214 | rs.skip(r); 215 | return; 216 | } 217 | } 218 | } 219 | #elif defined(NEUJSON_SSE2) 220 | /** 221 | * @brief Skip whitespace with SSE2 instructions, testing 16 8-byte characters 222 | * at once. 223 | * @tparam ReadStream 224 | * @param rs 225 | */ 226 | template 227 | void Reader::ParseWhitespaceSSE2(ReadStream &rs) { 228 | // Fast return for single non-whitespace 229 | char ch = rs.peek(); 230 | if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { 231 | rs.next(); 232 | } else { 233 | return; 234 | } 235 | 236 | // 16-byte align to the next boundary 237 | const char *p = rs.getAddr(); 238 | const char *nextAligned = reinterpret_cast( 239 | (reinterpret_cast(p) + 15) & static_cast(~15)); 240 | while (p != nextAligned) { 241 | if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') { 242 | ++p; 243 | rs.next(); 244 | } else { 245 | return; 246 | } 247 | } 248 | 249 | // The rest of string 250 | #define C16(c) {c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c} 251 | static const char whitespaces[4][16] = {C16(' '), C16('\n'), C16('\r'), 252 | C16('\t')}; 253 | #undef C16 254 | 255 | const __m128i w0 = 256 | _mm_loadu_si128(reinterpret_cast(&whitespaces[0][0])); 257 | const __m128i w1 = 258 | _mm_loadu_si128(reinterpret_cast(&whitespaces[1][0])); 259 | const __m128i w2 = 260 | _mm_loadu_si128(reinterpret_cast(&whitespaces[2][0])); 261 | const __m128i w3 = 262 | _mm_loadu_si128(reinterpret_cast(&whitespaces[3][0])); 263 | 264 | for (;; p += 16, rs.skip(16)) { 265 | const __m128i s = _mm_load_si128(reinterpret_cast(p)); 266 | __m128i x = _mm_cmpeq_epi8(s, w0); 267 | x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); 268 | x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); 269 | x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); 270 | auto r = static_cast(~_mm_movemask_epi8(x)); 271 | if (r != 0) { // some characters may be non-whitespace 272 | #ifdef _MSC_VER // Find the index of first non-whitespace 273 | unsigned long offset; 274 | _BitScanForward(&offset, r); 275 | rs.skip(offset); 276 | return; 277 | #else 278 | rs.skip(__builtin_ffs(r) - 1); 279 | return; 280 | #endif 281 | } 282 | } 283 | } 284 | #elif defined(NEUJSON_NEON) 285 | /** 286 | * @brief Skip whitespace with ARM Neon instructions, testing 16 8-byte 287 | * characters at once. 288 | * @tparam ReadStream 289 | * @param rs 290 | */ 291 | template 292 | void Reader::ParseWhitespaceNEON(ReadStream &rs) { 293 | char ch = rs.peek(); 294 | if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { 295 | rs.next(); 296 | } else { 297 | return; 298 | } 299 | 300 | // 16-byte align to the next boundary 301 | const char *p = rs.getAddr(); 302 | const char *nextAligned = reinterpret_cast( 303 | (reinterpret_cast(p) + 15) & static_cast(~15)); 304 | while (p != nextAligned) { 305 | if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') { 306 | ++p; 307 | rs.next(); 308 | } else { 309 | return; 310 | } 311 | } 312 | 313 | const uint8x16_t w0 = vmovq_n_u8(' '); 314 | const uint8x16_t w1 = vmovq_n_u8('\n'); 315 | const uint8x16_t w2 = vmovq_n_u8('\r'); 316 | const uint8x16_t w3 = vmovq_n_u8('\t'); 317 | 318 | for (;; p += 16, rs.skip(16)) { 319 | const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); 320 | uint8x16_t x = vceqq_u8(s, w0); 321 | x = vorrq_u8(x, vceqq_u8(s, w1)); 322 | x = vorrq_u8(x, vceqq_u8(s, w2)); 323 | x = vorrq_u8(x, vceqq_u8(s, w3)); 324 | 325 | x = vmvnq_u8(x); // Negate 326 | x = vrev64q_u8(x); // Rev in 64 327 | uint64_t low = vgetq_lane_u64(vreinterpretq_u64_u8(x), 0); // extract 328 | uint64_t high = vgetq_lane_u64(vreinterpretq_u64_u8(x), 1); // extract 329 | 330 | if (low == 0) { 331 | if (high != 0) { 332 | uint32_t lz = internal::clzll(high); 333 | rs.skip(8 + (lz >> 3)); 334 | return; 335 | } 336 | } else { 337 | uint32_t lz = internal::clzll(low); 338 | rs.skip(lz >> 3); 339 | return; 340 | } 341 | } 342 | } 343 | #else 344 | template 345 | void Reader::ParseWhitespaceBasic(ReadStream &rs) { 346 | while (rs.hasNext()) { 347 | if (const char ch = rs.peek(); 348 | ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { 349 | rs.next(); 350 | } else { 351 | break; 352 | } 353 | } 354 | } 355 | #endif 356 | 357 | template 358 | void Reader::ParseWhitespace(ReadStream &rs) { 359 | #if defined(NEUJSON_SSE42) 360 | return ParseWhitespaceSSE42(rs); 361 | #elif defined(NEUJSON_SSE2) 362 | return ParseWhitespaceSSE2(rs); 363 | #elif defined(NEUJSON_NEON) 364 | return ParseWhitespaceNEON(rs); 365 | #else 366 | return ParseWhitespaceBasic(rs); 367 | #endif 368 | } 369 | 370 | #define CALL(_expr) \ 371 | if (!(_expr)) \ 372 | throw Exception(error::USER_STOPPED) 373 | 374 | template 376 | void Reader::ParseLiteral(ReadStream &rs, Handler &handler, const char *literal, 377 | const Type type) { 378 | const char c = *literal; 379 | 380 | rs.assertNext(*literal++); 381 | for (; *literal != '\0'; literal++, rs.next()) { 382 | if (*literal != rs.peek()) { 383 | throw Exception(error::BAD_VALUE); 384 | } 385 | } 386 | 387 | switch (type) { 388 | case NEU_NULL: 389 | CALL(handler.Null()); 390 | return; 391 | case NEU_BOOL: 392 | CALL(handler.Bool(c == 't')); 393 | return; 394 | case NEU_DOUBLE: 395 | CALL(handler.Double( 396 | internal::Double(c == 'N' ? std::numeric_limits::quiet_NaN() 397 | : std::numeric_limits::infinity()))); 398 | return; 399 | default: 400 | NEUJSON_ASSERT(false && "bad type"); 401 | } 402 | } 403 | 404 | template 406 | void Reader::ParseNumber(ReadStream &rs, Handler &handler) { 407 | // parse 'NaN' (Not a Number) && 'Infinity' 408 | if (rs.peek() == 'N') { 409 | ParseLiteral(rs, handler, "NaN", NEU_DOUBLE); 410 | return; 411 | } 412 | if (rs.peek() == 'I') { 413 | ParseLiteral(rs, handler, "Infinity", NEU_DOUBLE); 414 | return; 415 | } 416 | 417 | std::string buffer; 418 | 419 | if (rs.peek() == '-') { 420 | buffer.push_back(rs.next()); 421 | } 422 | if (rs.peek() == '0') { 423 | buffer.push_back(rs.next()); 424 | } else { 425 | if (!IsDigit1To9(rs.peek())) { 426 | throw Exception(error::BAD_VALUE); 427 | } 428 | for (buffer.push_back(rs.next()); IsDigit(rs.peek()); 429 | buffer.push_back(rs.next())) 430 | ; 431 | } 432 | 433 | auto expectType = NEU_NULL; 434 | 435 | if (rs.peek() == '.') { 436 | expectType = NEU_DOUBLE; 437 | buffer.push_back(rs.next()); 438 | if (!IsDigit(rs.peek())) { 439 | throw Exception(error::BAD_VALUE); 440 | } 441 | for (buffer.push_back(rs.next()); IsDigit(rs.peek()); 442 | buffer.push_back(rs.next())) 443 | ; 444 | } 445 | 446 | if (rs.peek() == 'e' || rs.peek() == 'E') { 447 | expectType = NEU_DOUBLE; 448 | buffer.push_back(rs.next()); 449 | if (rs.peek() == '+' || rs.peek() == '-') { 450 | buffer.push_back(rs.next()); 451 | } 452 | if (!IsDigit(rs.peek())) { 453 | throw Exception(error::BAD_VALUE); 454 | } 455 | for (buffer.push_back(rs.next()); IsDigit(rs.peek()); 456 | buffer.push_back(rs.next())) 457 | ; 458 | } 459 | 460 | if (buffer.empty()) { 461 | throw Exception(error::BAD_VALUE); 462 | } 463 | 464 | try { 465 | std::size_t idx; 466 | if (expectType == NEU_DOUBLE) { 467 | double d; 468 | #if defined(__clang__) || defined(_MSC_VER) 469 | d = std::stod(buffer, &idx); 470 | #elif defined(__GNUC__) 471 | d = __gnu_cxx::__stoa(&std::strtod, "stod", buffer.data(), &idx); 472 | #else 473 | #error "complier no support" 474 | #endif 475 | NEUJSON_ASSERT(buffer.size() == idx); 476 | CALL(handler.Double(internal::Double(d))); 477 | } else { 478 | int64_t i64; 479 | #if defined(__clang__) || defined(_MSC_VER) 480 | i64 = std::stoll(buffer, &idx, 10); 481 | #elif defined(__GNUC__) 482 | i64 = __gnu_cxx::__stoa(&std::strtoll, "stoll", buffer.data(), &idx, 10); 483 | #else 484 | #error "complier no support" 485 | #endif 486 | if (i64 <= std::numeric_limits::max() && 487 | i64 >= std::numeric_limits::min()) { 488 | CALL(handler.Int32(static_cast(i64))); 489 | } else { 490 | CALL(handler.Int64(i64)); 491 | } 492 | } 493 | } catch (...) { 494 | throw Exception(error::NUMBER_TOO_BIG); 495 | } 496 | } 497 | 498 | template 500 | void Reader::ParseString(ReadStream &rs, Handler &handler, const bool is_key) { 501 | rs.assertNext('"'); 502 | std::string buffer; 503 | while (rs.hasNext()) { 504 | switch (char ch = rs.next()) { 505 | case '"': 506 | if (is_key) { 507 | CALL(handler.Key(std::move(buffer))); 508 | } else { 509 | CALL(handler.String(std::move(buffer))); 510 | } 511 | return; 512 | #if defined(__clang__) || defined(__GNUC__) 513 | case '\x01' ... '\x1f': 514 | throw Exception(error::BAD_STRING_CHAR); 515 | #endif 516 | case '\\': 517 | switch (rs.next()) { 518 | case '"': 519 | buffer.push_back('"'); 520 | break; 521 | case '\\': 522 | buffer.push_back('\\'); 523 | break; 524 | case '/': 525 | buffer.push_back('/'); 526 | break; 527 | case 'b': 528 | buffer.push_back('\b'); 529 | break; 530 | case 'f': 531 | buffer.push_back('\f'); 532 | break; 533 | case 'n': 534 | buffer.push_back('\n'); 535 | break; 536 | case 'r': 537 | buffer.push_back('\r'); 538 | break; 539 | case 't': 540 | buffer.push_back('\t'); 541 | break; 542 | case 'u': { 543 | // unicode stuff from Milo's tutorial 544 | unsigned u = ParseHex4(rs); 545 | if (u >= 0xD800 && u <= 0xDBFF) { 546 | if (rs.next() != '\\') { 547 | throw Exception(error::BAD_UNICODE_SURROGATE); 548 | } 549 | if (rs.next() != 'u') { 550 | throw Exception(error::BAD_UNICODE_SURROGATE); 551 | } 552 | if (const unsigned u2 = ParseHex4(rs); u2 >= 0xDC00 && u2 <= 0xDFFF) { 553 | u = 0x10000 + (u - 0xD800) * 0x400 + (u2 - 0xDC00); 554 | } else { 555 | throw Exception(error::BAD_UNICODE_SURROGATE); 556 | } 557 | } 558 | EncodeUtf8(buffer, u); 559 | break; 560 | } 561 | default: 562 | throw Exception(error::BAD_STRING_ESCAPE); 563 | } 564 | break; 565 | default: 566 | #if defined(_MSC_VER) 567 | if (static_cast(ch) < 0x20) { 568 | throw Exception(error::BAD_STRING_CHAR); 569 | } 570 | #endif 571 | buffer.push_back(ch); 572 | } 573 | } 574 | throw Exception(error::MISS_QUOTATION_MARK); 575 | } 576 | 577 | template 579 | void Reader::ParseArray(ReadStream &rs, Handler &handler) { 580 | CALL(handler.StartArray()); 581 | 582 | rs.assertNext('['); 583 | ParseWhitespace(rs); 584 | if (rs.peek() == ']') { 585 | rs.next(); 586 | CALL(handler.EndArray()); 587 | return; 588 | } 589 | 590 | while (true) { 591 | ParseValue(rs, handler); 592 | ParseWhitespace(rs); 593 | switch (rs.next()) { 594 | case ',': 595 | ParseWhitespace(rs); 596 | break; 597 | case ']': 598 | CALL(handler.EndArray()); 599 | return; 600 | default: 601 | throw Exception(error::MISS_COMMA_OR_SQUARE_BRACKET); 602 | } 603 | } 604 | } 605 | 606 | template 608 | void Reader::ParseObject(ReadStream &rs, Handler &handler) { 609 | CALL(handler.StartObject()); 610 | 611 | rs.assertNext('{'); 612 | ParseWhitespace(rs); 613 | if (rs.peek() == '}') { 614 | rs.next(); 615 | CALL(handler.EndObject()); 616 | return; 617 | } 618 | 619 | while (true) { 620 | if (rs.peek() != '"') { 621 | throw Exception(error::MISS_KEY); 622 | } 623 | 624 | ParseString(rs, handler, true); 625 | 626 | // parse ':' 627 | ParseWhitespace(rs); 628 | if (rs.next() != ':') { 629 | throw Exception(error::MISS_COLON); 630 | } 631 | ParseWhitespace(rs); 632 | 633 | // go on 634 | ParseValue(rs, handler); 635 | ParseWhitespace(rs); 636 | switch (rs.next()) { 637 | case ',': 638 | ParseWhitespace(rs); 639 | break; 640 | case '}': 641 | CALL(handler.EndObject()); 642 | return; 643 | default: 644 | throw Exception(error::MISS_COMMA_OR_CURLY_BRACKET); 645 | } 646 | } 647 | } 648 | 649 | #undef CALL 650 | 651 | template 653 | void Reader::ParseValue(ReadStream &rs, Handler &handler) { 654 | if (!rs.hasNext()) { 655 | throw Exception(error::EXPECT_VALUE); 656 | } 657 | 658 | switch (rs.peek()) { 659 | case 'n': 660 | return ParseLiteral(rs, handler, "null", NEU_NULL); 661 | case 't': 662 | return ParseLiteral(rs, handler, "true", NEU_BOOL); 663 | case 'f': 664 | return ParseLiteral(rs, handler, "false", NEU_BOOL); 665 | case '"': 666 | return ParseString(rs, handler, false); 667 | case '[': 668 | return ParseArray(rs, handler); 669 | case '{': 670 | return ParseObject(rs, handler); 671 | default: 672 | return ParseNumber(rs, handler); 673 | } 674 | } 675 | 676 | inline void Reader::EncodeUtf8(std::string &buffer, const unsigned int u) { 677 | if (u <= 0x7F) { 678 | buffer.push_back(static_cast(u & 0xFF)); 679 | } else if (u <= 0x7FF) { 680 | buffer.push_back(static_cast(0xC0 | ((u >> 6) & 0xFF))); 681 | buffer.push_back(static_cast(0x80 | (u & 0x3F))); 682 | } else if (u <= 0xFFFF) { 683 | buffer.push_back(static_cast(0xE0 | ((u >> 12) & 0xFF))); 684 | buffer.push_back(static_cast(0x80 | ((u >> 6) & 0x3F))); 685 | buffer.push_back(static_cast(0x80 | (u & 0x3F))); 686 | } else if (u <= 0x10FFFF) { 687 | NEUJSON_ASSERT(u <= 0x10FFFF && "out of range"); 688 | buffer.push_back(static_cast(0xF0 | ((u >> 18) & 0xFF))); 689 | buffer.push_back(static_cast(0x80 | ((u >> 12) & 0x3F))); 690 | buffer.push_back(static_cast(0x80 | ((u >> 6) & 0x3F))); 691 | buffer.push_back(static_cast(0x80 | (u & 0x3F))); 692 | } 693 | } 694 | 695 | } // namespace neujson 696 | 697 | #if defined(NEUJSON_SSE42_OFF) 698 | #define NEUJSON_SSE42 699 | #elif defined(NEUJSON_SSE2_OFF) 700 | #define NEUJSON_SSE2 701 | #elif defined(NEUJSON_NEON_OFF) 702 | #define NEUJSON_NEON 703 | #endif 704 | 705 | #endif // NEUJSON_NEUJSON_READER_H_ 706 | --------------------------------------------------------------------------------