librats 1.0.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -331
- package/binding.gyp +16 -3
- package/lib/index.d.ts +288 -696
- package/lib/index.js +407 -44
- package/native-src/3rdparty/android/ifaddrs-android.c +1 -0
- package/native-src/3rdparty/android/ifaddrs-android.h +1 -0
- package/native-src/CMakeLists.txt +404 -179
- package/native-src/LICENSE +1 -1
- package/native-src/src/librats/bindings/rats.cpp +762 -0
- package/native-src/src/librats/bindings/rats.h +380 -0
- package/native-src/src/librats/bittorrent/bencode.cpp +437 -0
- package/native-src/src/librats/bittorrent/bencode.h +176 -0
- package/native-src/src/librats/bittorrent/bitfield.cpp +97 -0
- package/native-src/src/librats/bittorrent/bitfield.h +76 -0
- package/native-src/src/librats/bittorrent/byte_io.h +58 -0
- package/native-src/src/librats/bittorrent/choker.cpp +25 -0
- package/native-src/src/librats/bittorrent/choker.h +46 -0
- package/native-src/src/librats/bittorrent/client.cpp +413 -0
- package/native-src/src/librats/bittorrent/client.h +227 -0
- package/native-src/src/librats/bittorrent/disk_io.cpp +209 -0
- package/native-src/src/librats/bittorrent/disk_io.h +150 -0
- package/native-src/src/librats/bittorrent/extensions.cpp +191 -0
- package/native-src/src/librats/bittorrent/extensions.h +94 -0
- package/native-src/src/librats/bittorrent/file_storage.cpp +77 -0
- package/native-src/src/librats/bittorrent/file_storage.h +79 -0
- package/native-src/src/librats/bittorrent/log.h +42 -0
- package/native-src/src/librats/bittorrent/magnet_uri.cpp +98 -0
- package/native-src/src/librats/bittorrent/magnet_uri.h +35 -0
- package/native-src/src/librats/bittorrent/peer_connection.cpp +502 -0
- package/native-src/src/librats/bittorrent/peer_connection.h +194 -0
- package/native-src/src/librats/bittorrent/peer_list.cpp +68 -0
- package/native-src/src/librats/bittorrent/peer_list.h +75 -0
- package/native-src/src/librats/bittorrent/piece_picker.cpp +352 -0
- package/native-src/src/librats/bittorrent/piece_picker.h +201 -0
- package/native-src/src/librats/bittorrent/reactor.cpp +97 -0
- package/native-src/src/librats/bittorrent/reactor.h +89 -0
- package/native-src/src/librats/bittorrent/resume_data.cpp +72 -0
- package/native-src/src/librats/bittorrent/resume_data.h +41 -0
- package/native-src/src/librats/bittorrent/store_buffer.cpp +48 -0
- package/native-src/src/librats/bittorrent/store_buffer.h +47 -0
- package/native-src/src/librats/bittorrent/torrent.cpp +870 -0
- package/native-src/src/librats/bittorrent/torrent.h +260 -0
- package/native-src/src/librats/bittorrent/torrent_creator.cpp +129 -0
- package/native-src/src/librats/bittorrent/torrent_creator.h +58 -0
- package/native-src/src/librats/bittorrent/torrent_info.cpp +314 -0
- package/native-src/src/librats/bittorrent/torrent_info.h +118 -0
- package/native-src/src/librats/bittorrent/tracker.cpp +374 -0
- package/native-src/src/librats/bittorrent/tracker.h +108 -0
- package/native-src/src/librats/bittorrent/types.cpp +206 -0
- package/native-src/src/librats/bittorrent/types.h +86 -0
- package/native-src/src/librats/core/address.cpp +35 -0
- package/native-src/src/librats/core/address.h +78 -0
- package/native-src/src/librats/core/bytes.h +69 -0
- package/native-src/src/librats/core/chained_send_buffer.cpp +172 -0
- package/native-src/src/librats/core/chained_send_buffer.h +183 -0
- package/native-src/src/librats/core/endpoint_parse.cpp +41 -0
- package/native-src/src/librats/core/endpoint_parse.h +31 -0
- package/native-src/src/librats/core/event_bus.h +70 -0
- package/native-src/src/librats/core/host_endpoint.h +56 -0
- package/native-src/src/{io_poller.cpp → librats/core/io_poller.cpp} +520 -65
- package/native-src/src/{io_poller.h → librats/core/io_poller.h} +12 -6
- package/native-src/src/librats/core/ip_address.cpp +120 -0
- package/native-src/src/librats/core/ip_address.h +109 -0
- package/native-src/src/librats/core/mpsc_queue.h +47 -0
- package/native-src/src/librats/core/notifier.h +74 -0
- package/native-src/src/librats/core/receive_buffer.cpp +219 -0
- package/native-src/src/librats/core/receive_buffer.h +171 -0
- package/native-src/src/librats/core/service_registry.h +58 -0
- package/native-src/src/{socket.cpp → librats/core/socket.cpp} +625 -118
- package/native-src/src/librats/core/socket.h +496 -0
- package/native-src/src/librats/core/timer_queue.h +105 -0
- package/native-src/src/librats/core/types.cpp +43 -0
- package/native-src/src/librats/core/types.h +103 -0
- package/native-src/src/librats/core/wakeup_pipe.h +83 -0
- package/native-src/src/{crypto → librats/crypto}/blake2_endian.h +21 -23
- package/native-src/src/{crypto → librats/crypto}/blake2b.c +34 -33
- package/native-src/src/{crypto → librats/crypto}/blake2b.h +7 -6
- package/native-src/src/{crypto → librats/crypto}/blake2s.c +55 -54
- package/native-src/src/{crypto → librats/crypto}/blake2s.h +13 -12
- package/native-src/src/{crypto → librats/crypto}/chacha.c +22 -21
- package/native-src/src/{crypto → librats/crypto}/chacha.h +14 -13
- package/native-src/src/{crypto → librats/crypto}/chachapoly.c +56 -56
- package/native-src/src/{crypto → librats/crypto}/chachapoly.h +24 -17
- package/native-src/src/{crc32.cpp → librats/crypto/crc32.cpp} +1 -1
- package/native-src/src/{crc32.h → librats/crypto/crc32.h} +3 -1
- package/native-src/src/{crypto → librats/crypto}/curve25519.c +6 -4
- package/native-src/src/{crypto → librats/crypto}/curve25519.h +6 -3
- package/native-src/src/librats/crypto/hkdf.c +266 -0
- package/native-src/src/{crypto → librats/crypto}/hkdf.h +19 -19
- package/native-src/src/{noise.cpp → librats/crypto/noise.cpp} +84 -73
- package/native-src/src/{noise.h → librats/crypto/noise.h} +18 -8
- package/native-src/src/{crypto → librats/crypto}/poly1305.c +47 -46
- package/native-src/src/librats/crypto/poly1305.h +37 -0
- package/native-src/src/{sha1.cpp → librats/crypto/sha1.cpp} +33 -1
- package/native-src/src/{sha1.h → librats/crypto/sha1.h} +14 -6
- package/native-src/src/{crypto → librats/crypto}/sha256.c +15 -14
- package/native-src/src/{crypto → librats/crypto}/sha256.h +8 -7
- package/native-src/src/{crypto → librats/crypto}/sha512.c +15 -14
- package/native-src/src/{crypto → librats/crypto}/sha512.h +8 -7
- package/native-src/src/librats/dht/announce.cpp +37 -0
- package/native-src/src/librats/dht/announce.h +41 -0
- package/native-src/src/librats/dht/bep42.cpp +109 -0
- package/native-src/src/librats/dht/bep42.h +48 -0
- package/native-src/src/librats/dht/dht.cpp +501 -0
- package/native-src/src/librats/dht/dht.h +119 -0
- package/native-src/src/librats/dht/dht_runner.cpp +103 -0
- package/native-src/src/librats/dht/dht_runner.h +71 -0
- package/native-src/src/librats/dht/dos_blocker.cpp +42 -0
- package/native-src/src/librats/dht/dos_blocker.h +47 -0
- package/native-src/src/librats/dht/find_peers.cpp +52 -0
- package/native-src/src/librats/dht/find_peers.h +73 -0
- package/native-src/src/librats/dht/id.h +167 -0
- package/native-src/src/{krpc.cpp → librats/dht/krpc.cpp} +32 -81
- package/native-src/src/{krpc.h → librats/dht/krpc.h} +19 -23
- package/native-src/src/librats/dht/log.h +38 -0
- package/native-src/src/librats/dht/node.cpp +473 -0
- package/native-src/src/librats/dht/node.h +164 -0
- package/native-src/src/librats/dht/node_entry.h +81 -0
- package/native-src/src/librats/dht/observer.h +72 -0
- package/native-src/src/librats/dht/persistence.cpp +90 -0
- package/native-src/src/librats/dht/persistence.h +32 -0
- package/native-src/src/librats/dht/routing_table.cpp +559 -0
- package/native-src/src/librats/dht/routing_table.h +185 -0
- package/native-src/src/librats/dht/rpc_manager.cpp +127 -0
- package/native-src/src/librats/dht/rpc_manager.h +77 -0
- package/native-src/src/librats/dht/storage.cpp +92 -0
- package/native-src/src/librats/dht/storage.h +74 -0
- package/native-src/src/librats/dht/transport.h +27 -0
- package/native-src/src/librats/dht/traversal.cpp +326 -0
- package/native-src/src/librats/dht/traversal.h +120 -0
- package/native-src/src/librats/dht/udp_transport.cpp +49 -0
- package/native-src/src/librats/dht/udp_transport.h +51 -0
- package/native-src/src/librats/mdns/log.h +22 -0
- package/native-src/src/{mdns.cpp → librats/mdns/mdns.cpp} +75 -40
- package/native-src/src/{mdns.h → librats/mdns/mdns.h} +9 -8
- package/native-src/src/{natpmp.cpp → librats/nat/natpmp.cpp} +12 -9
- package/native-src/src/{natpmp.h → librats/nat/natpmp.h} +3 -3
- package/native-src/src/{port_mapping.h → librats/nat/port_mapping.h} +3 -2
- package/native-src/src/{stun.cpp → librats/nat/stun.cpp} +4 -4
- package/native-src/src/{stun.h → librats/nat/stun.h} +1 -1
- package/native-src/src/{upnp.cpp → librats/nat/upnp.cpp} +6 -6
- package/native-src/src/{upnp.h → librats/nat/upnp.h} +2 -2
- package/native-src/src/librats/node/circuit_service.h +84 -0
- package/native-src/src/librats/node/config.h +110 -0
- package/native-src/src/librats/node/dial_service.h +54 -0
- package/native-src/src/librats/node/dialer.cpp +264 -0
- package/native-src/src/librats/node/dialer.h +188 -0
- package/native-src/src/librats/node/host_events.h +26 -0
- package/native-src/src/librats/node/identify.cpp +130 -0
- package/native-src/src/librats/node/identify.h +71 -0
- package/native-src/src/librats/node/nat_status.cpp +103 -0
- package/native-src/src/librats/node/nat_status.h +118 -0
- package/native-src/src/librats/node/node.cpp +865 -0
- package/native-src/src/librats/node/node.h +344 -0
- package/native-src/src/librats/node/node_context.h +33 -0
- package/native-src/src/librats/node/peer_network.h +91 -0
- package/native-src/src/librats/peer/peer.h +49 -0
- package/native-src/src/librats/peer/peer_book.cpp +181 -0
- package/native-src/src/librats/peer/peer_book.h +88 -0
- package/native-src/src/librats/peer/peer_id.cpp +72 -0
- package/native-src/src/librats/peer/peer_id.h +62 -0
- package/native-src/src/librats/peer/peer_info.h +37 -0
- package/native-src/src/librats/peer/peer_table.cpp +170 -0
- package/native-src/src/librats/peer/peer_table.h +148 -0
- package/native-src/src/librats/security/handshaker.h +66 -0
- package/native-src/src/librats/security/identity.h +43 -0
- package/native-src/src/librats/security/noise_security.cpp +122 -0
- package/native-src/src/librats/security/noise_security.h +37 -0
- package/native-src/src/librats/security/plaintext_security.h +106 -0
- package/native-src/src/librats/security/session.h +37 -0
- package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
- package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
- package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
- package/native-src/src/librats/subsystems/bittorrent.h +136 -0
- package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
- package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
- package/native-src/src/librats/subsystems/dht_service.h +36 -0
- package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
- package/native-src/src/librats/subsystems/file_transfer.h +367 -0
- package/native-src/src/librats/subsystems/hole_punch.cpp +605 -0
- package/native-src/src/librats/subsystems/hole_punch.h +290 -0
- package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
- package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
- package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
- package/native-src/src/librats/subsystems/message_json.cpp +112 -0
- package/native-src/src/librats/subsystems/message_json.h +88 -0
- package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
- package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
- package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
- package/native-src/src/librats/subsystems/ping_service.h +66 -0
- package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
- package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
- package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
- package/native-src/src/librats/subsystems/pubsub.h +175 -0
- package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
- package/native-src/src/librats/subsystems/reconnection.h +126 -0
- package/native-src/src/librats/subsystems/relay.cpp +1142 -0
- package/native-src/src/librats/subsystems/relay.h +211 -0
- package/native-src/src/librats/subsystems/relay_service.h +46 -0
- package/native-src/src/librats/transport/connection.cpp +343 -0
- package/native-src/src/librats/transport/connection.h +283 -0
- package/native-src/src/librats/transport/link.h +96 -0
- package/native-src/src/librats/transport/reactor.cpp +588 -0
- package/native-src/src/librats/transport/reactor.h +262 -0
- package/native-src/src/librats/transport/reactor_pool.h +81 -0
- package/native-src/src/librats/transport/relay_link.cpp +208 -0
- package/native-src/src/librats/transport/relay_link.h +303 -0
- package/native-src/src/librats/transport/tcp_link.cpp +49 -0
- package/native-src/src/librats/transport/tcp_link.h +43 -0
- package/native-src/src/librats/transport/udp_mux.cpp +617 -0
- package/native-src/src/librats/transport/udp_mux.h +363 -0
- package/native-src/src/librats/transport/udp_packet.cpp +121 -0
- package/native-src/src/librats/transport/udp_packet.h +190 -0
- package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
- package/native-src/src/librats/transport/udp_stream.h +614 -0
- package/native-src/src/librats/util/features.h.in +51 -0
- package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
- package/native-src/src/librats/util/fs.h +136 -0
- package/native-src/src/librats/util/json.cpp +1002 -0
- package/native-src/src/librats/util/json.h +444 -0
- package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
- package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
- package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
- package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
- package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
- package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
- package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
- package/native-src/src/librats/util/rats_export.h +69 -0
- package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
- package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
- package/native-src/src/librats/wire/frame.cpp +76 -0
- package/native-src/src/librats/wire/frame.h +111 -0
- package/native-src/src/librats/wire/message_router.cpp +45 -0
- package/native-src/src/librats/wire/message_router.h +47 -0
- package/package.json +5 -4
- package/scripts/build-librats.js +1 -0
- package/scripts/postinstall.js +3 -3
- package/scripts/prepare-package.js +4 -4
- package/scripts/verify-installation.js +63 -105
- package/src/librats_node.cpp +1067 -1323
- package/native-src/src/bencode.cpp +0 -485
- package/native-src/src/bencode.h +0 -145
- package/native-src/src/bittorrent.cpp +0 -14
- package/native-src/src/bittorrent.h +0 -74
- package/native-src/src/bt_bitfield.cpp +0 -372
- package/native-src/src/bt_bitfield.h +0 -316
- package/native-src/src/bt_choker.cpp +0 -228
- package/native-src/src/bt_choker.h +0 -147
- package/native-src/src/bt_client.cpp +0 -1047
- package/native-src/src/bt_client.h +0 -445
- package/native-src/src/bt_create_torrent.cpp +0 -677
- package/native-src/src/bt_create_torrent.h +0 -473
- package/native-src/src/bt_extension.cpp +0 -469
- package/native-src/src/bt_extension.h +0 -309
- package/native-src/src/bt_file_storage.cpp +0 -261
- package/native-src/src/bt_file_storage.h +0 -298
- package/native-src/src/bt_handshake.cpp +0 -134
- package/native-src/src/bt_handshake.h +0 -157
- package/native-src/src/bt_messages.cpp +0 -364
- package/native-src/src/bt_messages.h +0 -324
- package/native-src/src/bt_network.cpp +0 -1007
- package/native-src/src/bt_network.h +0 -417
- package/native-src/src/bt_peer_connection.cpp +0 -742
- package/native-src/src/bt_peer_connection.h +0 -592
- package/native-src/src/bt_piece_picker.cpp +0 -786
- package/native-src/src/bt_piece_picker.h +0 -473
- package/native-src/src/bt_resume_data.cpp +0 -410
- package/native-src/src/bt_resume_data.h +0 -249
- package/native-src/src/bt_torrent.cpp +0 -2120
- package/native-src/src/bt_torrent.h +0 -641
- package/native-src/src/bt_torrent_info.cpp +0 -659
- package/native-src/src/bt_torrent_info.h +0 -418
- package/native-src/src/bt_types.h +0 -621
- package/native-src/src/chained_send_buffer.cpp +0 -75
- package/native-src/src/chained_send_buffer.h +0 -137
- package/native-src/src/crypto/hkdf.c +0 -266
- package/native-src/src/crypto/poly1305.h +0 -36
- package/native-src/src/dht.cpp +0 -3311
- package/native-src/src/dht.h +0 -717
- package/native-src/src/disk_io.cpp +0 -632
- package/native-src/src/disk_io.h +0 -315
- package/native-src/src/file_transfer.cpp +0 -1415
- package/native-src/src/file_transfer.h +0 -286
- package/native-src/src/fs.h +0 -108
- package/native-src/src/gossipsub.cpp +0 -1139
- package/native-src/src/gossipsub.h +0 -403
- package/native-src/src/ice.cpp +0 -893
- package/native-src/src/ice.h +0 -559
- package/native-src/src/json.hpp +0 -25526
- package/native-src/src/librats.cpp +0 -2378
- package/native-src/src/librats.h +0 -2324
- package/native-src/src/librats_bittorrent.cpp +0 -601
- package/native-src/src/librats_c.cpp +0 -1557
- package/native-src/src/librats_c.h +0 -323
- package/native-src/src/librats_discovery.cpp +0 -402
- package/native-src/src/librats_encryption.cpp +0 -275
- package/native-src/src/librats_file_transfer.cpp +0 -144
- package/native-src/src/librats_gossipsub.cpp +0 -289
- package/native-src/src/librats_ice.cpp +0 -213
- package/native-src/src/librats_log_macros.h +0 -36
- package/native-src/src/librats_logging.cpp +0 -173
- package/native-src/src/librats_mdns.cpp +0 -166
- package/native-src/src/librats_persistence.cpp +0 -796
- package/native-src/src/librats_portmap.cpp +0 -419
- package/native-src/src/librats_reconnection.cpp +0 -218
- package/native-src/src/librats_statistic.cpp +0 -105
- package/native-src/src/librats_storage.cpp +0 -189
- package/native-src/src/rats_export.h +0 -17
- package/native-src/src/receive_buffer.cpp +0 -82
- package/native-src/src/receive_buffer.h +0 -127
- package/native-src/src/socket.h +0 -228
- package/native-src/src/threadmanager.cpp +0 -105
- package/native-src/src/threadmanager.h +0 -53
- package/native-src/src/tracker.cpp +0 -1264
- package/native-src/src/tracker.h +0 -319
- package/native-src/src/turn.cpp +0 -762
- package/native-src/src/turn.h +0 -460
- package/native-src/src/wakeup_pipe.h +0 -60
- /package/native-src/src/{os.h → librats/util/os.h} +0 -0
|
@@ -0,0 +1,1002 @@
|
|
|
1
|
+
#include "librats/util/json.h"
|
|
2
|
+
|
|
3
|
+
#include <cerrno>
|
|
4
|
+
#include <charconv>
|
|
5
|
+
#include <cmath>
|
|
6
|
+
#include <cstdio>
|
|
7
|
+
#include <cstdlib>
|
|
8
|
+
#include <cstring>
|
|
9
|
+
#include <functional>
|
|
10
|
+
#include <istream>
|
|
11
|
+
#include <iterator>
|
|
12
|
+
#include <ostream>
|
|
13
|
+
|
|
14
|
+
// std::to_chars / std::from_chars for floating-point is a C++17 feature, but
|
|
15
|
+
// some standard libraries shipped it later than the integer overloads. The
|
|
16
|
+
// feature-test macro is defined (to 201611L) only once full support — including
|
|
17
|
+
// the floating-point overloads — is present (libstdc++ ≥ 11, MSVC STL ≥ 19.24,
|
|
18
|
+
// libc++ once complete). When it is absent we fall back to the classic
|
|
19
|
+
// snprintf / strtod path, which is slower but produces identical results.
|
|
20
|
+
#if defined(__cpp_lib_to_chars) && __cpp_lib_to_chars >= 201611L
|
|
21
|
+
# define LIBRATS_JSON_CHARCONV 1
|
|
22
|
+
#else
|
|
23
|
+
# define LIBRATS_JSON_CHARCONV 0
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
// Caveat: libc++ defines __cpp_lib_to_chars (it has the floating-point to_chars
|
|
27
|
+
// overloads) yet still ships floating-point from_chars as = delete (true as of
|
|
28
|
+
// Xcode 16.4 / current libc++). So the macro above does NOT imply from_chars
|
|
29
|
+
// works for double — gate the float-parsing path on its own predicate and fall
|
|
30
|
+
// back to strtod where the overload is missing.
|
|
31
|
+
#if LIBRATS_JSON_CHARCONV && !defined(_LIBCPP_VERSION)
|
|
32
|
+
# define LIBRATS_JSON_FROM_CHARS_FLOAT 1
|
|
33
|
+
#else
|
|
34
|
+
# define LIBRATS_JSON_FROM_CHARS_FLOAT 0
|
|
35
|
+
#endif
|
|
36
|
+
|
|
37
|
+
namespace librats {
|
|
38
|
+
|
|
39
|
+
// ── Object ──────────────────────────────────────────────────────────────────
|
|
40
|
+
//
|
|
41
|
+
// The index is a power-of-two open-addressing table of 1-based positions into
|
|
42
|
+
// items_ (a slot value of 0 means empty). It owns no keys: every comparison
|
|
43
|
+
// reads the key back from items_, so a key exists exactly once — in items_.
|
|
44
|
+
// An erase shifts positions, so it rebuilds the table; between rebuilds the
|
|
45
|
+
// table only grows, which means linear probing needs no tombstones.
|
|
46
|
+
|
|
47
|
+
std::size_t Json::Object::hash_key(const std::string& key) {
|
|
48
|
+
return std::hash<std::string>{}(key);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
void Json::Object::rebuild_index() {
|
|
52
|
+
// Smallest power-of-two capacity that keeps the load factor below 0.5 once
|
|
53
|
+
// the key about to be inserted is in (hence the +1), so probe chains stay
|
|
54
|
+
// short. Slots are uint32_t, so even at this headroom the whole index is a
|
|
55
|
+
// few KB for thousands of keys — negligible beside the keys and values.
|
|
56
|
+
std::size_t cap = 32;
|
|
57
|
+
while (cap < (items_.size() + 1) * 2) cap <<= 1;
|
|
58
|
+
index_.assign(cap, 0);
|
|
59
|
+
const std::size_t mask = cap - 1;
|
|
60
|
+
for (std::size_t i = 0; i < items_.size(); ++i) {
|
|
61
|
+
std::size_t slot = hash_key(items_[i].first) & mask;
|
|
62
|
+
while (index_[slot]) slot = (slot + 1) & mask;
|
|
63
|
+
index_[slot] = static_cast<std::uint32_t>(i + 1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
void Json::Object::reindex() {
|
|
68
|
+
// Called after an erase shifts positions: rebuild while large, else drop the
|
|
69
|
+
// index and fall back to linear scans.
|
|
70
|
+
if (items_.size() > kIndexThreshold) rebuild_index();
|
|
71
|
+
else index_ = {};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
template <typename K>
|
|
75
|
+
Json& Json::Object::emplace_key(K&& key) {
|
|
76
|
+
if (indexed()) {
|
|
77
|
+
// Grow first if seating one more key would crowd the table; growing
|
|
78
|
+
// rehashes, so it must happen before we compute a slot below.
|
|
79
|
+
if ((items_.size() + 1) * 2 > index_.size()) rebuild_index();
|
|
80
|
+
|
|
81
|
+
// A single probe both finds an existing key and locates the empty slot
|
|
82
|
+
// a new one would take.
|
|
83
|
+
const std::size_t mask = index_.size() - 1;
|
|
84
|
+
std::size_t slot = hash_key(key) & mask;
|
|
85
|
+
while (std::uint32_t e = index_[slot]) {
|
|
86
|
+
if (items_[e - 1].first == key) return items_[e - 1].second;
|
|
87
|
+
slot = (slot + 1) & mask;
|
|
88
|
+
}
|
|
89
|
+
items_.emplace_back(std::forward<K>(key), Json());
|
|
90
|
+
index_[slot] = static_cast<std::uint32_t>(items_.size()); // 1-based position
|
|
91
|
+
return items_.back().second;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Small object: linear scan, no index, no allocation.
|
|
95
|
+
for (auto& kv : items_)
|
|
96
|
+
if (kv.first == key) return kv.second;
|
|
97
|
+
items_.emplace_back(std::forward<K>(key), Json());
|
|
98
|
+
if (items_.size() > kIndexThreshold) rebuild_index();
|
|
99
|
+
return items_.back().second;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Json& Json::Object::operator[](const std::string& key) { return emplace_key(key); }
|
|
103
|
+
Json& Json::Object::operator[](std::string&& key) { return emplace_key(std::move(key)); }
|
|
104
|
+
|
|
105
|
+
const Json* Json::Object::find(const std::string& key) const {
|
|
106
|
+
if (indexed()) {
|
|
107
|
+
const std::size_t mask = index_.size() - 1;
|
|
108
|
+
std::size_t slot = hash_key(key) & mask;
|
|
109
|
+
while (std::uint32_t e = index_[slot]) {
|
|
110
|
+
if (items_[e - 1].first == key) return &items_[e - 1].second;
|
|
111
|
+
slot = (slot + 1) & mask;
|
|
112
|
+
}
|
|
113
|
+
return nullptr;
|
|
114
|
+
}
|
|
115
|
+
for (const auto& kv : items_)
|
|
116
|
+
if (kv.first == key) return &kv.second;
|
|
117
|
+
return nullptr;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
Json* Json::Object::find(const std::string& key) {
|
|
121
|
+
const Object* self = this;
|
|
122
|
+
return const_cast<Json*>(self->find(key));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
bool Json::Object::erase(const std::string& key) {
|
|
126
|
+
std::size_t pos = items_.size();
|
|
127
|
+
if (indexed()) {
|
|
128
|
+
const std::size_t mask = index_.size() - 1;
|
|
129
|
+
std::size_t slot = hash_key(key) & mask;
|
|
130
|
+
while (std::uint32_t e = index_[slot]) {
|
|
131
|
+
if (items_[e - 1].first == key) { pos = e - 1; break; }
|
|
132
|
+
slot = (slot + 1) & mask;
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
for (std::size_t i = 0; i < items_.size(); ++i)
|
|
136
|
+
if (items_[i].first == key) { pos = i; break; }
|
|
137
|
+
}
|
|
138
|
+
if (pos == items_.size()) return false;
|
|
139
|
+
items_.erase(items_.begin() + static_cast<std::ptrdiff_t>(pos));
|
|
140
|
+
reindex();
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
bool Json::Object::operator==(const Object& other) const {
|
|
145
|
+
if (items_.size() != other.items_.size()) return false;
|
|
146
|
+
for (const auto& kv : items_) {
|
|
147
|
+
const Json* o = other.find(kv.first);
|
|
148
|
+
if (!o || !(*o == kv.second)) return false;
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Lifetime ────────────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
void Json::destroy() noexcept {
|
|
156
|
+
switch (type_) {
|
|
157
|
+
case Type::String: delete str_; break;
|
|
158
|
+
case Type::Array: delete arr_; break;
|
|
159
|
+
case Type::Object: delete obj_; break;
|
|
160
|
+
default: break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
void Json::copy_from(const Json& o) {
|
|
165
|
+
// Commit type_ only after the (possibly throwing) allocation succeeds: if
|
|
166
|
+
// `new` throws, this value keeps its prior type_/payload untouched, so a
|
|
167
|
+
// caller that started from a valid state (Null in the ctor / after the reset
|
|
168
|
+
// in operator=) stays valid and its destructor never frees a stale pointer.
|
|
169
|
+
switch (o.type_) {
|
|
170
|
+
case Type::Boolean: bool_ = o.bool_; break;
|
|
171
|
+
case Type::Integer: int_ = o.int_; break;
|
|
172
|
+
case Type::Unsigned: uint_ = o.uint_; break;
|
|
173
|
+
case Type::Float: float_ = o.float_; break;
|
|
174
|
+
case Type::String: { auto* p = new std::string(*o.str_); str_ = p; } break;
|
|
175
|
+
case Type::Array: { auto* p = new Array(*o.arr_); arr_ = p; } break;
|
|
176
|
+
case Type::Object: { auto* p = new Object(*o.obj_); obj_ = p; } break;
|
|
177
|
+
default: break; // Null / Discarded carry no payload
|
|
178
|
+
}
|
|
179
|
+
type_ = o.type_;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
void Json::move_from(Json& o) noexcept {
|
|
183
|
+
type_ = o.type_;
|
|
184
|
+
switch (o.type_) {
|
|
185
|
+
case Type::Boolean: bool_ = o.bool_; break;
|
|
186
|
+
case Type::Integer: int_ = o.int_; break;
|
|
187
|
+
case Type::Unsigned: uint_ = o.uint_; break;
|
|
188
|
+
case Type::Float: float_ = o.float_; break;
|
|
189
|
+
case Type::String: str_ = o.str_; break;
|
|
190
|
+
case Type::Array: arr_ = o.arr_; break;
|
|
191
|
+
case Type::Object: obj_ = o.obj_; break;
|
|
192
|
+
default: break;
|
|
193
|
+
}
|
|
194
|
+
o.type_ = Type::Null; // ownership transferred; leave source a valid null
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
Json& Json::operator=(const Json& o) {
|
|
198
|
+
if (this == &o) return *this;
|
|
199
|
+
destroy();
|
|
200
|
+
// Reset to a valid Null first: destroy() freed the old payload but left the
|
|
201
|
+
// old type_/pointer in place, so if copy_from below throws, the destructor
|
|
202
|
+
// would otherwise free a dangling pointer. As Null, a failed copy is safe.
|
|
203
|
+
type_ = Type::Null;
|
|
204
|
+
copy_from(o);
|
|
205
|
+
return *this;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
Json& Json::operator=(Json&& o) noexcept {
|
|
209
|
+
if (this == &o) return *this;
|
|
210
|
+
destroy();
|
|
211
|
+
move_from(o);
|
|
212
|
+
return *this;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── initializer_list construction ───────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
Json::Json(std::initializer_list<Json> init) {
|
|
218
|
+
// nlohmann's heuristic: a non-empty list whose every element is a
|
|
219
|
+
// two-element array starting with a string is an object; else an array.
|
|
220
|
+
bool looks_like_object = init.size() > 0;
|
|
221
|
+
for (const Json& el : init) {
|
|
222
|
+
if (!(el.is_array() && el.size() == 2 && el.as_array()[0].is_string())) {
|
|
223
|
+
looks_like_object = false;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (looks_like_object) {
|
|
229
|
+
type_ = Type::Object;
|
|
230
|
+
obj_ = new Object();
|
|
231
|
+
for (const Json& el : init) {
|
|
232
|
+
const Array& pair = el.as_array();
|
|
233
|
+
(*obj_)[pair[0].as_string()] = pair[1];
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
type_ = Type::Array;
|
|
237
|
+
arr_ = new Array(init.begin(), init.end());
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── Scalar extraction ───────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
bool Json::as_bool() const {
|
|
244
|
+
switch (type_) {
|
|
245
|
+
case Type::Boolean: return bool_;
|
|
246
|
+
case Type::Integer: return int_ != 0;
|
|
247
|
+
case Type::Unsigned: return uint_ != 0;
|
|
248
|
+
case Type::Float: return float_ != 0.0;
|
|
249
|
+
default: throw JsonError("Json: value is not convertible to bool");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
int64_t Json::as_int64() const {
|
|
254
|
+
switch (type_) {
|
|
255
|
+
case Type::Integer: return int_;
|
|
256
|
+
case Type::Unsigned: return static_cast<int64_t>(uint_);
|
|
257
|
+
case Type::Float: return static_cast<int64_t>(float_);
|
|
258
|
+
case Type::Boolean: return bool_ ? 1 : 0;
|
|
259
|
+
default: throw JsonError("Json: value is not a number");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
uint64_t Json::as_uint64() const {
|
|
264
|
+
switch (type_) {
|
|
265
|
+
case Type::Integer: return static_cast<uint64_t>(int_);
|
|
266
|
+
case Type::Unsigned: return uint_;
|
|
267
|
+
case Type::Float: return static_cast<uint64_t>(float_);
|
|
268
|
+
case Type::Boolean: return bool_ ? 1u : 0u;
|
|
269
|
+
default: throw JsonError("Json: value is not a number");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
double Json::as_double() const {
|
|
274
|
+
switch (type_) {
|
|
275
|
+
case Type::Integer: return static_cast<double>(int_);
|
|
276
|
+
case Type::Unsigned: return static_cast<double>(uint_);
|
|
277
|
+
case Type::Float: return float_;
|
|
278
|
+
case Type::Boolean: return bool_ ? 1.0 : 0.0;
|
|
279
|
+
default: throw JsonError("Json: value is not a number");
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const std::string& Json::as_string() const {
|
|
284
|
+
if (type_ != Type::String) throw JsonError("Json: value is not a string");
|
|
285
|
+
return *str_;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ── Size / emptiness ────────────────────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
std::size_t Json::size() const noexcept {
|
|
291
|
+
switch (type_) {
|
|
292
|
+
case Type::Null:
|
|
293
|
+
case Type::Discarded: return 0;
|
|
294
|
+
case Type::Array: return arr_->size();
|
|
295
|
+
case Type::Object: return obj_->size();
|
|
296
|
+
default: return 1; // a scalar counts as one element
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
bool Json::empty() const noexcept {
|
|
301
|
+
switch (type_) {
|
|
302
|
+
case Type::Null: return true;
|
|
303
|
+
case Type::Array: return arr_->empty();
|
|
304
|
+
case Type::Object: return obj_->empty();
|
|
305
|
+
default: return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ── Object / array access ───────────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
Json& Json::operator[](const std::string& key) {
|
|
312
|
+
if (is_null()) { type_ = Type::Object; obj_ = new Object(); }
|
|
313
|
+
if (!is_object()) throw JsonError("Json: operator[](key) on a non-object value");
|
|
314
|
+
return (*obj_)[key];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const Json& Json::operator[](const std::string& key) const {
|
|
318
|
+
if (!is_object()) throw JsonError("Json: operator[](key) on a non-object value");
|
|
319
|
+
const Json* v = obj_->find(key);
|
|
320
|
+
if (!v) throw JsonError("Json: key not found: " + key);
|
|
321
|
+
return *v;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
Json& Json::operator[](std::size_t index) {
|
|
325
|
+
if (is_null()) { type_ = Type::Array; arr_ = new Array(); }
|
|
326
|
+
if (!is_array()) throw JsonError("Json: operator[](index) on a non-array value");
|
|
327
|
+
if (index >= arr_->size()) arr_->resize(index + 1);
|
|
328
|
+
return (*arr_)[index];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const Json& Json::operator[](std::size_t index) const {
|
|
332
|
+
if (!is_array()) throw JsonError("Json: operator[](index) on a non-array value");
|
|
333
|
+
if (index >= arr_->size()) throw JsonError("Json: array index out of range");
|
|
334
|
+
return (*arr_)[index];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
Json& Json::at(const std::string& key) {
|
|
338
|
+
if (!is_object()) throw JsonError("Json: at(key) on a non-object value");
|
|
339
|
+
Json* v = obj_->find(key);
|
|
340
|
+
if (!v) throw JsonError("Json: key not found: " + key);
|
|
341
|
+
return *v;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const Json& Json::at(const std::string& key) const {
|
|
345
|
+
if (!is_object()) throw JsonError("Json: at(key) on a non-object value");
|
|
346
|
+
const Json* v = obj_->find(key);
|
|
347
|
+
if (!v) throw JsonError("Json: key not found: " + key);
|
|
348
|
+
return *v;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
Json& Json::at(std::size_t index) {
|
|
352
|
+
if (!is_array()) throw JsonError("Json: at(index) on a non-array value");
|
|
353
|
+
if (index >= arr_->size()) throw JsonError("Json: array index out of range");
|
|
354
|
+
return (*arr_)[index];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const Json& Json::at(std::size_t index) const {
|
|
358
|
+
if (!is_array()) throw JsonError("Json: at(index) on a non-array value");
|
|
359
|
+
if (index >= arr_->size()) throw JsonError("Json: array index out of range");
|
|
360
|
+
return (*arr_)[index];
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
bool Json::erase(const std::string& key) {
|
|
364
|
+
if (!is_object()) return false;
|
|
365
|
+
return obj_->erase(key);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
void Json::erase(std::size_t index) {
|
|
369
|
+
if (!is_array()) throw JsonError("Json: erase(index) on a non-array value");
|
|
370
|
+
if (index >= arr_->size()) throw JsonError("Json: array index out of range");
|
|
371
|
+
arr_->erase(arr_->begin() + static_cast<std::ptrdiff_t>(index));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
Json& Json::front() {
|
|
375
|
+
if (is_array()) { if (arr_->empty()) throw JsonError("Json: front() on empty array"); return arr_->front(); }
|
|
376
|
+
if (is_object()) { if (obj_->empty()) throw JsonError("Json: front() on empty object"); return obj_->begin()->second; }
|
|
377
|
+
throw JsonError("Json: front() on a non-container value");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const Json& Json::front() const {
|
|
381
|
+
if (is_array()) { if (arr_->empty()) throw JsonError("Json: front() on empty array"); return arr_->front(); }
|
|
382
|
+
if (is_object()) { if (obj_->empty()) throw JsonError("Json: front() on empty object"); return obj_->begin()->second; }
|
|
383
|
+
throw JsonError("Json: front() on a non-container value");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
Json& Json::back() {
|
|
387
|
+
if (is_array()) { if (arr_->empty()) throw JsonError("Json: back() on empty array"); return arr_->back(); }
|
|
388
|
+
if (is_object()) { if (obj_->empty()) throw JsonError("Json: back() on empty object"); return (obj_->end() - 1)->second; }
|
|
389
|
+
throw JsonError("Json: back() on a non-container value");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const Json& Json::back() const {
|
|
393
|
+
if (is_array()) { if (arr_->empty()) throw JsonError("Json: back() on empty array"); return arr_->back(); }
|
|
394
|
+
if (is_object()) { if (obj_->empty()) throw JsonError("Json: back() on empty object"); return (obj_->end() - 1)->second; }
|
|
395
|
+
throw JsonError("Json: back() on a non-container value");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
void Json::push_back(const Json& value) {
|
|
399
|
+
if (is_null()) { type_ = Type::Array; arr_ = new Array(); }
|
|
400
|
+
if (!is_array()) throw JsonError("Json: push_back on a non-array value");
|
|
401
|
+
arr_->push_back(value);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
void Json::push_back(Json&& value) {
|
|
405
|
+
if (is_null()) { type_ = Type::Array; arr_ = new Array(); }
|
|
406
|
+
if (!is_array()) throw JsonError("Json: push_back on a non-array value");
|
|
407
|
+
arr_->push_back(std::move(value));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
void Json::clear() {
|
|
411
|
+
switch (type_) {
|
|
412
|
+
case Type::Array: arr_->clear(); break;
|
|
413
|
+
case Type::Object: obj_->clear(); break;
|
|
414
|
+
case Type::String: str_->clear(); break;
|
|
415
|
+
case Type::Integer: int_ = 0; break;
|
|
416
|
+
case Type::Unsigned: uint_ = 0; break;
|
|
417
|
+
case Type::Float: float_ = 0.0; break;
|
|
418
|
+
case Type::Boolean: bool_ = false; break;
|
|
419
|
+
default: break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
Json::Array& Json::as_array() {
|
|
424
|
+
if (!is_array()) throw JsonError("Json: value is not an array");
|
|
425
|
+
return *arr_;
|
|
426
|
+
}
|
|
427
|
+
const Json::Array& Json::as_array() const {
|
|
428
|
+
if (!is_array()) throw JsonError("Json: value is not an array");
|
|
429
|
+
return *arr_;
|
|
430
|
+
}
|
|
431
|
+
Json::Object& Json::as_object() {
|
|
432
|
+
if (!is_object()) throw JsonError("Json: value is not an object");
|
|
433
|
+
return *obj_;
|
|
434
|
+
}
|
|
435
|
+
const Json::Object& Json::as_object() const {
|
|
436
|
+
if (!is_object()) throw JsonError("Json: value is not an object");
|
|
437
|
+
return *obj_;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ── Equality ────────────────────────────────────────────────────────────────
|
|
441
|
+
|
|
442
|
+
bool Json::operator==(const Json& o) const {
|
|
443
|
+
// Numbers compare by mathematical value across the three numeric kinds.
|
|
444
|
+
if (is_number() && o.is_number()) {
|
|
445
|
+
if (is_number_float() || o.is_number_float()) return as_double() == o.as_double();
|
|
446
|
+
if (type_ == Type::Unsigned && o.type_ == Type::Unsigned) return uint_ == o.uint_;
|
|
447
|
+
if (type_ == Type::Integer && o.type_ == Type::Integer) return int_ == o.int_;
|
|
448
|
+
// Mixed signed/unsigned: a negative signed value never equals an unsigned.
|
|
449
|
+
auto eq = [](int64_t s, uint64_t u) {
|
|
450
|
+
return s >= 0 && static_cast<uint64_t>(s) == u;
|
|
451
|
+
};
|
|
452
|
+
if (type_ == Type::Integer) return eq(int_, o.uint_);
|
|
453
|
+
return eq(o.int_, uint_);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (type_ != o.type_) return false;
|
|
457
|
+
switch (type_) {
|
|
458
|
+
case Type::Null:
|
|
459
|
+
case Type::Discarded: return true;
|
|
460
|
+
case Type::Boolean: return bool_ == o.bool_;
|
|
461
|
+
case Type::String: return *str_ == *o.str_;
|
|
462
|
+
case Type::Array: return *arr_ == *o.arr_; // element-wise via Json::operator==
|
|
463
|
+
case Type::Object: return *obj_ == *o.obj_; // order-independent
|
|
464
|
+
default: return false;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ── Serialisation ───────────────────────────────────────────────────────────
|
|
469
|
+
|
|
470
|
+
namespace {
|
|
471
|
+
|
|
472
|
+
void dump_string(std::string& out, const std::string& s) {
|
|
473
|
+
// Escapes are rare in real payloads (ids, ips, agent strings carry none), so
|
|
474
|
+
// we copy maximal runs of pass-through bytes in one append() and only break
|
|
475
|
+
// the run for a character that needs escaping.
|
|
476
|
+
out += '"';
|
|
477
|
+
const char* const begin = s.data();
|
|
478
|
+
const char* const end = begin + s.size();
|
|
479
|
+
const char* run = begin; // start of the current pass-through run
|
|
480
|
+
for (const char* p = begin; p != end; ++p) {
|
|
481
|
+
const char* esc = nullptr; // two-char escape for *p, if any
|
|
482
|
+
switch (*p) {
|
|
483
|
+
case '"': esc = "\\\""; break;
|
|
484
|
+
case '\\': esc = "\\\\"; break;
|
|
485
|
+
case '\b': esc = "\\b"; break;
|
|
486
|
+
case '\f': esc = "\\f"; break;
|
|
487
|
+
case '\n': esc = "\\n"; break;
|
|
488
|
+
case '\r': esc = "\\r"; break;
|
|
489
|
+
case '\t': esc = "\\t"; break;
|
|
490
|
+
default: break;
|
|
491
|
+
}
|
|
492
|
+
if (esc) {
|
|
493
|
+
out.append(run, static_cast<std::size_t>(p - run));
|
|
494
|
+
out += esc;
|
|
495
|
+
run = p + 1;
|
|
496
|
+
} else if (static_cast<unsigned char>(*p) < 0x20) {
|
|
497
|
+
// Other control characters use the \uXXXX form.
|
|
498
|
+
out.append(run, static_cast<std::size_t>(p - run));
|
|
499
|
+
char buf[7];
|
|
500
|
+
std::snprintf(buf, sizeof buf, "\\u%04x", static_cast<unsigned char>(*p));
|
|
501
|
+
out += buf;
|
|
502
|
+
run = p + 1;
|
|
503
|
+
}
|
|
504
|
+
// Printable ASCII and raw UTF-8 bytes stay in the run.
|
|
505
|
+
}
|
|
506
|
+
out.append(run, static_cast<std::size_t>(end - run));
|
|
507
|
+
out += '"';
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// ── Fast integer formatting ──────────────────────────────────────────────────
|
|
511
|
+
//
|
|
512
|
+
// Integers are emitted two decimal digits at a time by indexing a table of all
|
|
513
|
+
// hundred digit pairs, which halves the number of (comparatively slow) integer
|
|
514
|
+
// divisions a digit-at-a-time loop performs. This is the well-worn approach used
|
|
515
|
+
// inside libstdc++, abseil and fmt; spelling it out keeps the fast path byte-for
|
|
516
|
+
// -byte identical on every toolchain instead of silently degrading to an
|
|
517
|
+
// allocating std::to_string wherever std::to_chars' integer overloads are
|
|
518
|
+
// missing.
|
|
519
|
+
|
|
520
|
+
// Every two-digit value "00", "01", … "99" laid out back to back, so the digits
|
|
521
|
+
// of `n` live at kDigitPairs[2*n] and kDigitPairs[2*n + 1].
|
|
522
|
+
constexpr char kDigitPairs[] =
|
|
523
|
+
"00010203040506070809"
|
|
524
|
+
"10111213141516171819"
|
|
525
|
+
"20212223242526272829"
|
|
526
|
+
"30313233343536373839"
|
|
527
|
+
"40414243444546474849"
|
|
528
|
+
"50515253545556575859"
|
|
529
|
+
"60616263646566676869"
|
|
530
|
+
"70717273747576777879"
|
|
531
|
+
"80818283848586878889"
|
|
532
|
+
"90919293949596979899";
|
|
533
|
+
|
|
534
|
+
// Write the decimal digits of an unsigned magnitude backward, finishing at
|
|
535
|
+
// `last`, and return a pointer to the most significant digit produced. The
|
|
536
|
+
// caller owns the buffer; 20 digits span the full 64-bit range.
|
|
537
|
+
inline char* write_decimal(char* last, uint64_t value) {
|
|
538
|
+
while (value >= 100) {
|
|
539
|
+
const unsigned pair = static_cast<unsigned>(value % 100) * 2;
|
|
540
|
+
value /= 100;
|
|
541
|
+
*--last = kDigitPairs[pair + 1];
|
|
542
|
+
*--last = kDigitPairs[pair];
|
|
543
|
+
}
|
|
544
|
+
if (value >= 10) {
|
|
545
|
+
const unsigned pair = static_cast<unsigned>(value) * 2;
|
|
546
|
+
*--last = kDigitPairs[pair + 1];
|
|
547
|
+
*--last = kDigitPairs[pair];
|
|
548
|
+
} else {
|
|
549
|
+
*--last = static_cast<char>('0' + value);
|
|
550
|
+
}
|
|
551
|
+
return last;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Append the shortest decimal form of an integer with no heap allocation.
|
|
555
|
+
template <typename Int>
|
|
556
|
+
void dump_int(std::string& out, Int v) {
|
|
557
|
+
char buf[21]; // 20 digits + sign: the 64-bit worst case
|
|
558
|
+
char* const last = buf + sizeof buf;
|
|
559
|
+
char* first;
|
|
560
|
+
if constexpr (std::is_signed<Int>::value) {
|
|
561
|
+
uint64_t mag = static_cast<uint64_t>(v);
|
|
562
|
+
if (v < 0) {
|
|
563
|
+
mag = ~mag + 1; // two's-complement magnitude (safe at INT64_MIN)
|
|
564
|
+
first = write_decimal(last, mag);
|
|
565
|
+
*--first = '-';
|
|
566
|
+
} else {
|
|
567
|
+
first = write_decimal(last, mag);
|
|
568
|
+
}
|
|
569
|
+
} else {
|
|
570
|
+
first = write_decimal(last, static_cast<uint64_t>(v));
|
|
571
|
+
}
|
|
572
|
+
out.append(first, last);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
void dump_double(std::string& out, double d) {
|
|
576
|
+
if (std::isnan(d) || std::isinf(d)) { out += "null"; return; } // JSON has no NaN/Inf
|
|
577
|
+
|
|
578
|
+
char buf[32];
|
|
579
|
+
char* last;
|
|
580
|
+
#if LIBRATS_JSON_CHARCONV
|
|
581
|
+
// to_chars (no precision) yields the shortest string that round-trips exactly.
|
|
582
|
+
last = std::to_chars(buf, buf + sizeof buf, d).ptr;
|
|
583
|
+
#else
|
|
584
|
+
// Fallback: grow precision until the value round-trips.
|
|
585
|
+
int n = 0;
|
|
586
|
+
for (int prec = 15; prec <= 17; ++prec) {
|
|
587
|
+
n = std::snprintf(buf, sizeof buf, "%.*g", prec, d);
|
|
588
|
+
if (std::strtod(buf, nullptr) == d) break;
|
|
589
|
+
}
|
|
590
|
+
last = buf + n;
|
|
591
|
+
#endif
|
|
592
|
+
out.append(buf, last);
|
|
593
|
+
|
|
594
|
+
// Keep it recognisable as a float so it parses back to a float, not an int:
|
|
595
|
+
// a token with no '.', 'e' or 'E' (e.g. "2") gets a trailing ".0".
|
|
596
|
+
for (const char* q = buf; q != last; ++q) {
|
|
597
|
+
if (*q == '.' || *q == 'e' || *q == 'E') return;
|
|
598
|
+
}
|
|
599
|
+
out += ".0";
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
void newline_indent(std::string& out, int indent, int depth) {
|
|
603
|
+
if (indent < 0) return;
|
|
604
|
+
out += '\n';
|
|
605
|
+
out.append(static_cast<std::size_t>(indent) * static_cast<std::size_t>(depth), ' ');
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
} // namespace
|
|
609
|
+
|
|
610
|
+
void Json::dump_to(std::string& out, int indent, int depth) const {
|
|
611
|
+
switch (type_) {
|
|
612
|
+
case Type::Null: out += "null"; break;
|
|
613
|
+
case Type::Discarded: out += "null"; break; // dumping a discarded value falls back to null
|
|
614
|
+
case Type::Boolean: out += bool_ ? "true" : "false"; break;
|
|
615
|
+
case Type::Integer: dump_int(out, int_); break;
|
|
616
|
+
case Type::Unsigned: dump_int(out, uint_); break;
|
|
617
|
+
case Type::Float: dump_double(out, float_); break;
|
|
618
|
+
case Type::String: dump_string(out, *str_); break;
|
|
619
|
+
case Type::Array: {
|
|
620
|
+
if (arr_->empty()) { out += "[]"; break; }
|
|
621
|
+
out += '[';
|
|
622
|
+
bool first = true;
|
|
623
|
+
for (const Json& el : *arr_) {
|
|
624
|
+
if (!first) out += ',';
|
|
625
|
+
first = false;
|
|
626
|
+
newline_indent(out, indent, depth + 1);
|
|
627
|
+
el.dump_to(out, indent, depth + 1);
|
|
628
|
+
}
|
|
629
|
+
newline_indent(out, indent, depth);
|
|
630
|
+
out += ']';
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
case Type::Object: {
|
|
634
|
+
if (obj_->empty()) { out += "{}"; break; }
|
|
635
|
+
out += '{';
|
|
636
|
+
bool first = true;
|
|
637
|
+
for (const auto& kv : *obj_) {
|
|
638
|
+
if (!first) out += ',';
|
|
639
|
+
first = false;
|
|
640
|
+
newline_indent(out, indent, depth + 1);
|
|
641
|
+
dump_string(out, kv.first);
|
|
642
|
+
out += ':';
|
|
643
|
+
if (indent >= 0) out += ' ';
|
|
644
|
+
kv.second.dump_to(out, indent, depth + 1);
|
|
645
|
+
}
|
|
646
|
+
newline_indent(out, indent, depth);
|
|
647
|
+
out += '}';
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
std::string Json::dump(int indent) const {
|
|
654
|
+
std::string out;
|
|
655
|
+
dump_to(out, indent, 0);
|
|
656
|
+
return out;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// ── Parsing ─────────────────────────────────────────────────────────────────
|
|
660
|
+
|
|
661
|
+
class JsonParser {
|
|
662
|
+
public:
|
|
663
|
+
JsonParser(const char* begin, const char* end) : p_(begin), end_(end) {}
|
|
664
|
+
|
|
665
|
+
Json parse() {
|
|
666
|
+
skip_ws();
|
|
667
|
+
if (p_ == end_) error("empty document");
|
|
668
|
+
Json v = parse_value(0);
|
|
669
|
+
skip_ws();
|
|
670
|
+
if (p_ != end_) error("unexpected trailing characters");
|
|
671
|
+
return v;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
private:
|
|
675
|
+
const char* p_;
|
|
676
|
+
const char* end_;
|
|
677
|
+
static constexpr int kMaxDepth = 1000;
|
|
678
|
+
|
|
679
|
+
[[noreturn]] void error(const std::string& msg) const {
|
|
680
|
+
throw JsonError("JSON parse error: " + msg);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
static bool is_digit(char c) { return c >= '0' && c <= '9'; }
|
|
684
|
+
|
|
685
|
+
void skip_ws() {
|
|
686
|
+
while (p_ != end_) {
|
|
687
|
+
char c = *p_;
|
|
688
|
+
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') ++p_;
|
|
689
|
+
else break;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
char peek() const { return p_ != end_ ? *p_ : '\0'; }
|
|
694
|
+
|
|
695
|
+
Json parse_value(int depth) {
|
|
696
|
+
if (depth > kMaxDepth) error("maximum nesting depth exceeded");
|
|
697
|
+
skip_ws();
|
|
698
|
+
if (p_ == end_) error("unexpected end of input");
|
|
699
|
+
switch (*p_) {
|
|
700
|
+
case '{': return parse_object(depth);
|
|
701
|
+
case '[': return parse_array(depth);
|
|
702
|
+
case '"': return Json(parse_string());
|
|
703
|
+
case 't': case 'f': return parse_bool();
|
|
704
|
+
case 'n': return parse_null();
|
|
705
|
+
case '-': return parse_number();
|
|
706
|
+
default:
|
|
707
|
+
if (is_digit(*p_)) return parse_number();
|
|
708
|
+
error(std::string("unexpected character '") + *p_ + "'");
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
Json parse_object(int depth) {
|
|
713
|
+
++p_; // consume '{'
|
|
714
|
+
Json obj = Json::object();
|
|
715
|
+
Json::Object& o = obj.as_object();
|
|
716
|
+
skip_ws();
|
|
717
|
+
if (peek() == '}') { ++p_; return obj; }
|
|
718
|
+
while (true) {
|
|
719
|
+
skip_ws();
|
|
720
|
+
if (peek() != '"') error("expected string key in object");
|
|
721
|
+
std::string key = parse_string();
|
|
722
|
+
skip_ws();
|
|
723
|
+
if (peek() != ':') error("expected ':' after object key");
|
|
724
|
+
++p_;
|
|
725
|
+
o[std::move(key)] = parse_value(depth + 1);
|
|
726
|
+
skip_ws();
|
|
727
|
+
char c = peek();
|
|
728
|
+
if (c == ',') { ++p_; continue; }
|
|
729
|
+
if (c == '}') { ++p_; break; }
|
|
730
|
+
error("expected ',' or '}' in object");
|
|
731
|
+
}
|
|
732
|
+
return obj;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
Json parse_array(int depth) {
|
|
736
|
+
++p_; // consume '['
|
|
737
|
+
Json arr = Json::array();
|
|
738
|
+
Json::Array& a = arr.as_array();
|
|
739
|
+
skip_ws();
|
|
740
|
+
if (peek() == ']') { ++p_; return arr; }
|
|
741
|
+
while (true) {
|
|
742
|
+
a.push_back(parse_value(depth + 1));
|
|
743
|
+
skip_ws();
|
|
744
|
+
char c = peek();
|
|
745
|
+
if (c == ',') { ++p_; continue; }
|
|
746
|
+
if (c == ']') { ++p_; break; }
|
|
747
|
+
error("expected ',' or ']' in array");
|
|
748
|
+
}
|
|
749
|
+
return arr;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
Json parse_bool() {
|
|
753
|
+
if (end_ - p_ >= 4 && std::strncmp(p_, "true", 4) == 0) { p_ += 4; return Json(true); }
|
|
754
|
+
if (end_ - p_ >= 5 && std::strncmp(p_, "false", 5) == 0) { p_ += 5; return Json(false); }
|
|
755
|
+
error("invalid literal");
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
Json parse_null() {
|
|
759
|
+
if (end_ - p_ >= 4 && std::strncmp(p_, "null", 4) == 0) { p_ += 4; return Json(nullptr); }
|
|
760
|
+
error("invalid literal");
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
unsigned parse_hex4() {
|
|
764
|
+
if (end_ - p_ < 4) error("invalid \\u escape");
|
|
765
|
+
unsigned v = 0;
|
|
766
|
+
for (int i = 0; i < 4; ++i) {
|
|
767
|
+
char h = *p_++;
|
|
768
|
+
v <<= 4;
|
|
769
|
+
if (h >= '0' && h <= '9') v |= static_cast<unsigned>(h - '0');
|
|
770
|
+
else if (h >= 'a' && h <= 'f') v |= static_cast<unsigned>(h - 'a' + 10);
|
|
771
|
+
else if (h >= 'A' && h <= 'F') v |= static_cast<unsigned>(h - 'A' + 10);
|
|
772
|
+
else error("invalid hex digit in \\u escape");
|
|
773
|
+
}
|
|
774
|
+
return v;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
static void append_utf8(std::string& out, unsigned cp) {
|
|
778
|
+
if (cp <= 0x7F) {
|
|
779
|
+
out += static_cast<char>(cp);
|
|
780
|
+
} else if (cp <= 0x7FF) {
|
|
781
|
+
out += static_cast<char>(0xC0 | (cp >> 6));
|
|
782
|
+
out += static_cast<char>(0x80 | (cp & 0x3F));
|
|
783
|
+
} else if (cp <= 0xFFFF) {
|
|
784
|
+
out += static_cast<char>(0xE0 | (cp >> 12));
|
|
785
|
+
out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
|
786
|
+
out += static_cast<char>(0x80 | (cp & 0x3F));
|
|
787
|
+
} else {
|
|
788
|
+
out += static_cast<char>(0xF0 | (cp >> 18));
|
|
789
|
+
out += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
|
|
790
|
+
out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
|
791
|
+
out += static_cast<char>(0x80 | (cp & 0x3F));
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Scan from the current position to the closing quote, counting '\X' as two
|
|
796
|
+
// bytes so an escaped quote isn't mistaken for the end. Returns a pointer to
|
|
797
|
+
// the closing '"' (or end_ if unterminated — the caller's decode loop then
|
|
798
|
+
// reports it). Used only to size the decode buffer; all escape validation
|
|
799
|
+
// still happens in parse_string, so this stays deliberately permissive.
|
|
800
|
+
const char* scan_to_close() const {
|
|
801
|
+
const char* s = p_;
|
|
802
|
+
while (s != end_) {
|
|
803
|
+
if (*s == '"') return s;
|
|
804
|
+
if (*s == '\\' && ++s == end_) break;
|
|
805
|
+
++s;
|
|
806
|
+
}
|
|
807
|
+
return end_;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
std::string parse_string() {
|
|
811
|
+
++p_; // consume opening '"'
|
|
812
|
+
std::string out;
|
|
813
|
+
// Most string bytes need no translation, so copy maximal escape-free
|
|
814
|
+
// runs in a single append() and only stop for '"', '\\' or a control
|
|
815
|
+
// character. `run` marks the start of the run still to be flushed.
|
|
816
|
+
// `str_begin` anchors the whole content for one-shot buffer sizing.
|
|
817
|
+
const char* const str_begin = p_;
|
|
818
|
+
const char* run = p_;
|
|
819
|
+
bool sized = false;
|
|
820
|
+
while (true) {
|
|
821
|
+
if (p_ == end_) error("unterminated string");
|
|
822
|
+
unsigned char c = static_cast<unsigned char>(*p_);
|
|
823
|
+
if (c == '"') { out.append(run, static_cast<std::size_t>(p_ - run)); ++p_; break; }
|
|
824
|
+
if (c == '\\') {
|
|
825
|
+
if (!sized) {
|
|
826
|
+
// First escape: an escape-free string never reaches here and
|
|
827
|
+
// stays on the single-append fast path. Once we must decode,
|
|
828
|
+
// the result can be no longer than the raw bytes up to the
|
|
829
|
+
// closing quote, so reserve that exactly once — every later
|
|
830
|
+
// append() then stays in place instead of reallocating.
|
|
831
|
+
out.reserve(static_cast<std::size_t>(scan_to_close() - str_begin));
|
|
832
|
+
sized = true;
|
|
833
|
+
}
|
|
834
|
+
out.append(run, static_cast<std::size_t>(p_ - run));
|
|
835
|
+
++p_; // consume backslash
|
|
836
|
+
if (p_ == end_) error("unterminated escape sequence");
|
|
837
|
+
char e = *p_++;
|
|
838
|
+
switch (e) {
|
|
839
|
+
case '"': out += '"'; break;
|
|
840
|
+
case '\\': out += '\\'; break;
|
|
841
|
+
case '/': out += '/'; break;
|
|
842
|
+
case 'b': out += '\b'; break;
|
|
843
|
+
case 'f': out += '\f'; break;
|
|
844
|
+
case 'n': out += '\n'; break;
|
|
845
|
+
case 'r': out += '\r'; break;
|
|
846
|
+
case 't': out += '\t'; break;
|
|
847
|
+
case 'u': {
|
|
848
|
+
unsigned cp = parse_hex4();
|
|
849
|
+
if (cp >= 0xD800 && cp <= 0xDBFF) {
|
|
850
|
+
// High surrogate must be followed by a low surrogate.
|
|
851
|
+
if (end_ - p_ < 2 || p_[0] != '\\' || p_[1] != 'u')
|
|
852
|
+
error("invalid surrogate pair");
|
|
853
|
+
p_ += 2;
|
|
854
|
+
unsigned lo = parse_hex4();
|
|
855
|
+
if (lo < 0xDC00 || lo > 0xDFFF) error("invalid low surrogate");
|
|
856
|
+
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
|
857
|
+
} else if (cp >= 0xDC00 && cp <= 0xDFFF) {
|
|
858
|
+
error("unexpected low surrogate");
|
|
859
|
+
}
|
|
860
|
+
append_utf8(out, cp);
|
|
861
|
+
break;
|
|
862
|
+
}
|
|
863
|
+
default: error("invalid escape sequence");
|
|
864
|
+
}
|
|
865
|
+
run = p_; // next run resumes after the escape
|
|
866
|
+
} else if (c < 0x20) {
|
|
867
|
+
error("unescaped control character in string");
|
|
868
|
+
} else {
|
|
869
|
+
++p_; // ordinary byte: defer the copy until the run is flushed
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return out;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
Json parse_number() {
|
|
876
|
+
const char* start = p_;
|
|
877
|
+
bool is_float = false;
|
|
878
|
+
|
|
879
|
+
if (peek() == '-') ++p_;
|
|
880
|
+
if (p_ == end_) error("invalid number");
|
|
881
|
+
|
|
882
|
+
if (peek() == '0') {
|
|
883
|
+
++p_;
|
|
884
|
+
} else if (is_digit(peek())) {
|
|
885
|
+
while (p_ != end_ && is_digit(*p_)) ++p_;
|
|
886
|
+
} else {
|
|
887
|
+
error("invalid number");
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (p_ != end_ && *p_ == '.') {
|
|
891
|
+
is_float = true;
|
|
892
|
+
++p_;
|
|
893
|
+
if (p_ == end_ || !is_digit(*p_)) error("invalid number: expected digits after '.'");
|
|
894
|
+
while (p_ != end_ && is_digit(*p_)) ++p_;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (p_ != end_ && (*p_ == 'e' || *p_ == 'E')) {
|
|
898
|
+
is_float = true;
|
|
899
|
+
++p_;
|
|
900
|
+
if (p_ != end_ && (*p_ == '+' || *p_ == '-')) ++p_;
|
|
901
|
+
if (p_ == end_ || !is_digit(*p_)) error("invalid number: expected digits in exponent");
|
|
902
|
+
while (p_ != end_ && is_digit(*p_)) ++p_;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// The grammar above is already validated, so the only conversion
|
|
906
|
+
// outcome we still handle is integer overflow → fall back to double.
|
|
907
|
+
return finish_number(start, is_float);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Parse [start, end) as a double. On magnitude overflow std::from_chars
|
|
911
|
+
// reports result_out_of_range and — on libstdc++ — leaves the out-param
|
|
912
|
+
// untouched, which would silently yield 0.0; map that to ±infinity instead,
|
|
913
|
+
// matching the strtod fallback path and nlohmann (Json then dumps it as null).
|
|
914
|
+
static double parse_double(const char* start, const char* end) {
|
|
915
|
+
double d = 0.0;
|
|
916
|
+
#if LIBRATS_JSON_FROM_CHARS_FLOAT
|
|
917
|
+
auto res = std::from_chars(start, end, d);
|
|
918
|
+
if (res.ec == std::errc::result_out_of_range)
|
|
919
|
+
d = (*start == '-') ? -HUGE_VAL : HUGE_VAL;
|
|
920
|
+
#else
|
|
921
|
+
// No floating-point from_chars (e.g. libc++): strtod over a NUL-terminated
|
|
922
|
+
// copy. strtod already returns ±HUGE_VAL on overflow, so no fixup needed.
|
|
923
|
+
std::string num(start, end);
|
|
924
|
+
d = std::strtod(num.c_str(), nullptr);
|
|
925
|
+
#endif
|
|
926
|
+
return d;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Convert the already-scanned token [start, p_) into a Json number.
|
|
930
|
+
Json finish_number(const char* start, bool is_float) {
|
|
931
|
+
#if LIBRATS_JSON_CHARCONV
|
|
932
|
+
if (is_float) return Json(parse_double(start, p_));
|
|
933
|
+
if (*start == '-') {
|
|
934
|
+
int64_t v = 0;
|
|
935
|
+
if (std::from_chars(start, p_, v).ec == std::errc{}) return Json(v);
|
|
936
|
+
} else {
|
|
937
|
+
uint64_t v = 0;
|
|
938
|
+
if (std::from_chars(start, p_, v).ec == std::errc{}) {
|
|
939
|
+
// Prefer signed storage when it fits, so small positive ints
|
|
940
|
+
// compare equal to literal-constructed ones however they were built.
|
|
941
|
+
if (v <= static_cast<uint64_t>(INT64_MAX))
|
|
942
|
+
return Json(static_cast<int64_t>(v));
|
|
943
|
+
return Json(v);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
// Integer overflowed 64 bits: represent it (approximately) as a double.
|
|
947
|
+
return Json(parse_double(start, p_));
|
|
948
|
+
#else
|
|
949
|
+
std::string num(start, p_);
|
|
950
|
+
if (is_float) return Json(std::strtod(num.c_str(), nullptr));
|
|
951
|
+
|
|
952
|
+
errno = 0;
|
|
953
|
+
if (num[0] == '-') {
|
|
954
|
+
long long v = std::strtoll(num.c_str(), nullptr, 10);
|
|
955
|
+
if (errno == ERANGE) return Json(std::strtod(num.c_str(), nullptr));
|
|
956
|
+
return Json(static_cast<int64_t>(v));
|
|
957
|
+
}
|
|
958
|
+
unsigned long long v = std::strtoull(num.c_str(), nullptr, 10);
|
|
959
|
+
if (errno == ERANGE) return Json(std::strtod(num.c_str(), nullptr));
|
|
960
|
+
if (v <= static_cast<unsigned long long>(INT64_MAX))
|
|
961
|
+
return Json(static_cast<int64_t>(v));
|
|
962
|
+
return Json(static_cast<uint64_t>(v));
|
|
963
|
+
#endif
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
|
|
967
|
+
Json Json::parse(const std::string& text, std::nullptr_t, bool allow_exceptions) {
|
|
968
|
+
const char* begin = text.data();
|
|
969
|
+
const char* end = begin + text.size();
|
|
970
|
+
// Skip a leading UTF-8 byte-order mark if present.
|
|
971
|
+
if (text.size() >= 3 && static_cast<unsigned char>(begin[0]) == 0xEF &&
|
|
972
|
+
static_cast<unsigned char>(begin[1]) == 0xBB &&
|
|
973
|
+
static_cast<unsigned char>(begin[2]) == 0xBF) {
|
|
974
|
+
begin += 3;
|
|
975
|
+
}
|
|
976
|
+
try {
|
|
977
|
+
JsonParser parser(begin, end);
|
|
978
|
+
return parser.parse();
|
|
979
|
+
} catch (const std::exception&) {
|
|
980
|
+
if (allow_exceptions) throw;
|
|
981
|
+
return make_discarded();
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
Json Json::parse(const char* text, std::nullptr_t, bool allow_exceptions) {
|
|
986
|
+
return parse(std::string(text ? text : ""), nullptr, allow_exceptions);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// ── Stream operators ────────────────────────────────────────────────────────
|
|
990
|
+
|
|
991
|
+
std::istream& operator>>(std::istream& is, Json& j) {
|
|
992
|
+
std::string content((std::istreambuf_iterator<char>(is)),
|
|
993
|
+
std::istreambuf_iterator<char>());
|
|
994
|
+
j = Json::parse(content); // throws JsonError on malformed input
|
|
995
|
+
return is;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
std::ostream& operator<<(std::ostream& os, const Json& j) {
|
|
999
|
+
return os << j.dump();
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
} // namespace librats
|