From 25a8076064fed78f6d4002e9038eacdb4de50e6d Mon Sep 17 00:00:00 2001 From: Local Seed Date: Wed, 11 Feb 2026 16:14:01 -0500 Subject: [PATCH] Seed Core_IO from doc snapshot --- Core_IO.xcodeproj/project.pbxproj | 673 +++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../UserInterfaceState.xcuserstate | Bin 0 -> 36798 bytes .../xcschemes/Core_IO_Tests.xcscheme | 88 + .../xcdebugger/Breakpoints_v2.xcbkptlist | 6 + .../xcschemes/xcschememanagement.plist | 37 + LICENSE | 26 + Makefile | 2 + Makefile.def | 6 + Makefile.project | 15 + ReadMe.md | 157 + tests/Core_IO_Tests.txt | 2 + tests/Makefile | 1 + tests/Makefile.project | 32 + tests/main.cpp | 21 + tjp/core/io/Exception.h | 14 + tjp/core/io/IO+config.h | 56 + tjp/core/io/IO.cpp | 4 + tjp/core/io/IO.h | 49 + tjp/core/io/IO_.h | 17 + tjp/core/io/IO_dispatch.h | 23 + tjp/core/io/IO_encoder.h | 112 + tjp/core/io/Notes.txt | 7 + tjp/core/io/bin/Debug.h | 13 + tjp/core/io/bin/IO.h | 357 +++ tjp/core/io/bin/_tests/ConstString+IO.cpp | 105 + tjp/core/io/bin/_tests/IO_bin.cpp | 361 +++ tjp/core/io/bin/_tests/IO_bin_versioning.cpp | 179 ++ tjp/core/io/bin/_tests/Ptr+IO.cpp | 56 + tjp/core/io/bin/dictionary.h | 86 + tjp/core/io/bin/in.h | 619 ++++ tjp/core/io/bin/in.inl | 86 + tjp/core/io/bin/in_file.hpp | 18 + tjp/core/io/bin/in_fwd.h | 16 + tjp/core/io/bin/json.inl | 26 + tjp/core/io/bin/out.h | 543 ++++ tjp/core/io/bin/out.inl | 65 + tjp/core/io/bin/out_file.hpp | 18 + tjp/core/io/bin/out_fwd.h | 23 + tjp/core/io/calls.hpp | 38 + tjp/core/io/json/IO.h | 227 ++ tjp/core/io/json/_tests/IO_json.cpp | 415 +++ tjp/core/io/json/in_file.hpp | 39 + tjp/core/io/json/in_sajson.h | 831 ++++++ tjp/core/io/json/in_sajson.inl | 76 + tjp/core/io/json/json.inl | 26 + tjp/core/io/json/out.h | 650 ++++ tjp/core/io/json/out.inl | 72 + tjp/core/io/json/out_file.hpp | 21 + tjp/core/json/sajson-save.h | 2493 ++++++++++++++++ tjp/core/json/sajson.h | 2642 +++++++++++++++++ transfer-to-lava | 8 + 53 files changed, 11472 insertions(+) create mode 100644 Core_IO.xcodeproj/project.pbxproj create mode 100644 Core_IO.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Core_IO.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Core_IO.xcodeproj/project.xcworkspace/xcuserdata/tprepscius.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 Core_IO.xcodeproj/xcshareddata/xcschemes/Core_IO_Tests.xcscheme create mode 100644 Core_IO.xcodeproj/xcuserdata/tprepscius.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist create mode 100644 Core_IO.xcodeproj/xcuserdata/tprepscius.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100755 LICENSE create mode 100644 Makefile create mode 100755 Makefile.def create mode 100755 Makefile.project create mode 100755 ReadMe.md create mode 100644 tests/Core_IO_Tests.txt create mode 120000 tests/Makefile create mode 100755 tests/Makefile.project create mode 100644 tests/main.cpp create mode 100644 tjp/core/io/Exception.h create mode 100755 tjp/core/io/IO+config.h create mode 100644 tjp/core/io/IO.cpp create mode 100755 tjp/core/io/IO.h create mode 100644 tjp/core/io/IO_.h create mode 100755 tjp/core/io/IO_dispatch.h create mode 100755 tjp/core/io/IO_encoder.h create mode 100644 tjp/core/io/Notes.txt create mode 100644 tjp/core/io/bin/Debug.h create mode 100644 tjp/core/io/bin/IO.h create mode 100644 tjp/core/io/bin/_tests/ConstString+IO.cpp create mode 100644 tjp/core/io/bin/_tests/IO_bin.cpp create mode 100644 tjp/core/io/bin/_tests/IO_bin_versioning.cpp create mode 100644 tjp/core/io/bin/_tests/Ptr+IO.cpp create mode 100644 tjp/core/io/bin/dictionary.h create mode 100644 tjp/core/io/bin/in.h create mode 100644 tjp/core/io/bin/in.inl create mode 100644 tjp/core/io/bin/in_file.hpp create mode 100644 tjp/core/io/bin/in_fwd.h create mode 100644 tjp/core/io/bin/json.inl create mode 100644 tjp/core/io/bin/out.h create mode 100644 tjp/core/io/bin/out.inl create mode 100644 tjp/core/io/bin/out_file.hpp create mode 100644 tjp/core/io/bin/out_fwd.h create mode 100755 tjp/core/io/calls.hpp create mode 100644 tjp/core/io/json/IO.h create mode 100644 tjp/core/io/json/_tests/IO_json.cpp create mode 100644 tjp/core/io/json/in_file.hpp create mode 100644 tjp/core/io/json/in_sajson.h create mode 100644 tjp/core/io/json/in_sajson.inl create mode 100644 tjp/core/io/json/json.inl create mode 100644 tjp/core/io/json/out.h create mode 100644 tjp/core/io/json/out.inl create mode 100644 tjp/core/io/json/out_file.hpp create mode 100644 tjp/core/json/sajson-save.h create mode 100644 tjp/core/json/sajson.h create mode 100755 transfer-to-lava diff --git a/Core_IO.xcodeproj/project.pbxproj b/Core_IO.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f8323cf --- /dev/null +++ b/Core_IO.xcodeproj/project.pbxproj @@ -0,0 +1,673 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + F608A97B2826B5D5005C276B /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F608A97A2826B5D5005C276B /* main.cpp */; }; + F608A9812826B5FA005C276B /* IO_bin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F608A8FC2826A471005C276B /* IO_bin.cpp */; }; + F608AAFA28271F4F005C276B /* libCore_Zero.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F608AAF928271F4F005C276B /* libCore_Zero.a */; }; + F60A19B1284579080040CD24 /* IO_json.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F608A9042826A471005C276B /* IO_json.cpp */; }; + F61F9BD72C6E0EE800F79137 /* Ptr+IO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F61F9BD62C6E0EE800F79137 /* Ptr+IO.cpp */; }; + F61F9BD92C6E0F2A00F79137 /* ConstString+IO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F61F9BD82C6E0F2A00F79137 /* ConstString+IO.cpp */; }; + F61F9BE02C6E2DD200F79137 /* libCore_Allocator.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F61F9BDF2C6E2DD200F79137 /* libCore_Allocator.a */; }; + F62B987928D60EF700ACC388 /* sajson.h in Headers */ = {isa = PBXBuildFile; fileRef = F62B987828D60EF700ACC388 /* sajson.h */; }; + F62B987A28D60EF700ACC388 /* sajson.h in Headers */ = {isa = PBXBuildFile; fileRef = F62B987828D60EF700ACC388 /* sajson.h */; }; + F62B987C28D618E800ACC388 /* libCore_Misc.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F62B987B28D618E800ACC388 /* libCore_Misc.a */; }; + F62B987E28D61A7300ACC388 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = F62B987D28D61A7300ACC388 /* libresolv.tbd */; }; + F633E94E2916E027007A4C26 /* IO_bin_versioning.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F633E94D2916DB18007A4C26 /* IO_bin_versioning.cpp */; }; + F69548882828853E005D1B64 /* IO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F695488728288532005D1B64 /* IO.cpp */; }; + F6971F25282B1171008FBD17 /* IO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F695488728288532005D1B64 /* IO.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + F608AAF728271F30005C276B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F608A8E52826A411005C276B /* Project object */; + proxyType = 1; + remoteGlobalIDString = F608A8EC2826A412005C276B; + remoteInfo = Core_IO; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + F608A9762826B5D5005C276B /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + F608A8ED2826A412005C276B /* libCore_IO.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCore_IO.a; sourceTree = BUILT_PRODUCTS_DIR; }; + F608A8F62826A470005C276B /* in_file.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = in_file.hpp; sourceTree = ""; }; + F608A8F72826A470005C276B /* out.inl */ = {isa = PBXFileReference; lastKnownFileType = text; path = out.inl; sourceTree = ""; }; + F608A8F82826A470005C276B /* Debug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Debug.h; sourceTree = ""; }; + F608A8F92826A470005C276B /* in_sajson.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = in_sajson.h; sourceTree = ""; }; + F608A8FA2826A470005C276B /* out.inl */ = {isa = PBXFileReference; lastKnownFileType = text; path = out.inl; sourceTree = ""; }; + F608A8FB2826A471005C276B /* in.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = in.h; sourceTree = ""; }; + F608A8FC2826A471005C276B /* IO_bin.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IO_bin.cpp; sourceTree = ""; }; + F608A8FD2826A471005C276B /* in_file.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = in_file.hpp; sourceTree = ""; }; + F608A8FE2826A471005C276B /* out_file.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = out_file.hpp; sourceTree = ""; }; + F608A8FF2826A471005C276B /* out_file.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = out_file.hpp; sourceTree = ""; }; + F608A9002826A471005C276B /* IO.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IO.h; sourceTree = ""; }; + F608A9012826A471005C276B /* json.inl */ = {isa = PBXFileReference; lastKnownFileType = text; path = json.inl; sourceTree = ""; }; + F608A9022826A471005C276B /* in_sajson.inl */ = {isa = PBXFileReference; lastKnownFileType = text; path = in_sajson.inl; sourceTree = ""; }; + F608A9032826A471005C276B /* out.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = out.h; sourceTree = ""; }; + F608A9042826A471005C276B /* IO_json.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IO_json.cpp; sourceTree = ""; }; + F608A9062826A471005C276B /* IO.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IO.h; sourceTree = ""; }; + F608A9072826A471005C276B /* IO.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IO.h; sourceTree = ""; }; + F608A9082826A471005C276B /* out.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = out.h; sourceTree = ""; }; + F608A9092826A471005C276B /* in.inl */ = {isa = PBXFileReference; lastKnownFileType = text; path = in.inl; sourceTree = ""; }; + F608A9782826B5D5005C276B /* Core_IO_Tests */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Core_IO_Tests; sourceTree = BUILT_PRODUCTS_DIR; }; + F608A97A2826B5D5005C276B /* main.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; + F608AAF928271F4F005C276B /* libCore_Zero.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libCore_Zero.a; sourceTree = BUILT_PRODUCTS_DIR; }; + F608AB672827F3B1005C276B /* Makefile.def */ = {isa = PBXFileReference; lastKnownFileType = text; path = Makefile.def; sourceTree = ""; }; + F608AB682827F3B1005C276B /* Makefile.project */ = {isa = PBXFileReference; lastKnownFileType = text; path = Makefile.project; sourceTree = ""; }; + F61DB8F428580CCF00B74C99 /* Exception.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Exception.h; sourceTree = ""; }; + F61F9BD62C6E0EE800F79137 /* Ptr+IO.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "Ptr+IO.cpp"; sourceTree = ""; }; + F61F9BD82C6E0F2A00F79137 /* ConstString+IO.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "ConstString+IO.cpp"; sourceTree = ""; }; + F61F9BDF2C6E2DD200F79137 /* libCore_Allocator.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libCore_Allocator.a; sourceTree = BUILT_PRODUCTS_DIR; }; + F61F9BF42C6E4FAD00F79137 /* Makefile.project */ = {isa = PBXFileReference; lastKnownFileType = text; path = Makefile.project; sourceTree = ""; }; + F62B987828D60EF700ACC388 /* sajson.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sajson.h; sourceTree = ""; }; + F62B987B28D618E800ACC388 /* libCore_Misc.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libCore_Misc.a; sourceTree = BUILT_PRODUCTS_DIR; }; + F62B987D28D61A7300ACC388 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; }; + F62DCFDE2A69F9B9001E4F2C /* dictionary.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dictionary.h; sourceTree = ""; }; + F633E94D2916DB18007A4C26 /* IO_bin_versioning.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IO_bin_versioning.cpp; sourceTree = ""; }; + F64D834B2E4B6A1F009AD431 /* json.inl */ = {isa = PBXFileReference; lastKnownFileType = text; path = json.inl; sourceTree = ""; }; + F6757C802BD3EDA600B22032 /* IO_encoder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IO_encoder.h; sourceTree = ""; }; + F6757C812BD3EDC500B22032 /* IO_dispatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IO_dispatch.h; sourceTree = ""; }; + F695488728288532005D1B64 /* IO.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IO.cpp; sourceTree = ""; }; + F6971F2A282B1171008FBD17 /* libCore_IO_iOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCore_IO_iOS.a; sourceTree = BUILT_PRODUCTS_DIR; }; + F69AB7332B1579A9000115BC /* out_fwd.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = out_fwd.h; sourceTree = ""; }; + F69AB7342B157A54000115BC /* in_fwd.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = in_fwd.h; sourceTree = ""; }; + F6ACACB62B65FCD400581C16 /* sajson-save.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "sajson-save.h"; sourceTree = ""; }; + F6BF9BE62E39022F002E6AF0 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + F6F10D1D2E37C2750082E9D9 /* IO_.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IO_.h; sourceTree = ""; }; + F6F858C42E7C2F6E002A1971 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + F6F858C62E7C3125002A1971 /* ReadMe.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ReadMe.md; sourceTree = ""; }; + F6FC229D284F736500A6381D /* Notes.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = Notes.txt; sourceTree = ""; }; + F6FD255D2E5E0EC300C2380E /* calls.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = calls.hpp; sourceTree = ""; }; + F6FD255E2E5E0F2400C2380E /* IO+config.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "IO+config.h"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + F608A8EB2826A412005C276B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F608A9752826B5D5005C276B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F62B987E28D61A7300ACC388 /* libresolv.tbd in Frameworks */, + F61F9BE02C6E2DD200F79137 /* libCore_Allocator.a in Frameworks */, + F62B987C28D618E800ACC388 /* libCore_Misc.a in Frameworks */, + F608AAFA28271F4F005C276B /* libCore_Zero.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F6971F26282B1171008FBD17 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + F608A8E42826A411005C276B = { + isa = PBXGroup; + children = ( + F608A97F2826B5F0005C276B /* Frameworks */, + F6F858C42E7C2F6E002A1971 /* LICENSE */, + F6BF9BE62E39022F002E6AF0 /* Makefile */, + F608AB672827F3B1005C276B /* Makefile.def */, + F608AB682827F3B1005C276B /* Makefile.project */, + F608A8EE2826A412005C276B /* Products */, + F6F858C62E7C3125002A1971 /* ReadMe.md */, + F608A9822826B613005C276B /* tests */, + F61D7C432E381697002A1AED /* tjp */, + ); + sourceTree = ""; + }; + F608A8EE2826A412005C276B /* Products */ = { + isa = PBXGroup; + children = ( + F608A8ED2826A412005C276B /* libCore_IO.a */, + F608A9782826B5D5005C276B /* Core_IO_Tests */, + F6971F2A282B1171008FBD17 /* libCore_IO_iOS.a */, + ); + name = Products; + sourceTree = ""; + }; + F608A8F42826A422005C276B /* core */ = { + isa = PBXGroup; + children = ( + F62B987728D60EF700ACC388 /* json */, + F608A8F52826A42A005C276B /* io */, + ); + path = core; + sourceTree = ""; + }; + F608A8F52826A42A005C276B /* io */ = { + isa = PBXGroup; + children = ( + F662436C2E4552CE00859693 /* json */, + F662436B2E4552C800859693 /* bin */, + F6FC229D284F736500A6381D /* Notes.txt */, + F61DB8F428580CCF00B74C99 /* Exception.h */, + F6F10D1D2E37C2750082E9D9 /* IO_.h */, + F6757C802BD3EDA600B22032 /* IO_encoder.h */, + F6757C812BD3EDC500B22032 /* IO_dispatch.h */, + F695488728288532005D1B64 /* IO.cpp */, + F6FD255D2E5E0EC300C2380E /* calls.hpp */, + F6FD255E2E5E0F2400C2380E /* IO+config.h */, + F608A9072826A471005C276B /* IO.h */, + ); + path = io; + sourceTree = ""; + }; + F608A97F2826B5F0005C276B /* Frameworks */ = { + isa = PBXGroup; + children = ( + F61F9BDF2C6E2DD200F79137 /* libCore_Allocator.a */, + F62B987D28D61A7300ACC388 /* libresolv.tbd */, + F62B987B28D618E800ACC388 /* libCore_Misc.a */, + F608AAF928271F4F005C276B /* libCore_Zero.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + F608A9822826B613005C276B /* tests */ = { + isa = PBXGroup; + children = ( + F61F9BF42C6E4FAD00F79137 /* Makefile.project */, + F608A97A2826B5D5005C276B /* main.cpp */, + ); + path = tests; + sourceTree = ""; + }; + F61D7C432E381697002A1AED /* tjp */ = { + isa = PBXGroup; + children = ( + F608A8F42826A422005C276B /* core */, + ); + path = tjp; + sourceTree = ""; + }; + F61F9BD52C6DE0CE00F79137 /* _tests */ = { + isa = PBXGroup; + children = ( + F608A8FC2826A471005C276B /* IO_bin.cpp */, + F61F9BD62C6E0EE800F79137 /* Ptr+IO.cpp */, + F61F9BD82C6E0F2A00F79137 /* ConstString+IO.cpp */, + F633E94D2916DB18007A4C26 /* IO_bin_versioning.cpp */, + ); + path = _tests; + sourceTree = ""; + }; + F62B987728D60EF700ACC388 /* json */ = { + isa = PBXGroup; + children = ( + F6ACACB62B65FCD400581C16 /* sajson-save.h */, + F62B987828D60EF700ACC388 /* sajson.h */, + ); + path = json; + sourceTree = ""; + }; + F662436B2E4552C800859693 /* bin */ = { + isa = PBXGroup; + children = ( + F61F9BD52C6DE0CE00F79137 /* _tests */, + F608A8F82826A470005C276B /* Debug.h */, + F69AB7332B1579A9000115BC /* out_fwd.h */, + F62DCFDE2A69F9B9001E4F2C /* dictionary.h */, + F608A8FD2826A471005C276B /* in_file.hpp */, + F608A8FB2826A471005C276B /* in.h */, + F69AB7342B157A54000115BC /* in_fwd.h */, + F608A9092826A471005C276B /* in.inl */, + F608A9012826A471005C276B /* json.inl */, + F608A8FE2826A471005C276B /* out_file.hpp */, + F608A9082826A471005C276B /* out.h */, + F608A8FA2826A470005C276B /* out.inl */, + F608A9062826A471005C276B /* IO.h */, + ); + path = bin; + sourceTree = ""; + }; + F662436C2E4552CE00859693 /* json */ = { + isa = PBXGroup; + children = ( + F662436D2E455EB700859693 /* _tests */, + F608A8F62826A470005C276B /* in_file.hpp */, + F608A8F92826A470005C276B /* in_sajson.h */, + F608A9022826A471005C276B /* in_sajson.inl */, + F608A8FF2826A471005C276B /* out_file.hpp */, + F64D834B2E4B6A1F009AD431 /* json.inl */, + F608A9032826A471005C276B /* out.h */, + F608A8F72826A470005C276B /* out.inl */, + F608A9002826A471005C276B /* IO.h */, + ); + path = json; + sourceTree = ""; + }; + F662436D2E455EB700859693 /* _tests */ = { + isa = PBXGroup; + children = ( + F608A9042826A471005C276B /* IO_json.cpp */, + ); + path = _tests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + F608A8E92826A412005C276B /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + F62B987928D60EF700ACC388 /* sajson.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F6971F23282B1171008FBD17 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + F62B987A28D60EF700ACC388 /* sajson.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + F608A8EC2826A412005C276B /* Core_IO */ = { + isa = PBXNativeTarget; + buildConfigurationList = F608A8F12826A412005C276B /* Build configuration list for PBXNativeTarget "Core_IO" */; + buildPhases = ( + F608A8E92826A412005C276B /* Headers */, + F608A8EA2826A412005C276B /* Sources */, + F608A8EB2826A412005C276B /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Core_IO; + productName = Core_IO; + productReference = F608A8ED2826A412005C276B /* libCore_IO.a */; + productType = "com.apple.product-type.library.static"; + }; + F608A9772826B5D5005C276B /* Core_IO_Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = F608A97E2826B5D5005C276B /* Build configuration list for PBXNativeTarget "Core_IO_Tests" */; + buildPhases = ( + F608A9742826B5D5005C276B /* Sources */, + F608A9752826B5D5005C276B /* Frameworks */, + F608A9762826B5D5005C276B /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + F608AAF828271F30005C276B /* PBXTargetDependency */, + ); + name = Core_IO_Tests; + productName = Core_IO_Tests; + productReference = F608A9782826B5D5005C276B /* Core_IO_Tests */; + productType = "com.apple.product-type.tool"; + }; + F6971F22282B1171008FBD17 /* Core_IO_iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = F6971F27282B1171008FBD17 /* Build configuration list for PBXNativeTarget "Core_IO_iOS" */; + buildPhases = ( + F6971F23282B1171008FBD17 /* Headers */, + F6971F24282B1171008FBD17 /* Sources */, + F6971F26282B1171008FBD17 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Core_IO_iOS; + productName = Core_IO; + productReference = F6971F2A282B1171008FBD17 /* libCore_IO_iOS.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F608A8E52826A411005C276B /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + DefaultBuildSystemTypeForWorkspace = Original; + LastUpgradeCheck = 1330; + TargetAttributes = { + F608A8EC2826A412005C276B = { + CreatedOnToolsVersion = 13.3; + }; + F608A9772826B5D5005C276B = { + CreatedOnToolsVersion = 13.3; + }; + }; + }; + buildConfigurationList = F608A8E82826A411005C276B /* Build configuration list for PBXProject "Core_IO" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = F608A8E42826A411005C276B; + productRefGroup = F608A8EE2826A412005C276B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F608A8EC2826A412005C276B /* Core_IO */, + F6971F22282B1171008FBD17 /* Core_IO_iOS */, + F608A9772826B5D5005C276B /* Core_IO_Tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + F608A8EA2826A412005C276B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F69548882828853E005D1B64 /* IO.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F608A9742826B5D5005C276B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F60A19B1284579080040CD24 /* IO_json.cpp in Sources */, + F633E94E2916E027007A4C26 /* IO_bin_versioning.cpp in Sources */, + F61F9BD92C6E0F2A00F79137 /* ConstString+IO.cpp in Sources */, + F608A97B2826B5D5005C276B /* main.cpp in Sources */, + F608A9812826B5FA005C276B /* IO_bin.cpp in Sources */, + F61F9BD72C6E0EE800F79137 /* Ptr+IO.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F6971F24282B1171008FBD17 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F6971F25282B1171008FBD17 /* IO.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + F608AAF828271F30005C276B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F608A8EC2826A412005C276B /* Core_IO */; + targetProxy = F608AAF728271F30005C276B /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + F608A8EF2826A412005C276B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + ../Core_Zero, + ../Core_Misc, + ../Core_Allocator, + ); + IPHONEOS_DEPLOYMENT_TARGET = 13.6; + LIBRARY_SEARCH_PATHS = ../Libraries/project/Debug.osx; + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ../Libraries/project/Debug.ios; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + USE_HEADERMAP = NO; + }; + name = Debug; + }; + F608A8F02826A412005C276B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_SYMBOLS_PRIVATE_EXTERN = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + ../Core_Zero, + ../Core_Misc, + ../Core_Allocator, + ); + IPHONEOS_DEPLOYMENT_TARGET = 13.6; + LIBRARY_SEARCH_PATHS = ../Libraries/project/Release.osx; + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ../Libraries/project/Release.ios; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + USE_HEADERMAP = NO; + }; + name = Release; + }; + F608A8F22826A412005C276B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = T2M28D3T75; + EXECUTABLE_PREFIX = lib; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + F608A8F32826A412005C276B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = T2M28D3T75; + EXECUTABLE_PREFIX = lib; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + F608A97C2826B5D5005C276B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = T2M28D3T75; + ENABLE_HARDENED_RUNTIME = YES; + HEADER_SEARCH_PATHS = ( + "${inherited}", + ../Core_IO, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + F608A97D2826B5D5005C276B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = T2M28D3T75; + ENABLE_HARDENED_RUNTIME = YES; + HEADER_SEARCH_PATHS = ( + "${inherited}", + ../Core_IO, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + F6971F28282B1171008FBD17 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = T2M28D3T75; + EXECUTABLE_PREFIX = lib; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + F6971F29282B1171008FBD17 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = T2M28D3T75; + EXECUTABLE_PREFIX = lib; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + F608A8E82826A411005C276B /* Build configuration list for PBXProject "Core_IO" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F608A8EF2826A412005C276B /* Debug */, + F608A8F02826A412005C276B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F608A8F12826A412005C276B /* Build configuration list for PBXNativeTarget "Core_IO" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F608A8F22826A412005C276B /* Debug */, + F608A8F32826A412005C276B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F608A97E2826B5D5005C276B /* Build configuration list for PBXNativeTarget "Core_IO_Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F608A97C2826B5D5005C276B /* Debug */, + F608A97D2826B5D5005C276B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F6971F27282B1171008FBD17 /* Build configuration list for PBXNativeTarget "Core_IO_iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F6971F28282B1171008FBD17 /* Debug */, + F6971F29282B1171008FBD17 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = F608A8E52826A411005C276B /* Project object */; +} diff --git a/Core_IO.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Core_IO.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Core_IO.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Core_IO.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Core_IO.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Core_IO.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Core_IO.xcodeproj/project.xcworkspace/xcuserdata/tprepscius.xcuserdatad/UserInterfaceState.xcuserstate b/Core_IO.xcodeproj/project.xcworkspace/xcuserdata/tprepscius.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..07f709b13b0c7c2cbcce15242bc950034f6c348c GIT binary patch literal 36798 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 literal 0 HcmV?d00001 diff --git a/Core_IO.xcodeproj/xcshareddata/xcschemes/Core_IO_Tests.xcscheme b/Core_IO.xcodeproj/xcshareddata/xcschemes/Core_IO_Tests.xcscheme new file mode 100644 index 0000000..12eed22 --- /dev/null +++ b/Core_IO.xcodeproj/xcshareddata/xcschemes/Core_IO_Tests.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core_IO.xcodeproj/xcuserdata/tprepscius.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Core_IO.xcodeproj/xcuserdata/tprepscius.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..b47eb0c --- /dev/null +++ b/Core_IO.xcodeproj/xcuserdata/tprepscius.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/Core_IO.xcodeproj/xcuserdata/tprepscius.xcuserdatad/xcschemes/xcschememanagement.plist b/Core_IO.xcodeproj/xcuserdata/tprepscius.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..911d723 --- /dev/null +++ b/Core_IO.xcodeproj/xcuserdata/tprepscius.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,37 @@ + + + + + SchemeUserState + + Core_IO copy.xcscheme_^#shared#^_ + + orderHint + 76 + + Core_IO.xcscheme_^#shared#^_ + + orderHint + 22 + + Core_IO_Tests.xcscheme_^#shared#^_ + + orderHint + 3 + + Core_IO_iOS.xcscheme_^#shared#^_ + + orderHint + 19 + + + SuppressBuildableAutocreation + + F608A9772826B5D5005C276B + + primary + + + + + diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..1a747cb --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +MIT NON-AI License + +Copyright (c) 2025, Timothy Prepscius + +Permission is hereby granted, free of charge, to any person obtaining a copy of the software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions. + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +In addition, the following restrictions apply: + +1. The Software and any modifications made to it may not be used for the purpose of training or improving machine learning algorithms, +including but not limited to artificial intelligence, natural language processing, or data mining. This condition applies to any derivatives, +modifications, or updates based on the Software code. Any usage of the Software in an AI-training dataset is considered a breach of this License. + +2. The Software may not be included in any dataset used for training or improving machine learning algorithms, +including but not limited to artificial intelligence, natural language processing, or data mining. + +3. Any person or organization found to be in violation of these restrictions will be subject to legal action and may be held liable +for any damages resulting from such use. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f61a565 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +ROOTDIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))..) +include $(ROOTDIR)/Core_Make/tjp/Make/Makefile diff --git a/Makefile.def b/Makefile.def new file mode 100755 index 0000000..d31814e --- /dev/null +++ b/Makefile.def @@ -0,0 +1,6 @@ +timprepscius.core.include := $(timprepscius.core.include) -I $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) +timprepscius.core.link := $(timprepscius.core.link) -L $(dir $(realpath $(lastword $(MAKEFILE_LIST))))/.bin/$(OBJDIR) + +timprepscius.core_io.include := -I $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) +timprepscius.core_io.link := -L $(dir $(realpath $(lastword $(MAKEFILE_LIST))))/.bin/$(OBJDIR) + diff --git a/Makefile.project b/Makefile.project new file mode 100755 index 0000000..de2525d --- /dev/null +++ b/Makefile.project @@ -0,0 +1,15 @@ +include $(MAKEDIR)/Makefile.base + +# use: ls -d tjp/core/*/ tjp/core/*/*/ | rev | cut -c 2- | rev | sed 's/$/ \\/' + +PROJECTS := \ + tjp/core/io \ + tjp/core/json \ + +#SRC_PCH := tjp/core/Precompile.pch +INCPATH := $(timprepscius.libraries.cpp.include) +LIBFILE := libCore_IO.a + +COPYTO := $(LIBRARIES_PROJECT) + +include $(MAKEDIR)/Makefile.lib diff --git a/ReadMe.md b/ReadMe.md new file mode 100755 index 0000000..28e9e63 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,157 @@ +# Core_IO + +IO foundational classes + +IO classes which optimize super well, super fast, super memory efficient. +JSON is based on sajson which is nearly as fast as memcpy (which blows my mind). + +## Building + +IO is header only. + +## How to use + +### Declare your data + +``` + +namespace my_namespace { + +struct MyOtherData { + float f; +} ; + +struct MyData { + char c; + int i; + std::string s; + MyOtherData o; +} ; + +} // my_namespace + +``` + +### Somewhere in the same namespace, declare the serializer: + +``` + +namespace my_namespace { + +template +void io_(IO &io, MyOtherData &v) +{ + io.object( + "f", v.f + ); +) + + +template +void io_(IO &io, MyData &v) +{ + io.object( + "c", v.c, + "i", v.i, + "s", v.s, + "o", v.o + ); +) + +} // my_namespace + +``` + +### To serialize and deserialize as json: + +``` + +#include +#include + +namespace any_namespace { + +void somewhere () +{ + my_namespace::MyData myData; + auto serialized = tjp::core::io::json::serialize(myData); + auto deserialized = tjp::core::io::json::deserialize(serialized); +} + +} // any_namespace + +``` + +### To serialize and deserialize as binary: + +``` +#include +#include + +namespace any_namespace { + +void somewhere () +{ + my_namespace::MyData myData; + auto serialized = tjp::core::io::bin::serialize(myData); + auto deserialized = tjp::core::io::bin::deserialize(serialized); +} + +} // any_namespace + +``` + +### To do versioning + +I use versioning, however I declared it outside the scope of the library. + +The method I ended up using was to deserialize a version number at the beginning of a +deserialization and store in the IO class. Then when deserializing data which is versioned, +check the version number and dispatch to the appropriate function. + +It ends up looking like this: + +``` +template +void io_(IO &io, TimeSpecification &v, serializer::Version<100> &&) +{ + io.object( + "clock", v.clock, + "bounds", v.bounds + ); +} + +template +void io_(IO &io, TimeSpecification &v, serializer::Version<104> &&) +{ + io.object( + "clock", v.clock, + "bounds", v.bounds, + "flags", v.flags + ); +} + + +template +void io_(IO &io, TimeSpecification &v) +{ + if (io.version.my_version_number < 104) + return io_(io, v, serializer::Version<104>{}); + + return io_(io, v, serializer::Version<104>{}); +} +``` + + +### + +You can make your own serialization IO by implementing a new IO class + + +It's not trivial, but it's not hard. + +Generally, I'll be putting in some time to clean up these IO classes in the end of 2025. + +### Other examples + +Look at `Core_IO/bin/_tests/IO_bin.cpp` for more examples on how to use this library. diff --git a/tests/Core_IO_Tests.txt b/tests/Core_IO_Tests.txt new file mode 100644 index 0000000..318d800 --- /dev/null +++ b/tests/Core_IO_Tests.txt @@ -0,0 +1,2 @@ +20240816_17:25:39.857162 1eefafac0 | [testing] ius::core::log::Logs::activate logging activated +20240816_17:25:39.857224 1eefafac0 | [debug] ius::core::log::Logs::activate logging activated diff --git a/tests/Makefile b/tests/Makefile new file mode 120000 index 0000000..b8f87c1 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1 @@ +../../Make/Makefile \ No newline at end of file diff --git a/tests/Makefile.project b/tests/Makefile.project new file mode 100755 index 0000000..6f14818 --- /dev/null +++ b/tests/Makefile.project @@ -0,0 +1,32 @@ +include $(MAKEDIR)/Makefile.base + +# use: ls -d core/*/ core/*/*/ | rev | cut -c 2- | rev | sed 's/$/ \\/' + +PROJECTS := \ + . \ + ../core/io/_tests \ + + +INCPATH := \ + $(timprepscius.libraries.cpp.include) \ + $(timprepscius.core.include) + +LDPATH := $(timprepscius.libraries.cpp.link) + +LIBS := \ + -lCore_Zero \ + -lCore_Misc \ + -lCore_IO \ + -lCore_Allocator \ + -lz_ + +EXEFILE := Core_IO_Tests.exe + +#COPYTO := $(ROOTDIR)/.bin + +ifeq (Darwin,$(SYS_NAME)) + LIBS += -lc++ +endif + + +include $(MAKEDIR)/Makefile.bin diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..d85177b --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,21 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#define CATCH_CONFIG_RUNNER +#include + +#include + +using namespace tjp; +using namespace core; + +int main( int argc, char* argv[] ) +{ + xLogInitialize("Core_IO_Tests.txt"); + xLogActivateStory("testing"); + xLogActivateStory("debug"); + + int result = Catch::Session().run( argc, argv ); + return result; +} diff --git a/tjp/core/io/Exception.h b/tjp/core/io/Exception.h new file mode 100644 index 0000000..13e0027 --- /dev/null +++ b/tjp/core/io/Exception.h @@ -0,0 +1,14 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include + +namespace tjp::core::io { + +DECLARE_EXCEPTION(Exception); + +} // namespace + diff --git a/tjp/core/io/IO+config.h b/tjp/core/io/IO+config.h new file mode 100755 index 0000000..d4f74b5 --- /dev/null +++ b/tjp/core/io/IO+config.h @@ -0,0 +1,56 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#define IO_USE_NON_RECURSIVE_APPLY + +#ifdef IO_USE_NON_RECURSIVE_APPLY +#include "calls.hpp" +#endif + +namespace tjp::core::io { + +#ifdef IO_USE_NON_RECURSIVE_APPLY + +template +void call_expand_1( + F &&f, + Args && ...args +) +{ + call_f_1s(std::forward(f), std::forward(args)...); +} + +template +void call_expand_2( + F &&f, + Args && ...args +) +{ + call_f_2s(std::forward(f), std::forward(args)...); +} + +#else + +template +void call_expand_1(F &&f, A &&a Args&& ...args) +{ + f(std::forward(a)); + + call_expand_2(std::forward(f), std::forward(args)...); +} + +template +void call_expand_2(F &&f, A &&a, B &&b, Args&& ...args) +{ + f(std::forward(a), std::forward(b)); + + call_expand_2(std::forward(f), std::forward(args)...); +} + +#endif + +} + diff --git a/tjp/core/io/IO.cpp b/tjp/core/io/IO.cpp new file mode 100644 index 0000000..c61b69d --- /dev/null +++ b/tjp/core/io/IO.cpp @@ -0,0 +1,4 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + diff --git a/tjp/core/io/IO.h b/tjp/core/io/IO.h new file mode 100755 index 0000000..d877fa7 --- /dev/null +++ b/tjp/core/io/IO.h @@ -0,0 +1,49 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include + +namespace tjp::core { + +//namespace variant { struct Variant; } + +template +void io_dispatch_failed(IO &io, T &t) +{ + static_assert(always_false::value, "io_ or (io_w and io_r) must be defined for non trivial types"); +} + +template +void io_(IO &io, T &t) +{ + io.on_dispatch_any(t); +} + +template +void io_w(IO &io, const T &t) +{ + io_(io, const_cast(t)); +} + +template +void io_w_(IO &io, const T &t) +{ + io_w(io, t); +} + +template +void io_r(IO &io, T &t) +{ + io_(io, t); +} + +template +void io_r_(IO &io, T &t) +{ + io_r(io, t); +} + +} // namespace diff --git a/tjp/core/io/IO_.h b/tjp/core/io/IO_.h new file mode 100644 index 0000000..98ffdc8 --- /dev/null +++ b/tjp/core/io/IO_.h @@ -0,0 +1,17 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +namespace tjp { +namespace core { + +template +void io_resize_vector(T &t, size_t size) +{ + t.resize(size); +} + +} // +} // diff --git a/tjp/core/io/IO_dispatch.h b/tjp/core/io/IO_dispatch.h new file mode 100755 index 0000000..033a3ae --- /dev/null +++ b/tjp/core/io/IO_dispatch.h @@ -0,0 +1,23 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include "IO.h" + +namespace tjp::core { + +template +struct DispatchIO +{ + T *t; +} ; + +template +DispatchIO makeDispatchIO(T &t) +{ + return DispatchIO{&t}; +} + +} // namespace diff --git a/tjp/core/io/IO_encoder.h b/tjp/core/io/IO_encoder.h new file mode 100755 index 0000000..c65cb3b --- /dev/null +++ b/tjp/core/io/IO_encoder.h @@ -0,0 +1,112 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include "IO.h" +#include +#include + +#include +#include +#include + +namespace tjp::core { + +template +struct BinaryEncoder +{ + T *t; +} ; + +template +BinaryEncoder makeBinaryEncoder(T &t) +{ + return BinaryEncoder{&t}; +} + +template +struct StringEncoder +{ + T *t; +} ; + +template +StringEncoder makeStringEncoder(T &t) +{ + return StringEncoder{&t}; +} + +template +void io_bin_r(IO &io, BinaryEncoder &v) +{ + io.any(*v.t); +} + +template +void io_bin_w(IO &io, const BinaryEncoder &v) +{ + io.any(*v.t); +} + +template +void io_bin_r(IO &io, StringEncoder &v) +{ + io.any(*v.t); +} + +template +void io_bin_w(IO &io, const StringEncoder &v) +{ + io.any(*v.t); +} + +template +void io_r(IO &io, BinaryEncoder &v) +{ + auto t = v.t; + + using U = std::remove_reference_tdata())>; + static_assert(std::is_fundamental_v == true); + + String s; + io.any(s); + auto size_byte = base64DecodeSize(s.size()); + auto size = size_byte / sizeof(U); + + t->resize(size); + fromBase64(s, (char *)t->data(), size_byte); +} + +template +void io_w(IO &io, const BinaryEncoder &v) +{ + auto t = v.t; + + using U = std::remove_reference_tdata())>; + static_assert(std::is_fundamental_v == true); + + auto size_byte = sizeof(U) * t->size(); + io.any(toBase64((const char *)t->data(), size_byte)); +} + +template +void io_r(IO &io, StringEncoder &v) +{ + String s; + io.any(s); + + auto t = v.t; + core::from_string(*t, s); +} + +template +void io_w(IO &io, const StringEncoder &v) +{ + auto t = v.t; + io.any(core::to_string(*t)); +} + + +} // namespace diff --git a/tjp/core/io/Notes.txt b/tjp/core/io/Notes.txt new file mode 100644 index 0000000..928236b --- /dev/null +++ b/tjp/core/io/Notes.txt @@ -0,0 +1,7 @@ +I want to put a checkSize when I read a size, to verify that the readSize is reasonable + +Or - I want to check every read before I do it. + +It should just be f.validateReadSize(s) +and be used everywhere. + diff --git a/tjp/core/io/bin/Debug.h b/tjp/core/io/bin/Debug.h new file mode 100644 index 0000000..309ba3a --- /dev/null +++ b/tjp/core/io/bin/Debug.h @@ -0,0 +1,13 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +//#define IO_BIN_USE_LOGGING + +#ifdef IO_BIN_USE_LOGGING +#define IO_BIN_LOG(...) sLogRelease("io_bin", x) +#else +#define IO_BIN_LOG(...) +#endif diff --git a/tjp/core/io/bin/IO.h b/tjp/core/io/bin/IO.h new file mode 100644 index 0000000..276ba83 --- /dev/null +++ b/tjp/core/io/bin/IO.h @@ -0,0 +1,357 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include "../IO.h" +#include "../IO_encoder.h" +#include "../IO_dispatch.h" +#include "../IO_.h" + +#include "Debug.h" + +#include "../Exception.h" +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + + +//#include + +#include + +#include "dictionary.h" + +namespace tjp::core::io::bin { + +using Size = u32; +typedef const char *KeyType; + +template +struct has_io_bin : std::false_type {}; + +template +struct has_io_bin(), + std::declval())) >> : std::true_type {}; + + +//template +//void io_bin(IO &io, T &t) +//{ +// io_(io, t); +//} +// +template +void io_bin_w_integer(IO &io, const T &t_) +{ + constexpr u8 sevenBits = 0b01111111; + constexpr u8 eighthBit = 0b10000000; + +// // check +// io.intrinsic(t_); + + constexpr size_t maxBytes = sizeof(T) + sizeof(T)/8 + 1; + u8 bytes[maxBytes]; + size_t count = 0; + u8 *out = bytes; + + const T zero = T(0); + T t = t_; + do + { + u8 byte = t & sevenBits; + t >>= 7; + if (t != zero) + byte |= eighthBit; + + *out++ = byte; + count++; + } + while (t > 0); + + debug_assert(0 < count && count <= maxBytes); + + io.intrinsic((const char *)bytes, count); +} + +template +void io_bin_r_integer(IO &io, T &t) +{ + constexpr u8 sevenBits = 0b01111111; + constexpr u8 eighthBit = 0b10000000; + +// T check; +// io.intrinsic(check); + + u8 reverse[sizeof(T) + sizeof(T)/8 + 1]; + u8 *r = reverse; + + t = 0; + u8 byte; + do + { + io.intrinsic(byte); + *r = byte; + ++r; + } + while ((byte & eighthBit) != 0); + + while (r != reverse) + { + --r; + t <<= 7; + t |= T(*r & sevenBits); + } +} + +template +void io_bin_w_integer_signed(IO &io, const T &t_) +{ + using UT = typename std::make_unsigned::type; + + UT t; + if (t_ < 0) + { + t = -t_; + t <<= 1; + t |= UT(1); + } + else + { + t = UT(t_) << 1; + } + + io_bin_w_integer(io, t); +} + +template +void io_bin_r_integer_signed(IO &io, T &t_) +{ + using UT = typename std::make_unsigned::type; + + UT t; + io_bin_r_integer(io, t); + + auto isNegative = t & 1; + t_ = t >> 1; + + if (isNegative) + t_ = -t_; +} + +template void io_bin(IO &io, bool &t) { io.intrinsic((s8&)t); } +template void io_bin(IO &io, u8 &t) { io.intrinsic(t); } +template void io_bin(IO &io, char &t) { io.intrinsic((s8&)t); } +template void io_bin(IO &io, s8 &t) { io.intrinsic(t); } + +/* +template void io_bin(IO &io, u16 &t) { io.intrinsic(t); } +template void io_bin(IO &io, u32 &t) { io.intrinsic(t); } +template void io_bin(IO &io, u64 &t) { io.intrinsic(t); } +template void io_bin(IO &io, u128 &t) { io.intrinsic(t); } + +template void io_bin(IO &io, s16 &t) { io.intrinsic(t); } +template void io_bin(IO &io, s32 &t) { io.intrinsic(t); } +template void io_bin(IO &io, s64 &t) { io.intrinsic(t); } +template void io_bin(IO &io, s128 &t) { io.intrinsic(t); } +*/ + +template void io_bin_w(IO &io, const u16 &t) { io_bin_w_integer(io, t); } +template void io_bin_w(IO &io, const u32 &t) { io_bin_w_integer(io, t); } +template void io_bin_w(IO &io, const u64 &t) { io_bin_w_integer(io, t); } +template void io_bin_w(IO &io, const u128 &t) { io_bin_w_integer(io, t); } + +template void io_bin_w(IO &io, const s16 &t) { io_bin_w_integer_signed(io, t); } +template void io_bin_w(IO &io, const s32 &t) { io_bin_w_integer_signed(io, t); } +template void io_bin_w(IO &io, const s64 &t) { io_bin_w_integer_signed(io, t); } +template void io_bin_w(IO &io, const s128 &t) { io_bin_w_integer_signed(io, t); } + +template void io_bin_r(IO &io, u16 &t) { io_bin_r_integer(io, t); } +template void io_bin_r(IO &io, u32 &t) { io_bin_r_integer(io, t); } +template void io_bin_r(IO &io, u64 &t) { io_bin_r_integer(io, t); } +template void io_bin_r(IO &io, u128 &t) { io_bin_r_integer(io, t); } + +template void io_bin_r(IO &io, s16 &t) { io_bin_r_integer_signed(io, t); } +template void io_bin_r(IO &io, s32 &t) { io_bin_r_integer_signed(io, t); } +template void io_bin_r(IO &io, s64 &t) { io_bin_r_integer_signed(io, t); } +template void io_bin_r(IO &io, s128 &t) { io_bin_r_integer_signed(io, t); } + + +template +void io_bin_w_real(IO &io, const T &t_) +{ + bool downcast = false; // io.flags & IO_FLAGS_DOWNCAST_REAL; + if (downcast) + { + io.intrinsic(static_cast(t_)); + } + else + { + io.intrinsic(t_); + } +} + +template +void io_bin_r_real(IO &io, T &t) +{ + bool downcast = false; // io.flags & IO_FLAGS_DOWNCAST_REAL; + if (downcast) + { + r32 t_; + io.intrinsic(t_); + t = static_cast(t_); + } + else + { + io.intrinsic(t); + } +} + +template void io_bin_r(IO &io, r32 &t) { io_bin_r_real(io, t); } +template void io_bin_r(IO &io, r64 &t) { io_bin_r_real(io, t); } +template void io_bin_r(IO &io, r128 &t) { io_bin_r_real(io, t); } + +template void io_bin_w(IO &io, const r32 &t) { io_bin_w_real(io, t); } +template void io_bin_w(IO &io, const r64 &t) { io_bin_w_real(io, t); } +template void io_bin_w(IO &io, const r128 &t) { io_bin_w_real(io, t); } + + +template void io_bin(IO &io, std::string &t) { io.intrinsic_string(t); } + +#if defined(SYS_MAC) || defined(SYS_IOS) +/* +template void io_bin(IO &io, unsigned long &t) { io.intrinsic(t); } +template void io_bin(IO &io, signed long &t) { io.intrinsic(t); } +*/ + +template void io_bin_w(IO &io, const unsigned long &t) { io_bin_w_integer(io, t); } +template void io_bin_w(IO &io, const signed long &t) { io_bin_w_integer(io, t); } + +template void io_bin_r(IO &io, unsigned long &t) { io_bin_r_integer(io, t); } +template void io_bin_r(IO &io, signed long &t) { io_bin_r_integer(io, t); } + + +#endif + +template +struct Optional +{ + T &v; + bool write; +} ; + +template +Optional optional(T &t, bool write=true) { return Optional { t, write }; } + +template +void io_bin(IO &io, Optional &t) { + io.any(t.write); + if (t.write) + io.any(t.v); +} + +template +struct Pointer +{ + P *p; + bool write; +} ; + +template +void io_ptr_allocate(IO &io, T *&t) +{ + io.allocate(t); +} + +template +void io_ptr_allocate_default(IO &io, T *&t) +{ + t = new T(); +} + +template void io_bin(IO &io, T *&t) +{ + auto w = io.template pointer(t); + io.any(w); +} + +template +void io_bin_w(IO &io, const Pointer &t) +{ + io.any(t.write); + + if (t.write) + io.any(**t.p); +} + +template +void io_bin_r(IO &io, Pointer &t) +{ + io.any(t.write); + if (t.write) + { + io_ptr_allocate(io, *t.p); + io.any(**t.p); + } +} + +typedef std::vector Serialization; + +template +struct Custom +{ + T *p; +} ; + +template +void io_bin(IO &io, Custom &t) +{ + io.template dispatch_custom<>(io, *t.p); +} + +template +void io_bin(IO &io, std::tuple &t) +{ + std::apply([&io](auto &...x){(io.any(x), ...);}, t); +} + +//#ifdef SYS_APPLE +// std::vector fixes + +template +void io_bin_w(IO &io, const std::vector::const_iterator::reference t) +{ + bool b = t; + io.intrinsic(b); +} + +template +void io_bin_r(IO &io, std::vector::iterator::reference &t) +{ + bool b; + io.intrinsic(b); + t = b; +} + +//#endif + +} // namespace + +#include "in.h" +#include "out.h" diff --git a/tjp/core/io/bin/_tests/ConstString+IO.cpp b/tjp/core/io/bin/_tests/ConstString+IO.cpp new file mode 100644 index 0000000..fdee3bb --- /dev/null +++ b/tjp/core/io/bin/_tests/ConstString+IO.cpp @@ -0,0 +1,105 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#include +#include + +#include + +#include +#include +#include + +#include + +namespace tjp::core::str { +namespace { + +SCENARIO("core::string::str+io") +{ + GIVEN("args") + { + core::from_string("1"); + } + GIVEN("a const string") + { + Str one("1"); + Str one_again("1"); + Str two("2"); + + std::string v; + for (auto i=0;i<256;++i) + v += "a"; + + Str longString(v); + + REQUIRE(one == one); + REQUIRE(one == "1"); + REQUIRE(one != "2"); + REQUIRE(one != two); + REQUIRE(one == one_again); + REQUIRE(longString == longString); + REQUIRE(longString != one); + } + + GIVEN("a const string") + { + Str a("Aweoiwefo wefoij wefioj wefoi jwefo ijwefijo w"); + Str b("Bweoiwefo wefoij wefioj wefoi jwefo ijwefijo w wefiojwefoi jwefio wefoij wefoij wefijo ewfioj wefjio wef"); + Str c("Cweo"); + + WHEN("bin") + { + auto s = io::bin::serialize(a); + auto b = io::bin::deserialize(s); + + REQUIRE(a == b); + } + + WHEN("json") + { + auto s = io::json::serialize(a); + auto b = io::json::deserialize(s); + + REQUIRE(a == b); + } + + WHEN("bin multi") + { + io::bin::Writer o; + o.object("a", a, "b", b, "c", c); + auto s = o.serialization(); + + io::bin::Reader i(io::bin::memory_in(s.data(), s.size())); + + Str a_, b_, c_; + i.object("a", a_, "b", b_, "c", c_); + + REQUIRE(a == a_); + REQUIRE(b == b_); + REQUIRE(c == c_); + } + + WHEN("json multi") + { + io::json::Serialization s; + omemstream stream(s); + + io::json::Writer o(stream); + o.object("a", a, "b", b, "c", c); + + io::json::Reader i(std::move(s)); + + Str a_, b_, c_; + i.object("a", a_, "b", b_, "c", c_); + + REQUIRE(a == a_); + REQUIRE(b == b_); + REQUIRE(c == c_); + } + } +} + +} // namespace +} // namespace diff --git a/tjp/core/io/bin/_tests/IO_bin.cpp b/tjp/core/io/bin/_tests/IO_bin.cpp new file mode 100644 index 0000000..bad1c2e --- /dev/null +++ b/tjp/core/io/bin/_tests/IO_bin.cpp @@ -0,0 +1,361 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#include + +#include + +namespace tjp::core { +namespace { + +struct VV : std::vector +{ +} ; + +template +void io_bin(IO &io, VV &v) +{ + std::string b = "b"; + io.any( + b + ); +}; + + +struct VS { + int x; +} ; + +bool operator ==(const VS &l, const VS &r) +{ + return l.x == r.x; +} + +std::ostream &operator <<(std::ostream &o, const VS &v) +{ + return o << v.x; +} + +std::istream &operator >>(std::istream &i, VS &v) +{ + return i >> v.x; +} + +struct A { + bool w; + int x; + std::string s; + + std::vector y; + std::vector> z; + std::string empty; + std::vector v_b; + VS v_s; + + bool operator ==(const A &rhs) const + { + return + w == rhs.w && + x == rhs.x && + y == rhs.y && + z == rhs.z && + v_b == rhs.v_b && + v_s == rhs.v_s; + } +} ; + +struct B { + int i; +} ; + +template +void io_bin(IO &io, A &a) +{ + io.object( + "w", a.w, "x", a.x, "y", a.y, "z", a.z, "s", a.s, "e", a.empty, + "v_b", makeBinaryEncoder(a.v_b), + "v_s", makeStringEncoder(a.v_s) + ); +} + +template +void io_bin(IO &io, VS &v) +{ + io.object( + "x", v.x + ); +} + +SCENARIO("io bin serialization", "[core::io]" ) +{ + GIVEN("maliscous size") + { + io::bin::Size s; + s = 10000; + auto a = io::bin::serialize(s); + REQUIRE_THROWS(io::bin::deserialize(a)); + REQUIRE_THROWS(io::bin::deserialize>(a)); + } + + GIVEN("a custom array with serializer") + { + auto v = VV(); + auto vs = io::bin::serialize(v); + std::string b = "b"; + auto vsi = io::bin::serialize(b); + + REQUIRE(vs == vsi); + + v = io::bin::deserialize(vs); + REQUIRE(v.empty()); + } + + GIVEN("interesting -1 s64") + { + s64 a = -1; + + WHEN("serialize") + { + auto sa = io::bin::serialize(a); + + THEN("size is one byte") + { + REQUIRE(sa.size() == 1); + } + } + } + + GIVEN("interesting u32s") + { + typedef std::vector Numbers; + Numbers a { (u32)-1, (u32)0, (u32)1 }; + + WHEN("serialize and deserializee") + { + auto sa = io::bin::serialize(a); + auto a_ = io::bin::deserialize(std::move(sa)); + + THEN("is same") + { + REQUIRE(a_ == a); + } + } + } + + GIVEN("interesting s32s") + { + typedef s32 Number; + typedef std::vector Numbers; + Numbers a { (Number)-1, (Number)0, (Number)1 }; + + WHEN("serialize and deserializee") + { + auto sa = io::bin::serialize(a); + auto a_ = io::bin::deserialize(std::move(sa)); + + THEN("is same") + { + REQUIRE(a_ == a); + } + } + } + + GIVEN("vector of interesting s64 integers") + { + typedef std::vector Numbers; + Numbers a { 0xF, 0xF, 0xFF, 0xFFF, 0xFFFF, 0xFFFFF, 0xFFFFFF, 0xFFFFFFF, 0xFFFFFFFF }; + + WHEN("serialize and deserialize, is same") + { + auto sa = io::bin::serialize(a); + auto a_ = io::bin::deserialize(std::move(sa)); + + REQUIRE(a_ == a); + } + } + + GIVEN("a structure") + { + A a = { + true, 13, std::string("hi"), + { 2, 3, 4 }, + { { 1, 2, 3 }, { 4, 5, 6 } }, + .empty = {}, + .v_b = { 1, 2, 3, 4, 5, 6, 7 }, + .v_s = { 1 } + }; + + WHEN("serialize and deserialize, is same") + { + auto sa = io::bin::serialize(a); + auto a_ = io::bin::deserialize(std::move(sa)); + + bool same = a == a_; + + REQUIRE(same); + } + } + + GIVEN("an empty structure") + { + A a = { true, 13 }; + + WHEN("serialize and deserialize, is same") + { + auto sa = io::bin::serialize(a); + auto a_ = io::bin::deserialize(std::move(sa)); + + bool same = a == a_; + + REQUIRE(same); + } + } + + GIVEN("a vector of bool") + { + typedef std::vector Bools; + Bools a { true, false, true, true }; + + WHEN("serialize and deserialize, is same") + { + auto sa = io::bin::serialize(a); + auto a_ = io::bin::deserialize(std::move(sa)); + + REQUIRE(a_ == a); + } + } + + GIVEN("a vector of char") + { + typedef std::vector Chars; + Chars a { 'a', 'b', 'c', 'd' }; + + WHEN("serialize and deserialize, is same") + { + auto sa = io::bin::serialize(a); + auto a_ = io::bin::deserialize(std::move(sa)); + + REQUIRE(a_ == a); + } + } + + GIVEN("a vector of double including infinities") + { + typedef std::vector Doubles; + Doubles a { 1.0, 1.5, 2.0, 2.5, std::numeric_limits::infinity(), -std::numeric_limits::infinity() }; + + WHEN("serialize and deserialize, is same") + { + auto sa = io::bin::serialize(a); + auto a_ = io::bin::deserialize(std::move(sa)); + + THEN("is same") + { + REQUIRE(a_ == a); + } + } + } + + GIVEN("NAN") + { + double a = std::numeric_limits::quiet_NaN(); + + WHEN("serialize and deserialize, is same") + { + auto sa = io::bin::serialize(a); + auto a_ = io::bin::deserialize(std::move(sa)); + + REQUIRE(std::isnan(a_)); + } + } + + + GIVEN("an empty vector") + { + typedef std::vector V; + V a; + + WHEN("serialize and deserialize") + { + auto sa = io::bin::serialize(a); + auto a_ = io::bin::deserialize(std::move(sa)); + + THEN("is same") + { + REQUIRE(a_ == a); + } + } + } + + GIVEN("an empty string") + { + typedef std::string V; + V a; + + WHEN("serialize and deserialize") + { + auto sa = io::bin::serialize(a); + auto a_ = io::bin::deserialize(std::move(sa)); + + THEN("is same") + { + REQUIRE(a_ == a); + } + } + } + + GIVEN("same string multiple times") + { + typedef std::vector V; + V a = { "a", "a", "a", "a", "b", "c", "c", "c", "b", "", "" }; + + WHEN("serialize and deserialize") + { + auto sa = io::bin::serialize(a); + auto a_ = io::bin::deserialize(std::move(sa)); + + THEN("is same") + { + REQUIRE(a_ == a); + } + } + } + + GIVEN("multiple embeds") + { + typedef std::vector V; + V a = { "a", "a", "a", "a", "b", "c", "c", "c", "b", "", "" }; + + WHEN("serialize and deserialize") + { + io::bin::Writer e {}; + e.any(a); + + io::bin::Writer w {}; + w.embed(e); + w.embed(e); + w.embed(e); + auto numExpected = 3; + + auto sa = w.serialization(); + + io::bin::Reader r(io::bin::memory_in(sa.data(), sa.size())); + + int count = 0; + while (r) + { + auto e = r.embed(); + auto a_ = io::bin::deserializeFrom(e); + + REQUIRE(a_ == a); + count++; + } + + REQUIRE(count == numExpected); + } + } +} + +} // namespace +} // namespace diff --git a/tjp/core/io/bin/_tests/IO_bin_versioning.cpp b/tjp/core/io/bin/_tests/IO_bin_versioning.cpp new file mode 100644 index 0000000..7772be9 --- /dev/null +++ b/tjp/core/io/bin/_tests/IO_bin_versioning.cpp @@ -0,0 +1,179 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#include + +#include + +namespace tjp::core { +namespace { + +struct A_0 { + char v; +} ; + +template +void io_ (IO &io, A_0 &v) +{ + io.object("v", v.v); +} + +struct A_1 { + int v; +}; + +template +void io_ (IO &io, A_1 &v) +{ + io.object("v", v.v); +} + + +struct A_2 { + long v; +} ; + +template +void io_ (IO &io, A_2 &v) +{ + io.object("v", v.v); +} + + +struct A_3 { + char v; +} ; + +template +void io_ (IO &io, A_3 &v) +{ + io.object("v", v.v); +} + + +A_1 convert(A_0 a) +{ + return { (int)a.v + 1}; +} + +A_2 convert(A_1 a) +{ + return { (long)a.v + 1 }; +} + +A_3 convert(A_2 a) +{ + return { (char)(a.v + 1) }; +} + +using VersionNumber = s8; + +template +struct Version { + static constexpr VersionNumber V = 0; +}; + +template<> +struct Version { + typedef A_2 Previous; + static constexpr VersionNumber V = 3; +} ; + +template<> +struct Version { + typedef A_1 Previous; + static constexpr VersionNumber V = 2; +}; + +template<> +struct Version { + typedef A_0 Previous; + static constexpr VersionNumber V = 1; +} ; + +template +T deserialize_version(IO &io, VersionNumber v) +{ + if (v == Version::V) + { + T t; + io.any(t); + return t; + } + else + { + if constexpr(Version::V == 0) + { + throw Exception { "Unknown version" }; + } + else + { + return convert(deserialize_version::Previous>(io, v)); + } + } +} + +template +T deserialize_version(const S &s) +{ + io::bin::Reader io(io::bin::memory_in(s.data(), s.size())); + + s8 v; + io.object("v", v); + + return deserialize_version(io, v); +} + +template +auto serialize_version(const T &t) +{ + io::bin::Writer io; + s8 v = Version::V; + io.object("v", v); + + io.any(t); + + return io.serialization(); +} + +SCENARIO("io bin serialization with versioning", "[core::io]" ) +{ + GIVEN("versioned struct 0") + { + A_0 a; + a.v = 0; + + WHEN("serialize") + { + auto sa = serialize_version(a); + + THEN("deserialize as a version A_0") + { + auto b = deserialize_version(sa); + REQUIRE(b.v == 0); + } + + THEN("deserialize as a version A_1") + { + auto b = deserialize_version(sa); + REQUIRE(b.v == 1); + } + + THEN("deserialize as a version A_2") + { + auto b = deserialize_version(sa); + REQUIRE(b.v == 2); + } + + THEN("deserialize as a version A_3") + { + auto b = deserialize_version(sa); + REQUIRE(b.v == 3); + } + } + } +} + +} // namespace +} // namespace diff --git a/tjp/core/io/bin/_tests/Ptr+IO.cpp b/tjp/core/io/bin/_tests/Ptr+IO.cpp new file mode 100644 index 0000000..0a5d122 --- /dev/null +++ b/tjp/core/io/bin/_tests/Ptr+IO.cpp @@ -0,0 +1,56 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#include +#include +#include + +#include +#include + +namespace tjp { +namespace core { + +namespace memory { +namespace test_compile { + +struct A +{ + int i; +} ; + +template +void io_bin(IO &io, A &a) +{ + io.object("i", a.i); +} + +SCENARIO("core::ptr io") +{ + GIVEN("a ptr") + { + StrongPtr a = strong(); + + WHEN("ptr is serialized") + { + auto result = io::bin::serialize(a); + + WHEN("result is unserialized") + { + auto a2 = io::bin::deserialize>(result); + + THEN("results are equal") + { + REQUIRE(a2->i == a->i); + } + } + } + } +} + +} // namespace +} // namespace + +} // namespace +} // namespace diff --git a/tjp/core/io/bin/dictionary.h b/tjp/core/io/bin/dictionary.h new file mode 100644 index 0000000..4977f97 --- /dev/null +++ b/tjp/core/io/bin/dictionary.h @@ -0,0 +1,86 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace tjp::core::io::bin { + +using DictionaryIndex = u32; + +template +struct DictionaryOut +{ + typedef T K; + typedef DictionaryIndex V; + typedef std::pair KV; + typedef core::AllocatorPool Allocator; + + std::map, Allocator> values; + +public: + typedef T value_type; + + std::tuple on(const T &t) + { + auto [ i, inserted ] = values.emplace(t, values.size()); + return { i->second, inserted }; + } +} ; + +template +struct DictionaryIn +{ + std::vector> values; + +public: + typedef T value_type; + + bool has(const DictionaryIndex &i) + { + return i < values.size(); + } + + const T &get(const DictionaryIndex &i) + { + if (i >= values.size()) + debug_throw(io::Exception("Dictionary failure")); + + return values[i]; + } + + T &set(const DictionaryIndex &i, const T &t) + { + debug_assert(i == values.size()); + values.push_back(t); + return values.back(); + } + + template + T &emplace(const DictionaryIndex &i, Args&& ... args) + { + if (i != values.size()) + debug_throw(io::Exception("Dictionary failure")); + + values.emplace_back(std::forward(args)...); + return values.back(); + } + +} ; + +using DictionaryOutStandard = DictionaryOut; +using DictionaryInStandard = DictionaryIn; + +} // namespace diff --git a/tjp/core/io/bin/in.h b/tjp/core/io/bin/in.h new file mode 100644 index 0000000..1ef503d --- /dev/null +++ b/tjp/core/io/bin/in.h @@ -0,0 +1,619 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include "in_fwd.h" +#include "../IO+config.h" +#include "Debug.h" + +#include "../Exception.h" +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace tjp::core::io::bin { + +template +void io_bin_r(IO &io, T &t); + +template +void io_bin_r_dispatch(IO &io, T &t); + +inline +void readFrom(std::istream &f, char *v, size_t size) +{ + f.read(v, size); +} + +struct memory_in { + const char *block; + size_t size; + size_t position; + + memory_in(const Vector &v) : block(v.data()), size(v.size()), position(0) {} + + memory_in(const char *block_, size_t size_) : block(block_), size(size_), position(0) {} + + bool eof() const { + return position >= size; + } + + bool good() const { + return !eof(); + } +} ; + +inline +void validate_read_size(memory_in &f, size_t size) +{ + if (size > f.size || f.position > f.size - size) + { + debug_throw(io::Exception ("Buffer underflow")); + } +} + +inline +auto skip(memory_in &f, size_t size) +{ + validate_read_size(f, size); + + auto *result = f.block + f.position; + f.position += size; + + return result; +} + +inline +void readFrom(memory_in &f, char *v, size_t size) +{ + auto *start = skip(f, size); + + core::mem_copy(v, start, size); +} + +struct ReaderEmptyBaseClass { + template + void dispatch_custom(IO &io, T &t) {} +} ; + +struct ReaderAllocatorDefault +{ + template + void allocate(IO &io, T &t) + { + io_ptr_allocate_default(io, t); + } + + typedef std::true_type is_strong_allocator; + + template + auto strong(Args && ...args) + { + return core::strong(std::forward(args)...); + } +} ; + +template +class Reader : public Base +{ +public: + typedef Stream_ Stream; + typedef Allocator_ Allocator; + + Stream f; + typedef DictionaryInStandard Dictionary; + Dictionary dictionary; + + #ifdef IO_BIN_USE_HEADER_CHECK + u8 count=0; + #endif + +public: + Allocator allocator; + + template + void allocate(T &t) + { + allocator.allocate(*this, t); + } + + auto partial(const Serialization &s) + { + using ReaderT = Reader; + return strong(typename ReaderT::Stream(s.data(), s.size()), allocator); + } + +public: + Reader (Stream &&f_, Allocator allocator) : + f(std::move(f_)), + allocator(std::move(allocator)) + { + } + + Reader (Stream &&f_) : + f(std::move(f_)) + { + } + + Reader embed() + { + Size size; + any(size); + + auto *at = skip(f, size); + return Reader(Stream(at, size), allocator); + } + + operator bool() const + { + return f.good(); + } + + template + void intrinsic(T &t) + { + read(t); + } + + template + void intrinsic_string(T &t) + { + read_string(t); + } + + template + void any(T &t) + { + dispatch_any(t); + } + + template + void any(T &&t) + { + dispatch_any(t); + } + + template + void all(T&&... t) + { + begin(); + all_(t...); + end(); + } + + template + void object(T&&... t) + { + object_begin(); + object_(t...); + object_end(); + } + + void object_begin() + { + begin(); + } + + void object_k(KeyType) {}; + + template + void object_kv(T&&... t) + { + object_(t...); + } + + void object_end() + { + end(); + } + + template + void array(T&&... t) + { + begin(); + array_(t...); + end(); + } + + + template + void on_dispatch_any(T &t) + { + on_any(t); + } + + template + void on_dispatch_custom(T &t) + { + Base::dispatch_custom(*this, t); + } + +protected: + // ----- read + + void read(char *data, size_t size) + { + readFrom(f, data, size); + } + + void validateRead (size_t size) + { + validate_read_size(f, size); + } + + template + void read(T &t) + { + read((char *)&t, sizeof(T)); + } + + template + inline void read_string(T &t) + { + DictionaryIndex index; + any(index); + + if (dictionary.has(index)) + { + t = T(dictionary.get(index)); + } + else + { + Size size; + any(size); + + validateRead(size); + t = T(size, 0); + read((char *)t.data(), size); + + auto *begin = (char *)t.data(); + auto *end = begin + size; + + dictionary.emplace(index, begin, end); + } + + IO_BIN_LOG("read " << t); + } + + // ----- dispatch out and in + + template + void dispatch_any(T &t) + { + IO_BIN_LOG("one " << type_id().name()); + + io_bin_r_dispatch(*this, t); + } + + void begin () + { + #ifdef IO_BIN_USE_HEADER_CHECK + u8 c; + read(c); + ++count; + debug_assert(c == count); + #endif + } + + void end () + { + #ifdef IO_BIN_USE_HEADER_CHECK + u8 c; + read(c); + debug_assert(c == count); + --count; + #endif + } + + // -- handling different containers -- + +public: + template + void any_no_ref(T v) + { + any(v); + } + + Size array_begin() + { + Size s; + any(s); + + return s; + } + + void array_end() + { + } + + template + void on_vector_begin(T &t) + { + Size s = array_begin(); + t = std::move(T(s)); + } + + template + void on_vector_end (T &t) + { + array_end(); + } + + template + void on_vector_value(T &t) + { + any(t); + } + + auto map_begin() + { + Size s; + any(s); + + return s; + } + + template + void map_values(Size s, F &&f) + { + IO_BIN_LOG("map " << type_id().name()); + + for (auto i=0; i + void array_values(size_t size, F &&fun) + { + for (auto i=0;i + void on_vector(T &t) + { + IO_BIN_LOG("many " << type_id().name()); + on_vector_begin(t); + + for (auto &v : t) + on_vector_value(v); + + on_vector_end(t); + } + + void on_vector(std::vector &t) + { + typedef std::vector T; + IO_BIN_LOG("many " << type_id().name()); + + on_vector_begin(t); + + for (auto i = t.begin(); i!=t.end(); ++i) + any_no_ref(*i); + + on_vector_end(t); + } + + template + void on_set(T &t) + { + IO_BIN_LOG("set " << type_id().name()); + + Size s; + any(s); + + for (auto i=0; i + void on_map(T &t) + { + IO_BIN_LOG("map " << type_id().name()); + + Size s; + any(s); + + for (auto i=0; i + void on_optional(T &t) + { + IO_BIN_LOG("map " << type_id().name()); + + bool s; + any(s); + + if (s) + { + t.emplace(); + any(*t); + } + } + + // -- dispatching different containers -- + + template::value || is_string::value, T>::type* = nullptr> + void on_any(T &t) + { + io_dispatch_failed(*this, t); + } + + template + void on_any(std::set &t) + { + on_set(t); + } + + template::value && !is_mappish::value && !is_string::value, T>::type* = nullptr> + void on_any(T &t) + { + on_vector(t); + } + + template::value && is_mappish::value, T>::type* = nullptr> + void on_any(T &t) + { + on_map(t); + } + + template + void on_any(std::optional &t) + { + on_optional(t); + } + +// void on_any(variant::Variant &t) +// { +// on_variant(t); +// } +// + // -- handling all -- + + template + void all_(T &t) + { + any(t); + } + + template + void all_(Args&& ...args) + { + call_expand_1( + [&](auto && ...args) { any(std::forward(args)...); }, + std::forward(args)... + ); + } + + // ---- handling object array ---- + + void object_() + { + } + + template + void object__(KeyType, T &t) + { + any(t); + } + + template + void object_(Args&& ...args) + { + call_expand_2( + [&](auto && ...args) { object__(std::forward(args)...); }, + std::forward(args)... + ); + } + + template + void array__(T &t) + { + any(t); + } + + template + void array_(Args&& ...args) + { + call_expand_1( + [&](auto && ...args) { array__(std::forward(args)...); }, + std::forward(args)... + ); + } + +public: + bool null () + { + return f.is_null(); + } + + template + Optional optional(T &t, bool write=true) + { + return io::bin::optional(t, write); + } + + template + Pointer pointer(B &b) + { + return io::bin::Pointer { &b }; + } + + template + Custom custom(T &t) + { + return Custom { &t }; + } + +} ; + +// ----------------------- + +template +void deserializeFrom (Reader &reader, T &t); + +template +T deserializeFrom (Reader &reader); + +template +T deserialize (const Serialization &data, Allocator); + +template +T deserialize (const Serialization &data); + +template +T deserialize (const Serialization &data); + +template +void deserialize (const Serialization &data, T &t); + +template +void fromFile (const std::string &fileName, T &t); + +} // namespace + +#include "in.inl" diff --git a/tjp/core/io/bin/in.inl b/tjp/core/io/bin/in.inl new file mode 100644 index 0000000..d7b4987 --- /dev/null +++ b/tjp/core/io/bin/in.inl @@ -0,0 +1,86 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include "IO.h" + +namespace tjp::core::io::bin { + +template +void io_bin_r(IO &io_, T &t) +{ + if constexpr (has_io_bin::value) + { + io_bin(io_, t); + } + else + { + io_r(io_, t); + } +} + +template +void io_bin_r_dispatch(IO &io_, T &t) +{ + io_bin_r(io_, t); +} + +template +void deserializeFrom (Reader &reader, T &t) +{ + reader.any(t); +} + +template +T deserializeFrom (Reader &reader) +{ + T t; + deserializeFrom(reader, t); + return t; +} + +template +T deserialize (const Serialization &data, Allocator allocator) +{ + R reader(typename R::Stream(data.data(), data.size()), allocator); + return deserializeFrom(reader); +} + +template +T deserialize (const Serialization &data) +{ + R reader(typename R::Stream(data.data(), data.size())); + return deserializeFrom(reader); +} + +template +void deserialize (const Serialization &data, T &t) +{ + R reader(typename R::Stream(data.data(), data.size())); + deserializeFrom(reader, t); +} + +template +T deserialize (const Serialization &data) +{ + return deserialize, T>(data); +} + +template +T deserialize (const Serialization::value_type *begin, size_t size) +{ + using R = Reader; + R reader(typename R::Stream(begin, size)); + return deserializeFrom(reader); +} + +template +void deserialize (const Serialization &data, T &t) +{ + deserialize, T>(data, t); +} + + +} // namespace diff --git a/tjp/core/io/bin/in_file.hpp b/tjp/core/io/bin/in_file.hpp new file mode 100644 index 0000000..858e19e --- /dev/null +++ b/tjp/core/io/bin/in_file.hpp @@ -0,0 +1,18 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include + +namespace tjp::core::io::bin { + +template +void fromFile (const std::string &fileName, T &t) +{ + Reader reader(fileName.c_str(), std::ios::binary); + reader.any(t); +} + +} // namespace diff --git a/tjp/core/io/bin/in_fwd.h b/tjp/core/io/bin/in_fwd.h new file mode 100644 index 0000000..8dc8889 --- /dev/null +++ b/tjp/core/io/bin/in_fwd.h @@ -0,0 +1,16 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +namespace tjp::core::io::bin { + +struct memory_in; +struct ReaderEmptyBaseClass; +struct ReaderAllocatorDefault; + +template +class Reader; + +} // namespace diff --git a/tjp/core/io/bin/json.inl b/tjp/core/io/bin/json.inl new file mode 100644 index 0000000..39407c0 --- /dev/null +++ b/tjp/core/io/bin/json.inl @@ -0,0 +1,26 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include + +namespace nlohmann { + +template +void io_bin_w(IO &io, const json &j) +{ + auto cbor = json::to_cbor(j); + io.all(cbor); +} + +template +void io_bin_r(IO &io, json &j) +{ + std::vector v; + io.all(v); + j = json::from_cbor(v); +} + +} // namespace diff --git a/tjp/core/io/bin/out.h b/tjp/core/io/bin/out.h new file mode 100644 index 0000000..adbb2b2 --- /dev/null +++ b/tjp/core/io/bin/out.h @@ -0,0 +1,543 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include "out_fwd.h" +#include "../IO+config.h" + +#include "Debug.h" +#include +#include +#include + +namespace tjp::core::io::bin { + +template +void io_bin_w(IO &io, const T &t); + +template +void io_bin_w_dispatch(IO &io, const T &t); + +inline +void writeTo(std::ostream &f, const char *v, size_t size) +{ + f.write(v, size); +} + +struct memory_out_ptr +{ + using Block = std::vector; + Block *block; + + memory_out_ptr(Block *block_) : + block(block_) + { + } + + Block &serialization () + { + return *block; + } +} ; + +template +struct memory_out_sized +{ + using Block = std::vector; + Block block; + + memory_out_sized(Block &&block_) : + block(block_) + { + } + + memory_out_sized() + { + block.reserve(InitialSize); + } + + Block &serialization () + { + return block; + } +} ; + +using memory_out = memory_out_sized<4096>; + +inline +auto serialization_of(memory_out &m) +{ + return m.serialization(); +} + +inline +auto serialization_of(memory_out_ptr &m) +{ + return m.serialization(); +} + +inline +auto serialization_of(std::ostream &f) {} + +inline +void writeTo(memory_out_ptr &f, const char *v, size_t size) +{ + auto then = f.block->size(); + f.block->resize(then + size); + core::mem_copy(f.block->data() + then, v, size); +} + +inline +void writeTo(memory_out &f, const char *v, size_t size) +{ + auto then = f.block.size(); + f.block.resize(then + size); + core::mem_copy(f.block.data() + then, v, size); +} + +// ------------------------ + +struct EmptyWriterBaseClass { + template + void dispatch_custom(IO &io, T &t) {} +} ; + +struct WriterAllocatorBase { + template + void allocate(T &t) + { + io_ptr_allocate_default(*this, t); + } +} ; + +template +class Writer : + public Base, + public Allocator +{ +public: + S f; + typedef DictionaryOutStandard Dictionary; + Dictionary dictionary; + + #ifdef IO_BIN_USE_HEADER_CHECK + u8 count=0; + #endif + +public: + auto partial() + { + return Writer(); + } + + auto serialization () + { + return serialization_of(f); + } + + auto partial_serialization () + { + return serialization(); + } + +public: + template + Writer (Args&&... args) : + f(args...) + { + } + + void embed(Writer &writer) + { + any(writer.f.serialization()); + } + + operator bool() const + { + return f.good(); + } + + template + void intrinsic(const T &t) + { + write(t); + } + + template + void intrinsic_string(const T &t) + { + write_string(t); + } + + void intrinsic(const char *memory, size_t size) + { + write(memory, size); + } + + template + void any(const T &t) + { + dispatch_any(t); + } + + template + void all(T&&... t) + { + begin(); + all_(t...); + end(); + } + + template + void object(T&&... t) + { + object_begin(); + object_(t...); + object_end(); + } + + void object_begin() + { + begin(); + } + + void object_k(KeyType) {}; + + template + void object_kv(T&&... t) + { + object_(t...); + } + + void object_end() + { + end(); + } + + auto map_begin(Size size) + { + begin(); + any(size); + + return size; + } + + template + void map_value_with(const K &key, F &&f) + { + any(key); + f(); + } + + template + void map_value(const K &key, const V &value) + { + any(key); + any(value); + } + + void map_end() + { + end(); + } + + template + void array(T&&... t) + { + begin(); + array_(t...); + end(); + } + + template + void on_dispatch_any(const T &t) + { + on_any(t); + } + + template + void on_dispatch_custom(T &t) + { + Base::dispatch_custom(*this, t); + } + +protected: + template + void dispatch_any(const T &t) + { + IO_BIN_LOG("any " << type_id().name()); + + io_bin_w_dispatch(*this, t); + } + + void begin() + { + #ifdef IO_BIN_USE_HEADER_CHECK + write(++count); + #endif + } + + void end() + { + #ifdef IO_BIN_USE_HEADER_CHECK + write(count--); + #endif + } + + void write(const char *data, size_t size) + { + writeTo(f, data, size); + } + + template + void write(const T &t) + { + IO_BIN_LOG("write " << t); + write((const char *)&t, sizeof(T)); + } + + template + inline void write_string(const T &t) + { + auto [index, should_write] = dictionary.on(t); + any(index); + + if (should_write) + { + any((Size)t.size()); + write(t.data(), t.size()); + } + } + + // -- handling different containers -- + +public: + auto array_begin(Size s) + { + any(s); + return s; + } + + void array_end() + { + } + + template + void on_vector_begin (const T &t) + { + array_begin((Size)t.size()); + } + + template + void on_vector_end (const T &t) + { + array_end(); + } + + template + void on_vector_value(const T &t) + { + any(t); + } + +protected: + template + void on_vector(const T &t) + { + IO_BIN_LOG("many " << type_id().name()); + on_vector_begin(t); + + // this fails for std::vector on some platforms, because vector bool + // does some magic to compress to a bit field, and then the iterator + // doesn't pass back a reference + // for (const auto &v : t) + // any(v); + + for (auto i = t.begin(); i!=t.end(); ++i) + on_vector_value(*i); + + on_vector_end(t); + } + + void on_vector(const std::vector &t) + { + any((Size)t.size()); + intrinsic((char *)t.data(), t.size()); + } + + void on_vector(const std::vector &t) + { + any((Size)t.size()); + intrinsic((char *)t.data(), t.size()); + } + + void on_vector(const std::vector &t) + { + any((Size)t.size()); + intrinsic((char *)t.data(), t.size()); + } + + template + void on_map(const T &t) + { + IO_BIN_LOG("map " << type_id().name()); + any((Size)t.size()); + for (const auto &v : t) + { + any(v.first); + any(v.second); + } + } + + template + void on_optional(const T &t) + { + bool s = t.has_value(); + any(s); + + if (s) + { + any(*t); + } + } + + // -- dispatching different containers -- + + template::value || is_string::value, T>::type* = nullptr> + void on_any(const T &t) + { + io_dispatch_failed(*this, t); + } + + template::value && !is_mappish::value && !is_string::value, T>::type* = nullptr> + void on_any(const T &t) + { + on_vector(t); + } + + template::value && is_mappish::value, T>::type* = nullptr> + void on_any(const T &t) + { + on_map(t); + } + + template + void on_any(const std::optional &t) + { + on_optional(t); + } + +// void on_any(const variant::Variant &t) +// { +// on_variant(t); +// } +// + // ---- handling all ---- + + template + void all_(const T &t) + { + any(t); + } + + template + void all_(const T &t, Rest&&... rest) + { + any(t); + all_(rest...); + } + + // ---- handling object array ---- + + void object_() + { + } + + template + void object__(KeyType, const T &t) + { + any(t); + } + + template + void object_(Args&& ...args) + { + call_expand_2( + [&](auto && ...args) { object__(std::forward(args)...); }, + std::forward(args)... + ); + } + + template + void array__(const T &t) + { + any(t); + } + + template + void array_(Args&& ...args) + { + call_expand_1( + [&](auto && ...args) { array__(std::forward(args)...); }, + std::forward(args)... + ); + } + + +public: + // ----- miscellaneous ----- + + template + Optional optional(T &t, bool write=true) + { + return io::bin::optional(t, write); + } + + template::value, T>::type* = nullptr> + Pointer pointer(B &b) + { + return io::bin::Pointer { + &b, b != nullptr + }; + } + + template::value, T>::type* = nullptr> + Pointer pointer(B &b) + { + return io::bin::Pointer { + &b, dynamic_cast_ptr(b) != nullptr + }; + } + + + template + Custom custom(const T &t) + { + return Custom { const_cast(&t) }; + } + +} ; + + +// ---------- + +template +Serialization serialize (const T &t); + +template +Serialization serialize (const T &t); + +template +void serializeTo (Serialization &serialization, const T &t); + +template +Serialization serializeTo(W &w, const T &t); + +template +void toFile (const std::string &fileName, const T &t); + +} // namespace + +#include "out.inl" diff --git a/tjp/core/io/bin/out.inl b/tjp/core/io/bin/out.inl new file mode 100644 index 0000000..f2c0e4b --- /dev/null +++ b/tjp/core/io/bin/out.inl @@ -0,0 +1,65 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include "IO.h" + +namespace tjp::core::io::bin { + +template +void io_bin_w(IO &io_, const T &t) +{ + typedef typename std::remove_const::type TC; + + if constexpr (has_io_bin::value) + { + auto &t_ = *const_cast(&t); + io_bin(io_, t_); + } + else + { + io_w(io_, t); + } +} + +template +void io_bin_w_dispatch(IO &io_, const T &t) +{ + io_bin_w(io_, t); +} + +template +Serialization serialize (const T &t) +{ + W writer; + writer.any(t); + + return std::move(writer.serialization()); +} + +template +Serialization serialize (const T &t) +{ + return serialize>(t); +} + +template +void serializeTo (Serialization &serialization, const T &t) +{ + memory_out bout { std::move(serialization) }; + Writer writer(bout); + writer.any(t); + + serialization = writer.serialization(); +} + +template +Serialization serializeTo(W &w, const T &t) +{ + w.any(t); + return w.serialization(); +} + +} // namespace diff --git a/tjp/core/io/bin/out_file.hpp b/tjp/core/io/bin/out_file.hpp new file mode 100644 index 0000000..925caa6 --- /dev/null +++ b/tjp/core/io/bin/out_file.hpp @@ -0,0 +1,18 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include + +namespace tjp::core::io::json { + +template +void toFile (const std::string &fileName, const T &t) +{ + Writer writer(fileName.c_str(), std::ios::binary); + writer.any(t); +} + +} // namespace diff --git a/tjp/core/io/bin/out_fwd.h b/tjp/core/io/bin/out_fwd.h new file mode 100644 index 0000000..60b8e1e --- /dev/null +++ b/tjp/core/io/bin/out_fwd.h @@ -0,0 +1,23 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include + +namespace tjp::core::io::bin { + +template +struct memory_out_sized; + +using memory_out = memory_out_sized<4096>; + + +struct EmptyWriterBaseClass; +struct WriterAllocatorBase; + +template +class Writer; + +} // namespace diff --git a/tjp/core/io/calls.hpp b/tjp/core/io/calls.hpp new file mode 100755 index 0000000..eb8ebc6 --- /dev/null +++ b/tjp/core/io/calls.hpp @@ -0,0 +1,38 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include +#include + +namespace tjp::core::io { + +template +void call_f_1s(F&& f, Args&&... args) { + (f(std::forward(args)), ...); +} + +template +void call_f_2s_(F &&f, Tuple&& tup, std::index_sequence) { + (f( + std::forward(std::forward(tup)))>( + std::get(std::forward(tup))), + std::forward(std::forward(tup)))>( + std::get(std::forward(tup))) + ), ...); +} + +template +void call_f_2s(F &&f, Args&&... args) +{ + static_assert(sizeof...(Args) % 2 == 0, "Arguments must be in key-value pairs"); + + auto args_tuple = std::forward_as_tuple(std::forward(args)...); + constexpr std::size_t N = sizeof...(Args) / 2; + + call_f_2s_(std::forward(f), args_tuple, std::make_index_sequence{}); +} + +} // namespace diff --git a/tjp/core/io/json/IO.h b/tjp/core/io/json/IO.h new file mode 100644 index 0000000..ed268db --- /dev/null +++ b/tjp/core/io/json/IO.h @@ -0,0 +1,227 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include "../IO.h" +#include "../IO_dispatch.h" +#include "../IO_.h" +#include "../IO+config.h" + + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + + + +//#ifdef DEBUG +//#define IO_JSON_LOG std::cerr +//#else +#define IO_JSON_LOG if(false) std::cout +//#endif + +//#define IO_JSON_USE_LARGE_NUMBER_PROXY + +namespace tjp::core::io::json { + +using Size = u32; + +typedef std::vector Serialization; + +// detects whether there is an appropriate intermediate +// io function + +template +struct has_io_json : std::false_type {}; + +template +struct has_io_json(), + std::declval())) >> : std::true_type {}; + + +template void io_json(IO &io, bool &t) { io.intrinsic(t); } +template void io_json(IO &io, u8 &t) { io.intrinsic(t); } +template void io_json(IO &io, u16 &t) { io.intrinsic(t); } +template void io_json(IO &io, u32 &t) { io.intrinsic(t); } +template void io_json(IO &io, u64 &t) { io.intrinsic(t); } +template void io_json(IO &io, u128 &t) { io.intrinsic(t); } + + +template void io_json(IO &io, s8 &t) { io.intrinsic(t); } +template void io_json(IO &io, char &t) { io.intrinsic(t); } +template void io_json(IO &io, s16 &t) { io.intrinsic(t); } +template void io_json(IO &io, s32 &t) { io.intrinsic(t); } +template void io_json(IO &io, s64 &t) { io.intrinsic(t); } +template void io_json(IO &io, s128 &t) { io.intrinsic(t); } + +template void io_json(IO &io, r32 &t) { io.intrinsic(t); } +template void io_json(IO &io, r64 &t) { io.intrinsic(t); } +template void io_json(IO &io, r128 &t) { io.intrinsic(t); } + +template void io_json(IO &io, std::string &t) { io.intrinsic(t); } +template void io_json(IO &io, std::string_view &t) { io.intrinsic(t); } + +#if defined(SYS_MAC) || defined(SYS_IOS) +template void io_json(IO &io, unsigned long &t) { io.intrinsic(t); } +template void io_json(IO &io, signed long &t) { io.intrinsic(t); } +#endif + + +template +struct Optional +{ + T &v; + bool write; +} ; + +template +Optional optional(T &t, bool write=true) { return Optional { t, write }; } + +template +void io_json(IO &io, Optional &t) +{ + static_assert(always_false::value, "this should not be dispatched to"); +} + +template +struct Pointer +{ + P *p; + bool write; +} ; + +typedef const char *KeyType; + +template +void io_ptr_allocate(IO &io, T *&t) +{ + io.allocate(t); +} + +template +void io_ptr_allocate_default(IO &io, T *&t) +{ + t = new T(); +} + +template +void io_json(IO &io, Pointer &t) +{ + io.object("ptr", t); +} + +template +void io_json(IO &io, T *&t) +{ + auto p = io.template pointer(t); + io.any(p); +} + +template +struct AsKeyType { + std::string value; + + AsKeyType (const T &t) + { + value = core::to_string(t); + } + + operator KeyType() + { + return value.c_str(); + } +} ; + +template<> +struct AsKeyType { + const std::string &value; + AsKeyType (const std::string &value_) : + value(value_) + { + } + + operator KeyType() + { + return value.c_str(); + } +} ; + +template<> +struct AsKeyType { + KeyType value; + AsKeyType (KeyType value_) : + value(value_) + { + } + + operator KeyType() + { + return value; + } +} ; + +template +AsKeyType asKeyType(const T &t) +{ + return AsKeyType(t); +} + +template +struct Custom +{ + T *p; +} ; + +template +void io_json(IO &io, Custom &t) +{ + io.template dispatch_custom<>(io, *t.p); +} + + +template +void io_json(IO &io, std::tuple &t) +{ + io.tuple(t); +} + +//#ifdef SYS_APPLE +// std::vector fixes + +template +void io_json_w(IO &io, const std::vector::const_iterator::reference t) +{ + bool b = t; + io.intrinsic(b); +} + +template +void io_json_r(IO &io, std::vector::iterator::reference &t) +{ + bool b; + io.intrinsic(b); + t = b; +} + +//#endif + +} // namespace diff --git a/tjp/core/io/json/_tests/IO_json.cpp b/tjp/core/io/json/_tests/IO_json.cpp new file mode 100644 index 0000000..9fd10b3 --- /dev/null +++ b/tjp/core/io/json/_tests/IO_json.cpp @@ -0,0 +1,415 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#include +#include +#include + +#include + +#include + +namespace tjp::core::io_json_test { + +struct VS { + int x; +} ; + +bool operator ==(const VS &l, const VS &r) +{ + return l.x == r.x; +} + +std::ostream &operator <<(std::ostream &o, const VS &v) +{ + return o << v.x; +} + +std::istream &operator >>(std::istream &i, VS &v) +{ + return i >> v.x; +} + +struct VV : std::vector +{ +} ; + +template +void io_json(IO &io, VV &v) +{ + std::string b = "b"; + io.object( + "a",b + ); +}; + +struct A { + bool w; + int x; + std::string s; + u64 _u64; + s64 _s64; + u128 _u128; + s128 _s128; + + std::vector y; + std::vector> z; + std::vector v_b; + VS v_s; + + bool operator ==(const A &rhs) const + { + return + w == rhs.w && + x == rhs.x && + y == rhs.y && + z == rhs.z && + s == rhs.s && + v_b == rhs.v_b && + v_s == rhs.v_s && + + _u64 == rhs._u64 && + _s64 == rhs._s64 && + _u128 == rhs._u128 && + _s128 == rhs._s128 + ; + } +} ; + +struct B { + int i; +} ; + +struct C { + s32 i32; + u32 u32; + s64 i64; + u64 u64; + s128 i128; + u128 u128; + + bool operator ==(const C &rhs) const + { + return + i32 == rhs.i32 && + u32 == rhs.u32 && + i64 == rhs.i64 && + u64 == rhs.u64 && + i128 == rhs.i128 && + u128 == rhs.u128 + ; + } +} ; + + +template +void io_json(IO &io, C &a) +{ + io.object( + "i32", a.i32, + "u32", a.u32, + "i64", a.i64, + "u64", a.u64, + "i128", a.i128, + "u128", a.u128 + ); +}; + +template +void io_json(IO &io, A &a) +{ + io.object( + "w", a.w, + "x", a.x, + "y", a.y, + "z", a.z, + "s", a.s, + "_u64", a._u64, + "_s64", a._s64, + "_u128", a._u128, + "_s128", a._s128, + "v_b", makeBinaryEncoder(a.v_b), + "v_s", makeStringEncoder(a.v_s) + ); +} + +template +T make_number() +{ + T t; + char *b = (char *)&t; + auto *e = b + sizeof(T); + + int i=1; + for (char *v=b; v!=e; ++v) + *v = i++; + + return t; +} + +SCENARIO("io json serialization", "[core::io]" ) +{ + GIVEN("a custom array with serializer") + { + auto v = VV(); + auto vs = io::json::serialize(v); + auto vss = String(vs.begin(), vs.end()); + REQUIRE(vss == "{\"a\":\"b\"}"); + auto vs0 = io::json::deserialize(vs); + REQUIRE(vs0.empty()); + } + + GIVEN("a simple structure") + { + auto check_each = [](C &c) { + auto vi32_ = io::json::deserialize(io::json::serialize(c.i32)); + auto vi64_ = io::json::deserialize(io::json::serialize(c.i64)); + auto vi128_ = io::json::deserialize(io::json::serialize(c.i128)); + auto vu32_ = io::json::deserialize(io::json::serialize(c.u32)); + auto vu64_ = io::json::deserialize(io::json::serialize(c.u64)); + auto vu128_ = io::json::deserialize(io::json::serialize(c.u128)); + + REQUIRE(vi32_ == c.i32); + REQUIRE(vi64_ == c.i64); + REQUIRE(vi128_ == c.i128); + REQUIRE(vu32_ == c.u32); + REQUIRE(vu64_ == c.u64); + REQUIRE(vu128_ == c.u128); + } ; + + WHEN("interesting numbers 1") + { + C c = { + .i32 = 0, + .u32 = 0, + .i64 = 0, + .u64 = 0, + .i128 = 0, + .u128 = 0 + }; + + auto sc = io::json::serialize(c); + auto c_ = io::json::deserialize(std::move(sc)); + bool same = c == c_; + REQUIRE(same); + + check_each(c); + } + + WHEN("interesting numbers max") + { + C c = { + .i32 = std::numeric_limits::max(), + .u32 = std::numeric_limits::max(), + .i64 = std::numeric_limits::max(), + .u64 = std::numeric_limits::max(), + .i128 = std::numeric_limits::max(), + .u128 = std::numeric_limits::max() + }; + + auto sc = io::json::serialize(c); + auto c_ = io::json::deserialize(std::move(sc)); + bool same = c == c_; + + REQUIRE(same); + + check_each(c); + } + + WHEN("interesting numbers min") + { + C c = { + .i32 = std::numeric_limits::min(), + .u32 = std::numeric_limits::min(), + .i64 = std::numeric_limits::min(), + .u64 = std::numeric_limits::min(), + .i128 = std::numeric_limits::min(), + .u128 = std::numeric_limits::min() + }; + + auto sc = io::json::serialize(c); + auto sc_ = std::string(sc.begin(), sc.end()); + auto c_ = io::json::deserialize(std::move(sc)); + bool same = c == c_; + + REQUIRE(same); + + check_each(c); + } + + WHEN("interesting numbers max-1") + { + C c = { + .i32 = std::numeric_limits::max()-1, + .u32 = std::numeric_limits::max()-1, + .i64 = std::numeric_limits::max()-1, + .u64 = std::numeric_limits::max()-1, + .i128 = std::numeric_limits::max()-1, + .u128 = std::numeric_limits::max()-1 + }; + + auto sc = io::json::serialize(c); + auto c_ = io::json::deserialize(std::move(sc)); + bool same = c == c_; + + REQUIRE(same); + + check_each(c); + } + + WHEN("interesting numbers min+1") + { + C c = { + .i32 = std::numeric_limits::min()+1, + .u32 = std::numeric_limits::min()+1, + .i64 = std::numeric_limits::min()+1, + .u64 = std::numeric_limits::min()+1, + .i128 = std::numeric_limits::min()+1, + .u128 = std::numeric_limits::min()+1 + }; + + auto sc = io::json::serialize(c); + auto c_ = io::json::deserialize(std::move(sc)); + bool same = c == c_; + + REQUIRE(same); + + check_each(c); + } + + WHEN("interesting numbers max+1") + { + C c = { + .i32 = std::numeric_limits::max()+1, + .u32 = std::numeric_limits::max()+1, + .i64 = s64(std::numeric_limits::max())+1, + .u64 = u64(std::numeric_limits::max())+1, + .i128 = s128(std::numeric_limits::max())+1, + .u128 = u128(std::numeric_limits::max())+1 + }; + + auto sc = io::json::serialize(c); + auto c_ = io::json::deserialize(std::move(sc)); + bool same = c == c_; + + REQUIRE(same); + + check_each(c); + } + + + } + + GIVEN("a structure") + { + A a = { + true, 13, + std::string("hi"), + make_number(), make_number(), + make_number(), make_number(), + { 2, 3, 4 }, + { { 1, 2, 3 }, { 4, 5, 6 }}, + { 1, 2, 3, 4, 5, 6, 7, 8 }, + { 1 } + }; + + WHEN("base64 works") + { + A b; + + fromBase64(toBase64((char *)&a._s128, sizeof(a._s128)), (char *)&b._s128, sizeof(b._s128)); + fromBase64(toBase64((char *)&a._u128, sizeof(a._u128)), (char *)&b._u128, sizeof(b._u128)); + fromBase64(toBase64((char *)&a._s64, sizeof(a._s64)), (char *)&b._s64, sizeof(b._s64)); + fromBase64(toBase64((char *)&a._u64, sizeof(a._u64)), (char *)&b._u64, sizeof(b._u64)); + REQUIRE(a._s128 == b._s128); + REQUIRE(a._u128 == b._u128); + REQUIRE(a._s64 == b._s64); + REQUIRE(a._u64 == b._u64); + } + + WHEN("serialize and deserialize, is same") + { + auto sa = io::json::serialize(a); + auto saX = std::string(sa.data(), sa.size()); + + auto a_ = io::json::deserialize(std::move(sa)); + bool same = a == a_; + + REQUIRE(same); + + auto sa2 = io::json::serialize(a_); + auto sa2X = std::string(sa2.data(), sa2.size()); + + REQUIRE(saX == sa2X); + } + } + + GIVEN("a structure empty") + { + A a = { true, 13 }; + + WHEN("serialize and deserialize, is same") + { + auto sa = io::json::serialize(a); + auto a_ = io::json::deserialize(std::move(sa)); + + bool same = a == a_; + + REQUIRE(same); + } + } + + GIVEN("a vector of bool") + { + typedef std::vector Bools; + Bools a { true, false, true, true }; + + WHEN("serialize and deserialize") + { + auto sa = io::json::serialize(a); + auto a_ = io::json::deserialize(std::move(sa)); + + THEN("is same") + { + REQUIRE(a_ == a); + } + } + } + + GIVEN("an empty vector") + { + typedef std::vector V; + V a; + + WHEN("serialize and deserialize") + { + auto sa = io::json::serialize(a); + auto a_ = io::json::deserialize(std::move(sa)); + + THEN("is same") + { + REQUIRE(a_ == a); + } + } + } + + GIVEN("an empty string") + { + typedef std::string V; + V a; + + WHEN("serialize and deserialize") + { + auto sa = io::json::serialize(a); + auto a_ = io::json::deserialize(std::move(sa)); + + THEN("is same") + { + REQUIRE(a_ == a); + } + } + } +} + +} // namespace diff --git a/tjp/core/io/json/in_file.hpp b/tjp/core/io/json/in_file.hpp new file mode 100644 index 0000000..912aea4 --- /dev/null +++ b/tjp/core/io/json/in_file.hpp @@ -0,0 +1,39 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include + +namespace tjp::core::io::json { + +template +bool fromFile (const std::string &fileName, T &t) +{ + std::ifstream inFile; + + inFile.open(fileName); + if (!inFile) + return false; + + fromStream(inFile, t); + return true; +} + +template +T fromFile (const std::string &fileName) +{ + std::ifstream inFile; + + inFile.open(fileName); + if (!inFile) + throw Exception { "File could not be opened" }; + + T t; + fromStream(inFile, t); + + return t; +} + +} // namespace diff --git a/tjp/core/io/json/in_sajson.h b/tjp/core/io/json/in_sajson.h new file mode 100644 index 0000000..f0b6606 --- /dev/null +++ b/tjp/core/io/json/in_sajson.h @@ -0,0 +1,831 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + + +#include "IO.h" + +#include +#include "../../json/sajson.h" +#include +#include +#include +#include +#include +#include +#include "../Exception.h" + +#include + +#ifdef IO_JSON_USE_LARGE_NUMBER_PROXY +#include +#endif + +//#ifdef DEBUG +//#define IO_JSON_LOG std::cerr +//#else +#define IO_JSON_LOG if(false) std::cout +//#endif + +namespace tjp::core::io::json { + +template +void io_json_r(IO &io, T &t); + +template +void io_json_r_dispatch(IO &io, T &t); + +typedef const char *KeyType; + +struct json_in { + Serialization underlying; + typedef const sajson::document Document; + Document document; + + typedef const sajson::value Node; + + struct Current { + Node node; + + Current(const Node &node_) : + node(node_) + { + } + + int keyIndex = 0; + bool objectKeysFound = false; + std::vector objectKeys; + + size_t array_size () { + return node.get_length(); + } + + const std::vector &object_keys () + { + if (!objectKeysFound) + { + auto length = node.get_length(); + for (auto i = 0; i= node.get_length()) + throw Exception { "Index out of bounds" }; + + return node.get_array_element(i); + } + + Node operator[] (const std::string &i) + { + auto key = sajson::string(i.c_str(), i.length()); + auto element = node.find_object_key(key); + if (element >= node.get_length()) + throw Exception { "Key does not exist" }; + + return node.get_object_value(element); + } + + template + T get_int() + { + T t; + if (!node.get_integer_value(t)) + { + debug_assert(false); + throw Exception { "Value not convertible" }; + } + + return t; + } + +#ifdef IO_JSON_USE_LARGE_NUMBER_PROXY + + template + T get_proxy() + { + T t; + fromBase64( + node.as_cstring(), + node.get_string_length(), + (char *)&t, + sizeof(t) + ); + + return t; + } +#endif + +// s64 get_int64() +// { +// s64 v = -1; +// if (!node.get_int53_value(&v)) +// { +// debug_assert(false); +// // throw something +// } +// return v; +// } +// +// s64 get_int128() +// { +// s64 v = -1; +// if (!node.get_int53_value(&v)) +// { +// debug_assert(false); +// // throw something +// } +// return v; +// } + + double get_double() + { + return node.get_number_value(); + } + + std::string_view get_utf8() + { + return node.as_string(); + } + + bool get_bool () + { + return node.get_type() == sajson::TYPE_TRUE; + } + } ; + + std::list current; + + json_in(Serialization &&serialization) : + underlying(std::move(serialization)), + document(sajson::parse( + sajson::dynamic_allocation(), + sajson::mutable_string_view(underlying.size(), (char *)underlying.data()) + )) + { + current.emplace_back(document.get_root()); + } + + size_t array_size() + { + auto &doc = current.back(); + return doc.array_size(); + } + + std::vector object_keys() + { + auto &doc = current.back(); + return doc.object_keys(); + } + + void key(const KeyType &key) + { + current.emplace_back(current.back()[key]); + } + +// bool hasKey(const KeyType &key_) +// { +// std::string key(key_); +// auto &keys = current.back().object_keys(); +// return std::find(keys.begin(), keys.end(), key) != keys.end(); +// } + + bool hasKeyValue(const KeyType &key_) + { + std::string key(key_); + auto &keys = current.back().object_keys(); + auto i= std::find(keys.begin(), keys.end(), key); + if (i == keys.end()) + return false; + + return current.back()[*i].get_type() != sajson::TYPE_NULL; + } + + void next_array_key() + { + auto index = current.back().keyIndex++; + current.emplace_back(current.back()[index]); + } + + void endKey() + { + current.pop_back(); + } + + void begin_element () + { + } + + void begin_object() + { + begin_element(); + } + + void end_element() + { +// current.pop_back(); + } + + void end_object() + { + end_element(); + } + + void begin_array() + { + begin_element(); + } + + void end_array() + { + end_element(); + } + + void clearKey () + { + } + + template + void v_int(V &v) + { + v = current.back().get_int(); + } + +#ifdef IO_JSON_USE_LARGE_NUMBER_PROXY + + template + void v_proxy(V &v) + { + v = current.back().get_proxy(); + } + + template<> + void v_int(s64 &v) + { + return v_proxy(v); + } + + template<> + void v_int(u64 &v) + { + return v_proxy(v); + } + + template<> + void v_int(s128 &v) + { + return v_proxy(v); + } + + template<> + void v_int(u128 &v) + { + return v_proxy(v); + } + +#endif + + template + void v_string(V &v) + { + v = current.back().get_utf8(); + } + + template + void v_double(V &v) + { + v = current.back().get_double(); + } + + template + void v_bool(V &v) + { + v = current.back().get_bool(); + } + + void v(u8 &v) { v_int(v); } + void v(u16 &v) { v_int(v); } + void v(u32 &v) { v_int(v); } + void v(u64 &v) { v_int(v); } + void v(u128 &v) { v_int(v); } + void v(s8 &v) { v_int(v); } + void v(char &v) { v_int(v); } + void v(s16 &v) { v_int(v); } + void v(s32 &v) { v_int(v); } + void v(s64 &v) { v_int(v); } + void v(s128 &v) { v_int(v); } + void v(r32 &v) { v_double(v); } + void v(r64 &v) { v_double(v); } + void v(r128 &v) { v_double(v); } + void v(bool &v) { v_bool(v); } + void v(std::string &v) { v_string(v); } + +#if defined(SYS_MAC) || defined(SYS_IOS) + void v(unsigned long &v) { v_int(v); } + void v(signed long &v) { v_int(v); } +#endif + + bool is_null() + { + return current.back().is_null(); + } +} ; + +// ------------------------ + +struct ReaderEmptyBaseClass { + template + void dispatch_custom(IO &io, T &t) {} +}; + +struct ReaderAllocatorDefault { + template + void allocate(IO &io, T &t) + { + io_ptr_allocate_default(io, t); + } +} ; + +template +class Reader : public Base +{ +public: + typedef Stream_ Stream; + + Stream f; + u8 count = 0; + +public: + Allocator allocator; + + template + void allocate(T &t) + { + allocator.allocate(*this, t); + } + + auto partial(const Serialization &s) + { + using ReaderT = Reader; + return strong(std::move(Serialization(s))); + } + +public: + template + Reader (Args&&... args) : + f(std::forward(args)...) + { + } + + operator bool() const + { + return f.good(); + } + + template + void intrinsic(T &t) + { + read(t); + } + + template + void intrinsic_string(T &t) + { + std::string s; + read(s); + t = s; + } + + template + void intrinsic_string(std::string &t) + { + read(t); + } + + template + void any(T &t) + { + dispatch_any(t); + } + + template + void any(T &&t) + { + dispatch_any(t); + } + + template + void object(T&&... t) + { + object_begin(); + object_(t...); + object_end(); + } + + void object_begin() + { + f.begin_object(); + } + + template + void object_kf(KeyType k, F &&q) + { + f.key(k); + q(); + f.endKey(); + }; + + template + void object_kv(T&&... t) + { + object_(t...); + } + + void object_end() + { + f.end_object(); + } + + template + void array(T&&... t) + { + f.begin_array(); + array_(t...); + f.end_array(); + } + + template + void tuple(std::tuple &t) + { + f.begin_array(); + std::apply([this](auto &...x){(av(x), ...);}, t); + f.end_array(); + } + + template + void on_dispatch_any(T &t) + { + on_any(t); + } + + template + void on_dispatch_custom(T &t) + { + Base::dispatch_custom(*this, t); + } + +protected: + // ----- read + + template + void read(T &t) + { + f.v(t); + } + + // ----- dispatch out and in + + template + void dispatch_any(T &t) + { + IO_JSON_LOG << "dispatch_any " << type_id().name() << std::endl; + + io_json_r_dispatch(*this, t); + } + + // kv & av ----------- + + template + void kv(KeyType k, V &v) + { + f.key(k); + any(v); + f.endKey(); + } + + template + void kv(KeyType k, Optional &t) + { + if (f.hasKeyValue(k)) + { + kv(k, t.v); + } + else + { + } + } + + template + void kv(KeyType k, std::optional &t) + { + if (f.hasKeyValue(k)) + { + t.emplace(); + kv(k, *t); + } + else + { + } + } + + template + void kv(KeyType k, Pointer &t) + { + if (f.hasKeyValue(k)) + { + allocator.allocate(*this, *t.p); + kv(k, **t.p); + } + } + + template + void av(T &v) + { + f.next_array_key(); + any(v); + f.endKey(); + } + + template + void av_no_ref(T v) + { + f.next_array_key(); + any(v); + f.endKey(); + } + + // -- handling different containers -- + +public: + size_t array_begin() + { + f.begin_array(); + return f.array_size(); + } + + void array_end() + { + f.end_array(); + } + + template + void on_vector_begin (T &t) + { + auto count = array_begin(); + io_resize_vector(t, count); + } + + template + void on_vector_end (T &t) + { + array_end(); + } + + template + void on_vector_value(T &t) + { + av(t); + } + + template + void array_values(size_t size, F &&fun) + { + for (auto i=0;i + void object_values(F &&fun) + { + f.begin_object(); + + auto keys = f.object_keys(); + for (auto &k: keys) + { + typedef K Key; + Key key = core::from_string(k); + + f.key(k); + fun(k); + f.endKey(); + } + + f.end_object(); + } + +protected: + template + void on_vector(T &t) + { + IO_JSON_LOG << "many " << type_id().name() << std::endl; + on_vector_begin(t); + + for (auto &v : t) + on_vector_value(v); + + on_vector_end(t); + } + + void on_vector(std::vector &t) + { + typedef std::vector T; + IO_JSON_LOG << "many " << type_id().name() << std::endl; + + on_vector_begin(t); + + for (auto i = t.begin(); i!=t.end(); ++i) + av_no_ref(*i); + + on_vector_end(t); + } + + template + void on_map(T &t) + { + IO_JSON_LOG << "map " << type_id().name() << std::endl; + + f.begin_object(); + + auto keys = f.object_keys(); + for (auto &k: keys) + { + typedef typename T::key_type Key; + Key key = core::from_string(k); + + typename T::mapped_type value; + + kv(asKeyType(k), value); + t.emplace(key, value); + } + + f.end_object(); + } + + template + void on_set(T &t) + { + IO_JSON_LOG << "many " << type_id().name() << std::endl; + + f.begin_array(); + + auto count = f.array_size(); + + for (auto i=0; i::value || is_stringish::value, T>::type* = nullptr> + void on_any(T &t) + { + io_dispatch_failed(*this, t); + } + + template + void on_any(std::set &t) + { + on_set(t); + } + + template::value && !is_mappish::value && !is_stringish::value, T>::type* = nullptr> + void on_any(T &t) + { + on_vector(t); + } + + template::value && is_mappish::value, T>::type* = nullptr> + void on_any(T &t) + { + on_map(t); + } + +protected: + // ---- handling object & array ---- + + void object_() + { + } + + template + void object__(KeyType key, T &t) + { + IO_JSON_LOG << "key " << key << std::endl; + kv(key, t); + } + + template + void object_(Args&& ...args) + { + call_expand_2( + [&](auto && ...args) { object__(std::forward(args)...); }, + std::forward(args)... + ); + } + + template + void array__(T &t) + { + av(t); + } + + template + void array_(Args&& ...args) + { + call_expand_1( + [&](auto && ...args) { array__(std::forward(args)...); }, + std::forward(args)... + ); + } + + template + void tuple_(std::tuple &t) + { + std::apply([this](auto &...x){(av(x), ...);}, t); + } + + // ----- miscellaneous ----- + +public: + bool null () + { + return f.is_null(); + } + + template + Optional optional(T &t, bool write=true) + { + return io::json::optional(t, write); + } + + template + Pointer pointer(B &b) + { + return io::json::Pointer { &b }; + } + + template + AsKeyType asKeyType(const K &k) + { + return io::json::asKeyType(k); + } + + template + Custom custom(T &t) + { + return Custom { &t }; + } +} ; + +template +void deserializeFrom (Reader &reader, T &t); + +template +T deserializeFrom (Reader &reader); + +template +T deserialize (Serialization &&data); + +template +T deserialize (Serialization &&data); + +template +T deserialize (const Serialization &data); + +} // namespace + +#include "in_sajson.inl" diff --git a/tjp/core/io/json/in_sajson.inl b/tjp/core/io/json/in_sajson.inl new file mode 100644 index 0000000..da8ac7a --- /dev/null +++ b/tjp/core/io/json/in_sajson.inl @@ -0,0 +1,76 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include "IO.h" + +namespace tjp::core::io::json { + +template +void io_json_r(IO &io_, T &t) +{ + if constexpr (has_io_json::value) + { + io_json(io_, t); + } + else + { + io_r(io_, t); + } +} + +template +void io_json_r_dispatch(IO &io_, T &t) +{ + io_json_r(io_, t); +} + +template +void deserializeFrom (Reader &reader, T &t) +{ + reader.any(t); +} + +template +T deserializeFrom (Reader &reader) +{ + T t; + deserializeFrom(reader, t); + return t; +} + +template +T deserialize (Serialization &&data) +{ + R reader(std::move(data)); + return deserializeFrom(reader); +} + +template +T deserialize (Serialization &&data) +{ + return deserialize, T>(std::move(data)); +} + +template +T deserialize (const Serialization &data) +{ + return deserialize, T>(Serialization(data)); +} + + + +template +void fromStream (const std::istream &stream, T &t) +{ + std::vector v; + omemstream s(v); + s << stream.rdbuf(); //read the file + + Reader reader(std::move(v)); + reader.any(t); +} + +} // namespace diff --git a/tjp/core/io/json/json.inl b/tjp/core/io/json/json.inl new file mode 100644 index 0000000..c998666 --- /dev/null +++ b/tjp/core/io/json/json.inl @@ -0,0 +1,26 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include + +namespace nlohmann { + +template +void io_json_w(IO &io, const json &j) +{ + auto s = to_string(j); + io.any(s); +} + +template +void io_json_r(IO &io, json &j) +{ + std::string s; + io.any(s); + j = json(s); +} + +} // namespace diff --git a/tjp/core/io/json/out.h b/tjp/core/io/json/out.h new file mode 100644 index 0000000..f6919d0 --- /dev/null +++ b/tjp/core/io/json/out.h @@ -0,0 +1,650 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include "IO.h" + +#include +#include + +#include + +#ifdef IO_JSON_USE_LARGE_NUMBER_PROXY +#include +#include +#endif + +namespace tjp::core::io::json { + +template +void io_json_w(IO &io, const T &t); + +template +void io_json_w_dispatch(IO &io, const T &t); + +struct json_out { + std::ostream *out; + + char Q = '"'; + char C = ':'; + char SEP = ','; + bool firstValue=true; + int level = 0; + + json_out(std::ostream &out_) : + out(&out_) + { + } + + json_out() + { + } + + ~json_out() + { + } + + void prettyPrintX () + { + (*out) << std::endl; + for (auto i=0; i + void v_value(const V &v_) + { + valueSeparator(); + + (*out) << v_; + endValue(); + } + + template + void v_quoted(const T &v_) + { + valueSeparator(); + + (*out) << Q; + + auto &oss = *out; + for (const char c : v_) { + switch (c) { + case '\"': oss << "\\\""; break; + case '\\': oss << "\\\\"; break; + case '\b': oss << "\\b"; break; + case '\f': oss << "\\f"; break; + case '\n': oss << "\\n"; break; + case '\r': oss << "\\r"; break; + case '\t': oss << "\\t"; break; + default: + if (static_cast(c) < 0x20) + { + char buffer[7]; + std::snprintf(buffer, sizeof(buffer), "\\u%04X", static_cast(c)); + oss << buffer; + } + else + { + oss << c; + } + } + } + + (*out) << Q; + endValue(); + } + +#ifdef IO_JSON_USE_LARGE_NUMBER_PROXY + + template + void v_value_proxy(const V &v_) + { + constexpr size_t b64Size = sizeof(V) * 4; + InPlaceArray b64; + b64.resize(b64Size); + + auto size = toBase64((char *)&v_, sizeof(V), b64.data(), b64.size()); + + b64.resize(size); + v_quoted(b64); + } +#else + + template + void v_value_proxy(const V &v_) + { + v_value(v_); + } + +#endif + + + template + void v(const V &v_) + { + v_value(v_); + } + + void v(const std::string &v_) + { + v_quoted(v_); + } + + void v(const std::string_view &v_) + { + v_quoted(v_); + } + + void v(const u8 &v_) + { + v_value((u32)v_); + } + + void v(const s8 &v_) + { + v_value((s32)v_); + } + + void v(const char &v_) + { + v_value((s32)v_); + } + + void v(const s64 &v_) + { + v_value_proxy(v_); + } + + void v(const u64 &v_) + { + v_value_proxy(v_); + } + + void v(const s128 &v_) + { + v_value_proxy(v_); + } + + void v(const u128 &v_) + { + v_value_proxy(v_); + } + + void v(const bool &v_) + { + v_value(v_ ? "true" : "false"); + } + + void null() + { + v_value("null"); + } + +} ; + +struct json_out_mem : json_out +{ + Serialization block; + omemstream s; + + json_out_mem() : + s(block) + { + s.precision(17); + out = &s; + } +} ; + +inline +auto serialization_of(json_out &m) +{ +} + +inline +auto serialization_of(json_out_mem &m) +{ + return m.block; +} + +// ------------------------ + +struct EmptyWriterBaseClass { + template + void dispatch_custom(IO &io, T &t) {} +} ; + +struct WriterAllocatorBase { + template + void allocate(T &t) + { + io_ptr_allocate_default(*this, t); + } +} ; + +template +class Writer : + public Base, + public Allocator +{ +public: + S f; + u8 count=0; + +public: + auto partial() + { + return Writer(); + } + + auto serialization () + { + return serialization_of(f); + } + + auto partial_serialization () + { + return serialization(); + } + +public: + template + Writer (Args&&... args) : + f(args...) + { + } + + operator bool() const + { + return f.good(); + } + + template + void intrinsic(const T &t) + { + write(t); + } + + template + void intrinsic_string(const T &t) + { + write(std::string_view(t)); + } + + template + void any(const T &t) + { + dispatch_any(t); + } + + template + void object(T&&... t) + { + object_begin(); + object_(t...); + object_end(); + } + + void object_begin() + { + f.begin_object(); + } + + template + void object_kf(KeyType k, F &&q) + { + f.key(k); + q(); + }; + + template + void object_kv(T&&... t) + { + object_(t...); + } + + void object_end() + { + f.end_object(); + } + + + template + void array(T&&... t) + { + f.begin_array(); + array_(t...); + f.end_array(); + } + + template + void tuple(std::tuple &t) + { + f.begin_array(); + std::apply([this](auto &...x){(av(x), ...);}, t); + f.end_array(); + } + + template + void on_dispatch_any(const T &t) + { + on_any(t); + } + + void null() + { + f.null(); + } + + template + void on_dispatch_custom(T &t) + { + Base::dispatch_custom(*this, t); + } + +protected: + template + void dispatch_any(const T &t) + { + IO_JSON_LOG << "any " << type_id().name() << std::endl; + + io_json_w_dispatch(*this, t); + } + + template + void write(const T &t) + { + f.v(t); + } + + // kv & av ----------- + + template + void kv(KeyType k, const V &v) + { + f.key(k); + any(v); + } + + template + void kv(KeyType k, const Optional &t) + { + if (t.write) + kv(k, t.v); + } + + template + void kv(KeyType k, const std::optional &t) + { + if (t.has_value()) + kv(k, *t); + } + + template + void kv(KeyType k, const Pointer &t) + { + if (t.write) + kv(k, *(T *)*t.p); + } + + template + void av(const V &v) + { + any(v); + } + + // -- handling different containers -- +public: + void array_begin (Size size) + { + f.begin_array(); + } + + void array_end() + { + f.end_array(); + } + + template + void on_vector_begin (const T &t) + { + array_begin((Size)t.size()); + } + + template + void on_vector_end (const T &t) + { + array_end(); + } + + template + void on_vector_value(const T &t) + { + av(t); + } + +protected: + template + void on_vector(const T &t) + { + IO_JSON_LOG << "many " << type_id().name() << std::endl; + on_vector_begin(t); + + // this fails for std::vector, because vector bool + // does some magic to compress to a bit field, and then the iterator + // doesn't pass back a reference + // for (const auto &v : t) + // any(v); + + for (auto i = t.begin(); i!=t.end(); ++i) + on_vector_value(*i); + + on_vector_end(t); + } + + template + void on_map(const T &t) + { + IO_JSON_LOG << "map " << type_id().name() << std::endl; + + f.begin_object(); + + for (auto &v : t) + kv(asKeyType(v.first), v.second); + + f.end_object(); + } + + // -- dispatching different containers -- + + template::value || is_stringish::value, T>::type* = nullptr> + void on_any(const T &t) + { + io_dispatch_failed(*this, t); + } + + template::value && !is_mappish::value && !is_stringish::value, T>::type* = nullptr> + void on_any(const T &t) + { + on_vector(t); + } + + template::value && is_mappish::value, T>::type* = nullptr> + void on_any(const T &t) + { + on_map(t); + } + +// void on_any(const variant::Variant &t) +// { +// on_variant(t); +// } +// + // ---- handling object & array ---- + + void object_() + { + } + + template + void object__(KeyType key, const T &t) + { + kv(key, t); + } + + template + void object_(Args&& ...args) + { + call_expand_2( + [&](auto && ...args) { object__(std::forward(args)...); }, + std::forward(args)... + ); + } + + template + void array__(const T &t) + { + any(t); + } + + template + void array_(Args&& ...args) + { + call_expand_1( + [&](auto && ...args) { array__(std::forward(args)...); }, + std::forward(args)... + ); + } + + template + void tuple_(std::tuple &t) + { + std::apply([this](auto &...x){(av(x), ...);}, t); + } + +public: + // ----- miscellaneous ----- + + template + Optional optional(T &t, bool write=true) + { + return io::json::optional(t, write); + } + + template::value, T>::type* = nullptr> + Pointer pointer(B &b) + { + return io::json::Pointer { + &b, b != nullptr + }; + } + + template::value, T>::type* = nullptr> + Pointer pointer(B &b) + { + return io::json::Pointer { + &b, dynamic_cast_ptr(b) != nullptr + }; + } + + template + AsKeyType asKeyType(const K &k) + { + return io::json::asKeyType(k); + } + + template + Custom custom(T &t) + { + return Custom { &t }; + } +} ; + +// ------------------- + +template +Serialization serialize (X&&... x); + +template +Serialization serialize (X&&... x); + +template +void serializeTo (Serialization &serialization, const T &t); + +template +Serialization serializeTo(W &w, const T &t); + +template +void toStream (std::ostream &file, X&& ... x); + +template +bool toFile (const std::string &fileName, X&& ... x); + +} // namespace + +#include "out.inl" diff --git a/tjp/core/io/json/out.inl b/tjp/core/io/json/out.inl new file mode 100644 index 0000000..db38180 --- /dev/null +++ b/tjp/core/io/json/out.inl @@ -0,0 +1,72 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include "IO.h" + +namespace tjp::core::io::json { + +template +void io_json_w(IO &io_, const T &t) +{ + typedef typename std::remove_const::type TC; + + if constexpr (has_io_json::value) + { + auto &t_ = *const_cast(&t); + io_json(io_, t_); + } + else + { + io_w(io_, t); + } +} + +template +void io_json_w_dispatch(IO &io_, const T &t) +{ + io_json_w(io_, t); +} + +template +void serialize_many (W &writer, const T &t) +{ + writer.any(t); +} + +template +void serialize_many (W &writer, const T &t, X&& ... x) +{ + writer.any(t); + serialize_many(writer, x...); +} + +template +Serialization serialize (X&&... x) +{ + Serialization v; + omemstream s(v); + s.precision(17); + + W writer(s); + + serialize_many(writer, x...); + return v; +}; + +template +Serialization serialize (X&&... x) +{ + return serialize>(x...); +} + +template +void toStream (std::ostream &file, X&& ... x) +{ + Writer writer { file }; + serialize_many(writer, x...); +} + +} // namespace diff --git a/tjp/core/io/json/out_file.hpp b/tjp/core/io/json/out_file.hpp new file mode 100644 index 0000000..4846cc7 --- /dev/null +++ b/tjp/core/io/json/out_file.hpp @@ -0,0 +1,21 @@ +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include + +namespace tjp::core::io::json { + +template +bool toFile (const std::string &fileName, X&& ... x) +{ + std::ofstream stream(fileName); + stream.precision(17); + toStream(stream, x...); + + return true; +} + +} // namespace diff --git a/tjp/core/json/sajson-save.h b/tjp/core/json/sajson-save.h new file mode 100644 index 0000000..b459f4e --- /dev/null +++ b/tjp/core/json/sajson-save.h @@ -0,0 +1,2493 @@ +/* + * Copyright (c) 2012-2017 Chad Austin + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef SAJSON_NO_STD_STRING +#include // for convenient access to error messages and string values. +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define SAJSON_LIKELY(x) __builtin_expect(!!(x), 1) +#define SAJSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#define SAJSON_ALWAYS_INLINE __attribute__((always_inline)) +#define SAJSON_UNREACHABLE() __builtin_unreachable() +#define SAJSON_snprintf snprintf +#elif defined(_MSC_VER) +#define SAJSON_LIKELY(x) x +#define SAJSON_UNLIKELY(x) x +#define SAJSON_ALWAYS_INLINE __forceinline +#define SAJSON_UNREACHABLE() __assume(0) +#if (_MSC_VER <= 1800) +#define SAJSON_snprintf _snprintf +#else +#define SAJSON_snprintf snprintf +#endif +#else +#define SAJSON_LIKELY(x) x +#define SAJSON_UNLIKELY(x) x +#define SAJSON_ALWAYS_INLINE inline +#define SAJSON_UNREACHABLE() assert(!"unreachable") +#define SAJSON_snprintf snprintf +#endif + +/** + * sajson Public API + */ +namespace sajson { + + /// Tag indicating a JSON value's type. + enum type: uint8_t { + TYPE_INTEGER = 0, + TYPE_DOUBLE = 1, + TYPE_NULL = 2, + TYPE_FALSE = 3, + TYPE_TRUE = 4, + TYPE_STRING = 5, + TYPE_ARRAY = 6, + TYPE_OBJECT = 7, + }; + + namespace internal { + static const size_t TYPE_BITS = 3; + static const size_t TYPE_MASK = (1 << TYPE_BITS) - 1; + static const size_t VALUE_MASK = size_t(-1) >> TYPE_BITS; + + static const size_t ROOT_MARKER = VALUE_MASK; + + inline type get_element_type(size_t s) { + return static_cast(s & TYPE_MASK); + } + + inline size_t get_element_value(size_t s) { + return s >> TYPE_BITS; + } + + inline size_t make_element(type t, size_t value) { + //assert((value & ~VALUE_MASK) == 0); + //value &= VALUE_MASK; + return static_cast(t) | (value << TYPE_BITS); + } + + // This template utilizes the One Definition Rule to create global arrays in a header. + // This trick courtesy of Rich Geldreich's Purple JSON parser. + template + struct globals_struct { + static const unsigned char parse_flags[256]; + }; + typedef globals_struct<> globals; + + // bit 0 (1) - set if: plain ASCII string character + // bit 1 (2) - set if: whitespace + // bit 4 (0x10) - set if: 0-9 e E . + template + const uint8_t globals_struct::parse_flags[256] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 2, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 + 3, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0x11,1, // 2 + 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11, 0x11,0x11,1, 1, 1, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 0x11,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 0x11,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + + // 128-255 + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 + }; + + inline bool is_plain_string_character(char c) { + //return c >= 0x20 && c <= 0x7f && c != 0x22 && c != 0x5c; + return (globals::parse_flags[static_cast(c)] & 1) != 0; + } + + inline bool is_whitespace(char c) { + //return c == '\r' || c == '\n' || c == '\t' || c == ' '; + return (globals::parse_flags[static_cast(c)] & 2) != 0; + } + + class allocated_buffer { + public: + allocated_buffer() + : memory(0) + {} + + explicit allocated_buffer(size_t length) { + // throws std::bad_alloc upon allocation failure + void* buffer = operator new(sizeof(size_t) + length); + memory = static_cast(buffer); + memory->refcount = 1; + } + + allocated_buffer(const allocated_buffer& that) + : memory(that.memory) + { + incref(); + } + + allocated_buffer(allocated_buffer&& that) + : memory(that.memory) + { + that.memory = 0; + } + + ~allocated_buffer() { + decref(); + } + + allocated_buffer& operator=(const allocated_buffer& that) { + if (this != &that) { + decref(); + memory = that.memory; + incref(); + } + return *this; + } + + allocated_buffer& operator=(allocated_buffer&& that) { + if (this != &that) { + decref(); + memory = that.memory; + that.memory = 0; + } + return *this; + } + + char* get_data() const { + return memory ? memory->data : 0; + } + + private: + void incref() const { + if (memory) { + ++(memory->refcount); + } + } + + void decref() const { + if (memory && --(memory->refcount) == 0) { + operator delete(memory); + } + } + + struct layout { + size_t refcount; + char data[]; + }; + + layout* memory; + }; + } + + /// A simple type encoding a pointer to some memory and a length (in bytes). + /// Does not maintain any memory. + class string { + public: + string(const char* text_, size_t length) + : text(text_) + , _length(length) + {} + + const char* data() const { + return text; + } + + size_t length() const { + return _length; + } + +#ifndef SAJSON_NO_STD_STRING + std::string as_string() const { + return std::string(text, text + _length); + } +#endif + + private: + const char* const text; + const size_t _length; + + string(); /*=delete*/ + }; + + /// A convenient way to parse JSON from a string literal. The string ends + /// at its first NUL character. + class literal : public string { + public: + template + explicit literal(const char (&text_)[sz]) + : string(text_, sz - 1) + { + static_assert(sz > 0, "!"); + } + }; + + /// A pointer to a mutable buffer, its size in bytes, and strong ownership of any + /// copied memory. + class mutable_string_view { + public: + /// Creates an empty, zero-sized view. + mutable_string_view() + : length_(0) + , data(0) + , buffer() + {} + + /// Given a length in bytes and a pointer, constructs a view + /// that does not allocate a copy of the data or maintain its life. + /// The given pointer must stay valid for the duration of the parse and the + /// resulting \ref document's life. + mutable_string_view(size_t length, char* data_) + : length_(length) + , data(data_) + , buffer() + {} + + /// Allocates a copy of the given \ref literal string and exposes a + /// mutable view into it. Throws std::bad_alloc if allocation fails. + mutable_string_view(const literal& s) + : length_(s.length()) + , buffer(length_) + { + data = buffer.get_data(); + memcpy(data, s.data(), length_); + } + + /// Allocates a copy of the given \ref string and exposes a mutable view + /// into it. Throws std::bad_alloc if allocation fails. + mutable_string_view(const string& s) + : length_(s.length()) + , buffer(length_) + { + data = buffer.get_data(); + memcpy(data, s.data(), length_); + } + + /// Copies a mutable_string_view. If any backing memory has been + /// allocated, its refcount is incremented - both views can safely + /// use the memory. + mutable_string_view(const mutable_string_view& that) + : length_(that.length_) + , data(that.data) + , buffer(that.buffer) + {} + + /// Move constructor - neuters the old mutable_string_view. + mutable_string_view(mutable_string_view&& that) + : length_(that.length_) + , data(that.data) + , buffer(std::move(that.buffer)) + { + that.length_ = 0; + that.data = 0; + } + + mutable_string_view& operator=(mutable_string_view&& that) { + if (this != &that) { + length_ = that.length_; + data = that.data; + buffer = std::move(that.buffer); + that.length_ = 0; + that.data = 0; + } + return *this; + } + + mutable_string_view& operator=(const mutable_string_view& that) { + if (this != &that) { + length_ = that.length_; + data = that.data; + buffer = that.buffer; + } + return *this; + } + + size_t length() const { + return length_; + } + + char* get_data() const { + return data; + } + + private: + size_t length_; + char* data; + internal::allocated_buffer buffer; // may not be allocated + }; + + namespace internal { + struct object_key_record { + size_t key_start; + size_t key_end; + size_t value; + }; + + struct object_key_comparator { + object_key_comparator(const char* object_data) + : data(object_data) + {} + + bool operator()(const object_key_record& lhs, const string& rhs) const { + const size_t lhs_length = lhs.key_end - lhs.key_start; + const size_t rhs_length = rhs.length(); + if (lhs_length < rhs_length) { + return true; + } else if (lhs_length > rhs_length) { + return false; + } + return memcmp(data + lhs.key_start, rhs.data(), lhs_length) < 0; + } + + bool operator()(const string& lhs, const object_key_record& rhs) const { + return !(*this)(rhs, lhs); + } + + bool operator()( + const object_key_record& lhs, + const object_key_record& rhs + ) { + const size_t lhs_length = lhs.key_end - lhs.key_start; + const size_t rhs_length = rhs.key_end - rhs.key_start; + if (lhs_length < rhs_length) { + return true; + } else if (lhs_length > rhs_length) { + return false; + } + return memcmp( + data + lhs.key_start, + data + rhs.key_start, + lhs_length + ) < 0; + } + + const char* data; + }; + } + + namespace integer_storage { + enum { + word_length = 1 + }; + + inline int load(const size_t* location) { + int value; + memcpy(&value, location, sizeof(value)); + return value; + } + + inline void store(size_t* location, int value) { + // NOTE: Most modern compilers optimize away this constant-size + // memcpy into a single instruction. If any don't, and treat + // punning through a union as legal, they can be special-cased. + static_assert( + sizeof(value) <= sizeof(*location), + "size_t must not be smaller than int"); + memcpy(location, &value, sizeof(value)); + } + } + + namespace double_storage { + enum { + word_length = sizeof(double) / sizeof(size_t) + }; + + inline double load(const size_t* location) { + double value; + memcpy(&value, location, sizeof(double)); + return value; + } + + inline void store(size_t* location, double value) { + // NOTE: Most modern compilers optimize away this constant-size + // memcpy into a single instruction. If any don't, and treat + // punning through a union as legal, they can be special-cased. + memcpy(location, &value, sizeof(double)); + } + } + + /// Represents a JSON value. First, call get_type() to check its type, + /// which determines which methods are available. + /// + /// Note that \ref value does not maintain any backing memory, only the + /// corresponding \ref document does. It is illegal to access a \ref value + /// after its \ref document has been destroyed. + class value { + public: + /// Returns the JSON value's \ref type. + type get_type() const { + return value_type; + } + + /// Returns the length of the object or array. + /// Only legal if get_type() is TYPE_ARRAY or TYPE_OBJECT. + size_t get_length() const { + assert_type_2(TYPE_ARRAY, TYPE_OBJECT); + return payload[0]; + } + + /// Returns the nth element of an array. Calling with an out-of-bound + /// index is undefined behavior. + /// Only legal if get_type() is TYPE_ARRAY. + value get_array_element(size_t index) const { + using namespace internal; + assert_type(TYPE_ARRAY); + size_t element = payload[1 + index]; + return value(get_element_type(element), payload + get_element_value(element), text); + } + + /// Returns the nth key of an object. Calling with an out-of-bound + /// index is undefined behavior. + /// Only legal if get_type() is TYPE_OBJECT. + string get_object_key(size_t index) const { + assert_type(TYPE_OBJECT); + const size_t* s = payload + 1 + index * 3; + return string(text + s[0], s[1] - s[0]); + } + + /// Returns the nth value of an object. Calling with an out-of-bound + /// index is undefined behavior. Only legal if get_type() is TYPE_OBJECT. + value get_object_value(size_t index) const { + using namespace internal; + assert_type(TYPE_OBJECT); + size_t element = payload[3 + index * 3]; + return value(get_element_type(element), payload + get_element_value(element), text); + } + + /// Given a string key, returns the value with that key or a null value + /// if the key is not found. Running time is O(lg N). + /// Only legal if get_type() is TYPE_OBJECT. + value get_value_of_key(const string& key) const { + assert_type(TYPE_OBJECT); + size_t i = find_object_key(key); + if (i < get_length()) { + return get_object_value(i); + } else { + return value(TYPE_NULL, 0, 0); + } + } + + /// Given a string key, returns the index of the associated value if + /// one exists. Returns get_length() if there is no such key. + /// Note: sajson sorts object keys, so the running time is O(lg N). + /// Only legal if get_type() is TYPE_OBJECT + size_t find_object_key(const string& key) const { + using namespace internal; + assert_type(TYPE_OBJECT); + const object_key_record* start = reinterpret_cast(payload + 1); + const object_key_record* end = start + get_length(); +#ifdef SAJSON_UNSORTED_OBJECT_KEYS + for (const object_key_record* i = start; i != end; ++i) +#else + const object_key_record* i = std::lower_bound(start, end, key, object_key_comparator(text)); +#endif + if (i != end + && (i->key_end - i->key_start) == key.length() + && memcmp(key.data(), text + i->key_start, key.length()) == 0) { + return i - start; + } + return get_length(); + } + + /// If a numeric value was parsed as a 32-bit integer, returns it. + /// Only legal if get_type() is TYPE_INTEGER. + int get_integer_value() const { + assert_type(TYPE_INTEGER); + return integer_storage::load(payload); + } + + /// If a numeric value was parsed as a double, returns it. + /// Only legal if get_type() is TYPE_DOUBLE. + double get_double_value() const { + assert_type(TYPE_DOUBLE); + return double_storage::load(payload); + } + + /// Returns a numeric value as a double-precision float. + /// Only legal if get_type() is TYPE_INTEGER or TYPE_DOUBLE. + double get_number_value() const { + assert_type_2(TYPE_INTEGER, TYPE_DOUBLE); + if (get_type() == TYPE_INTEGER) { + return get_integer_value(); + } else { + return get_double_value(); + } + } + + /// Returns true and writes to the output argument if the numeric value + /// fits in a 53-bit integer. This is useful for timestamps and other + /// situations where integral values with greater than 32-bit precision + /// are used, as 64-bit values are not understood by all JSON + /// implementations or languages. + /// Returns false if the value is not an integer or not in range. + /// Only legal if get_type() is TYPE_INTEGER or TYPE_DOUBLE. + bool get_int53_value(int64_t* out) const { + // Make sure the output variable is always defined to avoid any + // possible situation like + // https://gist.github.com/chadaustin/2c249cb850619ddec05b23ca42cf7a18 + *out = 0; + + assert_type_2(TYPE_INTEGER, TYPE_DOUBLE); + if (get_type() == TYPE_INTEGER) { + *out = get_integer_value(); + return true; + } else if (get_type() == TYPE_DOUBLE) { + double v = get_double_value(); + if (v < -(1LL << 53) || v > (1LL << 53)) { + return false; + } + int64_t as_int = static_cast(v); + if (as_int != v) { + return false; + } + *out = as_int; + return true; + } else { + return false; + } + } + + /// Returns the length of the string. + /// Only legal if get_type() is TYPE_STRING. + size_t get_string_length() const { + assert_type(TYPE_STRING); + return payload[1] - payload[0]; + } + + /// Returns a pointer to the beginning of a string value's data. + /// WARNING: Calling this function and using the return value as a + /// C-style string (that is, without also using get_string_length()) + /// will cause the string to appear truncated if the string has + /// embedded NULs. + /// Only legal if get_type() is TYPE_STRING. + const char* as_cstring() const { + assert_type(TYPE_STRING); + return text + payload[0]; + } + +#ifndef SAJSON_NO_STD_STRING + /// Returns a string's value as a std::string. + /// Only legal if get_type() is TYPE_STRING. + std::string as_string() const { + assert_type(TYPE_STRING); + return std::string(text + payload[0], text + payload[1]); + } +#endif + + /// \cond INTERNAL + const size_t* _internal_get_payload() const { + return payload; + } + /// \endcond + + private: + explicit value(type value_type_, const size_t* payload_, const char* text_) + : value_type(value_type_) + , payload(payload_) + , text(text_) + {} + + void assert_type(type expected) const { + assert(expected == get_type()); + } + + void assert_type_2(type e1, type e2) const { + assert(e1 == get_type() || e2 == get_type()); + } + + void assert_in_bounds(size_t i) const { + assert(i < get_length()); + } + + const type value_type; + const size_t* const payload; + const char* const text; + + friend class document; + }; + + /// Error code indicating why parse failed. + enum error { + ERROR_NO_ERROR, + ERROR_OUT_OF_MEMORY, + ERROR_UNEXPECTED_END, + ERROR_MISSING_ROOT_ELEMENT, + ERROR_BAD_ROOT, + ERROR_EXPECTED_COMMA, + ERROR_MISSING_OBJECT_KEY, + ERROR_EXPECTED_COLON, + ERROR_EXPECTED_END_OF_INPUT, + ERROR_UNEXPECTED_COMMA, + ERROR_EXPECTED_VALUE, + ERROR_EXPECTED_NULL, + ERROR_EXPECTED_FALSE, + ERROR_EXPECTED_TRUE, + ERROR_INVALID_NUMBER, + ERROR_MISSING_EXPONENT, + ERROR_ILLEGAL_CODEPOINT, + ERROR_INVALID_UNICODE_ESCAPE, + ERROR_UNEXPECTED_END_OF_UTF16, + ERROR_EXPECTED_U, + ERROR_INVALID_UTF16_TRAIL_SURROGATE, + ERROR_UNKNOWN_ESCAPE, + ERROR_INVALID_UTF8, + }; + + namespace internal { + class ownership { + public: + ownership() = delete; + ownership(const ownership&) = delete; + void operator=(const ownership&) = delete; + + explicit ownership(size_t* p_) + : p(p_) + {} + + ownership(ownership&& p_) + : p(p_.p) { + p_.p = 0; + } + + ~ownership() { + delete[] p; + } + + bool is_valid() const { + return !!p; + } + + private: + size_t* p; + }; + + inline const char* get_error_text(error error_code) { + switch (error_code) { + case ERROR_NO_ERROR: return "no error"; + case ERROR_OUT_OF_MEMORY: return "out of memory"; + case ERROR_UNEXPECTED_END: return "unexpected end of input"; + case ERROR_MISSING_ROOT_ELEMENT: return "missing root element"; + case ERROR_BAD_ROOT: return "document root must be object or array"; + case ERROR_EXPECTED_COMMA: return "expected ,"; + case ERROR_MISSING_OBJECT_KEY: return "missing object key"; + case ERROR_EXPECTED_COLON: return "expected :"; + case ERROR_EXPECTED_END_OF_INPUT: return "expected end of input"; + case ERROR_UNEXPECTED_COMMA: return "unexpected comma"; + case ERROR_EXPECTED_VALUE: return "expected value"; + case ERROR_EXPECTED_NULL: return "expected 'null'"; + case ERROR_EXPECTED_FALSE: return "expected 'false'"; + case ERROR_EXPECTED_TRUE: return "expected 'true'"; + case ERROR_INVALID_NUMBER: return "invalid number"; + case ERROR_MISSING_EXPONENT: return "missing exponent"; + case ERROR_ILLEGAL_CODEPOINT: return "illegal unprintable codepoint in string"; + case ERROR_INVALID_UNICODE_ESCAPE: return "invalid character in unicode escape"; + case ERROR_UNEXPECTED_END_OF_UTF16: return "unexpected end of input during UTF-16 surrogate pair"; + case ERROR_EXPECTED_U: return "expected \\u"; + case ERROR_INVALID_UTF16_TRAIL_SURROGATE: return "invalid UTF-16 trail surrogate"; + case ERROR_UNKNOWN_ESCAPE: return "unknown escape"; + case ERROR_INVALID_UTF8: return "invalid UTF-8"; + } + + SAJSON_UNREACHABLE(); + } + } + + /** + * Represents the result of a JSON parse: either is_valid() and the document + * contains a root value or parse error information is available. + * + * Note that the document holds a strong reference to any memory allocated: + * any mutable copy of the input text and any memory allocated for the + * AST data structure. Thus, the document must not be deallocated while any + * \ref value is in use. + */ + class document { + public: + document(document&& rhs) + : input(rhs.input) + , structure(std::move(rhs.structure)) + , root_type(rhs.root_type) + , root(rhs.root) + , error_line(rhs.error_line) + , error_column(rhs.error_column) + , error_code(rhs.error_code) + , error_arg(rhs.error_arg) + { + // Yikes... but strcpy is okay here because formatted_error is + // guaranteed to be null-terminated. + strcpy(formatted_error_message, rhs.formatted_error_message); + // should rhs's fields be zeroed too? + } + + /** + * Returns true if the document was parsed successfully. + * If true, call get_root() to access the document's root value. + * If false, call get_error_line(), get_error_column(), and + * get_error_message_as_cstring() to see why the parse failed. + */ + bool is_valid() const { + return root_type == TYPE_ARRAY || root_type == TYPE_OBJECT; + } + + /// If is_valid(), returns the document's root \ref value. + value get_root() const { + return value(root_type, root, input.get_data()); + } + + /// If not is_valid(), returns the one-based line number where the parse failed. + size_t get_error_line() const { + return error_line; + } + + /// If not is_valid(), returns the one-based column number where the parse failed. + size_t get_error_column() const { + return error_column; + } + +#ifndef SAJSON_NO_STD_STRING + /// If not is_valid(), returns a std::string indicating why the parse failed. + std::string get_error_message_as_string() const { + return formatted_error_message; + } +#endif + + /// If not is_valid(), returns a null-terminated C string indicating why the parse failed. + const char* get_error_message_as_cstring() const { + return formatted_error_message; + } + + /// \cond INTERNAL + + // WARNING: Internal function which is subject to change + error _internal_get_error_code() const { + return error_code; + } + + // WARNING: Internal function which is subject to change + int _internal_get_error_argument() const { + return error_arg; + } + + // WARNING: Internal function which is subject to change + const char* _internal_get_error_text() const { + return internal::get_error_text(error_code); + } + + // WARNING: Internal function exposed only for high-performance language bindings. + type _internal_get_root_type() const { + return root_type; + } + + // WARNING: Internal function exposed only for high-performance language bindings. + const size_t* _internal_get_root() const { + return root; + } + + // WARNING: Internal function exposed only for high-performance language bindings. + const mutable_string_view& _internal_get_input() const { + return input; + } + + /// \endcond + + private: + document(const document&) = delete; + void operator=(const document&) = delete; + + explicit document(const mutable_string_view& input_, internal::ownership&& structure_, type root_type_, const size_t* root_) + : input(input_) + , structure(std::move(structure_)) + , root_type(root_type_) + , root(root_) + , error_line(0) + , error_column(0) + , error_code(ERROR_NO_ERROR) + , error_arg(0) + { + formatted_error_message[0] = 0; + } + + explicit document(const mutable_string_view& input_, size_t error_line_, size_t error_column_, const error error_code_, int error_arg_) + : input(input_) + , structure(0) + , root_type(TYPE_NULL) + , root(0) + , error_line(error_line_) + , error_column(error_column_) + , error_code(error_code_) + , error_arg(error_arg_) + { + formatted_error_message[ERROR_BUFFER_LENGTH - 1] = 0; + int written = has_significant_error_arg() + ? SAJSON_snprintf(formatted_error_message, ERROR_BUFFER_LENGTH - 1, "%s: %d", _internal_get_error_text(), error_arg) + : SAJSON_snprintf(formatted_error_message, ERROR_BUFFER_LENGTH - 1, "%s", _internal_get_error_text()); + (void)written; + assert(written >= 0 && written < ERROR_BUFFER_LENGTH); + } + + bool has_significant_error_arg() const { + return error_code == ERROR_ILLEGAL_CODEPOINT; + } + + mutable_string_view input; + internal::ownership structure; + const type root_type; + const size_t* const root; + const size_t error_line; + const size_t error_column; + const error error_code; + const int error_arg; + + enum { ERROR_BUFFER_LENGTH = 128 }; + char formatted_error_message[ERROR_BUFFER_LENGTH]; + + template + friend document parse(const AllocationStrategy& strategy, const StringType& string); + template + friend class parser; + }; + + /// Allocation policy that allocates one large buffer guaranteed to hold the + /// resulting AST. This allocation policy is the fastest since it requires + /// no conditionals to see if more memory must be allocated. + class single_allocation { + public: + /// \cond INTERNAL + + class stack_head { + public: + stack_head(stack_head&& other) + : stack_bottom(other.stack_bottom) + , stack_top(other.stack_top) + {} + + bool push(size_t element) { + *stack_top++ = element; + return true; + } + + size_t* reserve(size_t amount, bool* success) { + size_t* rv = stack_top; + stack_top += amount; + *success = true; + return rv; + } + + // The compiler does not see the stack_head (stored in a local) + // and the allocator (stored as a field) have the same stack_bottom + // values, so it does a bit of redundant work. + // So there's a microoptimization available here: introduce a type + // "stack_mark" and make it polymorphic on the allocator. For + // single_allocation, it merely needs to be a single pointer. + + void reset(size_t new_top) { + stack_top = stack_bottom + new_top; + } + + size_t get_size() { + return stack_top - stack_bottom; + } + + size_t* get_top() { + return stack_top; + } + + size_t* get_pointer_from_offset(size_t offset) { + return stack_bottom + offset; + } + + private: + stack_head() = delete; + stack_head(const stack_head&) = delete; + void operator=(const stack_head&) = delete; + + explicit stack_head(size_t* base) + : stack_bottom(base) + , stack_top(base) + {} + + size_t* const stack_bottom; + size_t* stack_top; + + friend class single_allocation; + }; + + class allocator { + public: + allocator() = delete; + allocator(const allocator&) = delete; + void operator=(const allocator&) = delete; + + explicit allocator(size_t* buffer, size_t input_size, bool should_deallocate_) + : structure(buffer) + , structure_end(buffer ? buffer + input_size : 0) + , write_cursor(structure_end) + , should_deallocate(should_deallocate_) + {} + + explicit allocator(std::nullptr_t) + : structure(0) + , structure_end(0) + , write_cursor(0) + , should_deallocate(false) + {} + + allocator(allocator&& other) + : structure(other.structure) + , structure_end(other.structure_end) + , write_cursor(other.write_cursor) + , should_deallocate(other.should_deallocate) + { + other.structure = 0; + other.structure_end = 0; + other.write_cursor = 0; + other.should_deallocate = false; + } + + ~allocator() { + if (should_deallocate) { + delete[] structure; + } + } + + stack_head get_stack_head(bool* success) { + *success = true; + return stack_head(structure); + } + + size_t get_write_offset() { + return structure_end - write_cursor; + } + + size_t* get_write_pointer_of(size_t v) { + return structure_end - v; + } + + size_t* reserve(size_t size, bool* success) { + *success = true; + write_cursor -= size; + return write_cursor; + } + + size_t* get_ast_root() { + return write_cursor; + } + + internal::ownership transfer_ownership() { + auto p = structure; + structure = 0; + structure_end = 0; + write_cursor = 0; + if (should_deallocate) { + return internal::ownership(p); + } else { + return internal::ownership(0); + } + } + + private: + size_t* structure; + size_t* structure_end; + size_t* write_cursor; + bool should_deallocate; + }; + + /// \endcond + + /// Allocate a single worst-case AST buffer with one word per byte in + /// the input document. + single_allocation() + : has_existing_buffer(false) + , existing_buffer(0) + , existing_buffer_size(0) + {} + + /// Write the AST into an existing buffer. Will fail with an out of + /// memory error if the buffer is not guaranteed to be big enough for + /// the document. The caller must guarantee the memory is valid for + /// the duration of the parse and the AST traversal. + single_allocation(size_t* existing_buffer_, size_t size_in_words) + : has_existing_buffer(true) + , existing_buffer(existing_buffer_) + , existing_buffer_size(size_in_words) + {} + + /// Convenience wrapper for single_allocation(size_t*, size_t) that + /// automatically infers the length of a given array. + template + explicit single_allocation(size_t (&existing_buffer_)[N]) + : single_allocation(existing_buffer_, N) + {} + + /// \cond INTERNAL + + allocator make_allocator(size_t input_document_size_in_bytes, bool* succeeded) const { + if (has_existing_buffer) { + if (existing_buffer_size < input_document_size_in_bytes) { + *succeeded = false; + return allocator(nullptr); + } + *succeeded = true; + return allocator(existing_buffer, input_document_size_in_bytes, false); + } else { + size_t* buffer = new(std::nothrow) size_t[input_document_size_in_bytes]; + if (!buffer) { + *succeeded = false; + return allocator(nullptr); + } + *succeeded = true; + return allocator(buffer, input_document_size_in_bytes, true); + } + } + + /// \endcond + + private: + bool has_existing_buffer; + size_t* existing_buffer; + size_t existing_buffer_size; + }; + + /// Allocation policy that uses dynamically-growing buffers for both the + /// parse stack and the AST. This allocation policy minimizes peak memory + /// usage at the cost of some allocation and copying churn. + class dynamic_allocation { + public: + /// \cond INTERNAL + + class stack_head { + public: + stack_head(stack_head&& other) + : stack_top(other.stack_top) + , stack_bottom(other.stack_bottom) + , stack_limit(other.stack_limit) + { + other.stack_top = 0; + other.stack_bottom = 0; + other.stack_limit = 0; + } + + ~stack_head() { + delete[] stack_bottom; + } + + bool push(size_t element) { + if (can_grow(1)) { + *stack_top++ = element; + return true; + } else { + return false; + } + } + + size_t* reserve(size_t amount, bool* success) { + if (can_grow(amount)) { + size_t* rv = stack_top; + stack_top += amount; + *success = true; + return rv; + } else { + *success = false; + return 0; + } + } + + void reset(size_t new_top) { + stack_top = stack_bottom + new_top; + } + + size_t get_size() { + return stack_top - stack_bottom; + } + + size_t* get_top() { + return stack_top; + } + + size_t* get_pointer_from_offset(size_t offset) { + return stack_bottom + offset; + } + + private: + stack_head(const stack_head&) = delete; + void operator=(const stack_head&) = delete; + + explicit stack_head(size_t initial_capacity, bool* success) { + assert(initial_capacity); + stack_bottom = new(std::nothrow) size_t[initial_capacity]; + stack_top = stack_bottom; + if (stack_bottom) { + stack_limit = stack_bottom + initial_capacity; + } else { + stack_limit = 0; + } + *success = !!stack_bottom; + } + + bool can_grow(size_t amount) { + if (SAJSON_LIKELY(amount <= static_cast(stack_limit - stack_top))) { + return true; + } + + size_t current_size = stack_top - stack_bottom; + size_t old_capacity = stack_limit - stack_bottom; + size_t new_capacity = old_capacity * 2; + while (new_capacity < amount + current_size) { + new_capacity *= 2; + } + size_t* new_stack = new(std::nothrow) size_t[new_capacity]; + if (!new_stack) { + stack_top = 0; + stack_bottom = 0; + stack_limit = 0; + return false; + } + + memcpy(new_stack, stack_bottom, current_size * sizeof(size_t)); + delete[] stack_bottom; + stack_top = new_stack + current_size; + stack_bottom = new_stack; + stack_limit = stack_bottom + new_capacity; + return true; + } + + size_t* stack_top; // stack grows up: stack_top >= stack_bottom + size_t* stack_bottom; + size_t* stack_limit; + + friend class dynamic_allocation; + }; + + class allocator { + public: + allocator() = delete; + allocator(const allocator&) = delete; + void operator=(const allocator&) = delete; + + explicit allocator(size_t* buffer_, size_t current_capacity, size_t initial_stack_capacity_) + : ast_buffer_bottom(buffer_) + , ast_buffer_top(buffer_ + current_capacity) + , ast_write_head(ast_buffer_top) + , initial_stack_capacity(initial_stack_capacity_) + {} + + explicit allocator(std::nullptr_t) + : ast_buffer_bottom(0) + , ast_buffer_top(0) + , ast_write_head(0) + , initial_stack_capacity(0) + {} + + allocator(allocator&& other) + : ast_buffer_bottom(other.ast_buffer_bottom) + , ast_buffer_top(other.ast_buffer_top) + , ast_write_head(other.ast_write_head) + , initial_stack_capacity(other.initial_stack_capacity) + { + other.ast_buffer_bottom = 0; + other.ast_buffer_top = 0; + other.ast_write_head = 0; + } + + ~allocator() { + delete[] ast_buffer_bottom; + } + + stack_head get_stack_head(bool* success) { + return stack_head(initial_stack_capacity, success); + } + + size_t get_write_offset() { + return ast_buffer_top - ast_write_head; + } + + size_t* get_write_pointer_of(size_t v) { + return ast_buffer_top - v; + } + + size_t* reserve(size_t size, bool* success) { + if (can_grow(size)) { + ast_write_head -= size; + *success = true; + return ast_write_head; + } else { + *success = false; + return 0; + } + } + + size_t* get_ast_root() { + return ast_write_head; + } + + internal::ownership transfer_ownership() { + auto p = ast_buffer_bottom; + ast_buffer_bottom = 0; + ast_buffer_top = 0; + ast_write_head = 0; + return internal::ownership(p); + } + + private: + bool can_grow(size_t amount) { + if (SAJSON_LIKELY(amount <= static_cast(ast_write_head - ast_buffer_bottom))) { + return true; + } + size_t current_capacity = ast_buffer_top - ast_buffer_bottom; + + size_t current_size = ast_buffer_top - ast_write_head; + size_t new_capacity = current_capacity * 2; + while (new_capacity < amount + current_size) { + new_capacity *= 2; + } + + size_t* old_buffer = ast_buffer_bottom; + size_t* new_buffer = new(std::nothrow) size_t[new_capacity]; + if (!new_buffer) { + ast_buffer_bottom = 0; + ast_buffer_top = 0; + ast_write_head = 0; + return false; + } + + size_t* old_write_head = ast_write_head; + ast_buffer_bottom = new_buffer; + ast_buffer_top = new_buffer + new_capacity; + ast_write_head = ast_buffer_top - current_size; + memcpy(ast_write_head, old_write_head, current_size * sizeof(size_t)); + delete[] old_buffer; + + return true; + } + + size_t* ast_buffer_bottom; // base address of the ast buffer - it grows down + size_t* ast_buffer_top; + size_t* ast_write_head; + size_t initial_stack_capacity; + }; + + /// \endcond + + /// Creates a dynamic_allocation policy with the given initial AST + /// and stack buffer sizes. + dynamic_allocation(size_t initial_ast_capacity_ = 0, size_t initial_stack_capacity_ = 0) + : initial_ast_capacity(initial_ast_capacity_) + , initial_stack_capacity(initial_stack_capacity_) + {} + + /// \cond INTERNAL + + allocator make_allocator(size_t input_document_size_in_bytes, bool* succeeded) const { + size_t capacity = initial_ast_capacity; + if (!capacity) { + // TODO: guess based on input document size + capacity = 1024; + } + + size_t* buffer = new(std::nothrow) size_t[capacity]; + if (!buffer) { + *succeeded = false; + return allocator(nullptr); + } + + size_t stack_capacity = initial_stack_capacity; + if (!stack_capacity) { + stack_capacity = 256; + } + + *succeeded = true; + return allocator(buffer, capacity, stack_capacity); + } + + /// \endcond + + private: + size_t initial_ast_capacity; + size_t initial_stack_capacity; + }; + + /// Allocation policy that attempts to fit the parsed AST into an existing + /// memory buffer. This allocation policy is useful when using sajson in + /// a zero-allocation context or when there are constraints on the amount + // of memory that can be used. + class bounded_allocation { + public: + /// \cond INTERNAL + + class allocator; + + class stack_head { + public: + stack_head(stack_head&& other) + : source_allocator(other.source_allocator) + { + other.source_allocator = 0; + } + + bool push(size_t element) { + if (SAJSON_LIKELY(source_allocator->can_grow(1))) { + *(source_allocator->stack_top)++ = element; + return true; + } else { + return false; + } + } + + size_t* reserve(size_t amount, bool* success) { + if (SAJSON_LIKELY(source_allocator->can_grow(amount))) { + size_t* rv = source_allocator->stack_top; + source_allocator->stack_top += amount; + *success = true; + return rv; + } else { + *success = false; + return 0; + } + } + + void reset(size_t new_top) { + source_allocator->stack_top = source_allocator->structure + new_top; + } + + size_t get_size() { + return source_allocator->stack_top - source_allocator->structure; + } + + size_t* get_top() { + return source_allocator->stack_top; + } + + size_t* get_pointer_from_offset(size_t offset) { + return source_allocator->structure + offset; + } + + private: + stack_head(const stack_head&) = delete; + void operator=(const stack_head&) = delete; + + explicit stack_head(allocator* source_allocator_) + : source_allocator(source_allocator_) + {} + + allocator* source_allocator; + + friend class bounded_allocation; + }; + + class allocator { + public: + allocator() = delete; + allocator(const allocator&) = delete; + void operator=(const allocator&) = delete; + + explicit allocator(size_t* existing_buffer, size_t existing_buffer_size) + : structure(existing_buffer) + , structure_end(existing_buffer + existing_buffer_size) + , write_cursor(structure_end) + , stack_top(structure) + {} + + allocator(allocator&& other) + : structure(other.structure) + , structure_end(other.structure_end) + , write_cursor(other.write_cursor) + , stack_top(other.stack_top) + { + other.structure = 0; + other.structure_end = 0; + other.write_cursor = 0; + other.stack_top = 0; + } + + stack_head get_stack_head(bool* success) { + *success = true; + return stack_head(this); + } + + size_t get_write_offset() { + return structure_end - write_cursor; + } + + size_t* get_write_pointer_of(size_t v) { + return structure_end - v; + } + + size_t* reserve(size_t size, bool* success) { + if (can_grow(size)) { + write_cursor -= size; + *success = true; + return write_cursor; + } else { + *success = false; + return 0; + } + } + + size_t* get_ast_root() { + return write_cursor; + } + + internal::ownership transfer_ownership() { + structure = 0; + structure_end = 0; + write_cursor = 0; + return internal::ownership(0); + } + + private: + bool can_grow(size_t amount) { + // invariant: stack_top <= write_cursor + // thus: write_cursor - stack_top is positive + return static_cast(write_cursor - stack_top) >= amount; + } + + size_t* structure; + size_t* structure_end; + size_t* write_cursor; + size_t* stack_top; + + friend class bounded_allocation; + }; + + /// \endcond + + /// Uses an existing buffer to hold the parsed AST, if it fits. The + /// specified buffer must not be deallocated until after the document + /// is parsed and the AST traversed. + bounded_allocation(size_t* existing_buffer_, size_t size_in_words) + : existing_buffer(existing_buffer_) + , existing_buffer_size(size_in_words) + {} + + /// Convenience wrapper for bounded_allocation(size_t*, size) that + /// automatically infers the size of the given array. + template + explicit bounded_allocation(size_t (&existing_buffer_)[N]) + : bounded_allocation(existing_buffer_, N) + {} + + /// \cond INTERNAL + + allocator make_allocator(size_t input_document_size_in_bytes, bool* succeeded) const { + *succeeded = true; + return allocator(existing_buffer, existing_buffer_size); + } + + /// \endcond + + private: + size_t* existing_buffer; + size_t existing_buffer_size; + }; + + // I thought about putting parser in the internal namespace but I don't + // want to indent it further... + /// \cond INTERNAL + template + class parser { + public: + parser(const mutable_string_view& msv, Allocator&& allocator_) + : input(msv) + , input_end(input.get_data() + input.length()) + , allocator(std::move(allocator_)) + , root_type(TYPE_NULL) + , error_line(0) + , error_column(0) + {} + + document get_document() { + if (parse()) { + size_t* ast_root = allocator.get_ast_root(); + return document(input, allocator.transfer_ownership(), root_type, ast_root); + } else { + return document(input, error_line, error_column, error_code, error_arg); + } + } + + private: + struct error_result { + operator bool() const { + return false; + } + operator char*() const { + return 0; + } + }; + + bool at_eof(const char* p) { + return p == input_end; + } + + char* skip_whitespace(char* p) { + // There is an opportunity to make better use of superscalar + // hardware here* but if someone cares about JSON parsing + // performance the first thing they do is minify, so prefer + // to optimize for code size here. + // * https://github.com/chadaustin/Web-Benchmarks/blob/master/json/third-party/pjson/pjson.h#L1873 + for (;;) { + if (SAJSON_UNLIKELY(p == input_end)) { + return 0; + } else if (internal::is_whitespace(*p)) { + ++p; + } else { + return p; + } + } + } + + error_result oom(char* p) { + return make_error(p, ERROR_OUT_OF_MEMORY); + } + + error_result unexpected_end() { + return make_error(0, ERROR_UNEXPECTED_END); + } + + error_result unexpected_end(char* p) { + return make_error(p, ERROR_UNEXPECTED_END); + } + + error_result make_error(char* p, error code, int arg = 0) { + if (!p) { + p = input_end; + } + + error_line = 1; + error_column = 1; + + char* c = input.get_data(); + while (c < p) { + if (*c == '\r') { + if (c + 1 < p && c[1] == '\n') { + ++error_line; + error_column = 1; + ++c; + } else { + ++error_line; + error_column = 1; + } + } else if (*c == '\n') { + ++error_line; + error_column = 1; + } else { + // TODO: count UTF-8 characters + ++error_column; + } + ++c; + } + + error_code = code; + error_arg = arg; + return error_result(); + } + + bool parse() { + using namespace internal; + + // p points to the character currently being parsed + char* p = input.get_data(); + + bool success; + auto stack = allocator.get_stack_head(&success); + if (SAJSON_UNLIKELY(!success)) { + return oom(p); + } + + p = skip_whitespace(p); + if (SAJSON_UNLIKELY(!p)) { + return make_error(p, ERROR_MISSING_ROOT_ELEMENT); + } + + // current_base is an offset to the first element of the current structure (object or array) + size_t current_base = ROOT_MARKER; + size_t bottom_base = stack.get_size(); + type current_structure_type = TYPE_NULL; + + goto next_element; + + // BEGIN STATE MACHINE + + size_t pop_element; // used as an argument into the `pop` routine + + if (0) { // purely for structure + + // ASSUMES: byte at p SHOULD be skipped + array_close_or_element: + p = skip_whitespace(p + 1); + if (SAJSON_UNLIKELY(!p)) { + return unexpected_end(); + } + if (*p == ']') { + goto pop_array; + } else { + goto next_element; + } + SAJSON_UNREACHABLE(); + + // ASSUMES: byte at p SHOULD be skipped + object_close_or_element: + p = skip_whitespace(p + 1); + if (SAJSON_UNLIKELY(!p)) { + return unexpected_end(); + } + if (*p == '}') { + goto pop_object; + } else { + goto object_key; + } + SAJSON_UNREACHABLE(); + + end_or_structure_close_or_comma: + + // ASSUMES: byte at p SHOULD NOT be skipped + structure_close_or_comma: + p = skip_whitespace(p); + if (SAJSON_UNLIKELY(!p)) { + if (current_base == ROOT_MARKER) + { + stack.reset(bottom_base); + root_type = get_element_type(*stack.get_top()); + return true; + } + return unexpected_end(); + } + + if (current_structure_type == TYPE_ARRAY) { + if (*p == ']') { + goto pop_array; + } else { + if (SAJSON_UNLIKELY(*p != ',')) { + return make_error(p, ERROR_EXPECTED_COMMA); + } + ++p; + goto next_element; + } + } else { + assert(current_structure_type == TYPE_OBJECT); + if (*p == '}') { + goto pop_object; + } else { + if (SAJSON_UNLIKELY(*p != ',')) { + return make_error(p, ERROR_EXPECTED_COMMA); + } + ++p; + goto object_key; + } + } + SAJSON_UNREACHABLE(); + + // ASSUMES: *p == '}' + pop_object: { + ++p; + size_t* base_ptr = stack.get_pointer_from_offset(current_base); + pop_element = *base_ptr; + if (SAJSON_UNLIKELY(!install_object(base_ptr + 1, stack.get_top()))) { + return oom(p); + } + goto pop; + } + + // ASSUMES: *p == ']' + pop_array: { + ++p; + size_t* base_ptr = stack.get_pointer_from_offset(current_base); + pop_element = *base_ptr; + if (SAJSON_UNLIKELY(!install_array(base_ptr + 1, stack.get_top()))) { + return oom(p); + } + goto pop; + } + + // ASSUMES: byte at p SHOULD NOT be skipped + object_key: { + p = skip_whitespace(p); + if (SAJSON_UNLIKELY(!p)) { + return unexpected_end(); + } + if (SAJSON_UNLIKELY(*p != '"')) { + return make_error(p, ERROR_MISSING_OBJECT_KEY); + } + bool success_; + size_t* out = stack.reserve(2, &success_); + if (SAJSON_UNLIKELY(!success_)) { + return oom(p); + } + p = parse_string(p, out); + if (SAJSON_UNLIKELY(!p)) { + return false; + } + p = skip_whitespace(p); + if (SAJSON_UNLIKELY(!p || *p != ':')) { + return make_error(p, ERROR_EXPECTED_COLON); + } + ++p; + goto next_element; + } + + // ASSUMES: byte at p SHOULD NOT be skipped + next_element: + p = skip_whitespace(p); + if (SAJSON_UNLIKELY(!p)) { + return unexpected_end(); + } + + type value_type_result; + switch (*p) { + case 0: + return unexpected_end(p); + case 'n': + p = parse_null(p); + if (!p) { + return false; + } + value_type_result = TYPE_NULL; + break; + case 'f': + p = parse_false(p); + if (!p) { + return false; + } + value_type_result = TYPE_FALSE; + break; + case 't': + p = parse_true(p); + if (!p) { + return false; + } + value_type_result = TYPE_TRUE; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': { + auto result = parse_number(p); + p = result.first; + if (!p) { + return false; + } + value_type_result = result.second; + break; + } + case '"': { + bool success_; + size_t* string_tag = allocator.reserve(2, &success_); + if (SAJSON_UNLIKELY(!success_)) { + return oom(p); + } + p = parse_string(p, string_tag); + if (!p) { + return false; + } + value_type_result = TYPE_STRING; + break; + } + + case '[': { + size_t previous_base = current_base; + current_base = stack.get_size(); + bool s = stack.push(make_element(current_structure_type, previous_base)); + if (SAJSON_UNLIKELY(!s)) { + return oom(p); + } + current_structure_type = TYPE_ARRAY; + goto array_close_or_element; + } + case '{': { + size_t previous_base = current_base; + current_base = stack.get_size(); + bool s = stack.push(make_element(current_structure_type, previous_base)); + if (SAJSON_UNLIKELY(!s)) { + return oom(p); + } + current_structure_type = TYPE_OBJECT; + goto object_close_or_element; + } + pop: { + size_t parent = get_element_value(pop_element); + if (parent == ROOT_MARKER) { + root_type = current_structure_type; + p = skip_whitespace(p); + if (SAJSON_UNLIKELY(p)) { + return make_error(p, ERROR_EXPECTED_END_OF_INPUT); + } + return true; + } + stack.reset(current_base); + current_base = parent; + value_type_result = current_structure_type; + current_structure_type = get_element_type(pop_element); + break; + } + + case ',': + return make_error(p, ERROR_UNEXPECTED_COMMA); + default: + return make_error(p, ERROR_EXPECTED_VALUE); + } + + + bool s = stack.push(make_element( + value_type_result, + allocator.get_write_offset()) + ); + + if (SAJSON_UNLIKELY(!s)) { + return oom(p); + } + + goto structure_close_or_comma; + + } + + SAJSON_UNREACHABLE(); + } + + bool has_remaining_characters(char* p, ptrdiff_t remaining) { + return input_end - p >= remaining; + } + + char* parse_null(char* p) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 4))) { + make_error(p, ERROR_UNEXPECTED_END); + return 0; + } + char p1 = p[1]; + char p2 = p[2]; + char p3 = p[3]; + if (SAJSON_UNLIKELY(p1 != 'u' || p2 != 'l' || p3 != 'l')) { + make_error(p, ERROR_EXPECTED_NULL); + return 0; + } + return p + 4; + } + + char* parse_false(char* p) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 5))) { + return make_error(p, ERROR_UNEXPECTED_END); + } + char p1 = p[1]; + char p2 = p[2]; + char p3 = p[3]; + char p4 = p[4]; + if (SAJSON_UNLIKELY(p1 != 'a' || p2 != 'l' || p3 != 's' || p4 != 'e')) { + return make_error(p, ERROR_EXPECTED_FALSE); + } + return p + 5; + } + + char* parse_true(char* p) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 4))) { + return make_error(p, ERROR_UNEXPECTED_END); + } + char p1 = p[1]; + char p2 = p[2]; + char p3 = p[3]; + if (SAJSON_UNLIKELY(p1 != 'r' || p2 != 'u' || p3 != 'e')) { + return make_error(p, ERROR_EXPECTED_TRUE); + } + return p + 4; + } + + static double pow10(int64_t exponent) { + if (SAJSON_UNLIKELY(exponent > 308)) { + return std::numeric_limits::infinity(); + } else if (SAJSON_UNLIKELY(exponent < -323)) { + return 0.0; + } + static const double constants[] = { + 1e-323,1e-322,1e-321,1e-320,1e-319,1e-318,1e-317,1e-316,1e-315,1e-314, + 1e-313,1e-312,1e-311,1e-310,1e-309,1e-308,1e-307,1e-306,1e-305,1e-304, + 1e-303,1e-302,1e-301,1e-300,1e-299,1e-298,1e-297,1e-296,1e-295,1e-294, + 1e-293,1e-292,1e-291,1e-290,1e-289,1e-288,1e-287,1e-286,1e-285,1e-284, + 1e-283,1e-282,1e-281,1e-280,1e-279,1e-278,1e-277,1e-276,1e-275,1e-274, + 1e-273,1e-272,1e-271,1e-270,1e-269,1e-268,1e-267,1e-266,1e-265,1e-264, + 1e-263,1e-262,1e-261,1e-260,1e-259,1e-258,1e-257,1e-256,1e-255,1e-254, + 1e-253,1e-252,1e-251,1e-250,1e-249,1e-248,1e-247,1e-246,1e-245,1e-244, + 1e-243,1e-242,1e-241,1e-240,1e-239,1e-238,1e-237,1e-236,1e-235,1e-234, + 1e-233,1e-232,1e-231,1e-230,1e-229,1e-228,1e-227,1e-226,1e-225,1e-224, + 1e-223,1e-222,1e-221,1e-220,1e-219,1e-218,1e-217,1e-216,1e-215,1e-214, + 1e-213,1e-212,1e-211,1e-210,1e-209,1e-208,1e-207,1e-206,1e-205,1e-204, + 1e-203,1e-202,1e-201,1e-200,1e-199,1e-198,1e-197,1e-196,1e-195,1e-194, + 1e-193,1e-192,1e-191,1e-190,1e-189,1e-188,1e-187,1e-186,1e-185,1e-184, + 1e-183,1e-182,1e-181,1e-180,1e-179,1e-178,1e-177,1e-176,1e-175,1e-174, + 1e-173,1e-172,1e-171,1e-170,1e-169,1e-168,1e-167,1e-166,1e-165,1e-164, + 1e-163,1e-162,1e-161,1e-160,1e-159,1e-158,1e-157,1e-156,1e-155,1e-154, + 1e-153,1e-152,1e-151,1e-150,1e-149,1e-148,1e-147,1e-146,1e-145,1e-144, + 1e-143,1e-142,1e-141,1e-140,1e-139,1e-138,1e-137,1e-136,1e-135,1e-134, + 1e-133,1e-132,1e-131,1e-130,1e-129,1e-128,1e-127,1e-126,1e-125,1e-124, + 1e-123,1e-122,1e-121,1e-120,1e-119,1e-118,1e-117,1e-116,1e-115,1e-114, + 1e-113,1e-112,1e-111,1e-110,1e-109,1e-108,1e-107,1e-106,1e-105,1e-104, + 1e-103,1e-102,1e-101,1e-100,1e-99,1e-98,1e-97,1e-96,1e-95,1e-94,1e-93, + 1e-92,1e-91,1e-90,1e-89,1e-88,1e-87,1e-86,1e-85,1e-84,1e-83,1e-82,1e-81, + 1e-80,1e-79,1e-78,1e-77,1e-76,1e-75,1e-74,1e-73,1e-72,1e-71,1e-70,1e-69, + 1e-68,1e-67,1e-66,1e-65,1e-64,1e-63,1e-62,1e-61,1e-60,1e-59,1e-58,1e-57, + 1e-56,1e-55,1e-54,1e-53,1e-52,1e-51,1e-50,1e-49,1e-48,1e-47,1e-46,1e-45, + 1e-44,1e-43,1e-42,1e-41,1e-40,1e-39,1e-38,1e-37,1e-36,1e-35,1e-34,1e-33, + 1e-32,1e-31,1e-30,1e-29,1e-28,1e-27,1e-26,1e-25,1e-24,1e-23,1e-22,1e-21, + 1e-20,1e-19,1e-18,1e-17,1e-16,1e-15,1e-14,1e-13,1e-12,1e-11,1e-10,1e-9, + 1e-8,1e-7,1e-6,1e-5,1e-4,1e-3,1e-2,1e-1,1e0,1e1,1e2,1e3,1e4,1e5,1e6,1e7, + 1e8,1e9,1e10,1e11,1e12,1e13,1e14,1e15,1e16,1e17,1e18,1e19,1e20,1e21, + 1e22,1e23,1e24,1e25,1e26,1e27,1e28,1e29,1e30,1e31,1e32,1e33,1e34,1e35, + 1e36,1e37,1e38,1e39,1e40,1e41,1e42,1e43,1e44,1e45,1e46,1e47,1e48,1e49, + 1e50,1e51,1e52,1e53,1e54,1e55,1e56,1e57,1e58,1e59,1e60,1e61,1e62,1e63, + 1e64,1e65,1e66,1e67,1e68,1e69,1e70,1e71,1e72,1e73,1e74,1e75,1e76,1e77, + 1e78,1e79,1e80,1e81,1e82,1e83,1e84,1e85,1e86,1e87,1e88,1e89,1e90,1e91, + 1e92,1e93,1e94,1e95,1e96,1e97,1e98,1e99,1e100,1e101,1e102,1e103,1e104, + 1e105,1e106,1e107,1e108,1e109,1e110,1e111,1e112,1e113,1e114,1e115,1e116, + 1e117,1e118,1e119,1e120,1e121,1e122,1e123,1e124,1e125,1e126,1e127,1e128, + 1e129,1e130,1e131,1e132,1e133,1e134,1e135,1e136,1e137,1e138,1e139,1e140, + 1e141,1e142,1e143,1e144,1e145,1e146,1e147,1e148,1e149,1e150,1e151,1e152, + 1e153,1e154,1e155,1e156,1e157,1e158,1e159,1e160,1e161,1e162,1e163,1e164, + 1e165,1e166,1e167,1e168,1e169,1e170,1e171,1e172,1e173,1e174,1e175,1e176, + 1e177,1e178,1e179,1e180,1e181,1e182,1e183,1e184,1e185,1e186,1e187,1e188, + 1e189,1e190,1e191,1e192,1e193,1e194,1e195,1e196,1e197,1e198,1e199,1e200, + 1e201,1e202,1e203,1e204,1e205,1e206,1e207,1e208,1e209,1e210,1e211,1e212, + 1e213,1e214,1e215,1e216,1e217,1e218,1e219,1e220,1e221,1e222,1e223,1e224, + 1e225,1e226,1e227,1e228,1e229,1e230,1e231,1e232,1e233,1e234,1e235,1e236, + 1e237,1e238,1e239,1e240,1e241,1e242,1e243,1e244,1e245,1e246,1e247,1e248, + 1e249,1e250,1e251,1e252,1e253,1e254,1e255,1e256,1e257,1e258,1e259,1e260, + 1e261,1e262,1e263,1e264,1e265,1e266,1e267,1e268,1e269,1e270,1e271,1e272, + 1e273,1e274,1e275,1e276,1e277,1e278,1e279,1e280,1e281,1e282,1e283,1e284, + 1e285,1e286,1e287,1e288,1e289,1e290,1e291,1e292,1e293,1e294,1e295,1e296, + 1e297,1e298,1e299,1e300,1e301,1e302,1e303,1e304,1e305,1e306,1e307,1e308 + }; + return constants[exponent + 323]; + } + + std::pair parse_number(char* p) { + bool negative = false; + if ('-' == *p) { + ++p; + negative = true; + + if (SAJSON_UNLIKELY(at_eof(p))) { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + } + + bool try_double = false; + + int i = 0; + double d = 0.0; // gcc complains that d might be used uninitialized which isn't true. appease the warning anyway. + if (*p == '0') { + ++p; +// if (SAJSON_UNLIKELY(at_eof(p))) { +// return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); +// } + } else { + unsigned char c = *p; + if (c < '0' || c > '9') { + return std::make_pair(make_error(p, ERROR_INVALID_NUMBER), TYPE_NULL); + } + + do { + ++p; +// if (SAJSON_UNLIKELY(at_eof(p))) { +// return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); +// } + + unsigned char digit = c - '0'; + + if (SAJSON_UNLIKELY(!try_double && i > INT_MAX / 10 - 9)) { + // TODO: could split this into two loops + try_double = true; + d = i; + } + if (SAJSON_UNLIKELY(try_double)) { + d = 10.0 * d + digit; + } else { + i = 10 * i + digit; + } + + c = *p; + } while (c >= '0' && c <= '9' && !at_eof(p)); + } + + int64_t exponent = 0; + + if (!at_eof(p)) { + if ('.' == *p) { + if (!try_double) { + try_double = true; + d = i; + } + ++p; + if (SAJSON_UNLIKELY(at_eof(p))) { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + char c = *p; + if (c < '0' || c > '9') { + return std::make_pair(make_error(p, ERROR_INVALID_NUMBER), TYPE_NULL); + } + + do { + ++p; + if (SAJSON_UNLIKELY(at_eof(p))) { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + d = d * 10 + (c - '0'); + // One option to avoid underflow would be to clamp + // to INT_MIN, but int64 subtraction is cheap and + // in the absurd case of parsing 2 GB of digits + // with an extremely high exponent, this will + // produce accurate results. Instead, we just + // leave exponent as int64_t and it will never + // underflow. + --exponent; + + c = *p; + } while (c >= '0' && c <= '9'); + } + + char e = *p; + if ('e' == e || 'E' == e) { + if (!try_double) { + try_double = true; + d = i; + } + ++p; + if (SAJSON_UNLIKELY(at_eof(p))) { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + + bool negativeExponent = false; + if ('-' == *p) { + negativeExponent = true; + ++p; + if (SAJSON_UNLIKELY(at_eof(p))) { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + } else if ('+' == *p) { + ++p; + if (SAJSON_UNLIKELY(at_eof(p))) { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + } + + int exp = 0; + + char c = *p; + if (SAJSON_UNLIKELY(c < '0' || c > '9')) { + return std::make_pair(make_error(p, ERROR_MISSING_EXPONENT), TYPE_NULL); + } + for (;;) { + // c guaranteed to be between '0' and '9', inclusive + unsigned char digit = c - '0'; + if (exp > (INT_MAX - digit) / 10) { + // The exponent overflowed. Keep parsing, but + // it will definitely be out of range when + // pow10 is called. + exp = INT_MAX; + } else { + exp = 10 * exp + digit; + } + + ++p; + if (SAJSON_UNLIKELY(at_eof(p))) { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + + c = *p; + if (c < '0' || c > '9') { + break; + } + } + static_assert(-INT_MAX >= INT_MIN, "exp can be negated without loss or UB"); + exponent += (negativeExponent ? -exp : exp); + } + } + + if (exponent) { + assert(try_double); + // If d is zero but the exponent is huge, don't + // multiply zero by inf which gives nan. + if (d != 0.0) { + d *= pow10(exponent); + } + } + + if (negative) { + if (try_double) { + d = -d; + } else { + i = -i; + } + } + if (try_double) { + bool success; + size_t* out = allocator.reserve(double_storage::word_length, &success); + if (SAJSON_UNLIKELY(!success)) { + return std::make_pair(oom(p), TYPE_NULL); + } + double_storage::store(out, d); + return std::make_pair(p, TYPE_DOUBLE); + } else { + bool success; + size_t* out = allocator.reserve(integer_storage::word_length, &success); + if (SAJSON_UNLIKELY(!success)) { + return std::make_pair(oom(p), TYPE_NULL); + } + integer_storage::store(out, i); + return std::make_pair(p, TYPE_INTEGER); + } + } + + bool install_array(size_t* array_base, size_t* array_end) { + using namespace sajson::internal; + + const size_t length = array_end - array_base; + bool success; + size_t* const new_base = allocator.reserve(length + 1, &success); + if (SAJSON_UNLIKELY(!success)) { + return false; + } + size_t* out = new_base + length + 1; + size_t* const structure_end = allocator.get_write_pointer_of(0); + + while (array_end > array_base) { + size_t element = *--array_end; + type element_type = get_element_type(element); + size_t element_value = get_element_value(element); + size_t* element_ptr = structure_end - element_value; + *--out = make_element(element_type, element_ptr - new_base); + } + *--out = length; + return true; + } + + bool install_object(size_t* object_base, size_t* object_end) { + using namespace internal; + + assert((object_end - object_base) % 3 == 0); + const size_t length_times_3 = object_end - object_base; +#ifndef SAJSON_UNSORTED_OBJECT_KEYS + std::sort( + reinterpret_cast(object_base), + reinterpret_cast(object_end), + object_key_comparator(input.get_data())); +#endif + + bool success; + size_t* const new_base = allocator.reserve(length_times_3 + 1, &success); + if (SAJSON_UNLIKELY(!success)) { + return false; + } + size_t* out = new_base + length_times_3 + 1; + size_t* const structure_end = allocator.get_write_pointer_of(0); + + while (object_end > object_base) { + size_t element = *--object_end; + type element_type = get_element_type(element); + size_t element_value = get_element_value(element); + size_t* element_ptr = structure_end - element_value; + + *--out = make_element(element_type, element_ptr - new_base); + *--out = *--object_end; + *--out = *--object_end; + } + *--out = length_times_3 / 3; + return true; + } + + char* parse_string(char* p, size_t* tag) { + using namespace internal; + + ++p; // " + size_t start = p - input.get_data(); + char* input_end_local = input_end; + while (input_end_local - p >= 4) { + if (!is_plain_string_character(p[0])) { goto found; } + if (!is_plain_string_character(p[1])) { p += 1; goto found; } + if (!is_plain_string_character(p[2])) { p += 2; goto found; } + if (!is_plain_string_character(p[3])) { p += 3; goto found; } + p += 4; + } + for (;;) { + if (SAJSON_UNLIKELY(p >= input_end_local)) { + return make_error(p, ERROR_UNEXPECTED_END); + } + + if (!is_plain_string_character(*p)) { + break; + } + + ++p; + } + found: + if (SAJSON_LIKELY(*p == '"')) { + tag[0] = start; + tag[1] = p - input.get_data(); + *p = '\0'; + return p + 1; + } + + if (*p >= 0 && *p < 0x20) { + return make_error(p, ERROR_ILLEGAL_CODEPOINT, static_cast(*p)); + } else { + // backslash or >0x7f + return parse_string_slow(p, tag, start); + } + } + + char* read_hex(char* p, unsigned& u) { + unsigned v = 0; + int i = 4; + while (i--) { + unsigned char c = *p++; + if (c >= '0' && c <= '9') { + c -= '0'; + } else if (c >= 'a' && c <= 'f') { + c = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + c = c - 'A' + 10; + } else { + return make_error(p, ERROR_INVALID_UNICODE_ESCAPE); + } + v = (v << 4) + c; + } + + u = v; + return p; + } + + void write_utf8(unsigned codepoint, char*& end) { + if (codepoint < 0x80) { + *end++ = codepoint; + } else if (codepoint < 0x800) { + *end++ = 0xC0 | (codepoint >> 6); + *end++ = 0x80 | (codepoint & 0x3F); + } else if (codepoint < 0x10000) { + *end++ = 0xE0 | (codepoint >> 12); + *end++ = 0x80 | ((codepoint >> 6) & 0x3F); + *end++ = 0x80 | (codepoint & 0x3F); + } else { + assert(codepoint < 0x200000); + *end++ = 0xF0 | (codepoint >> 18); + *end++ = 0x80 | ((codepoint >> 12) & 0x3F); + *end++ = 0x80 | ((codepoint >> 6) & 0x3F); + *end++ = 0x80 | (codepoint & 0x3F); + } + } + + char* parse_string_slow(char* p, size_t* tag, size_t start) { + char* end = p; + char* input_end_local = input_end; + + for (;;) { + if (SAJSON_UNLIKELY(p >= input_end_local)) { + return make_error(p, ERROR_UNEXPECTED_END); + } + + if (SAJSON_UNLIKELY(*p >= 0 && *p < 0x20)) { + return make_error(p, ERROR_ILLEGAL_CODEPOINT, static_cast(*p)); + } + + switch (*p) { + case '"': + tag[0] = start; + tag[1] = end - input.get_data(); + *end = '\0'; + return p + 1; + + case '\\': + ++p; + if (SAJSON_UNLIKELY(p >= input_end_local)) { + return make_error(p, ERROR_UNEXPECTED_END); + } + + char replacement; + switch (*p) { + case '"': replacement = '"'; goto replace; + case '\\': replacement = '\\'; goto replace; + case '/': replacement = '/'; goto replace; + case 'b': replacement = '\b'; goto replace; + case 'f': replacement = '\f'; goto replace; + case 'n': replacement = '\n'; goto replace; + case 'r': replacement = '\r'; goto replace; + case 't': replacement = '\t'; goto replace; + replace: + *end++ = replacement; + ++p; + break; + case 'u': { + ++p; + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 4))) { + return make_error(p, ERROR_UNEXPECTED_END); + } + unsigned u = 0; // gcc's complaining that this could be used uninitialized. wrong. + p = read_hex(p, u); + if (!p) { + return 0; + } + if (u >= 0xD800 && u <= 0xDBFF) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 6))) { + return make_error(p, ERROR_UNEXPECTED_END_OF_UTF16); + } + char p0 = p[0]; + char p1 = p[1]; + if (p0 != '\\' || p1 != 'u') { + return make_error(p, ERROR_EXPECTED_U); + } + p += 2; + unsigned v = 0; // gcc's complaining that this could be used uninitialized. wrong. + p = read_hex(p, v); + if (!p) { + return p; + } + + if (v < 0xDC00 || v > 0xDFFF) { + return make_error(p, ERROR_INVALID_UTF16_TRAIL_SURROGATE); + } + u = 0x10000 + (((u - 0xD800) << 10) | (v - 0xDC00)); + } + write_utf8(u, end); + break; + } + default: + return make_error(p, ERROR_UNKNOWN_ESCAPE); + } + break; + + default: + // validate UTF-8 + unsigned char c0 = p[0]; + if (c0 < 128) { + *end++ = *p++; + } else if (c0 < 224) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 2))) { + return unexpected_end(p); + } + unsigned char c1 = p[1]; + if (c1 < 128 || c1 >= 192) { + return make_error(p + 1, ERROR_INVALID_UTF8); + } + end[0] = c0; + end[1] = c1; + end += 2; + p += 2; + } else if (c0 < 240) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 3))) { + return unexpected_end(p); + } + unsigned char c1 = p[1]; + if (c1 < 128 || c1 >= 192) { + return make_error(p + 1, ERROR_INVALID_UTF8); + } + unsigned char c2 = p[2]; + if (c2 < 128 || c2 >= 192) { + return make_error(p + 2, ERROR_INVALID_UTF8); + } + end[0] = c0; + end[1] = c1; + end[2] = c2; + end += 3; + p += 3; + } else if (c0 < 248) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 4))) { + return unexpected_end(p); + } + unsigned char c1 = p[1]; + if (c1 < 128 || c1 >= 192) { + return make_error(p + 1, ERROR_INVALID_UTF8); + } + unsigned char c2 = p[2]; + if (c2 < 128 || c2 >= 192) { + return make_error(p + 2, ERROR_INVALID_UTF8); + } + unsigned char c3 = p[3]; + if (c3 < 128 || c3 >= 192) { + return make_error(p + 3, ERROR_INVALID_UTF8); + } + end[0] = c0; + end[1] = c1; + end[2] = c2; + end[3] = c3; + end += 4; + p += 4; + } else { + return make_error(p, ERROR_INVALID_UTF8); + } + break; + } + } + } + + mutable_string_view input; + char* const input_end; + Allocator allocator; + + type root_type; + size_t error_line; + size_t error_column; + error error_code; + int error_arg; // optional argument for the error + }; + /// \endcond + + /** + * Parses a string of JSON bytes into a \ref document, given an allocation + * strategy instance. Any kind of string type is valid as long as a + * mutable_string_view can be constructed from it. + * + * Valid allocation strategies are \ref single_allocation, + * \ref dynamic_allocation, and \ref bounded_allocation. + * + * A \ref document is returned whether or not the parse succeeds: success + * state is available by calling document::is_valid(). + */ + template + document parse(const AllocationStrategy& strategy, const StringType& string) { + mutable_string_view input(string); + + bool success; + auto allocator = strategy.make_allocator(input.length(), &success); + if (!success) { + return document(input, 1, 1, ERROR_OUT_OF_MEMORY, 0); + } + + return parser( + input, + std::move(allocator) + ).get_document(); + } +} diff --git a/tjp/core/json/sajson.h b/tjp/core/json/sajson.h new file mode 100644 index 0000000..a803341 --- /dev/null +++ b/tjp/core/json/sajson.h @@ -0,0 +1,2642 @@ +/* + * Copyright (c) 2012-2017 Chad Austin + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// Abide by the above license, and as well: +// License: Modified MIT (NON-AI) +// See the LICENSE file in the root directory for license information. +// Copyright 2025 Timothy Prepscius + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifndef SAJSON_NO_STD_STRING +#include // for convenient access to error messages and string values. +#endif + +#include + +#if defined(__GNUC__) || defined(__clang__) +#define SAJSON_LIKELY(x) __builtin_expect(!!(x), 1) +#define SAJSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#define SAJSON_ALWAYS_INLINE __attribute__((always_inline)) +#define SAJSON_UNREACHABLE() __builtin_unreachable() +#define SAJSON_snprintf snprintf +#elif defined(_MSC_VER) +#define SAJSON_LIKELY(x) x +#define SAJSON_UNLIKELY(x) x +#define SAJSON_ALWAYS_INLINE __forceinline +#define SAJSON_UNREACHABLE() __assume(0) +#if (_MSC_VER <= 1800) +#define SAJSON_snprintf _snprintf +#else +#define SAJSON_snprintf snprintf +#endif +#else +#define SAJSON_LIKELY(x) x +#define SAJSON_UNLIKELY(x) x +#define SAJSON_ALWAYS_INLINE inline +#define SAJSON_UNREACHABLE() assert(!"unreachable") +#define SAJSON_snprintf snprintf +#endif + +#include +#include + +#ifdef SAJSON_ASSERTS +#define sajson_assert(x) test_assert(x) +#else +#define sajson_assert(x) (!(x)) +#endif + +/** + * sajson Public API + */ +namespace sajson { + + DECLARE_EXCEPTION_MESSAGE(type_error, "json-type-error"); + DECLARE_EXCEPTION_MESSAGE(bounds_error, "json-bounds-error"); + DECLARE_EXCEPTION_MESSAGE(buffer_error, "json-buffer-error"); + DECLARE_EXCEPTION_MESSAGE(internal_error, "json-internal-error"); + + // + using uint128_t = __uint128_t; + using int128_t = __int128_t; + + + /// Tag indicating a JSON value's type. + enum type: uint8_t { + TYPE_INTEGER_32 = 0, + TYPE_INTEGER_64 = 1, + TYPE_INTEGER_128 = 2, + TYPE_UNSIGNED_INTEGER_32 = 3, + TYPE_UNSIGNED_INTEGER_64 = 4, + TYPE_UNSIGNED_INTEGER_128 = 5, + TYPE_DOUBLE = 6, + TYPE_NULL = 7, + TYPE_FALSE = 8, + TYPE_TRUE = 9, + TYPE_STRING = 10, + TYPE_ARRAY = 11, + TYPE_OBJECT = 12, + TYPE_MAX = 13 + }; + + namespace internal { + static const size_t TYPE_BITS = 4; // TYPE_MAX + static const size_t TYPE_MASK = (1 << TYPE_BITS) - 1; + static const size_t VALUE_MASK = size_t(-1) >> TYPE_BITS; + + static const size_t ROOT_MARKER = VALUE_MASK; + + inline type get_element_type(size_t s) { + return static_cast(s & TYPE_MASK); + } + + inline size_t get_element_value(size_t s) { + return s >> TYPE_BITS; + } + + inline size_t make_element(type t, size_t value) { + //assert((value & ~VALUE_MASK) == 0); + //value &= VALUE_MASK; + return static_cast(t) | (value << TYPE_BITS); + } + + // This template utilizes the One Definition Rule to create global arrays in a header. + // This trick courtesy of Rich Geldreich's Purple JSON parser. + template + struct globals_struct { + static const unsigned char parse_flags[256]; + }; + typedef globals_struct<> globals; + + // bit 0 (1) - set if: plain ASCII string character + // bit 1 (2) - set if: whitespace + // bit 4 (0x10) - set if: 0-9 e E . + template + const uint8_t globals_struct::parse_flags[256] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 2, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 + 3, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0x11,1, // 2 + 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11, 0x11,0x11,1, 1, 1, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 0x11,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 0x11,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + + // 128-255 + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 + }; + + inline bool is_plain_string_character(char c) { + //return c >= 0x20 && c <= 0x7f && c != 0x22 && c != 0x5c; + return (globals::parse_flags[static_cast(c)] & 1) != 0; + } + + inline bool is_whitespace(char c) { + //return c == '\r' || c == '\n' || c == '\t' || c == ' '; + return (globals::parse_flags[static_cast(c)] & 2) != 0; + } + + class allocated_buffer { + public: + allocated_buffer() + : memory(0) + {} + + explicit allocated_buffer(size_t length) { + // throws std::bad_alloc upon allocation failure + void* buffer = operator new(sizeof(size_t) + length); + memory = static_cast(buffer); + memory->refcount = 1; + } + + allocated_buffer(const allocated_buffer& that) + : memory(that.memory) + { + incref(); + } + + allocated_buffer(allocated_buffer&& that) + : memory(that.memory) + { + that.memory = 0; + } + + ~allocated_buffer() { + decref(); + } + + allocated_buffer& operator=(const allocated_buffer& that) { + if (this != &that) { + decref(); + memory = that.memory; + incref(); + } + return *this; + } + + allocated_buffer& operator=(allocated_buffer&& that) { + if (this != &that) { + decref(); + memory = that.memory; + that.memory = 0; + } + return *this; + } + + char* get_data() const { + return memory ? memory->data : 0; + } + + private: + void incref() const { + if (memory) { + ++(memory->refcount); + } + } + + void decref() const { + if (memory && --(memory->refcount) == 0) { + operator delete(memory); + } + } + + struct layout { + size_t refcount; + char data[]; + }; + + layout* memory; + }; + } + + /// A simple type encoding a pointer to some memory and a length (in bytes). + /// Does not maintain any memory. + class string { + public: + string(const char* text_, size_t length) + : text(text_) + , _length(length) + {} + + const char* data() const { + return text; + } + + size_t length() const { + return _length; + } + +#ifndef SAJSON_NO_STD_STRING + std::string as_string() const { + return std::string(text, text + _length); + } +#endif + + private: + const char* const text; + const size_t _length; + + string(); /*=delete*/ + }; + + /// A convenient way to parse JSON from a string literal. The string ends + /// at its first NUL character. + class literal : public string { + public: + template + explicit literal(const char (&text_)[sz]) + : string(text_, sz - 1) + { + static_assert(sz > 0, "!"); + } + }; + + /// A pointer to a mutable buffer, its size in bytes, and strong ownership of any + /// copied memory. + class mutable_string_view { + public: + /// Creates an empty, zero-sized view. + mutable_string_view() + : length_(0) + , data(0) + , buffer() + {} + + /// Given a length in bytes and a pointer, constructs a view + /// that does not allocate a copy of the data or maintain its life. + /// The given pointer must stay valid for the duration of the parse and the + /// resulting \ref document's life. + mutable_string_view(size_t length, char* data_) + : length_(length) + , data(data_) + , buffer() + {} + + /// Allocates a copy of the given \ref literal string and exposes a + /// mutable view into it. Throws std::bad_alloc if allocation fails. + mutable_string_view(const literal& s) + : length_(s.length()) + , buffer(length_) + { + data = buffer.get_data(); + memcpy(data, s.data(), length_); + } + + /// Allocates a copy of the given \ref string and exposes a mutable view + /// into it. Throws std::bad_alloc if allocation fails. + mutable_string_view(const string& s) + : length_(s.length()) + , buffer(length_) + { + data = buffer.get_data(); + memcpy(data, s.data(), length_); + } + + /// Copies a mutable_string_view. If any backing memory has been + /// allocated, its refcount is incremented - both views can safely + /// use the memory. + mutable_string_view(const mutable_string_view& that) + : length_(that.length_) + , data(that.data) + , buffer(that.buffer) + {} + + /// Move constructor - neuters the old mutable_string_view. + mutable_string_view(mutable_string_view&& that) + : length_(that.length_) + , data(that.data) + , buffer(std::move(that.buffer)) + { + that.length_ = 0; + that.data = 0; + } + + mutable_string_view& operator=(mutable_string_view&& that) { + if (this != &that) { + length_ = that.length_; + data = that.data; + buffer = std::move(that.buffer); + that.length_ = 0; + that.data = 0; + } + return *this; + } + + mutable_string_view& operator=(const mutable_string_view& that) { + if (this != &that) { + length_ = that.length_; + data = that.data; + buffer = that.buffer; + } + return *this; + } + + size_t length() const { + return length_; + } + + char* get_data() const { + return data; + } + + private: + size_t length_; + char* data; + internal::allocated_buffer buffer; // may not be allocated + }; + + namespace internal { + struct object_key_record { + size_t key_start; + size_t key_end; + size_t value; + }; + + struct object_key_comparator { + object_key_comparator(const char* object_data) + : data(object_data) + {} + + bool operator()(const object_key_record& lhs, const string& rhs) const { + const size_t lhs_length = lhs.key_end - lhs.key_start; + const size_t rhs_length = rhs.length(); + if (lhs_length < rhs_length) { + return true; + } else if (lhs_length > rhs_length) { + return false; + } + return memcmp(data + lhs.key_start, rhs.data(), lhs_length) < 0; + } + + bool operator()(const string& lhs, const object_key_record& rhs) const { + return !(*this)(rhs, lhs); + } + + bool operator()( + const object_key_record& lhs, + const object_key_record& rhs + ) { + const size_t lhs_length = lhs.key_end - lhs.key_start; + const size_t rhs_length = rhs.key_end - rhs.key_start; + if (lhs_length < rhs_length) { + return true; + } else if (lhs_length > rhs_length) { + return false; + } + return memcmp( + data + lhs.key_start, + data + rhs.key_start, + lhs_length + ) < 0; + } + + const char* data; + }; + } + + template + struct integer_storage { + enum { + word_length = std::max(sizeof(T) / sizeof(size_t), size_t(1)) + }; + + static inline T load(const size_t* location) { + T value; + memcpy(&value, location, sizeof(value)); + return value; + } + + static inline void store(size_t* location, T value) { + memcpy(location, &value, sizeof(value)); + } + } ; + + namespace double_storage { + enum { + word_length = sizeof(double) / sizeof(size_t) + }; + + inline double load(const size_t* location) { + double value; + memcpy(&value, location, sizeof(double)); + return value; + } + + inline void store(size_t* location, double value) { + // NOTE: Most modern compilers optimize away this constant-size + // memcpy into a single instruction. If any don't, and treat + // punning through a union as legal, they can be special-cased. + memcpy(location, &value, sizeof(double)); + } + } + + /// Represents a JSON value. First, call get_type() to check its type, + /// which determines which methods are available. + /// + /// Note that \ref value does not maintain any backing memory, only the + /// corresponding \ref document does. It is illegal to access a \ref value + /// after its \ref document has been destroyed. + class value { + public: + /// Returns the JSON value's \ref type. + type get_type() const { + return value_type; + } + + /// Returns the length of the object or array. + /// Only legal if get_type() is TYPE_ARRAY or TYPE_OBJECT. + size_t get_length() const { + assert_type_2(TYPE_ARRAY, TYPE_OBJECT); + return payload[0]; + } + + /// Returns the nth element of an array. Calling with an out-of-bound + /// index is undefined behavior. + /// Only legal if get_type() is TYPE_ARRAY. + value get_array_element(size_t index) const { + using namespace internal; + assert_type(TYPE_ARRAY); + size_t element = payload[1 + index]; + return value(get_element_type(element), payload + get_element_value(element), text); + } + + /// Returns the nth key of an object. Calling with an out-of-bound + /// index is undefined behavior. + /// Only legal if get_type() is TYPE_OBJECT. + string get_object_key(size_t index) const { + assert_type(TYPE_OBJECT); + const size_t* s = payload + 1 + index * 3; + return string(text + s[0], s[1] - s[0]); + } + + /// Returns the nth value of an object. Calling with an out-of-bound + /// index is undefined behavior. Only legal if get_type() is TYPE_OBJECT. + value get_object_value(size_t index) const { + using namespace internal; + assert_type(TYPE_OBJECT); + size_t element = payload[3 + index * 3]; + return value(get_element_type(element), payload + get_element_value(element), text); + } + + /// Given a string key, returns the value with that key or a null value + /// if the key is not found. Running time is O(lg N). + /// Only legal if get_type() is TYPE_OBJECT. + value get_value_of_key(const string& key) const { + assert_type(TYPE_OBJECT); + size_t i = find_object_key(key); + if (i < get_length()) { + return get_object_value(i); + } else { + return value(TYPE_NULL, 0, 0); + } + } + + /// Given a string key, returns the index of the associated value if + /// one exists. Returns get_length() if there is no such key. + /// Note: sajson sorts object keys, so the running time is O(lg N). + /// Only legal if get_type() is TYPE_OBJECT + size_t find_object_key(const string& key) const { + using namespace internal; + assert_type(TYPE_OBJECT); + const object_key_record* start = reinterpret_cast(payload + 1); + const object_key_record* end = start + get_length(); +#ifdef SAJSON_UNSORTED_OBJECT_KEYS + for (const object_key_record* i = start; i != end; ++i) +#else + const object_key_record* i = std::lower_bound(start, end, key, object_key_comparator(text)); +#endif + if (i != end + && (i->key_end - i->key_start) == key.length() + && memcmp(key.data(), text + i->key_start, key.length()) == 0) { + return i - start; + } + return get_length(); + } + + template + bool get_integer_value(T &t) const { + switch (get_type()) { + case TYPE_INTEGER_32: + { + int32_t v = integer_storage::load(payload); + if (v > std::numeric_limits::max()) + return false; + + t = (T)v; + return true; + } + + case TYPE_INTEGER_64: + if (sizeof(t) < sizeof(int64_t)) + return false; + + t = integer_storage::load(payload); + return true; + + case TYPE_INTEGER_128: + t = integer_storage::load(payload); + return true; + + case TYPE_UNSIGNED_INTEGER_32: + { + uint32_t v = integer_storage::load(payload); + if (v > std::numeric_limits::max()) + return false; + + t = (T)v; + return true; + } + case TYPE_UNSIGNED_INTEGER_64: + if (sizeof(t) < sizeof(uint64_t)) + return false; + + t = integer_storage::load(payload); + return true; + + + case TYPE_UNSIGNED_INTEGER_128: + t = integer_storage::load(payload); + return true; + + default: + return false; + } + } + + /// Returns a numeric value as a double-precision float. + /// Only legal if get_type() is TYPE_INTEGER or TYPE_DOUBLE. + double get_number_value() const { + if (get_type() == TYPE_DOUBLE) + return get_double_value(); + + int128_t v; + if (sajson_assert(get_integer_value(v))) + throw type_error(); + + return v; + } + + /// If a numeric value was parsed as a double, returns it. + /// Only legal if get_type() is TYPE_DOUBLE. + double get_double_value() const { + assert_type(TYPE_DOUBLE); + return double_storage::load(payload); + } + + /// Returns the length of the string. + /// Only legal if get_type() is TYPE_STRING. + size_t get_string_length() const { + assert_type(TYPE_STRING); + return payload[1] - payload[0]; + } + + /// Returns a pointer to the beginning of a string value's data. + /// WARNING: Calling this function and using the return value as a + /// C-style string (that is, without also using get_string_length()) + /// will cause the string to appear truncated if the string has + /// embedded NULs. + /// Only legal if get_type() is TYPE_STRING. + const char* as_cstring() const { + assert_type(TYPE_STRING); + return text + payload[0]; + } + +#ifndef SAJSON_NO_STD_STRING + /// Returns a string's value as a std::string. + /// Only legal if get_type() is TYPE_STRING. + std::string_view as_string() const { + assert_type(TYPE_STRING); + return std::string_view(text + payload[0], payload[1] - payload[0]); + } +#endif + + /// \cond INTERNAL + const size_t* _internal_get_payload() const { + return payload; + } + /// \endcond + + private: + explicit value(type value_type_, const size_t* payload_, const char* text_) + : value_type(value_type_) + , payload(payload_) + , text(text_) + {} + + void assert_type(type expected) const { + if (sajson_assert(expected == get_type())) + throw type_error(); + } + + void assert_type_2(type e1, type e2) const { + if (sajson_assert(e1 == get_type() || e2 == get_type())) + throw type_error(); + } + + void assert_in_bounds(size_t i) const { + if (sajson_assert(i < get_length())) + throw bounds_error(); + } + + const type value_type; + const size_t* const payload; + const char* const text; + + friend class document; + }; + + /// Error code indicating why parse failed. + enum error { + ERROR_NO_ERROR, + ERROR_OUT_OF_MEMORY, + ERROR_UNEXPECTED_END, + ERROR_MISSING_ROOT_ELEMENT, + ERROR_BAD_ROOT, + ERROR_EXPECTED_COMMA, + ERROR_MISSING_OBJECT_KEY, + ERROR_EXPECTED_COLON, + ERROR_EXPECTED_END_OF_INPUT, + ERROR_UNEXPECTED_COMMA, + ERROR_EXPECTED_VALUE, + ERROR_EXPECTED_NULL, + ERROR_EXPECTED_FALSE, + ERROR_EXPECTED_TRUE, + ERROR_INVALID_NUMBER, + ERROR_MISSING_EXPONENT, + ERROR_ILLEGAL_CODEPOINT, + ERROR_INVALID_UNICODE_ESCAPE, + ERROR_UNEXPECTED_END_OF_UTF16, + ERROR_EXPECTED_U, + ERROR_INVALID_UTF16_TRAIL_SURROGATE, + ERROR_UNKNOWN_ESCAPE, + ERROR_INVALID_UTF8, + }; + + namespace internal { + class ownership { + public: + ownership() = delete; + ownership(const ownership&) = delete; + void operator=(const ownership&) = delete; + + explicit ownership(size_t* p_) + : p(p_) + {} + + ownership(ownership&& p_) + : p(p_.p) { + p_.p = 0; + } + + ~ownership() { + delete[] p; + } + + bool is_valid() const { + return !!p; + } + + private: + size_t* p; + }; + + inline const char* get_error_text(error error_code) { + switch (error_code) { + case ERROR_NO_ERROR: return "no error"; + case ERROR_OUT_OF_MEMORY: return "out of memory"; + case ERROR_UNEXPECTED_END: return "unexpected end of input"; + case ERROR_MISSING_ROOT_ELEMENT: return "missing root element"; + case ERROR_BAD_ROOT: return "document root must be object or array"; + case ERROR_EXPECTED_COMMA: return "expected ,"; + case ERROR_MISSING_OBJECT_KEY: return "missing object key"; + case ERROR_EXPECTED_COLON: return "expected :"; + case ERROR_EXPECTED_END_OF_INPUT: return "expected end of input"; + case ERROR_UNEXPECTED_COMMA: return "unexpected comma"; + case ERROR_EXPECTED_VALUE: return "expected value"; + case ERROR_EXPECTED_NULL: return "expected 'null'"; + case ERROR_EXPECTED_FALSE: return "expected 'false'"; + case ERROR_EXPECTED_TRUE: return "expected 'true'"; + case ERROR_INVALID_NUMBER: return "invalid number"; + case ERROR_MISSING_EXPONENT: return "missing exponent"; + case ERROR_ILLEGAL_CODEPOINT: return "illegal unprintable codepoint in string"; + case ERROR_INVALID_UNICODE_ESCAPE: return "invalid character in unicode escape"; + case ERROR_UNEXPECTED_END_OF_UTF16: return "unexpected end of input during UTF-16 surrogate pair"; + case ERROR_EXPECTED_U: return "expected \\u"; + case ERROR_INVALID_UTF16_TRAIL_SURROGATE: return "invalid UTF-16 trail surrogate"; + case ERROR_UNKNOWN_ESCAPE: return "unknown escape"; + case ERROR_INVALID_UTF8: return "invalid UTF-8"; + } + + SAJSON_UNREACHABLE(); + } + } + + /** + * Represents the result of a JSON parse: either is_valid() and the document + * contains a root value or parse error information is available. + * + * Note that the document holds a strong reference to any memory allocated: + * any mutable copy of the input text and any memory allocated for the + * AST data structure. Thus, the document must not be deallocated while any + * \ref value is in use. + */ + class document { + public: + document(document&& rhs) + : input(rhs.input) + , structure(std::move(rhs.structure)) + , root_type(rhs.root_type) + , root(rhs.root) + , error_line(rhs.error_line) + , error_column(rhs.error_column) + , error_code(rhs.error_code) + , error_arg(rhs.error_arg) + { + // Yikes... but strcpy is okay here because formatted_error is + // guaranteed to be null-terminated. + strcpy(formatted_error_message, rhs.formatted_error_message); + // should rhs's fields be zeroed too? + } + + /** + * Returns true if the document was parsed successfully. + * If true, call get_root() to access the document's root value. + * If false, call get_error_line(), get_error_column(), and + * get_error_message_as_cstring() to see why the parse failed. + */ + bool is_valid() const { + return root_type == TYPE_ARRAY || root_type == TYPE_OBJECT; + } + + /// If is_valid(), returns the document's root \ref value. + value get_root() const { + return value(root_type, root, input.get_data()); + } + + /// If not is_valid(), returns the one-based line number where the parse failed. + size_t get_error_line() const { + return error_line; + } + + /// If not is_valid(), returns the one-based column number where the parse failed. + size_t get_error_column() const { + return error_column; + } + +#ifndef SAJSON_NO_STD_STRING + /// If not is_valid(), returns a std::string indicating why the parse failed. + std::string get_error_message_as_string() const { + return formatted_error_message; + } +#endif + + /// If not is_valid(), returns a null-terminated C string indicating why the parse failed. + const char* get_error_message_as_cstring() const { + return formatted_error_message; + } + + /// \cond INTERNAL + + // WARNING: Internal function which is subject to change + error _internal_get_error_code() const { + return error_code; + } + + // WARNING: Internal function which is subject to change + int _internal_get_error_argument() const { + return error_arg; + } + + // WARNING: Internal function which is subject to change + const char* _internal_get_error_text() const { + return internal::get_error_text(error_code); + } + + // WARNING: Internal function exposed only for high-performance language bindings. + type _internal_get_root_type() const { + return root_type; + } + + // WARNING: Internal function exposed only for high-performance language bindings. + const size_t* _internal_get_root() const { + return root; + } + + // WARNING: Internal function exposed only for high-performance language bindings. + const mutable_string_view& _internal_get_input() const { + return input; + } + + /// \endcond + + private: + document(const document&) = delete; + void operator=(const document&) = delete; + + explicit document(const mutable_string_view& input_, internal::ownership&& structure_, type root_type_, const size_t* root_) + : input(input_) + , structure(std::move(structure_)) + , root_type(root_type_) + , root(root_) + , error_line(0) + , error_column(0) + , error_code(ERROR_NO_ERROR) + , error_arg(0) + { + formatted_error_message[0] = 0; + } + + explicit document(const mutable_string_view& input_, size_t error_line_, size_t error_column_, const error error_code_, int error_arg_) + : input(input_) + , structure(0) + , root_type(TYPE_NULL) + , root(0) + , error_line(error_line_) + , error_column(error_column_) + , error_code(error_code_) + , error_arg(error_arg_) + { + formatted_error_message[ERROR_BUFFER_LENGTH - 1] = 0; + int written = has_significant_error_arg() + ? SAJSON_snprintf(formatted_error_message, ERROR_BUFFER_LENGTH - 1, "%s: %d", _internal_get_error_text(), error_arg) + : SAJSON_snprintf(formatted_error_message, ERROR_BUFFER_LENGTH - 1, "%s", _internal_get_error_text()); + (void)written; + if (sajson_assert(written >= 0 && written < ERROR_BUFFER_LENGTH)) + throw buffer_error(); + } + + bool has_significant_error_arg() const { + return error_code == ERROR_ILLEGAL_CODEPOINT; + } + + mutable_string_view input; + internal::ownership structure; + const type root_type; + const size_t* const root; + const size_t error_line; + const size_t error_column; + const error error_code; + const int error_arg; + + enum { ERROR_BUFFER_LENGTH = 128 }; + char formatted_error_message[ERROR_BUFFER_LENGTH]; + + template + friend document parse(const AllocationStrategy& strategy, const StringType& string); + template + friend class parser; + }; + + /// Allocation policy that allocates one large buffer guaranteed to hold the + /// resulting AST. This allocation policy is the fastest since it requires + /// no conditionals to see if more memory must be allocated. + class single_allocation { + public: + /// \cond INTERNAL + + class stack_head { + public: + stack_head(stack_head&& other) + : stack_bottom(other.stack_bottom) + , stack_top(other.stack_top) + {} + + bool push(size_t element) { + *stack_top++ = element; + return true; + } + + size_t* reserve(size_t amount, bool* success) { + size_t* rv = stack_top; + stack_top += amount; + *success = true; + return rv; + } + + // The compiler does not see the stack_head (stored in a local) + // and the allocator (stored as a field) have the same stack_bottom + // values, so it does a bit of redundant work. + // So there's a microoptimization available here: introduce a type + // "stack_mark" and make it polymorphic on the allocator. For + // single_allocation, it merely needs to be a single pointer. + + void reset(size_t new_top) { + stack_top = stack_bottom + new_top; + } + + size_t get_size() { + return stack_top - stack_bottom; + } + + size_t* get_top() { + return stack_top; + } + + size_t* get_pointer_from_offset(size_t offset) { + return stack_bottom + offset; + } + + private: + stack_head() = delete; + stack_head(const stack_head&) = delete; + void operator=(const stack_head&) = delete; + + explicit stack_head(size_t* base) + : stack_bottom(base) + , stack_top(base) + {} + + size_t* const stack_bottom; + size_t* stack_top; + + friend class single_allocation; + }; + + class allocator { + public: + allocator() = delete; + allocator(const allocator&) = delete; + void operator=(const allocator&) = delete; + + explicit allocator(size_t* buffer, size_t input_size, bool should_deallocate_) + : structure(buffer) + , structure_end(buffer ? buffer + input_size : 0) + , write_cursor(structure_end) + , should_deallocate(should_deallocate_) + {} + + explicit allocator(std::nullptr_t) + : structure(0) + , structure_end(0) + , write_cursor(0) + , should_deallocate(false) + {} + + allocator(allocator&& other) + : structure(other.structure) + , structure_end(other.structure_end) + , write_cursor(other.write_cursor) + , should_deallocate(other.should_deallocate) + { + other.structure = 0; + other.structure_end = 0; + other.write_cursor = 0; + other.should_deallocate = false; + } + + ~allocator() { + if (should_deallocate) { + delete[] structure; + } + } + + stack_head get_stack_head(bool* success) { + *success = true; + return stack_head(structure); + } + + size_t get_write_offset() { + return structure_end - write_cursor; + } + + size_t* get_write_pointer_of(size_t v) { + return structure_end - v; + } + + size_t* reserve(size_t size, bool* success) { + *success = true; + write_cursor -= size; + return write_cursor; + } + + size_t* get_ast_root() { + return write_cursor; + } + + internal::ownership transfer_ownership() { + auto p = structure; + structure = 0; + structure_end = 0; + write_cursor = 0; + if (should_deallocate) { + return internal::ownership(p); + } else { + return internal::ownership(0); + } + } + + private: + size_t* structure; + size_t* structure_end; + size_t* write_cursor; + bool should_deallocate; + }; + + /// \endcond + + /// Allocate a single worst-case AST buffer with one word per byte in + /// the input document. + single_allocation() + : has_existing_buffer(false) + , existing_buffer(0) + , existing_buffer_size(0) + {} + + /// Write the AST into an existing buffer. Will fail with an out of + /// memory error if the buffer is not guaranteed to be big enough for + /// the document. The caller must guarantee the memory is valid for + /// the duration of the parse and the AST traversal. + single_allocation(size_t* existing_buffer_, size_t size_in_words) + : has_existing_buffer(true) + , existing_buffer(existing_buffer_) + , existing_buffer_size(size_in_words) + {} + + /// Convenience wrapper for single_allocation(size_t*, size_t) that + /// automatically infers the length of a given array. + template + explicit single_allocation(size_t (&existing_buffer_)[N]) + : single_allocation(existing_buffer_, N) + {} + + /// \cond INTERNAL + + allocator make_allocator(size_t input_document_size_in_bytes, bool* succeeded) const { + if (has_existing_buffer) { + if (existing_buffer_size < input_document_size_in_bytes) { + *succeeded = false; + return allocator(nullptr); + } + *succeeded = true; + return allocator(existing_buffer, input_document_size_in_bytes, false); + } else { + size_t* buffer = new(std::nothrow) size_t[input_document_size_in_bytes]; + if (!buffer) { + *succeeded = false; + return allocator(nullptr); + } + *succeeded = true; + return allocator(buffer, input_document_size_in_bytes, true); + } + } + + /// \endcond + + private: + bool has_existing_buffer; + size_t* existing_buffer; + size_t existing_buffer_size; + }; + + /// Allocation policy that uses dynamically-growing buffers for both the + /// parse stack and the AST. This allocation policy minimizes peak memory + /// usage at the cost of some allocation and copying churn. + class dynamic_allocation { + public: + /// \cond INTERNAL + + class stack_head { + public: + stack_head(stack_head&& other) + : stack_top(other.stack_top) + , stack_bottom(other.stack_bottom) + , stack_limit(other.stack_limit) + { + other.stack_top = 0; + other.stack_bottom = 0; + other.stack_limit = 0; + } + + ~stack_head() { + delete[] stack_bottom; + } + + bool push(size_t element) { + if (can_grow(1)) { + *stack_top++ = element; + return true; + } else { + return false; + } + } + + size_t* reserve(size_t amount, bool* success) { + if (can_grow(amount)) { + size_t* rv = stack_top; + stack_top += amount; + *success = true; + return rv; + } else { + *success = false; + return 0; + } + } + + void reset(size_t new_top) { + stack_top = stack_bottom + new_top; + } + + size_t get_size() { + return stack_top - stack_bottom; + } + + size_t* get_top() { + return stack_top; + } + + size_t* get_pointer_from_offset(size_t offset) { + return stack_bottom + offset; + } + + private: + stack_head(const stack_head&) = delete; + void operator=(const stack_head&) = delete; + + explicit stack_head(size_t initial_capacity, bool* success) { + if (sajson_assert(initial_capacity)) + throw internal_error(); + + stack_bottom = new(std::nothrow) size_t[initial_capacity]; + stack_top = stack_bottom; + if (stack_bottom) { + stack_limit = stack_bottom + initial_capacity; + } else { + stack_limit = 0; + } + *success = !!stack_bottom; + } + + bool can_grow(size_t amount) { + if (SAJSON_LIKELY(amount <= static_cast(stack_limit - stack_top))) { + return true; + } + + size_t current_size = stack_top - stack_bottom; + size_t old_capacity = stack_limit - stack_bottom; + size_t new_capacity = old_capacity * 2; + while (new_capacity < amount + current_size) { + new_capacity *= 2; + } + size_t* new_stack = new(std::nothrow) size_t[new_capacity]; + if (!new_stack) { + stack_top = 0; + stack_bottom = 0; + stack_limit = 0; + return false; + } + + memcpy(new_stack, stack_bottom, current_size * sizeof(size_t)); + delete[] stack_bottom; + stack_top = new_stack + current_size; + stack_bottom = new_stack; + stack_limit = stack_bottom + new_capacity; + return true; + } + + size_t* stack_top; // stack grows up: stack_top >= stack_bottom + size_t* stack_bottom; + size_t* stack_limit; + + friend class dynamic_allocation; + }; + + class allocator { + public: + allocator() = delete; + allocator(const allocator&) = delete; + void operator=(const allocator&) = delete; + + explicit allocator(size_t* buffer_, size_t current_capacity, size_t initial_stack_capacity_) + : ast_buffer_bottom(buffer_) + , ast_buffer_top(buffer_ + current_capacity) + , ast_write_head(ast_buffer_top) + , initial_stack_capacity(initial_stack_capacity_) + {} + + explicit allocator(std::nullptr_t) + : ast_buffer_bottom(0) + , ast_buffer_top(0) + , ast_write_head(0) + , initial_stack_capacity(0) + {} + + allocator(allocator&& other) + : ast_buffer_bottom(other.ast_buffer_bottom) + , ast_buffer_top(other.ast_buffer_top) + , ast_write_head(other.ast_write_head) + , initial_stack_capacity(other.initial_stack_capacity) + { + other.ast_buffer_bottom = 0; + other.ast_buffer_top = 0; + other.ast_write_head = 0; + } + + ~allocator() { + delete[] ast_buffer_bottom; + } + + stack_head get_stack_head(bool* success) { + return stack_head(initial_stack_capacity, success); + } + + size_t get_write_offset() { + return ast_buffer_top - ast_write_head; + } + + size_t* get_write_pointer_of(size_t v) { + return ast_buffer_top - v; + } + + size_t* reserve(size_t size, bool* success) { + if (can_grow(size)) { + ast_write_head -= size; + *success = true; + return ast_write_head; + } else { + *success = false; + return 0; + } + } + + size_t* get_ast_root() { + return ast_write_head; + } + + internal::ownership transfer_ownership() { + auto p = ast_buffer_bottom; + ast_buffer_bottom = 0; + ast_buffer_top = 0; + ast_write_head = 0; + return internal::ownership(p); + } + + private: + bool can_grow(size_t amount) { + if (SAJSON_LIKELY(amount <= static_cast(ast_write_head - ast_buffer_bottom))) { + return true; + } + size_t current_capacity = ast_buffer_top - ast_buffer_bottom; + + size_t current_size = ast_buffer_top - ast_write_head; + size_t new_capacity = current_capacity * 2; + while (new_capacity < amount + current_size) { + new_capacity *= 2; + } + + size_t* old_buffer = ast_buffer_bottom; + size_t* new_buffer = new(std::nothrow) size_t[new_capacity]; + if (!new_buffer) { + ast_buffer_bottom = 0; + ast_buffer_top = 0; + ast_write_head = 0; + return false; + } + + size_t* old_write_head = ast_write_head; + ast_buffer_bottom = new_buffer; + ast_buffer_top = new_buffer + new_capacity; + ast_write_head = ast_buffer_top - current_size; + memcpy(ast_write_head, old_write_head, current_size * sizeof(size_t)); + delete[] old_buffer; + + return true; + } + + size_t* ast_buffer_bottom; // base address of the ast buffer - it grows down + size_t* ast_buffer_top; + size_t* ast_write_head; + size_t initial_stack_capacity; + }; + + /// \endcond + + /// Creates a dynamic_allocation policy with the given initial AST + /// and stack buffer sizes. + dynamic_allocation(size_t initial_ast_capacity_ = 0, size_t initial_stack_capacity_ = 0) + : initial_ast_capacity(initial_ast_capacity_) + , initial_stack_capacity(initial_stack_capacity_) + {} + + /// \cond INTERNAL + + allocator make_allocator(size_t input_document_size_in_bytes, bool* succeeded) const { + size_t capacity = initial_ast_capacity; + if (!capacity) { + // TODO: guess based on input document size + capacity = 1024; + } + + size_t* buffer = new(std::nothrow) size_t[capacity]; + if (!buffer) { + *succeeded = false; + return allocator(nullptr); + } + + size_t stack_capacity = initial_stack_capacity; + if (!stack_capacity) { + stack_capacity = 256; + } + + *succeeded = true; + return allocator(buffer, capacity, stack_capacity); + } + + /// \endcond + + private: + size_t initial_ast_capacity; + size_t initial_stack_capacity; + }; + + /// Allocation policy that attempts to fit the parsed AST into an existing + /// memory buffer. This allocation policy is useful when using sajson in + /// a zero-allocation context or when there are constraints on the amount + // of memory that can be used. + class bounded_allocation { + public: + /// \cond INTERNAL + + class allocator; + + class stack_head { + public: + stack_head(stack_head&& other) + : source_allocator(other.source_allocator) + { + other.source_allocator = 0; + } + + bool push(size_t element) { + if (SAJSON_LIKELY(source_allocator->can_grow(1))) { + *(source_allocator->stack_top)++ = element; + return true; + } else { + return false; + } + } + + size_t* reserve(size_t amount, bool* success) { + if (SAJSON_LIKELY(source_allocator->can_grow(amount))) { + size_t* rv = source_allocator->stack_top; + source_allocator->stack_top += amount; + *success = true; + return rv; + } else { + *success = false; + return 0; + } + } + + void reset(size_t new_top) { + source_allocator->stack_top = source_allocator->structure + new_top; + } + + size_t get_size() { + return source_allocator->stack_top - source_allocator->structure; + } + + size_t* get_top() { + return source_allocator->stack_top; + } + + size_t* get_pointer_from_offset(size_t offset) { + return source_allocator->structure + offset; + } + + private: + stack_head(const stack_head&) = delete; + void operator=(const stack_head&) = delete; + + explicit stack_head(allocator* source_allocator_) + : source_allocator(source_allocator_) + {} + + allocator* source_allocator; + + friend class bounded_allocation; + }; + + class allocator { + public: + allocator() = delete; + allocator(const allocator&) = delete; + void operator=(const allocator&) = delete; + + explicit allocator(size_t* existing_buffer, size_t existing_buffer_size) + : structure(existing_buffer) + , structure_end(existing_buffer + existing_buffer_size) + , write_cursor(structure_end) + , stack_top(structure) + {} + + allocator(allocator&& other) + : structure(other.structure) + , structure_end(other.structure_end) + , write_cursor(other.write_cursor) + , stack_top(other.stack_top) + { + other.structure = 0; + other.structure_end = 0; + other.write_cursor = 0; + other.stack_top = 0; + } + + stack_head get_stack_head(bool* success) { + *success = true; + return stack_head(this); + } + + size_t get_write_offset() { + return structure_end - write_cursor; + } + + size_t* get_write_pointer_of(size_t v) { + return structure_end - v; + } + + size_t* reserve(size_t size, bool* success) { + if (can_grow(size)) { + write_cursor -= size; + *success = true; + return write_cursor; + } else { + *success = false; + return 0; + } + } + + size_t* get_ast_root() { + return write_cursor; + } + + internal::ownership transfer_ownership() { + structure = 0; + structure_end = 0; + write_cursor = 0; + return internal::ownership(0); + } + + private: + bool can_grow(size_t amount) { + // invariant: stack_top <= write_cursor + // thus: write_cursor - stack_top is positive + return static_cast(write_cursor - stack_top) >= amount; + } + + size_t* structure; + size_t* structure_end; + size_t* write_cursor; + size_t* stack_top; + + friend class bounded_allocation; + }; + + /// \endcond + + /// Uses an existing buffer to hold the parsed AST, if it fits. The + /// specified buffer must not be deallocated until after the document + /// is parsed and the AST traversed. + bounded_allocation(size_t* existing_buffer_, size_t size_in_words) + : existing_buffer(existing_buffer_) + , existing_buffer_size(size_in_words) + {} + + /// Convenience wrapper for bounded_allocation(size_t*, size) that + /// automatically infers the size of the given array. + template + explicit bounded_allocation(size_t (&existing_buffer_)[N]) + : bounded_allocation(existing_buffer_, N) + {} + + /// \cond INTERNAL + + allocator make_allocator(size_t input_document_size_in_bytes, bool* succeeded) const { + *succeeded = true; + return allocator(existing_buffer, existing_buffer_size); + } + + /// \endcond + + private: + size_t* existing_buffer; + size_t existing_buffer_size; + }; + + // I thought about putting parser in the internal namespace but I don't + // want to indent it further... + /// \cond INTERNAL + template + class parser { + public: + parser(const mutable_string_view& msv, Allocator&& allocator_) + : input(msv) + , input_end(input.get_data() + input.length()) + , allocator(std::move(allocator_)) + , root_type(TYPE_NULL) + , error_line(0) + , error_column(0) + {} + + document get_document() { + if (parse()) { + size_t* ast_root = allocator.get_ast_root(); + return document(input, allocator.transfer_ownership(), root_type, ast_root); + } else { + return document(input, error_line, error_column, error_code, error_arg); + } + } + + private: + struct error_result { + operator bool() const { + return false; + } + operator char*() const { + return 0; + } + }; + + bool at_eof(const char* p) { + return p == input_end; + } + + char* skip_whitespace(char* p) { + // There is an opportunity to make better use of superscalar + // hardware here* but if someone cares about JSON parsing + // performance the first thing they do is minify, so prefer + // to optimize for code size here. + // * https://github.com/chadaustin/Web-Benchmarks/blob/master/json/third-party/pjson/pjson.h#L1873 + for (;;) { + if (SAJSON_UNLIKELY(p == input_end)) { + return 0; + } else if (internal::is_whitespace(*p)) { + ++p; + } else { + return p; + } + } + } + + error_result oom(char* p) { + return make_error(p, ERROR_OUT_OF_MEMORY); + } + + error_result unexpected_end() { + return make_error(0, ERROR_UNEXPECTED_END); + } + + error_result unexpected_end(char* p) { + return make_error(p, ERROR_UNEXPECTED_END); + } + + error_result make_error(char* p, error code, int arg = 0) { + if (!p) { + p = input_end; + } + + error_line = 1; + error_column = 1; + + char* c = input.get_data(); + while (c < p) { + if (*c == '\r') { + if (c + 1 < p && c[1] == '\n') { + ++error_line; + error_column = 1; + ++c; + } else { + ++error_line; + error_column = 1; + } + } else if (*c == '\n') { + ++error_line; + error_column = 1; + } else { + // TODO: count UTF-8 characters + ++error_column; + } + ++c; + } + + error_code = code; + error_arg = arg; + return error_result(); + } + + bool parse() { + using namespace internal; + + // p points to the character currently being parsed + char* p = input.get_data(); + + bool success; + auto stack = allocator.get_stack_head(&success); + if (SAJSON_UNLIKELY(!success)) { + return oom(p); + } + + p = skip_whitespace(p); + if (SAJSON_UNLIKELY(!p)) { + return make_error(p, ERROR_MISSING_ROOT_ELEMENT); + } + + // current_base is an offset to the first element of the current structure (object or array) + size_t current_base = ROOT_MARKER; + size_t bottom_base = stack.get_size(); + type current_structure_type = TYPE_NULL; + + goto next_element; + + // BEGIN STATE MACHINE + + size_t pop_element; // used as an argument into the `pop` routine + + if (0) { // purely for structure + + // ASSUMES: byte at p SHOULD be skipped + array_close_or_element: + p = skip_whitespace(p + 1); + if (SAJSON_UNLIKELY(!p)) { + return unexpected_end(); + } + if (*p == ']') { + goto pop_array; + } else { + goto next_element; + } + SAJSON_UNREACHABLE(); + + // ASSUMES: byte at p SHOULD be skipped + object_close_or_element: + p = skip_whitespace(p + 1); + if (SAJSON_UNLIKELY(!p)) { + return unexpected_end(); + } + if (*p == '}') { + goto pop_object; + } else { + goto object_key; + } + SAJSON_UNREACHABLE(); + + //end_or_structure_close_or_comma: + + // ASSUMES: byte at p SHOULD NOT be skipped + structure_close_or_comma: + p = skip_whitespace(p); + if (SAJSON_UNLIKELY(!p)) { + if (current_base == ROOT_MARKER) + { + stack.reset(bottom_base); + root_type = get_element_type(*stack.get_top()); + return true; + } + return unexpected_end(); + } + + if (current_structure_type == TYPE_ARRAY) { + if (*p == ']') { + goto pop_array; + } else { + if (SAJSON_UNLIKELY(*p != ',')) { + return make_error(p, ERROR_EXPECTED_COMMA); + } + ++p; + goto next_element; + } + } else { + if (sajson_assert(current_structure_type == TYPE_OBJECT)) + throw type_error(); + + if (*p == '}') { + goto pop_object; + } else { + if (SAJSON_UNLIKELY(*p != ',')) { + return make_error(p, ERROR_EXPECTED_COMMA); + } + ++p; + goto object_key; + } + } + SAJSON_UNREACHABLE(); + + // ASSUMES: *p == '}' + pop_object: { + ++p; + size_t* base_ptr = stack.get_pointer_from_offset(current_base); + pop_element = *base_ptr; + if (SAJSON_UNLIKELY(!install_object(base_ptr + 1, stack.get_top()))) { + return oom(p); + } + goto pop; + } + + // ASSUMES: *p == ']' + pop_array: { + ++p; + size_t* base_ptr = stack.get_pointer_from_offset(current_base); + pop_element = *base_ptr; + if (SAJSON_UNLIKELY(!install_array(base_ptr + 1, stack.get_top()))) { + return oom(p); + } + goto pop; + } + + // ASSUMES: byte at p SHOULD NOT be skipped + object_key: { + p = skip_whitespace(p); + if (SAJSON_UNLIKELY(!p)) { + return unexpected_end(); + } + if (SAJSON_UNLIKELY(*p != '"')) { + return make_error(p, ERROR_MISSING_OBJECT_KEY); + } + bool success_; + size_t* out = stack.reserve(2, &success_); + if (SAJSON_UNLIKELY(!success_)) { + return oom(p); + } + p = parse_string(p, out); + if (SAJSON_UNLIKELY(!p)) { + return false; + } + p = skip_whitespace(p); + if (SAJSON_UNLIKELY(!p || *p != ':')) { + return make_error(p, ERROR_EXPECTED_COLON); + } + ++p; + goto next_element; + } + + // ASSUMES: byte at p SHOULD NOT be skipped + next_element: + p = skip_whitespace(p); + if (SAJSON_UNLIKELY(!p)) { + return unexpected_end(); + } + + type value_type_result; + switch (*p) { + case 0: + return unexpected_end(p); + case 'n': + p = parse_null(p); + if (!p) { + return false; + } + value_type_result = TYPE_NULL; + break; + case 'f': + p = parse_false(p); + if (!p) { + return false; + } + value_type_result = TYPE_FALSE; + break; + case 't': + p = parse_true(p); + if (!p) { + return false; + } + value_type_result = TYPE_TRUE; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': { + auto result = parse_number(p); + p = result.first; + if (!p) { + return false; + } + value_type_result = result.second; + break; + } + case '"': { + bool success_; + size_t* string_tag = allocator.reserve(2, &success_); + if (SAJSON_UNLIKELY(!success_)) { + return oom(p); + } + p = parse_string(p, string_tag); + if (!p) { + return false; + } + value_type_result = TYPE_STRING; + break; + } + + case '[': { + size_t previous_base = current_base; + current_base = stack.get_size(); + bool s = stack.push(make_element(current_structure_type, previous_base)); + if (SAJSON_UNLIKELY(!s)) { + return oom(p); + } + current_structure_type = TYPE_ARRAY; + goto array_close_or_element; + } + case '{': { + size_t previous_base = current_base; + current_base = stack.get_size(); + bool s = stack.push(make_element(current_structure_type, previous_base)); + if (SAJSON_UNLIKELY(!s)) { + return oom(p); + } + current_structure_type = TYPE_OBJECT; + goto object_close_or_element; + } + pop: { + size_t parent = get_element_value(pop_element); + if (parent == ROOT_MARKER) { + root_type = current_structure_type; + p = skip_whitespace(p); + if (SAJSON_UNLIKELY(p)) { + return make_error(p, ERROR_EXPECTED_END_OF_INPUT); + } + return true; + } + stack.reset(current_base); + current_base = parent; + value_type_result = current_structure_type; + current_structure_type = get_element_type(pop_element); + break; + } + + case ',': + return make_error(p, ERROR_UNEXPECTED_COMMA); + default: + return make_error(p, ERROR_EXPECTED_VALUE); + } + + + bool s = stack.push(make_element( + value_type_result, + allocator.get_write_offset()) + ); + + if (SAJSON_UNLIKELY(!s)) { + return oom(p); + } + + goto structure_close_or_comma; + + } + + SAJSON_UNREACHABLE(); + } + + bool has_remaining_characters(char* p, ptrdiff_t remaining) { + return input_end - p >= remaining; + } + + char* parse_null(char* p) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 4))) { + make_error(p, ERROR_UNEXPECTED_END); + return 0; + } + char p1 = p[1]; + char p2 = p[2]; + char p3 = p[3]; + if (SAJSON_UNLIKELY(p1 != 'u' || p2 != 'l' || p3 != 'l')) { + make_error(p, ERROR_EXPECTED_NULL); + return 0; + } + return p + 4; + } + + char* parse_false(char* p) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 5))) { + return make_error(p, ERROR_UNEXPECTED_END); + } + char p1 = p[1]; + char p2 = p[2]; + char p3 = p[3]; + char p4 = p[4]; + if (SAJSON_UNLIKELY(p1 != 'a' || p2 != 'l' || p3 != 's' || p4 != 'e')) { + return make_error(p, ERROR_EXPECTED_FALSE); + } + return p + 5; + } + + char* parse_true(char* p) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 4))) { + return make_error(p, ERROR_UNEXPECTED_END); + } + char p1 = p[1]; + char p2 = p[2]; + char p3 = p[3]; + if (SAJSON_UNLIKELY(p1 != 'r' || p2 != 'u' || p3 != 'e')) { + return make_error(p, ERROR_EXPECTED_TRUE); + } + return p + 4; + } + + static double pow10(int64_t exponent) { + if (SAJSON_UNLIKELY(exponent > 308)) { + return std::numeric_limits::infinity(); + } else if (SAJSON_UNLIKELY(exponent < -323)) { + return 0.0; + } + static const double constants[] = { + 1e-323,1e-322,1e-321,1e-320,1e-319,1e-318,1e-317,1e-316,1e-315,1e-314, + 1e-313,1e-312,1e-311,1e-310,1e-309,1e-308,1e-307,1e-306,1e-305,1e-304, + 1e-303,1e-302,1e-301,1e-300,1e-299,1e-298,1e-297,1e-296,1e-295,1e-294, + 1e-293,1e-292,1e-291,1e-290,1e-289,1e-288,1e-287,1e-286,1e-285,1e-284, + 1e-283,1e-282,1e-281,1e-280,1e-279,1e-278,1e-277,1e-276,1e-275,1e-274, + 1e-273,1e-272,1e-271,1e-270,1e-269,1e-268,1e-267,1e-266,1e-265,1e-264, + 1e-263,1e-262,1e-261,1e-260,1e-259,1e-258,1e-257,1e-256,1e-255,1e-254, + 1e-253,1e-252,1e-251,1e-250,1e-249,1e-248,1e-247,1e-246,1e-245,1e-244, + 1e-243,1e-242,1e-241,1e-240,1e-239,1e-238,1e-237,1e-236,1e-235,1e-234, + 1e-233,1e-232,1e-231,1e-230,1e-229,1e-228,1e-227,1e-226,1e-225,1e-224, + 1e-223,1e-222,1e-221,1e-220,1e-219,1e-218,1e-217,1e-216,1e-215,1e-214, + 1e-213,1e-212,1e-211,1e-210,1e-209,1e-208,1e-207,1e-206,1e-205,1e-204, + 1e-203,1e-202,1e-201,1e-200,1e-199,1e-198,1e-197,1e-196,1e-195,1e-194, + 1e-193,1e-192,1e-191,1e-190,1e-189,1e-188,1e-187,1e-186,1e-185,1e-184, + 1e-183,1e-182,1e-181,1e-180,1e-179,1e-178,1e-177,1e-176,1e-175,1e-174, + 1e-173,1e-172,1e-171,1e-170,1e-169,1e-168,1e-167,1e-166,1e-165,1e-164, + 1e-163,1e-162,1e-161,1e-160,1e-159,1e-158,1e-157,1e-156,1e-155,1e-154, + 1e-153,1e-152,1e-151,1e-150,1e-149,1e-148,1e-147,1e-146,1e-145,1e-144, + 1e-143,1e-142,1e-141,1e-140,1e-139,1e-138,1e-137,1e-136,1e-135,1e-134, + 1e-133,1e-132,1e-131,1e-130,1e-129,1e-128,1e-127,1e-126,1e-125,1e-124, + 1e-123,1e-122,1e-121,1e-120,1e-119,1e-118,1e-117,1e-116,1e-115,1e-114, + 1e-113,1e-112,1e-111,1e-110,1e-109,1e-108,1e-107,1e-106,1e-105,1e-104, + 1e-103,1e-102,1e-101,1e-100,1e-99,1e-98,1e-97,1e-96,1e-95,1e-94,1e-93, + 1e-92,1e-91,1e-90,1e-89,1e-88,1e-87,1e-86,1e-85,1e-84,1e-83,1e-82,1e-81, + 1e-80,1e-79,1e-78,1e-77,1e-76,1e-75,1e-74,1e-73,1e-72,1e-71,1e-70,1e-69, + 1e-68,1e-67,1e-66,1e-65,1e-64,1e-63,1e-62,1e-61,1e-60,1e-59,1e-58,1e-57, + 1e-56,1e-55,1e-54,1e-53,1e-52,1e-51,1e-50,1e-49,1e-48,1e-47,1e-46,1e-45, + 1e-44,1e-43,1e-42,1e-41,1e-40,1e-39,1e-38,1e-37,1e-36,1e-35,1e-34,1e-33, + 1e-32,1e-31,1e-30,1e-29,1e-28,1e-27,1e-26,1e-25,1e-24,1e-23,1e-22,1e-21, + 1e-20,1e-19,1e-18,1e-17,1e-16,1e-15,1e-14,1e-13,1e-12,1e-11,1e-10,1e-9, + 1e-8,1e-7,1e-6,1e-5,1e-4,1e-3,1e-2,1e-1,1e0,1e1,1e2,1e3,1e4,1e5,1e6,1e7, + 1e8,1e9,1e10,1e11,1e12,1e13,1e14,1e15,1e16,1e17,1e18,1e19,1e20,1e21, + 1e22,1e23,1e24,1e25,1e26,1e27,1e28,1e29,1e30,1e31,1e32,1e33,1e34,1e35, + 1e36,1e37,1e38,1e39,1e40,1e41,1e42,1e43,1e44,1e45,1e46,1e47,1e48,1e49, + 1e50,1e51,1e52,1e53,1e54,1e55,1e56,1e57,1e58,1e59,1e60,1e61,1e62,1e63, + 1e64,1e65,1e66,1e67,1e68,1e69,1e70,1e71,1e72,1e73,1e74,1e75,1e76,1e77, + 1e78,1e79,1e80,1e81,1e82,1e83,1e84,1e85,1e86,1e87,1e88,1e89,1e90,1e91, + 1e92,1e93,1e94,1e95,1e96,1e97,1e98,1e99,1e100,1e101,1e102,1e103,1e104, + 1e105,1e106,1e107,1e108,1e109,1e110,1e111,1e112,1e113,1e114,1e115,1e116, + 1e117,1e118,1e119,1e120,1e121,1e122,1e123,1e124,1e125,1e126,1e127,1e128, + 1e129,1e130,1e131,1e132,1e133,1e134,1e135,1e136,1e137,1e138,1e139,1e140, + 1e141,1e142,1e143,1e144,1e145,1e146,1e147,1e148,1e149,1e150,1e151,1e152, + 1e153,1e154,1e155,1e156,1e157,1e158,1e159,1e160,1e161,1e162,1e163,1e164, + 1e165,1e166,1e167,1e168,1e169,1e170,1e171,1e172,1e173,1e174,1e175,1e176, + 1e177,1e178,1e179,1e180,1e181,1e182,1e183,1e184,1e185,1e186,1e187,1e188, + 1e189,1e190,1e191,1e192,1e193,1e194,1e195,1e196,1e197,1e198,1e199,1e200, + 1e201,1e202,1e203,1e204,1e205,1e206,1e207,1e208,1e209,1e210,1e211,1e212, + 1e213,1e214,1e215,1e216,1e217,1e218,1e219,1e220,1e221,1e222,1e223,1e224, + 1e225,1e226,1e227,1e228,1e229,1e230,1e231,1e232,1e233,1e234,1e235,1e236, + 1e237,1e238,1e239,1e240,1e241,1e242,1e243,1e244,1e245,1e246,1e247,1e248, + 1e249,1e250,1e251,1e252,1e253,1e254,1e255,1e256,1e257,1e258,1e259,1e260, + 1e261,1e262,1e263,1e264,1e265,1e266,1e267,1e268,1e269,1e270,1e271,1e272, + 1e273,1e274,1e275,1e276,1e277,1e278,1e279,1e280,1e281,1e282,1e283,1e284, + 1e285,1e286,1e287,1e288,1e289,1e290,1e291,1e292,1e293,1e294,1e295,1e296, + 1e297,1e298,1e299,1e300,1e301,1e302,1e303,1e304,1e305,1e306,1e307,1e308 + }; + return constants[exponent + 323]; + } + + std::pair parse_number(char* p) { + bool negative = false; + if ('-' == *p) { + ++p; + negative = true; + + if (SAJSON_UNLIKELY(at_eof(p))) { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + } + + bool is_128 = false; + bool is_double = false; + + uint128_t i128 = 0; + uint64_t i64 = 0; + double d = 0.0; // gcc complains that d might be used uninitialized which isn't true. appease the warning anyway. + + // the left part of the number + { + unsigned char c = *p; + if (c < '0' || c > '9') { + return std::make_pair(make_error(p, ERROR_INVALID_NUMBER), TYPE_NULL); + } + + do { + ++p; + + unsigned char digit = c - '0'; + + constexpr auto MAX_64_PRE_MULTIPLY = std::numeric_limits::max() / 10 - 9; + + if (SAJSON_UNLIKELY(!is_128 && i64 > MAX_64_PRE_MULTIPLY)) { + // TODO: could split this into two loops + is_128 = true; + i128 = i64; + } + if (SAJSON_UNLIKELY(is_128)) + { + i128 = 10 * i128 + digit; + } + else + { + i64 = 10 * i64 + digit; + } + + c = *p; + } while (c >= '0' && c <= '9' && !at_eof(p)); + } + + int64_t exponent = 0; + + if (!at_eof(p)) + { + if ('.' == *p) + { + if (!is_double) + { + is_double = true; + d = i128 ? i128 : i64; + } + + ++p; + if (SAJSON_UNLIKELY(at_eof(p))) + { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + + char c = *p; + if (c < '0' || c > '9') + { + return std::make_pair(make_error(p, ERROR_INVALID_NUMBER), TYPE_NULL); + } + + do { + ++p; + if (SAJSON_UNLIKELY(at_eof(p))) + { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + d = d * 10 + (c - '0'); + // One option to avoid underflow would be to clamp + // to INT_MIN, but int64 subtraction is cheap and + // in the absurd case of parsing 2 GB of digits + // with an extremely high exponent, this will + // produce accurate results. Instead, we just + // leave exponent as int64_t and it will never + // underflow. + --exponent; + + c = *p; + } while (c >= '0' && c <= '9'); + } + + char e = *p; + if ('e' == e || 'E' == e) { + if (!is_double) { + is_double = true; + d = i128 ? i128 : i64; + } + + ++p; + if (SAJSON_UNLIKELY(at_eof(p))) { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + + bool negativeExponent = false; + if ('-' == *p) { + negativeExponent = true; + ++p; + if (SAJSON_UNLIKELY(at_eof(p))) { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + } else if ('+' == *p) { + ++p; + if (SAJSON_UNLIKELY(at_eof(p))) { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + } + + int exp = 0; + + char c = *p; + if (SAJSON_UNLIKELY(c < '0' || c > '9')) { + return std::make_pair(make_error(p, ERROR_MISSING_EXPONENT), TYPE_NULL); + } + for (;;) { + // c guaranteed to be between '0' and '9', inclusive + unsigned char digit = c - '0'; + if (exp > (INT_MAX - digit) / 10) { + // The exponent overflowed. Keep parsing, but + // it will definitely be out of range when + // pow10 is called. + exp = INT_MAX; + } else { + exp = 10 * exp + digit; + } + + ++p; + if (SAJSON_UNLIKELY(at_eof(p))) { + return std::make_pair(make_error(p, ERROR_UNEXPECTED_END), TYPE_NULL); + } + + c = *p; + if (c < '0' || c > '9') { + break; + } + } + static_assert(-INT_MAX >= INT_MIN, "exp can be negated without loss or UB"); + exponent += (negativeExponent ? -exp : exp); + } + } + + if (exponent) { + if (sajson_assert(is_double)) + throw type_error(); + + // If d is zero but the exponent is huge, don't + // multiply zero by inf which gives nan. + if (d != 0.0) { + d *= pow10(exponent); + } + } + + if (is_double) + { + bool success; + size_t* out = allocator.reserve(double_storage::word_length, &success); + if (SAJSON_UNLIKELY(!success)) { + return std::make_pair(oom(p), TYPE_NULL); + } + double_storage::store(out, negative ? -d : d); + return std::make_pair(p, TYPE_DOUBLE); + } + else + { + // use just 128 for comparisons, not sure what impact this has on performance + if (!is_128) + i128 = i64; + + if (negative) + { + // the negative max is one more than the max + constexpr uint128_t MAX_NEGATIVE_128 = uint128_t(std::numeric_limits::max())+1; + constexpr uint128_t MAX_NEGATIVE_64 = uint128_t(std::numeric_limits::max())+1; + constexpr uint128_t MAX_NEGATIVE_32 = uint128_t(std::numeric_limits::max())+1; + + if (i128 > MAX_NEGATIVE_128) + { + return std::make_pair(make_error(p, ERROR_INVALID_NUMBER), TYPE_NULL); + } + else + if (i128 > MAX_NEGATIVE_64) + { + using storage = integer_storage; + + bool success; + size_t* out = allocator.reserve(storage::word_length, &success); + if (SAJSON_UNLIKELY(!success)) { + return std::make_pair(oom(p), TYPE_NULL); + } + storage::store(out, -i128); + return std::make_pair(p, TYPE_INTEGER_128); + } + else + if (i128 > MAX_NEGATIVE_32) + { + using storage = integer_storage; + + bool success; + size_t* out = allocator.reserve(storage::word_length, &success); + if (SAJSON_UNLIKELY(!success)) { + return std::make_pair(oom(p), TYPE_NULL); + } + storage::store(out, -i128); + return std::make_pair(p, TYPE_INTEGER_64); + } + else + { + using storage = integer_storage; + + bool success; + size_t* out = allocator.reserve(storage::word_length, &success); + if (SAJSON_UNLIKELY(!success)) { + return std::make_pair(oom(p), TYPE_NULL); + } + storage::store(out, -i128); + return std::make_pair(p, TYPE_INTEGER_32); + } + } + else + { + constexpr int128_t MAX_POSITIVE_64 = std::numeric_limits::max(); + constexpr int128_t MAX_POSITIVE_32 = std::numeric_limits::max(); + + if (i128 > MAX_POSITIVE_64) + { + using storage = integer_storage; + bool success; + size_t* out = allocator.reserve(storage::word_length, &success); + if (SAJSON_UNLIKELY(!success)) { + return std::make_pair(oom(p), TYPE_NULL); + } + storage::store(out, i128); + return std::make_pair(p, TYPE_UNSIGNED_INTEGER_128); + } + else + if (i128 > MAX_POSITIVE_32) + { + using storage = integer_storage; + + bool success; + size_t* out = allocator.reserve(storage::word_length, &success); + if (SAJSON_UNLIKELY(!success)) { + return std::make_pair(oom(p), TYPE_NULL); + } + storage::store(out, i128); + return std::make_pair(p, TYPE_UNSIGNED_INTEGER_64); + } + else + { + using storage = integer_storage; + + bool success; + size_t* out = allocator.reserve(storage::word_length, &success); + if (SAJSON_UNLIKELY(!success)) { + return std::make_pair(oom(p), TYPE_NULL); + } + storage::store(out, i128); + return std::make_pair(p, TYPE_UNSIGNED_INTEGER_32); + } + } + } + } + + bool install_array(size_t* array_base, size_t* array_end) { + using namespace sajson::internal; + + const size_t length = array_end - array_base; + bool success; + size_t* const new_base = allocator.reserve(length + 1, &success); + if (SAJSON_UNLIKELY(!success)) { + return false; + } + size_t* out = new_base + length + 1; + size_t* const structure_end = allocator.get_write_pointer_of(0); + + while (array_end > array_base) { + size_t element = *--array_end; + type element_type = get_element_type(element); + size_t element_value = get_element_value(element); + size_t* element_ptr = structure_end - element_value; + *--out = make_element(element_type, element_ptr - new_base); + } + *--out = length; + return true; + } + + bool install_object(size_t* object_base, size_t* object_end) { + using namespace internal; + + if (sajson_assert((object_end - object_base) % 3 == 0)) + throw internal_error(); + + const size_t length_times_3 = object_end - object_base; +#ifndef SAJSON_UNSORTED_OBJECT_KEYS + std::sort( + reinterpret_cast(object_base), + reinterpret_cast(object_end), + object_key_comparator(input.get_data())); +#endif + + bool success; + size_t* const new_base = allocator.reserve(length_times_3 + 1, &success); + if (SAJSON_UNLIKELY(!success)) { + return false; + } + size_t* out = new_base + length_times_3 + 1; + size_t* const structure_end = allocator.get_write_pointer_of(0); + + while (object_end > object_base) { + size_t element = *--object_end; + type element_type = get_element_type(element); + size_t element_value = get_element_value(element); + size_t* element_ptr = structure_end - element_value; + + *--out = make_element(element_type, element_ptr - new_base); + *--out = *--object_end; + *--out = *--object_end; + } + *--out = length_times_3 / 3; + return true; + } + + char* parse_string(char* p, size_t* tag) { + using namespace internal; + + ++p; // " + size_t start = p - input.get_data(); + char* input_end_local = input_end; + while (input_end_local - p >= 4) { + if (!is_plain_string_character(p[0])) { goto found; } + if (!is_plain_string_character(p[1])) { p += 1; goto found; } + if (!is_plain_string_character(p[2])) { p += 2; goto found; } + if (!is_plain_string_character(p[3])) { p += 3; goto found; } + p += 4; + } + for (;;) { + if (SAJSON_UNLIKELY(p >= input_end_local)) { + return make_error(p, ERROR_UNEXPECTED_END); + } + + if (!is_plain_string_character(*p)) { + break; + } + + ++p; + } + found: + if (SAJSON_LIKELY(*p == '"')) { + tag[0] = start; + tag[1] = p - input.get_data(); + *p = '\0'; + return p + 1; + } + + if (*p >= 0 && *p < 0x20) { + return make_error(p, ERROR_ILLEGAL_CODEPOINT, static_cast(*p)); + } else { + // backslash or >0x7f + return parse_string_slow(p, tag, start); + } + } + + char* read_hex(char* p, unsigned& u) { + unsigned v = 0; + int i = 4; + while (i--) { + unsigned char c = *p++; + if (c >= '0' && c <= '9') { + c -= '0'; + } else if (c >= 'a' && c <= 'f') { + c = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + c = c - 'A' + 10; + } else { + return make_error(p, ERROR_INVALID_UNICODE_ESCAPE); + } + v = (v << 4) + c; + } + + u = v; + return p; + } + + void write_utf8(unsigned codepoint, char*& end) { + if (codepoint < 0x80) { + *end++ = codepoint; + } else if (codepoint < 0x800) { + *end++ = 0xC0 | (codepoint >> 6); + *end++ = 0x80 | (codepoint & 0x3F); + } else if (codepoint < 0x10000) { + *end++ = 0xE0 | (codepoint >> 12); + *end++ = 0x80 | ((codepoint >> 6) & 0x3F); + *end++ = 0x80 | (codepoint & 0x3F); + } else { + if (sajson_assert(codepoint < 0x200000)) + throw internal_error(); + + *end++ = 0xF0 | (codepoint >> 18); + *end++ = 0x80 | ((codepoint >> 12) & 0x3F); + *end++ = 0x80 | ((codepoint >> 6) & 0x3F); + *end++ = 0x80 | (codepoint & 0x3F); + } + } + + char* parse_string_slow(char* p, size_t* tag, size_t start) { + char* end = p; + char* input_end_local = input_end; + + for (;;) { + if (SAJSON_UNLIKELY(p >= input_end_local)) { + return make_error(p, ERROR_UNEXPECTED_END); + } + + if (SAJSON_UNLIKELY(*p >= 0 && *p < 0x20)) { + return make_error(p, ERROR_ILLEGAL_CODEPOINT, static_cast(*p)); + } + + switch (*p) { + case '"': + tag[0] = start; + tag[1] = end - input.get_data(); + *end = '\0'; + return p + 1; + + case '\\': + ++p; + if (SAJSON_UNLIKELY(p >= input_end_local)) { + return make_error(p, ERROR_UNEXPECTED_END); + } + + char replacement; + switch (*p) { + case '"': replacement = '"'; goto replace; + case '\\': replacement = '\\'; goto replace; + case '/': replacement = '/'; goto replace; + case 'b': replacement = '\b'; goto replace; + case 'f': replacement = '\f'; goto replace; + case 'n': replacement = '\n'; goto replace; + case 'r': replacement = '\r'; goto replace; + case 't': replacement = '\t'; goto replace; + replace: + *end++ = replacement; + ++p; + break; + case 'u': { + ++p; + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 4))) { + return make_error(p, ERROR_UNEXPECTED_END); + } + unsigned u = 0; // gcc's complaining that this could be used uninitialized. wrong. + p = read_hex(p, u); + if (!p) { + return 0; + } + if (u >= 0xD800 && u <= 0xDBFF) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 6))) { + return make_error(p, ERROR_UNEXPECTED_END_OF_UTF16); + } + char p0 = p[0]; + char p1 = p[1]; + if (p0 != '\\' || p1 != 'u') { + return make_error(p, ERROR_EXPECTED_U); + } + p += 2; + unsigned v = 0; // gcc's complaining that this could be used uninitialized. wrong. + p = read_hex(p, v); + if (!p) { + return p; + } + + if (v < 0xDC00 || v > 0xDFFF) { + return make_error(p, ERROR_INVALID_UTF16_TRAIL_SURROGATE); + } + u = 0x10000 + (((u - 0xD800) << 10) | (v - 0xDC00)); + } + write_utf8(u, end); + break; + } + default: + return make_error(p, ERROR_UNKNOWN_ESCAPE); + } + break; + + default: + // validate UTF-8 + unsigned char c0 = p[0]; + if (c0 < 128) { + *end++ = *p++; + } else if (c0 < 224) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 2))) { + return unexpected_end(p); + } + unsigned char c1 = p[1]; + if (c1 < 128 || c1 >= 192) { + return make_error(p + 1, ERROR_INVALID_UTF8); + } + end[0] = c0; + end[1] = c1; + end += 2; + p += 2; + } else if (c0 < 240) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 3))) { + return unexpected_end(p); + } + unsigned char c1 = p[1]; + if (c1 < 128 || c1 >= 192) { + return make_error(p + 1, ERROR_INVALID_UTF8); + } + unsigned char c2 = p[2]; + if (c2 < 128 || c2 >= 192) { + return make_error(p + 2, ERROR_INVALID_UTF8); + } + end[0] = c0; + end[1] = c1; + end[2] = c2; + end += 3; + p += 3; + } else if (c0 < 248) { + if (SAJSON_UNLIKELY(!has_remaining_characters(p, 4))) { + return unexpected_end(p); + } + unsigned char c1 = p[1]; + if (c1 < 128 || c1 >= 192) { + return make_error(p + 1, ERROR_INVALID_UTF8); + } + unsigned char c2 = p[2]; + if (c2 < 128 || c2 >= 192) { + return make_error(p + 2, ERROR_INVALID_UTF8); + } + unsigned char c3 = p[3]; + if (c3 < 128 || c3 >= 192) { + return make_error(p + 3, ERROR_INVALID_UTF8); + } + end[0] = c0; + end[1] = c1; + end[2] = c2; + end[3] = c3; + end += 4; + p += 4; + } else { + return make_error(p, ERROR_INVALID_UTF8); + } + break; + } + } + } + + mutable_string_view input; + char* const input_end; + Allocator allocator; + + type root_type; + size_t error_line; + size_t error_column; + error error_code; + int error_arg; // optional argument for the error + }; + /// \endcond + + /** + * Parses a string of JSON bytes into a \ref document, given an allocation + * strategy instance. Any kind of string type is valid as long as a + * mutable_string_view can be constructed from it. + * + * Valid allocation strategies are \ref single_allocation, + * \ref dynamic_allocation, and \ref bounded_allocation. + * + * A \ref document is returned whether or not the parse succeeds: success + * state is available by calling document::is_valid(). + */ + template + document parse(const AllocationStrategy& strategy, const StringType& string) { + mutable_string_view input(string); + + bool success; + auto allocator = strategy.make_allocator(input.length(), &success); + if (!success) { + return document(input, 1, 1, ERROR_OUT_OF_MEMORY, 0); + } + + return parser( + input, + std::move(allocator) + ).get_document(); + } +} diff --git a/transfer-to-lava b/transfer-to-lava new file mode 100755 index 0000000..dd35f5a --- /dev/null +++ b/transfer-to-lava @@ -0,0 +1,8 @@ +HOST=lava +rsync -avz ./ genome@$HOST:Core_IO/ \ + --exclude '.bin' \ + --exclude 'venv' \ + --exclude '*.a' \ + --exclude '*.o' \ + --delete \ +