From caeb6cbc2b297bbdeeb692e0470537e510c200fe Mon Sep 17 00:00:00 2001 From: lyq Date: Thu, 15 Jan 2026 17:05:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9sub=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E8=8E=B7=E5=8F=96vid=E7=9A=84=E6=96=B9=E5=BC=8F=EF=BC=8C?= =?UTF-8?q?=E4=BB=A5=E5=8F=8Atopic=E7=9A=84=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.json | 4 +- src/communication/sub/CMakeLists.txt | 61 +- .../sub/include/sub/control_mapper.hpp | 17 + .../sub/include/sub/control_state.hpp | 61 + .../sub/include/sub/mqtt_receiver.hpp | 54 + .../sub/include/sub/sub_node.hpp | 38 +- src/communication/sub/src/control_mapper.cpp | 71 + src/communication/sub/src/jsoncpp.cpp | 8699 +++++++++-------- src/communication/sub/src/main.cpp | 92 + src/communication/sub/src/md5.cpp | 54 +- src/communication/sub/src/mqtt_receiver.cpp | 373 + src/communication/sub/src/sub_node.cpp | 514 +- 12 files changed, 5279 insertions(+), 4759 deletions(-) create mode 100644 src/communication/sub/include/sub/control_mapper.hpp create mode 100644 src/communication/sub/include/sub/control_state.hpp create mode 100644 src/communication/sub/include/sub/mqtt_receiver.hpp create mode 100644 src/communication/sub/src/control_mapper.cpp create mode 100644 src/communication/sub/src/main.cpp create mode 100644 src/communication/sub/src/mqtt_receiver.cpp diff --git a/config.json b/config.json index d201630..b0e76e1 100644 --- a/config.json +++ b/config.json @@ -1,13 +1,13 @@ { "mqtt": { - "external_net_address": "tcp://36.153.162.171", + "external_net_address": "36.153.162.171", "external_net_port": 19683, "username": "zxwl", "password": "zxwl1234@", "info_topic": "/zxwl/sweeper/{vid}/info", "fault_topic": "/zxwl/sweeper/{vid}/fault", "gps_topic": "/zxwl/sweeper/{vid}/gps", - "remote_topic": "/zxwl/sweeper/V060003/ctrl", + "remote_topic": "/zxwl/sweeper/{vid}/ctrl", "upload_url": "https://qsc.ntiov.com:8443/api/sys/route/upload", "download_url_pre": "http://36.153.162.171:9510/api/ccp-web/file/", "mqtt_topic_push_status": "/zxwl/sweeper/V060003/task/status", diff --git a/src/communication/sub/CMakeLists.txt b/src/communication/sub/CMakeLists.txt index 1062f27..08b6b9d 100644 --- a/src/communication/sub/CMakeLists.txt +++ b/src/communication/sub/CMakeLists.txt @@ -16,7 +16,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - add_compile_options(-w) # 禁用所有警告 + add_compile_options(-w) # 禁用所有警告 endif() # find dependencies @@ -26,37 +26,32 @@ find_package(std_msgs REQUIRED) find_package(sweeper_interfaces REQUIRED) find_package(CURL REQUIRED) -include_directories( - include/sub - include/paho_mqtt_3c - ${catkin_INCLUDE_DIRS} -) - -add_executable(sub_node - src/sub_node.cpp - src/jsoncpp.cpp - src/md5.cpp -) - -ament_target_dependencies(sub_node rclcpp std_msgs sweeper_interfaces CURL) - - -if(CMAKE_SYSTEM_PROCESSOR MATCHES aarch64) - target_link_libraries( - sub_node - ${PROJECT_SOURCE_DIR}/lib/libpaho-mqtt3c-static.a - ) -else() - target_link_libraries( - sub_node - ${PROJECT_SOURCE_DIR}/lib/libpaho-mqtt3c.a - ) -endif() - -install(TARGETS +add_executable( sub_node - DESTINATION lib/${PROJECT_NAME} -) + src/main.cpp + src/sub_node.cpp + src/mqtt_receiver.cpp + src/control_mapper.cpp + src/jsoncpp.cpp + src/md5.cpp) + +target_include_directories(sub_node PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/include/paho_mqtt_3c) + +ament_target_dependencies( + sub_node + rclcpp + std_msgs + sweeper_interfaces + CURL) + +if(CMAKE_SYSTEM_PROCESSOR MATCHES aarch64) + target_link_libraries(sub_node ${PROJECT_SOURCE_DIR}/lib/libpaho-mqtt3c-static.a) +else() + target_link_libraries(sub_node ${PROJECT_SOURCE_DIR}/lib/libpaho-mqtt3c.a) +endif() + +install(TARGETS sub_node DESTINATION lib/${PROJECT_NAME}) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) @@ -67,5 +62,5 @@ if(BUILD_TESTING) # uncomment the line when this package is not in a git repo #set(ament_cmake_cpplint_FOUND TRUE) ament_lint_auto_find_test_dependencies() -endif() -ament_package() \ No newline at end of file +endif() +ament_package() diff --git a/src/communication/sub/include/sub/control_mapper.hpp b/src/communication/sub/include/sub/control_mapper.hpp new file mode 100644 index 0000000..befdd55 --- /dev/null +++ b/src/communication/sub/include/sub/control_mapper.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "sub/control_state.hpp" +#include "sweeper_interfaces/msg/mc_ctrl.hpp" + +namespace sub_node_pkg +{ + +int mapGearToMcCtrl(int gear_mqtt); + +int mapThrottleToRpm(int throttle); + +int mapSteeringToAngle(int steering_raw); + +void fillMcCtrlFromCarCtrl(const CarCtrl& in, sweeper_interfaces::msg::McCtrl& out); + +} // namespace sub_node_pkg diff --git a/src/communication/sub/include/sub/control_state.hpp b/src/communication/sub/include/sub/control_state.hpp new file mode 100644 index 0000000..650e190 --- /dev/null +++ b/src/communication/sub/include/sub/control_state.hpp @@ -0,0 +1,61 @@ +#pragma once +#include +#include +#include +#include + +namespace sub_node_pkg +{ + +struct CarCtrl +{ + // 0: 手动驾驶, 3: 远程驾驶 + int mode = 0; + + // 0:N档,1:D档,2:R档(这是你 MQTT 侧的定义) + int gear = 0; + + // 0~65535 + int throttle = 0; + int steering = 32767; + int brake = 0; + + // 1: 清扫 0: 不清扫 + int sweepCtrl = 0; +}; + +struct MqttConfig +{ + // MQTT broker + std::string inter_net_address; + int inter_net_port = 0; + std::string external_net_address; + int external_net_port = 0; + + std::string mqtt_user; + std::string mqtt_password; + + // topic template (must contain {vid}) + std::string remote_topic_template; + + // runtime + std::string address; + std::string client_id; +}; + +struct ControlState +{ + std::mutex mtx; + + CarCtrl ctrl; + int get_route = 0; + + // identity(新增) + std::string vid; + bool identity_ready = false; + + std::atomic mqtt_connected{false}; + std::atomic last_msg_ms{0}; +}; + +} // namespace sub_node_pkg diff --git a/src/communication/sub/include/sub/mqtt_receiver.hpp b/src/communication/sub/include/sub/mqtt_receiver.hpp new file mode 100644 index 0000000..bff5c8b --- /dev/null +++ b/src/communication/sub/include/sub/mqtt_receiver.hpp @@ -0,0 +1,54 @@ +#pragma once +#include + +#include +#include +#include +#include + +#include "sub/control_state.hpp" + +namespace sub_node_pkg +{ + +class MqttReceiver +{ + public: + MqttReceiver(ControlState& state, MqttConfig cfg); + ~MqttReceiver(); + + bool start(); + void stop(); + + MqttReceiver(const MqttReceiver&) = delete; + MqttReceiver& operator=(const MqttReceiver&) = delete; + + private: + void runLoop(); + + // Paho C callbacks (static -> instance) + static void onDelivered(void* context, MQTTClient_deliveryToken dt); + static int onMessageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* message); + static void onConnLost(void* context, char* cause); + + // instance handlers + int handleMessage(char* topicName, MQTTClient_message* message); + void sendResponse(const std::string& topic, long long seqNo, int code, const std::string& msg); + + static std::string generateClientId(); + + private: + ControlState& state_; + MqttConfig cfg_; + + std::atomic running_{false}; + std::thread th_; + + MQTTClient client_{nullptr}; + MQTTClient_connectOptions conn_opts_ = MQTTClient_connectOptions_initializer; + + // 防止回调与重连线程同时 publish + std::mutex publish_mtx_; +}; + +} // namespace sub_node_pkg diff --git a/src/communication/sub/include/sub/sub_node.hpp b/src/communication/sub/include/sub/sub_node.hpp index fbf8002..b3d2feb 100644 --- a/src/communication/sub/include/sub/sub_node.hpp +++ b/src/communication/sub/include/sub/sub_node.hpp @@ -1,20 +1,30 @@ +#pragma once +#include -#include -#ifndef __SUB_NODE_H__ -#define __SUB_NODE_H__ +#include "sub/control_state.hpp" +#include "sweeper_interfaces/msg/mc_ctrl.hpp" +#include "sweeper_interfaces/msg/sub.hpp" +#include "sweeper_interfaces/msg/vehicle_identity.hpp" -struct car_ctrl +namespace sub_node_pkg { - // 1. 使能模式 - int mode; // 0:手动驾驶, 3:远程驾驶 - int gear; // 挡位 0:N档,1:D档,2:R档 - int throttle; // 油门 0~65535 - int steering; // 转向 0~65535 - int brake; // 刹车 0~65535 - int sweepCtrl; // 清扫 1:清扫 0:不清扫 +class SubNode : public rclcpp::Node +{ + public: + SubNode(ControlState& state, const std::string& name); + + private: + void timerCallback(); + void identityCallback(const sweeper_interfaces::msg::VehicleIdentity::SharedPtr msg); + + private: + ControlState& state_; + rclcpp::Subscription::SharedPtr identity_sub_; + + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::Publisher::SharedPtr pub_gather_; + rclcpp::Publisher::SharedPtr pub_mc_; }; -extern car_ctrl car_ctrl_mes; - -#endif \ No newline at end of file +} // namespace sub_node_pkg diff --git a/src/communication/sub/src/control_mapper.cpp b/src/communication/sub/src/control_mapper.cpp new file mode 100644 index 0000000..fa17732 --- /dev/null +++ b/src/communication/sub/src/control_mapper.cpp @@ -0,0 +1,71 @@ +#include "sub/control_mapper.hpp" + +#include +#include + +namespace sub_node_pkg +{ + +int mapGearToMcCtrl(int gear_mqtt) +{ + if (gear_mqtt == 0) return 0; + if (gear_mqtt == 1) return 2; + if (gear_mqtt == 2) return 1; + return 0; +} + +int mapThrottleToRpm(int throttle) +{ + // 修正:避免整数除法导致一直为0 + // 线性映射到 0~1000 + throttle = std::clamp(throttle, 0, 65535); + const double ratio = static_cast(throttle) / 65535.0; + return static_cast(ratio * 1000.0); +} + +int mapSteeringToAngle(int raw) +{ + raw = std::clamp(raw, 0, 65535); + + if (raw < 32200) + { + // -40 + raw*(40/32200) + return -40 + static_cast(static_cast(raw) * (40.0 / 32200.0)); + } + + // 33200~65535 -> 0~40 + if (raw > 33200) + { + const int in_w = 65535 - 33200; // 32335 + const int x = raw - 33200; + return static_cast(static_cast(x) * (40.0 / static_cast(in_w))); + } + + // deadzone + return 0; +} + +void fillMcCtrlFromCarCtrl(const CarCtrl& in, sweeper_interfaces::msg::McCtrl& out) +{ + // 默认安全态 + out.brake = 1; + out.gear = 0; + out.rpm = 0; + out.angle = 0; + out.angle_speed = 120; + out.sweep = in.sweepCtrl; + + if (in.mode == 3) + { + // 允许远控:逻辑 msg.brake=0 + out.brake = 0; + + out.gear = mapGearToMcCtrl(in.gear); + out.rpm = mapThrottleToRpm(in.throttle); + + out.angle = mapSteeringToAngle(in.steering); + out.angle_speed = 120; + } +} + +} // namespace sub_node_pkg diff --git a/src/communication/sub/src/jsoncpp.cpp b/src/communication/sub/src/jsoncpp.cpp index 89b7bc0..ffa0ac3 100644 --- a/src/communication/sub/src/jsoncpp.cpp +++ b/src/communication/sub/src/jsoncpp.cpp @@ -68,18 +68,12 @@ license you like. // End of content of file: LICENSE // ////////////////////////////////////////////////////////////////////// - - - - - -#include "json.h" +#include "sub/json.h" #ifndef JSON_IS_AMALGAMATION #error "Compile with -I PATH_TO_JSON_DIRECTORY" #endif - // ////////////////////////////////////////////////////////////////////// // Beginning of content of file: src/lib_json/json_tool.h // ////////////////////////////////////////////////////////////////////// @@ -111,49 +105,60 @@ license you like. * It is an internal header that must not be exposed. */ -namespace Json { -static inline char getDecimalPoint() { +namespace Json +{ +static inline char getDecimalPoint() +{ #ifdef JSONCPP_NO_LOCALE_SUPPORT - return '\0'; + return '\0'; #else - struct lconv* lc = localeconv(); - return lc ? *(lc->decimal_point) : '\0'; + struct lconv* lc = localeconv(); + return lc ? *(lc->decimal_point) : '\0'; #endif } /// Converts a unicode code-point to UTF-8. -static inline String codePointToUTF8(unsigned int cp) { - String result; +static inline String codePointToUTF8(unsigned int cp) +{ + String result; - // based on description from http://en.wikipedia.org/wiki/UTF-8 + // based on description from http://en.wikipedia.org/wiki/UTF-8 - if (cp <= 0x7f) { - result.resize(1); - result[0] = static_cast(cp); - } else if (cp <= 0x7FF) { - result.resize(2); - result[1] = static_cast(0x80 | (0x3f & cp)); - result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); - } else if (cp <= 0xFFFF) { - result.resize(3); - result[2] = static_cast(0x80 | (0x3f & cp)); - result[1] = static_cast(0x80 | (0x3f & (cp >> 6))); - result[0] = static_cast(0xE0 | (0xf & (cp >> 12))); - } else if (cp <= 0x10FFFF) { - result.resize(4); - result[3] = static_cast(0x80 | (0x3f & cp)); - result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); - result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); - result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); - } + if (cp <= 0x7f) + { + result.resize(1); + result[0] = static_cast(cp); + } + else if (cp <= 0x7FF) + { + result.resize(2); + result[1] = static_cast(0x80 | (0x3f & cp)); + result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); + } + else if (cp <= 0xFFFF) + { + result.resize(3); + result[2] = static_cast(0x80 | (0x3f & cp)); + result[1] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[0] = static_cast(0xE0 | (0xf & (cp >> 12))); + } + else if (cp <= 0x10FFFF) + { + result.resize(4); + result[3] = static_cast(0x80 | (0x3f & cp)); + result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); + result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); + } - return result; + return result; } -enum { - /// Constant that specify the size of the buffer that must be passed to - /// uintToString. - uintToStringBufferSize = 3 * sizeof(LargestUInt) + 1 +enum +{ + /// Constant that specify the size of the buffer that must be passed to + /// uintToString. + uintToStringBufferSize = 3 * sizeof(LargestUInt) + 1 }; // Defines a char buffer for use with uintToString(). @@ -164,12 +169,14 @@ using UIntToStringBuffer = char[uintToStringBufferSize]; * @param current Input/Output string buffer. * Must have at least uintToStringBufferSize chars free. */ -static inline void uintToString(LargestUInt value, char*& current) { - *--current = 0; - do { - *--current = static_cast(value % 10U + static_cast('0')); - value /= 10; - } while (value != 0); +static inline void uintToString(LargestUInt value, char*& current) +{ + *--current = 0; + do + { + *--current = static_cast(value % 10U + static_cast('0')); + value /= 10; + } while (value != 0); } /** Change ',' to '.' everywhere in buffer. @@ -177,25 +184,34 @@ static inline void uintToString(LargestUInt value, char*& current) { * We had a sophisticated way, but it did not work in WinCE. * @see https://github.com/open-source-parsers/jsoncpp/pull/9 */ -template Iter fixNumericLocale(Iter begin, Iter end) { - for (; begin != end; ++begin) { - if (*begin == ',') { - *begin = '.'; +template +Iter fixNumericLocale(Iter begin, Iter end) +{ + for (; begin != end; ++begin) + { + if (*begin == ',') + { + *begin = '.'; + } } - } - return begin; + return begin; } -template void fixNumericLocaleInput(Iter begin, Iter end) { - char decimalPoint = getDecimalPoint(); - if (decimalPoint == '\0' || decimalPoint == '.') { - return; - } - for (; begin != end; ++begin) { - if (*begin == '.') { - *begin = decimalPoint; +template +void fixNumericLocaleInput(Iter begin, Iter end) +{ + char decimalPoint = getDecimalPoint(); + if (decimalPoint == '\0' || decimalPoint == '.') + { + return; + } + for (; begin != end; ++begin) + { + if (*begin == '.') + { + *begin = decimalPoint; + } } - } } /** @@ -203,35 +219,35 @@ template void fixNumericLocaleInput(Iter begin, Iter end) { * were to delete zeros in the end of string, but not the last zero before '.'. */ template -Iter fixZerosInTheEnd(Iter begin, Iter end, unsigned int precision) { - for (; begin != end; --end) { - if (*(end - 1) != '0') { - return end; +Iter fixZerosInTheEnd(Iter begin, Iter end, unsigned int precision) +{ + for (; begin != end; --end) + { + if (*(end - 1) != '0') + { + return end; + } + // Don't delete the last zero before the decimal point. + if (begin != (end - 1) && begin != (end - 2) && *(end - 2) == '.') + { + if (precision) + { + return end; + } + return end - 2; + } } - // Don't delete the last zero before the decimal point. - if (begin != (end - 1) && begin != (end - 2) && *(end - 2) == '.') { - if (precision) { - return end; - } - return end - 2; - } - } - return end; + return end; } -} // namespace Json +} // namespace Json -#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED +#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED // ////////////////////////////////////////////////////////////////////// // End of content of file: src/lib_json/json_tool.h // ////////////////////////////////////////////////////////////////////// - - - - - // ////////////////////////////////////////////////////////////////////// // Beginning of content of file: src/lib_json/json_reader.cpp // ////////////////////////////////////////////////////////////////////// @@ -243,14 +259,16 @@ Iter fixZerosInTheEnd(Iter begin, Iter end, unsigned int precision) { // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE #if !defined(JSON_IS_AMALGAMATION) -#include "json_tool.h" #include #include #include -#endif // if !defined(JSON_IS_AMALGAMATION) + +#include "json_tool.h" +#endif // if !defined(JSON_IS_AMALGAMATION) #include #include #include +#include #include #include #include @@ -259,21 +277,19 @@ Iter fixZerosInTheEnd(Iter begin, Iter end, unsigned int precision) { #include #include #include - -#include #if __cplusplus >= 201103L #if !defined(sscanf) #define sscanf std::sscanf #endif -#endif //__cplusplus +#endif //__cplusplus #if defined(_MSC_VER) #if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) #define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 -#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES -#endif //_MSC_VER +#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES +#endif //_MSC_VER #if defined(_MSC_VER) // Disable warning about strdup being deprecated. @@ -286,10 +302,10 @@ Iter fixZerosInTheEnd(Iter begin, Iter end, unsigned int precision) { #define JSONCPP_DEPRECATED_STACK_LIMIT 1000 #endif -static size_t const stackLimit_g = - JSONCPP_DEPRECATED_STACK_LIMIT; // see readValue() +static size_t const stackLimit_g = JSONCPP_DEPRECATED_STACK_LIMIT; // see readValue() -namespace Json { +namespace Json +{ #if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) using CharReaderPtr = std::unique_ptr; @@ -304,20 +320,22 @@ Features::Features() = default; Features Features::all() { return {}; } -Features Features::strictMode() { - Features features; - features.allowComments_ = false; - features.strictRoot_ = true; - features.allowDroppedNullPlaceholders_ = false; - features.allowNumericKeys_ = false; - return features; +Features Features::strictMode() +{ + Features features; + features.allowComments_ = false; + features.strictRoot_ = true; + features.allowDroppedNullPlaceholders_ = false; + features.allowNumericKeys_ = false; + return features; } // Implementation of class Reader // //////////////////////////////// -bool Reader::containsNewLine(Reader::Location begin, Reader::Location end) { - return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); +bool Reader::containsNewLine(Reader::Location begin, Reader::Location end) +{ + return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); } // Class Reader @@ -327,795 +345,832 @@ Reader::Reader() : features_(Features::all()) {} Reader::Reader(const Features& features) : features_(features) {} -bool Reader::parse(const std::string& document, Value& root, - bool collectComments) { - document_.assign(document.begin(), document.end()); - const char* begin = document_.c_str(); - const char* end = begin + document_.length(); - return parse(begin, end, root, collectComments); +bool Reader::parse(const std::string& document, Value& root, bool collectComments) +{ + document_.assign(document.begin(), document.end()); + const char* begin = document_.c_str(); + const char* end = begin + document_.length(); + return parse(begin, end, root, collectComments); } -bool Reader::parse(std::istream& is, Value& root, bool collectComments) { - // std::istream_iterator begin(is); - // std::istream_iterator end; - // Those would allow streamed input from a file, if parse() were a - // template function. +bool Reader::parse(std::istream& is, Value& root, bool collectComments) +{ + // std::istream_iterator begin(is); + // std::istream_iterator end; + // Those would allow streamed input from a file, if parse() were a + // template function. - // Since String is reference-counted, this at least does not - // create an extra copy. - String doc(std::istreambuf_iterator(is), {}); - return parse(doc.data(), doc.data() + doc.size(), root, collectComments); + // Since String is reference-counted, this at least does not + // create an extra copy. + String doc(std::istreambuf_iterator(is), {}); + return parse(doc.data(), doc.data() + doc.size(), root, collectComments); } -bool Reader::parse(const char* beginDoc, const char* endDoc, Value& root, - bool collectComments) { - if (!features_.allowComments_) { - collectComments = false; - } - - begin_ = beginDoc; - end_ = endDoc; - collectComments_ = collectComments; - current_ = begin_; - lastValueEnd_ = nullptr; - lastValue_ = nullptr; - commentsBefore_.clear(); - errors_.clear(); - while (!nodes_.empty()) - nodes_.pop(); - nodes_.push(&root); - - bool successful = readValue(); - Token token; - skipCommentTokens(token); - if (collectComments_ && !commentsBefore_.empty()) - root.setComment(commentsBefore_, commentAfter); - if (features_.strictRoot_) { - if (!root.isArray() && !root.isObject()) { - // Set error location to start of doc, ideally should be first token found - // in doc - token.type_ = tokenError; - token.start_ = beginDoc; - token.end_ = endDoc; - addError( - "A valid JSON document must be either an array or an object value.", - token); - return false; +bool Reader::parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments) +{ + if (!features_.allowComments_) + { + collectComments = false; } - } - return successful; -} -bool Reader::readValue() { - // readValue() may call itself only if it calls readObject() or ReadArray(). - // These methods execute nodes_.push() just before and nodes_.pop)() just - // after calling readValue(). parse() executes one nodes_.push(), so > instead - // of >=. - if (nodes_.size() > stackLimit_g) - throwRuntimeError("Exceeded stackLimit in readValue()."); - - Token token; - skipCommentTokens(token); - bool successful = true; - - if (collectComments_ && !commentsBefore_.empty()) { - currentValue().setComment(commentsBefore_, commentBefore); + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = nullptr; + lastValue_ = nullptr; commentsBefore_.clear(); - } + errors_.clear(); + while (!nodes_.empty()) nodes_.pop(); + nodes_.push(&root); - switch (token.type_) { - case tokenObjectBegin: - successful = readObject(token); - currentValue().setOffsetLimit(current_ - begin_); - break; - case tokenArrayBegin: - successful = readArray(token); - currentValue().setOffsetLimit(current_ - begin_); - break; - case tokenNumber: - successful = decodeNumber(token); - break; - case tokenString: - successful = decodeString(token); - break; - case tokenTrue: { - Value v(true); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenFalse: { - Value v(false); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenNull: { - Value v; - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenArraySeparator: - case tokenObjectEnd: - case tokenArrayEnd: - if (features_.allowDroppedNullPlaceholders_) { - // "Un-read" the current token and mark the current value as a null - // token. - current_--; - Value v; - currentValue().swapPayload(v); - currentValue().setOffsetStart(current_ - begin_ - 1); - currentValue().setOffsetLimit(current_ - begin_); - break; - } // Else, fall through... - default: - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return addError("Syntax error: value, object or array expected.", token); - } - - if (collectComments_) { - lastValueEnd_ = current_; - lastValue_ = ¤tValue(); - } - - return successful; + bool successful = readValue(); + Token token; + skipCommentTokens(token); + if (collectComments_ && !commentsBefore_.empty()) root.setComment(commentsBefore_, commentAfter); + if (features_.strictRoot_) + { + if (!root.isArray() && !root.isObject()) + { + // Set error location to start of doc, ideally should be first token found + // in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError("A valid JSON document must be either an array or an object value.", token); + return false; + } + } + return successful; } -void Reader::skipCommentTokens(Token& token) { - if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); - } +bool Reader::readValue() +{ + // readValue() may call itself only if it calls readObject() or ReadArray(). + // These methods execute nodes_.push() just before and nodes_.pop)() just + // after calling readValue(). parse() executes one nodes_.push(), so > instead + // of >=. + if (nodes_.size() > stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue()."); + + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) + { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_.clear(); + } + + switch (token.type_) + { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: + { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenFalse: + { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenNull: + { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) + { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // Else, fall through... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return addError("Syntax error: value, object or array expected.", token); + } + + if (collectComments_) + { + lastValueEnd_ = current_; + lastValue_ = ¤tValue(); + } + + return successful; } -bool Reader::readToken(Token& token) { - skipSpaces(); - token.start_ = current_; - Char c = getNextChar(); - bool ok = true; - switch (c) { - case '{': - token.type_ = tokenObjectBegin; - break; - case '}': - token.type_ = tokenObjectEnd; - break; - case '[': - token.type_ = tokenArrayBegin; - break; - case ']': - token.type_ = tokenArrayEnd; - break; - case '"': - token.type_ = tokenString; - ok = readString(); - break; - case '/': - token.type_ = tokenComment; - ok = readComment(); - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - token.type_ = tokenNumber; - readNumber(); - break; - case 't': - token.type_ = tokenTrue; - ok = match("rue", 3); - break; - case 'f': - token.type_ = tokenFalse; - ok = match("alse", 4); - break; - case 'n': - token.type_ = tokenNull; - ok = match("ull", 3); - break; - case ',': - token.type_ = tokenArraySeparator; - break; - case ':': - token.type_ = tokenMemberSeparator; - break; - case 0: - token.type_ = tokenEndOfStream; - break; - default: - ok = false; - break; - } - if (!ok) - token.type_ = tokenError; - token.end_ = current_; - return ok; -} - -void Reader::skipSpaces() { - while (current_ != end_) { - Char c = *current_; - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') - ++current_; +void Reader::skipCommentTokens(Token& token) +{ + if (features_.allowComments_) + { + do + { + readToken(token); + } while (token.type_ == tokenComment); + } else - break; - } -} - -bool Reader::match(const Char* pattern, int patternLength) { - if (end_ - current_ < patternLength) - return false; - int index = patternLength; - while (index--) - if (current_[index] != pattern[index]) - return false; - current_ += patternLength; - return true; -} - -bool Reader::readComment() { - Location commentBegin = current_ - 1; - Char c = getNextChar(); - bool successful = false; - if (c == '*') - successful = readCStyleComment(); - else if (c == '/') - successful = readCppStyleComment(); - if (!successful) - return false; - - if (collectComments_) { - CommentPlacement placement = commentBefore; - if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { - if (c != '*' || !containsNewLine(commentBegin, current_)) - placement = commentAfterOnSameLine; + { + readToken(token); } - - addComment(commentBegin, current_, placement); - } - return true; } -String Reader::normalizeEOL(Reader::Location begin, Reader::Location end) { - String normalized; - normalized.reserve(static_cast(end - begin)); - Reader::Location current = begin; - while (current != end) { - char c = *current++; - if (c == '\r') { - if (current != end && *current == '\n') - // convert dos EOL - ++current; - // convert Mac EOL - normalized += '\n'; - } else { - normalized += c; - } - } - return normalized; -} - -void Reader::addComment(Location begin, Location end, - CommentPlacement placement) { - assert(collectComments_); - const String& normalized = normalizeEOL(begin, end); - if (placement == commentAfterOnSameLine) { - assert(lastValue_ != nullptr); - lastValue_->setComment(normalized, placement); - } else { - commentsBefore_ += normalized; - } -} - -bool Reader::readCStyleComment() { - while ((current_ + 1) < end_) { +bool Reader::readToken(Token& token) +{ + skipSpaces(); + token.start_ = current_; Char c = getNextChar(); - if (c == '*' && *current_ == '/') - break; - } - return getNextChar() == '/'; -} - -bool Reader::readCppStyleComment() { - while (current_ != end_) { - Char c = getNextChar(); - if (c == '\n') - break; - if (c == '\r') { - // Consume DOS EOL. It will be normalized in addComment. - if (current_ != end_ && *current_ == '\n') - getNextChar(); - // Break on Moc OS 9 EOL. - break; + bool ok = true; + switch (c) + { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + token.type_ = tokenNumber; + readNumber(); + break; + case 't': + token.type_ = tokenTrue; + ok = match("rue", 3); + break; + case 'f': + token.type_ = tokenFalse; + ok = match("alse", 4); + break; + case 'n': + token.type_ = tokenNull; + ok = match("ull", 3); + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; } - } - return true; + if (!ok) token.type_ = tokenError; + token.end_ = current_; + return ok; } -void Reader::readNumber() { - Location p = current_; - char c = '0'; // stopgap for already consumed character - // integral part - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : '\0'; - // fractional part - if (c == '.') { - c = (current_ = p) < end_ ? *p++ : '\0'; - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : '\0'; - } - // exponential part - if (c == 'e' || c == 'E') { - c = (current_ = p) < end_ ? *p++ : '\0'; - if (c == '+' || c == '-') - c = (current_ = p) < end_ ? *p++ : '\0'; - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : '\0'; - } -} - -bool Reader::readString() { - Char c = '\0'; - while (current_ != end_) { - c = getNextChar(); - if (c == '\\') - getNextChar(); - else if (c == '"') - break; - } - return c == '"'; -} - -bool Reader::readObject(Token& token) { - Token tokenName; - String name; - Value init(objectValue); - currentValue().swapPayload(init); - currentValue().setOffsetStart(token.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; - if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object - return true; - name.clear(); - if (tokenName.type_ == tokenString) { - if (!decodeString(tokenName, name)) - return recoverFromError(tokenObjectEnd); - } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { - Value numberName; - if (!decodeNumber(tokenName, numberName)) - return recoverFromError(tokenObjectEnd); - name = numberName.asString(); - } else { - break; +void Reader::skipSpaces() +{ + while (current_ != end_) + { + Char c = *current_; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + ++current_; + else + break; } - - Token colon; - if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { - return addErrorAndRecover("Missing ':' after object member name", colon, - tokenObjectEnd); - } - Value& value = currentValue()[name]; - nodes_.push(&value); - bool ok = readValue(); - nodes_.pop(); - if (!ok) // error already set - return recoverFromError(tokenObjectEnd); - - Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { - return addErrorAndRecover("Missing ',' or '}' in object declaration", - comma, tokenObjectEnd); - } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); - if (comma.type_ == tokenObjectEnd) - return true; - } - return addErrorAndRecover("Missing '}' or object member name", tokenName, - tokenObjectEnd); } -bool Reader::readArray(Token& token) { - Value init(arrayValue); - currentValue().swapPayload(init); - currentValue().setOffsetStart(token.start_ - begin_); - skipSpaces(); - if (current_ != end_ && *current_ == ']') // empty array - { - Token endArray; - readToken(endArray); +bool Reader::match(const Char* pattern, int patternLength) +{ + if (end_ - current_ < patternLength) return false; + int index = patternLength; + while (index--) + if (current_[index] != pattern[index]) return false; + current_ += patternLength; return true; - } - int index = 0; - for (;;) { - Value& value = currentValue()[index++]; - nodes_.push(&value); - bool ok = readValue(); - nodes_.pop(); - if (!ok) // error already set - return recoverFromError(tokenArrayEnd); +} - Token currentToken; - // Accept Comment after last item in the array. - ok = readToken(currentToken); - while (currentToken.type_ == tokenComment && ok) { - ok = readToken(currentToken); +bool Reader::readComment() +{ + Location commentBegin = current_ - 1; + Char c = getNextChar(); + bool successful = false; + if (c == '*') + successful = readCStyleComment(); + else if (c == '/') + successful = readCppStyleComment(); + if (!successful) return false; + + if (collectComments_) + { + CommentPlacement placement = commentBefore; + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) + { + if (c != '*' || !containsNewLine(commentBegin, current_)) placement = commentAfterOnSameLine; + } + + addComment(commentBegin, current_, placement); } - bool badTokenType = (currentToken.type_ != tokenArraySeparator && - currentToken.type_ != tokenArrayEnd); - if (!ok || badTokenType) { - return addErrorAndRecover("Missing ',' or ']' in array declaration", - currentToken, tokenArrayEnd); + return true; +} + +String Reader::normalizeEOL(Reader::Location begin, Reader::Location end) +{ + String normalized; + normalized.reserve(static_cast(end - begin)); + Reader::Location current = begin; + while (current != end) + { + char c = *current++; + if (c == '\r') + { + if (current != end && *current == '\n') + // convert dos EOL + ++current; + // convert Mac EOL + normalized += '\n'; + } + else + { + normalized += c; + } } - if (currentToken.type_ == tokenArrayEnd) - break; - } - return true; + return normalized; } -bool Reader::decodeNumber(Token& token) { - Value decoded; - if (!decodeNumber(token, decoded)) - return false; - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool Reader::decodeNumber(Token& token, Value& decoded) { - // Attempts to parse the number as an integer. If the number is - // larger than the maximum supported value of an integer then - // we decode the number as a double. - Location current = token.start_; - bool isNegative = *current == '-'; - if (isNegative) - ++current; - // TODO: Help the compiler do the div and mod at compile time or get rid of - // them. - Value::LargestUInt maxIntegerValue = - isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 - : Value::maxLargestUInt; - Value::LargestUInt threshold = maxIntegerValue / 10; - Value::LargestUInt value = 0; - while (current < token.end_) { - Char c = *current++; - if (c < '0' || c > '9') - return decodeDouble(token, decoded); - auto digit(static_cast(c - '0')); - if (value >= threshold) { - // We've hit or exceeded the max value divided by 10 (rounded down). If - // a) we've only just touched the limit, b) this is the last digit, and - // c) it's small enough to fit in that rounding delta, we're okay. - // Otherwise treat this number as a double to avoid overflow. - if (value > threshold || current != token.end_ || - digit > maxIntegerValue % 10) { - return decodeDouble(token, decoded); - } +void Reader::addComment(Location begin, Location end, CommentPlacement placement) +{ + assert(collectComments_); + const String& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) + { + assert(lastValue_ != nullptr); + lastValue_->setComment(normalized, placement); } - value = value * 10 + digit; - } - if (isNegative && value == maxIntegerValue) - decoded = Value::minLargestInt; - else if (isNegative) - decoded = -Value::LargestInt(value); - else if (value <= Value::LargestUInt(Value::maxInt)) - decoded = Value::LargestInt(value); - else - decoded = value; - return true; -} - -bool Reader::decodeDouble(Token& token) { - Value decoded; - if (!decodeDouble(token, decoded)) - return false; - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool Reader::decodeDouble(Token& token, Value& decoded) { - double value = 0; - String buffer(token.start_, token.end_); - IStringStream is(buffer); - if (!(is >> value)) { - if (value == std::numeric_limits::max()) - value = std::numeric_limits::infinity(); - else if (value == std::numeric_limits::lowest()) - value = -std::numeric_limits::infinity(); - else if (!std::isinf(value)) - return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); - } - decoded = value; - return true; -} - -bool Reader::decodeString(Token& token) { - String decoded_string; - if (!decodeString(token, decoded_string)) - return false; - Value decoded(decoded_string); - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool Reader::decodeString(Token& token, String& decoded) { - decoded.reserve(static_cast(token.end_ - token.start_ - 2)); - Location current = token.start_ + 1; // skip '"' - Location end = token.end_ - 1; // do not include '"' - while (current != end) { - Char c = *current++; - if (c == '"') - break; - if (c == '\\') { - if (current == end) - return addError("Empty escape sequence in string", token, current); - Char escape = *current++; - switch (escape) { - case '"': - decoded += '"'; - break; - case '/': - decoded += '/'; - break; - case '\\': - decoded += '\\'; - break; - case 'b': - decoded += '\b'; - break; - case 'f': - decoded += '\f'; - break; - case 'n': - decoded += '\n'; - break; - case 'r': - decoded += '\r'; - break; - case 't': - decoded += '\t'; - break; - case 'u': { - unsigned int unicode; - if (!decodeUnicodeCodePoint(token, current, end, unicode)) - return false; - decoded += codePointToUTF8(unicode); - } break; - default: - return addError("Bad escape sequence in string", token, current); - } - } else { - decoded += c; - } - } - return true; -} - -bool Reader::decodeUnicodeCodePoint(Token& token, Location& current, - Location end, unsigned int& unicode) { - - if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) - return false; - if (unicode >= 0xD800 && unicode <= 0xDBFF) { - // surrogate pairs - if (end - current < 6) - return addError( - "additional six characters expected to parse unicode surrogate pair.", - token, current); - if (*(current++) == '\\' && *(current++) == 'u') { - unsigned int surrogatePair; - if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { - unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); - } else - return false; - } else - return addError("expecting another \\u token to begin the second half of " - "a unicode surrogate pair", - token, current); - } - return true; -} - -bool Reader::decodeUnicodeEscapeSequence(Token& token, Location& current, - Location end, - unsigned int& ret_unicode) { - if (end - current < 4) - return addError( - "Bad unicode escape sequence in string: four digits expected.", token, - current); - int unicode = 0; - for (int index = 0; index < 4; ++index) { - Char c = *current++; - unicode *= 16; - if (c >= '0' && c <= '9') - unicode += c - '0'; - else if (c >= 'a' && c <= 'f') - unicode += c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - unicode += c - 'A' + 10; else - return addError( - "Bad unicode escape sequence in string: hexadecimal digit expected.", - token, current); - } - ret_unicode = static_cast(unicode); - return true; + { + commentsBefore_ += normalized; + } } -bool Reader::addError(const String& message, Token& token, Location extra) { - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = extra; - errors_.push_back(info); - return false; +bool Reader::readCStyleComment() +{ + while ((current_ + 1) < end_) + { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') break; + } + return getNextChar() == '/'; } -bool Reader::recoverFromError(TokenType skipUntilToken) { - size_t const errorCount = errors_.size(); - Token skip; - for (;;) { - if (!readToken(skip)) - errors_.resize(errorCount); // discard errors caused by recovery - if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) - break; - } - errors_.resize(errorCount); - return false; +bool Reader::readCppStyleComment() +{ + while (current_ != end_) + { + Char c = getNextChar(); + if (c == '\n') break; + if (c == '\r') + { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; } -bool Reader::addErrorAndRecover(const String& message, Token& token, - TokenType skipUntilToken) { - addError(message, token); - return recoverFromError(skipUntilToken); +void Reader::readNumber() +{ + Location p = current_; + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') c = (current_ = p) < end_ ? *p++ : '\0'; + // fractional part + if (c == '.') + { + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') c = (current_ = p) < end_ ? *p++ : '\0'; + } + // exponential part + if (c == 'e' || c == 'E') + { + c = (current_ = p) < end_ ? *p++ : '\0'; + if (c == '+' || c == '-') c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') c = (current_ = p) < end_ ? *p++ : '\0'; + } +} + +bool Reader::readString() +{ + Char c = '\0'; + while (current_ != end_) + { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '"') + break; + } + return c == '"'; +} + +bool Reader::readObject(Token& token) +{ + Token tokenName; + String name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + while (readToken(tokenName)) + { + bool initialTokenOk = true; + while (tokenName.type_ == tokenComment && initialTokenOk) initialTokenOk = readToken(tokenName); + if (!initialTokenOk) break; + if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + return true; + name.clear(); + if (tokenName.type_ == tokenString) + { + if (!decodeString(tokenName, name)) return recoverFromError(tokenObjectEnd); + } + else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) + { + Value numberName; + if (!decodeNumber(tokenName, numberName)) return recoverFromError(tokenObjectEnd); + name = numberName.asString(); + } + else + { + break; + } + + Token colon; + if (!readToken(colon) || colon.type_ != tokenMemberSeparator) + { + return addErrorAndRecover("Missing ':' after object member name", colon, tokenObjectEnd); + } + Value& value = currentValue()[name]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenObjectEnd); + + Token comma; + if (!readToken(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && comma.type_ != tokenComment)) + { + return addErrorAndRecover("Missing ',' or '}' in object declaration", comma, tokenObjectEnd); + } + bool finalizeTokenOk = true; + while (comma.type_ == tokenComment && finalizeTokenOk) finalizeTokenOk = readToken(comma); + if (comma.type_ == tokenObjectEnd) return true; + } + return addErrorAndRecover("Missing '}' or object member name", tokenName, tokenObjectEnd); +} + +bool Reader::readArray(Token& token) +{ + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + skipSpaces(); + if (current_ != end_ && *current_ == ']') // empty array + { + Token endArray; + readToken(endArray); + return true; + } + int index = 0; + for (;;) + { + Value& value = currentValue()[index++]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenArrayEnd); + + Token currentToken; + // Accept Comment after last item in the array. + ok = readToken(currentToken); + while (currentToken.type_ == tokenComment && ok) + { + ok = readToken(currentToken); + } + bool badTokenType = (currentToken.type_ != tokenArraySeparator && currentToken.type_ != tokenArrayEnd); + if (!ok || badTokenType) + { + return addErrorAndRecover("Missing ',' or ']' in array declaration", currentToken, tokenArrayEnd); + } + if (currentToken.type_ == tokenArrayEnd) break; + } + return true; +} + +bool Reader::decodeNumber(Token& token) +{ + Value decoded; + if (!decodeNumber(token, decoded)) return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeNumber(Token& token, Value& decoded) +{ + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + bool isNegative = *current == '-'; + if (isNegative) ++current; + // TODO: Help the compiler do the div and mod at compile time or get rid of + // them. + Value::LargestUInt maxIntegerValue = + isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::LargestUInt value = 0; + while (current < token.end_) + { + Char c = *current++; + if (c < '0' || c > '9') return decodeDouble(token, decoded); + auto digit(static_cast(c - '0')); + if (value >= threshold) + { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, b) this is the last digit, and + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || digit > maxIntegerValue % 10) + { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + if (isNegative && value == maxIntegerValue) + decoded = Value::minLargestInt; + else if (isNegative) + decoded = -Value::LargestInt(value); + else if (value <= Value::LargestUInt(Value::maxInt)) + decoded = Value::LargestInt(value); + else + decoded = value; + return true; +} + +bool Reader::decodeDouble(Token& token) +{ + Value decoded; + if (!decodeDouble(token, decoded)) return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeDouble(Token& token, Value& decoded) +{ + double value = 0; + String buffer(token.start_, token.end_); + IStringStream is(buffer); + if (!(is >> value)) + { + if (value == std::numeric_limits::max()) + value = std::numeric_limits::infinity(); + else if (value == std::numeric_limits::lowest()) + value = -std::numeric_limits::infinity(); + else if (!std::isinf(value)) + return addError("'" + String(token.start_, token.end_) + "' is not a number.", token); + } + decoded = value; + return true; +} + +bool Reader::decodeString(Token& token) +{ + String decoded_string; + if (!decodeString(token, decoded_string)) return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeString(Token& token, String& decoded) +{ + decoded.reserve(static_cast(token.end_ - token.start_ - 2)); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while (current != end) + { + Char c = *current++; + if (c == '"') break; + if (c == '\\') + { + if (current == end) return addError("Empty escape sequence in string", token, current); + Char escape = *current++; + switch (escape) + { + case '"': + decoded += '"'; + break; + case '/': + decoded += '/'; + break; + case '\\': + decoded += '\\'; + break; + case 'b': + decoded += '\b'; + break; + case 'f': + decoded += '\f'; + break; + case 'n': + decoded += '\n'; + break; + case 'r': + decoded += '\r'; + break; + case 't': + decoded += '\t'; + break; + case 'u': + { + unsigned int unicode; + if (!decodeUnicodeCodePoint(token, current, end, unicode)) return false; + decoded += codePointToUTF8(unicode); + } + break; + default: + return addError("Bad escape sequence in string", token, current); + } + } + else + { + decoded += c; + } + } + return true; +} + +bool Reader::decodeUnicodeCodePoint(Token& token, Location& current, Location end, unsigned int& unicode) +{ + if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) + { + // surrogate pairs + if (end - current < 6) + return addError("additional six characters expected to parse unicode surrogate pair.", token, current); + if (*(current++) == '\\' && *(current++) == 'u') + { + unsigned int surrogatePair; + if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) + { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } + else + return false; + } + else + return addError( + "expecting another \\u token to begin the second half of " + "a unicode surrogate pair", + token, current); + } + return true; +} + +bool Reader::decodeUnicodeEscapeSequence(Token& token, Location& current, Location end, unsigned int& ret_unicode) +{ + if (end - current < 4) + return addError("Bad unicode escape sequence in string: four digits expected.", token, current); + int unicode = 0; + for (int index = 0; index < 4; ++index) + { + Char c = *current++; + unicode *= 16; + if (c >= '0' && c <= '9') + unicode += c - '0'; + else if (c >= 'a' && c <= 'f') + unicode += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + unicode += c - 'A' + 10; + else + return addError("Bad unicode escape sequence in string: hexadecimal digit expected.", token, current); + } + ret_unicode = static_cast(unicode); + return true; +} + +bool Reader::addError(const String& message, Token& token, Location extra) +{ + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back(info); + return false; +} + +bool Reader::recoverFromError(TokenType skipUntilToken) +{ + size_t const errorCount = errors_.size(); + Token skip; + for (;;) + { + if (!readToken(skip)) errors_.resize(errorCount); // discard errors caused by recovery + if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) break; + } + errors_.resize(errorCount); + return false; +} + +bool Reader::addErrorAndRecover(const String& message, Token& token, TokenType skipUntilToken) +{ + addError(message, token); + return recoverFromError(skipUntilToken); } Value& Reader::currentValue() { return *(nodes_.top()); } -Reader::Char Reader::getNextChar() { - if (current_ == end_) - return 0; - return *current_++; +Reader::Char Reader::getNextChar() +{ + if (current_ == end_) return 0; + return *current_++; } -void Reader::getLocationLineAndColumn(Location location, int& line, - int& column) const { - Location current = begin_; - Location lastLineStart = current; - line = 0; - while (current < location && current != end_) { - Char c = *current++; - if (c == '\r') { - if (*current == '\n') - ++current; - lastLineStart = current; - ++line; - } else if (c == '\n') { - lastLineStart = current; - ++line; +void Reader::getLocationLineAndColumn(Location location, int& line, int& column) const +{ + Location current = begin_; + Location lastLineStart = current; + line = 0; + while (current < location && current != end_) + { + Char c = *current++; + if (c == '\r') + { + if (*current == '\n') ++current; + lastLineStart = current; + ++line; + } + else if (c == '\n') + { + lastLineStart = current; + ++line; + } } - } - // column & line start at 1 - column = int(location - lastLineStart) + 1; - ++line; + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; } -String Reader::getLocationLineAndColumn(Location location) const { - int line, column; - getLocationLineAndColumn(location, line, column); - char buffer[18 + 16 + 16 + 1]; - jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); - return buffer; +String Reader::getLocationLineAndColumn(Location location) const +{ + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; + jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + return buffer; } // Deprecated. Preserved for backward compatibility -String Reader::getFormatedErrorMessages() const { - return getFormattedErrorMessages(); +String Reader::getFormatedErrorMessages() const { return getFormattedErrorMessages(); } + +String Reader::getFormattedErrorMessages() const +{ + String formattedMessage; + for (const auto& error : errors_) + { + formattedMessage += "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if (error.extra_) formattedMessage += "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; + } + return formattedMessage; } -String Reader::getFormattedErrorMessages() const { - String formattedMessage; - for (const auto& error : errors_) { - formattedMessage += - "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; - formattedMessage += " " + error.message_ + "\n"; - if (error.extra_) - formattedMessage += - "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; - } - return formattedMessage; +std::vector Reader::getStructuredErrors() const +{ + std::vector allErrors; + for (const auto& error : errors_) + { + Reader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; } -std::vector Reader::getStructuredErrors() const { - std::vector allErrors; - for (const auto& error : errors_) { - Reader::StructuredError structured; - structured.offset_start = error.token_.start_ - begin_; - structured.offset_limit = error.token_.end_ - begin_; - structured.message = error.message_; - allErrors.push_back(structured); - } - return allErrors; +bool Reader::pushError(const Value& value, const String& message) +{ + ptrdiff_t const length = end_ - begin_; + if (value.getOffsetStart() > length || value.getOffsetLimit() > length) return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = nullptr; + errors_.push_back(info); + return true; } -bool Reader::pushError(const Value& value, const String& message) { - ptrdiff_t const length = end_ - begin_; - if (value.getOffsetStart() > length || value.getOffsetLimit() > length) - return false; - Token token; - token.type_ = tokenError; - token.start_ = begin_ + value.getOffsetStart(); - token.end_ = begin_ + value.getOffsetLimit(); - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = nullptr; - errors_.push_back(info); - return true; -} - -bool Reader::pushError(const Value& value, const String& message, - const Value& extra) { - ptrdiff_t const length = end_ - begin_; - if (value.getOffsetStart() > length || value.getOffsetLimit() > length || - extra.getOffsetLimit() > length) - return false; - Token token; - token.type_ = tokenError; - token.start_ = begin_ + value.getOffsetStart(); - token.end_ = begin_ + value.getOffsetLimit(); - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = begin_ + extra.getOffsetStart(); - errors_.push_back(info); - return true; +bool Reader::pushError(const Value& value, const String& message, const Value& extra) +{ + ptrdiff_t const length = end_ - begin_; + if (value.getOffsetStart() > length || value.getOffsetLimit() > length || extra.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = begin_ + extra.getOffsetStart(); + errors_.push_back(info); + return true; } bool Reader::good() const { return errors_.empty(); } // Originally copied from the Features class (now deprecated), used internally // for features implementation. -class OurFeatures { -public: - static OurFeatures all(); - bool allowComments_; - bool allowTrailingCommas_; - bool strictRoot_; - bool allowDroppedNullPlaceholders_; - bool allowNumericKeys_; - bool allowSingleQuotes_; - bool failIfExtra_; - bool rejectDupKeys_; - bool allowSpecialFloats_; - bool skipBom_; - size_t stackLimit_; -}; // OurFeatures +class OurFeatures +{ + public: + static OurFeatures all(); + bool allowComments_; + bool allowTrailingCommas_; + bool strictRoot_; + bool allowDroppedNullPlaceholders_; + bool allowNumericKeys_; + bool allowSingleQuotes_; + bool failIfExtra_; + bool rejectDupKeys_; + bool allowSpecialFloats_; + bool skipBom_; + size_t stackLimit_; +}; // OurFeatures OurFeatures OurFeatures::all() { return {}; } @@ -1124,1132 +1179,1195 @@ OurFeatures OurFeatures::all() { return {}; } // Originally copied from the Reader class (now deprecated), used internally // for implementing JSON reading. -class OurReader { -public: - using Char = char; - using Location = const Char*; - struct StructuredError { - ptrdiff_t offset_start; - ptrdiff_t offset_limit; - String message; - }; +class OurReader +{ + public: + using Char = char; + using Location = const Char*; + struct StructuredError + { + ptrdiff_t offset_start; + ptrdiff_t offset_limit; + String message; + }; - explicit OurReader(OurFeatures const& features); - bool parse(const char* beginDoc, const char* endDoc, Value& root, - bool collectComments = true); - String getFormattedErrorMessages() const; - std::vector getStructuredErrors() const; + explicit OurReader(OurFeatures const& features); + bool parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments = true); + String getFormattedErrorMessages() const; + std::vector getStructuredErrors() const; -private: - OurReader(OurReader const&); // no impl - void operator=(OurReader const&); // no impl + private: + OurReader(OurReader const&); // no impl + void operator=(OurReader const&); // no impl - enum TokenType { - tokenEndOfStream = 0, - tokenObjectBegin, - tokenObjectEnd, - tokenArrayBegin, - tokenArrayEnd, - tokenString, - tokenNumber, - tokenTrue, - tokenFalse, - tokenNull, - tokenNaN, - tokenPosInf, - tokenNegInf, - tokenArraySeparator, - tokenMemberSeparator, - tokenComment, - tokenError - }; + enum TokenType + { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenNaN, + tokenPosInf, + tokenNegInf, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; - class Token { - public: - TokenType type_; - Location start_; - Location end_; - }; + class Token + { + public: + TokenType type_; + Location start_; + Location end_; + }; - class ErrorInfo { - public: - Token token_; - String message_; - Location extra_; - }; + class ErrorInfo + { + public: + Token token_; + String message_; + Location extra_; + }; - using Errors = std::deque; + using Errors = std::deque; - bool readToken(Token& token); - void skipSpaces(); - void skipBom(bool skipBom); - bool match(const Char* pattern, int patternLength); - bool readComment(); - bool readCStyleComment(bool* containsNewLineResult); - bool readCppStyleComment(); - bool readString(); - bool readStringSingleQuote(); - bool readNumber(bool checkInf); - bool readValue(); - bool readObject(Token& token); - bool readArray(Token& token); - bool decodeNumber(Token& token); - bool decodeNumber(Token& token, Value& decoded); - bool decodeString(Token& token); - bool decodeString(Token& token, String& decoded); - bool decodeDouble(Token& token); - bool decodeDouble(Token& token, Value& decoded); - bool decodeUnicodeCodePoint(Token& token, Location& current, Location end, - unsigned int& unicode); - bool decodeUnicodeEscapeSequence(Token& token, Location& current, - Location end, unsigned int& unicode); - bool addError(const String& message, Token& token, Location extra = nullptr); - bool recoverFromError(TokenType skipUntilToken); - bool addErrorAndRecover(const String& message, Token& token, - TokenType skipUntilToken); - void skipUntilSpace(); - Value& currentValue(); - Char getNextChar(); - void getLocationLineAndColumn(Location location, int& line, - int& column) const; - String getLocationLineAndColumn(Location location) const; - void addComment(Location begin, Location end, CommentPlacement placement); - void skipCommentTokens(Token& token); + bool readToken(Token& token); + void skipSpaces(); + void skipBom(bool skipBom); + bool match(const Char* pattern, int patternLength); + bool readComment(); + bool readCStyleComment(bool* containsNewLineResult); + bool readCppStyleComment(); + bool readString(); + bool readStringSingleQuote(); + bool readNumber(bool checkInf); + bool readValue(); + bool readObject(Token& token); + bool readArray(Token& token); + bool decodeNumber(Token& token); + bool decodeNumber(Token& token, Value& decoded); + bool decodeString(Token& token); + bool decodeString(Token& token, String& decoded); + bool decodeDouble(Token& token); + bool decodeDouble(Token& token, Value& decoded); + bool decodeUnicodeCodePoint(Token& token, Location& current, Location end, unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, Location& current, Location end, unsigned int& unicode); + bool addError(const String& message, Token& token, Location extra = nullptr); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const String& message, Token& token, TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void getLocationLineAndColumn(Location location, int& line, int& column) const; + String getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token& token); - static String normalizeEOL(Location begin, Location end); - static bool containsNewLine(Location begin, Location end); + static String normalizeEOL(Location begin, Location end); + static bool containsNewLine(Location begin, Location end); - using Nodes = std::stack; + using Nodes = std::stack; - Nodes nodes_{}; - Errors errors_{}; - String document_{}; - Location begin_ = nullptr; - Location end_ = nullptr; - Location current_ = nullptr; - Location lastValueEnd_ = nullptr; - Value* lastValue_ = nullptr; - bool lastValueHasAComment_ = false; - String commentsBefore_{}; + Nodes nodes_{}; + Errors errors_{}; + String document_{}; + Location begin_ = nullptr; + Location end_ = nullptr; + Location current_ = nullptr; + Location lastValueEnd_ = nullptr; + Value* lastValue_ = nullptr; + bool lastValueHasAComment_ = false; + String commentsBefore_{}; - OurFeatures const features_; - bool collectComments_ = false; -}; // OurReader + OurFeatures const features_; + bool collectComments_ = false; +}; // OurReader // complete copy of Read impl, for OurReader -bool OurReader::containsNewLine(OurReader::Location begin, - OurReader::Location end) { - return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); +bool OurReader::containsNewLine(OurReader::Location begin, OurReader::Location end) +{ + return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); } OurReader::OurReader(OurFeatures const& features) : features_(features) {} -bool OurReader::parse(const char* beginDoc, const char* endDoc, Value& root, - bool collectComments) { - if (!features_.allowComments_) { - collectComments = false; - } - - begin_ = beginDoc; - end_ = endDoc; - collectComments_ = collectComments; - current_ = begin_; - lastValueEnd_ = nullptr; - lastValue_ = nullptr; - commentsBefore_.clear(); - errors_.clear(); - while (!nodes_.empty()) - nodes_.pop(); - nodes_.push(&root); - - // skip byte order mark if it exists at the beginning of the UTF-8 text. - skipBom(features_.skipBom_); - bool successful = readValue(); - nodes_.pop(); - Token token; - skipCommentTokens(token); - if (features_.failIfExtra_ && (token.type_ != tokenEndOfStream)) { - addError("Extra non-whitespace after JSON value.", token); - return false; - } - if (collectComments_ && !commentsBefore_.empty()) - root.setComment(commentsBefore_, commentAfter); - if (features_.strictRoot_) { - if (!root.isArray() && !root.isObject()) { - // Set error location to start of doc, ideally should be first token found - // in doc - token.type_ = tokenError; - token.start_ = beginDoc; - token.end_ = endDoc; - addError( - "A valid JSON document must be either an array or an object value.", - token); - return false; - } - } - return successful; -} - -bool OurReader::readValue() { - // To preserve the old behaviour we cast size_t to int. - if (nodes_.size() > features_.stackLimit_) - throwRuntimeError("Exceeded stackLimit in readValue()."); - Token token; - skipCommentTokens(token); - bool successful = true; - - if (collectComments_ && !commentsBefore_.empty()) { - currentValue().setComment(commentsBefore_, commentBefore); - commentsBefore_.clear(); - } - - switch (token.type_) { - case tokenObjectBegin: - successful = readObject(token); - currentValue().setOffsetLimit(current_ - begin_); - break; - case tokenArrayBegin: - successful = readArray(token); - currentValue().setOffsetLimit(current_ - begin_); - break; - case tokenNumber: - successful = decodeNumber(token); - break; - case tokenString: - successful = decodeString(token); - break; - case tokenTrue: { - Value v(true); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenFalse: { - Value v(false); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenNull: { - Value v; - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenNaN: { - Value v(std::numeric_limits::quiet_NaN()); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenPosInf: { - Value v(std::numeric_limits::infinity()); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenNegInf: { - Value v(-std::numeric_limits::infinity()); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } break; - case tokenArraySeparator: - case tokenObjectEnd: - case tokenArrayEnd: - if (features_.allowDroppedNullPlaceholders_) { - // "Un-read" the current token and mark the current value as a null - // token. - current_--; - Value v; - currentValue().swapPayload(v); - currentValue().setOffsetStart(current_ - begin_ - 1); - currentValue().setOffsetLimit(current_ - begin_); - break; - } // else, fall through ... - default: - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return addError("Syntax error: value, object or array expected.", token); - } - - if (collectComments_) { - lastValueEnd_ = current_; - lastValueHasAComment_ = false; - lastValue_ = ¤tValue(); - } - - return successful; -} - -void OurReader::skipCommentTokens(Token& token) { - if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); - } -} - -bool OurReader::readToken(Token& token) { - skipSpaces(); - token.start_ = current_; - Char c = getNextChar(); - bool ok = true; - switch (c) { - case '{': - token.type_ = tokenObjectBegin; - break; - case '}': - token.type_ = tokenObjectEnd; - break; - case '[': - token.type_ = tokenArrayBegin; - break; - case ']': - token.type_ = tokenArrayEnd; - break; - case '"': - token.type_ = tokenString; - ok = readString(); - break; - case '\'': - if (features_.allowSingleQuotes_) { - token.type_ = tokenString; - ok = readStringSingleQuote(); - } else { - // If we don't allow single quotes, this is a failure case. - ok = false; - } - break; - case '/': - token.type_ = tokenComment; - ok = readComment(); - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - token.type_ = tokenNumber; - readNumber(false); - break; - case '-': - if (readNumber(true)) { - token.type_ = tokenNumber; - } else { - token.type_ = tokenNegInf; - ok = features_.allowSpecialFloats_ && match("nfinity", 7); - } - break; - case '+': - if (readNumber(true)) { - token.type_ = tokenNumber; - } else { - token.type_ = tokenPosInf; - ok = features_.allowSpecialFloats_ && match("nfinity", 7); - } - break; - case 't': - token.type_ = tokenTrue; - ok = match("rue", 3); - break; - case 'f': - token.type_ = tokenFalse; - ok = match("alse", 4); - break; - case 'n': - token.type_ = tokenNull; - ok = match("ull", 3); - break; - case 'N': - if (features_.allowSpecialFloats_) { - token.type_ = tokenNaN; - ok = match("aN", 2); - } else { - ok = false; - } - break; - case 'I': - if (features_.allowSpecialFloats_) { - token.type_ = tokenPosInf; - ok = match("nfinity", 7); - } else { - ok = false; - } - break; - case ',': - token.type_ = tokenArraySeparator; - break; - case ':': - token.type_ = tokenMemberSeparator; - break; - case 0: - token.type_ = tokenEndOfStream; - break; - default: - ok = false; - break; - } - if (!ok) - token.type_ = tokenError; - token.end_ = current_; - return ok; -} - -void OurReader::skipSpaces() { - while (current_ != end_) { - Char c = *current_; - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') - ++current_; - else - break; - } -} - -void OurReader::skipBom(bool skipBom) { - // The default behavior is to skip BOM. - if (skipBom) { - if ((end_ - begin_) >= 3 && strncmp(begin_, "\xEF\xBB\xBF", 3) == 0) { - begin_ += 3; - current_ = begin_; - } - } -} - -bool OurReader::match(const Char* pattern, int patternLength) { - if (end_ - current_ < patternLength) - return false; - int index = patternLength; - while (index--) - if (current_[index] != pattern[index]) - return false; - current_ += patternLength; - return true; -} - -bool OurReader::readComment() { - const Location commentBegin = current_ - 1; - const Char c = getNextChar(); - bool successful = false; - bool cStyleWithEmbeddedNewline = false; - - const bool isCStyleComment = (c == '*'); - const bool isCppStyleComment = (c == '/'); - if (isCStyleComment) { - successful = readCStyleComment(&cStyleWithEmbeddedNewline); - } else if (isCppStyleComment) { - successful = readCppStyleComment(); - } - - if (!successful) - return false; - - if (collectComments_) { - CommentPlacement placement = commentBefore; - - if (!lastValueHasAComment_) { - if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { - if (isCppStyleComment || !cStyleWithEmbeddedNewline) { - placement = commentAfterOnSameLine; - lastValueHasAComment_ = true; - } - } - } - - addComment(commentBegin, current_, placement); - } - return true; -} - -String OurReader::normalizeEOL(OurReader::Location begin, - OurReader::Location end) { - String normalized; - normalized.reserve(static_cast(end - begin)); - OurReader::Location current = begin; - while (current != end) { - char c = *current++; - if (c == '\r') { - if (current != end && *current == '\n') - // convert dos EOL - ++current; - // convert Mac EOL - normalized += '\n'; - } else { - normalized += c; - } - } - return normalized; -} - -void OurReader::addComment(Location begin, Location end, - CommentPlacement placement) { - assert(collectComments_); - const String& normalized = normalizeEOL(begin, end); - if (placement == commentAfterOnSameLine) { - assert(lastValue_ != nullptr); - lastValue_->setComment(normalized, placement); - } else { - commentsBefore_ += normalized; - } -} - -bool OurReader::readCStyleComment(bool* containsNewLineResult) { - *containsNewLineResult = false; - - while ((current_ + 1) < end_) { - Char c = getNextChar(); - if (c == '*' && *current_ == '/') - break; - if (c == '\n') - *containsNewLineResult = true; - } - - return getNextChar() == '/'; -} - -bool OurReader::readCppStyleComment() { - while (current_ != end_) { - Char c = getNextChar(); - if (c == '\n') - break; - if (c == '\r') { - // Consume DOS EOL. It will be normalized in addComment. - if (current_ != end_ && *current_ == '\n') - getNextChar(); - // Break on Moc OS 9 EOL. - break; - } - } - return true; -} - -bool OurReader::readNumber(bool checkInf) { - Location p = current_; - if (checkInf && p != end_ && *p == 'I') { - current_ = ++p; - return false; - } - char c = '0'; // stopgap for already consumed character - // integral part - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : '\0'; - // fractional part - if (c == '.') { - c = (current_ = p) < end_ ? *p++ : '\0'; - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : '\0'; - } - // exponential part - if (c == 'e' || c == 'E') { - c = (current_ = p) < end_ ? *p++ : '\0'; - if (c == '+' || c == '-') - c = (current_ = p) < end_ ? *p++ : '\0'; - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : '\0'; - } - return true; -} -bool OurReader::readString() { - Char c = 0; - while (current_ != end_) { - c = getNextChar(); - if (c == '\\') - getNextChar(); - else if (c == '"') - break; - } - return c == '"'; -} - -bool OurReader::readStringSingleQuote() { - Char c = 0; - while (current_ != end_) { - c = getNextChar(); - if (c == '\\') - getNextChar(); - else if (c == '\'') - break; - } - return c == '\''; -} - -bool OurReader::readObject(Token& token) { - Token tokenName; - String name; - Value init(objectValue); - currentValue().swapPayload(init); - currentValue().setOffsetStart(token.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; - if (tokenName.type_ == tokenObjectEnd && - (name.empty() || - features_.allowTrailingCommas_)) // empty object or trailing comma - return true; - name.clear(); - if (tokenName.type_ == tokenString) { - if (!decodeString(tokenName, name)) - return recoverFromError(tokenObjectEnd); - } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { - Value numberName; - if (!decodeNumber(tokenName, numberName)) - return recoverFromError(tokenObjectEnd); - name = numberName.asString(); - } else { - break; - } - if (name.length() >= (1U << 30)) - throwRuntimeError("keylength >= 2^30"); - if (features_.rejectDupKeys_ && currentValue().isMember(name)) { - String msg = "Duplicate key: '" + name + "'"; - return addErrorAndRecover(msg, tokenName, tokenObjectEnd); - } - - Token colon; - if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { - return addErrorAndRecover("Missing ':' after object member name", colon, - tokenObjectEnd); - } - Value& value = currentValue()[name]; - nodes_.push(&value); - bool ok = readValue(); - nodes_.pop(); - if (!ok) // error already set - return recoverFromError(tokenObjectEnd); - - Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { - return addErrorAndRecover("Missing ',' or '}' in object declaration", - comma, tokenObjectEnd); - } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); - if (comma.type_ == tokenObjectEnd) - return true; - } - return addErrorAndRecover("Missing '}' or object member name", tokenName, - tokenObjectEnd); -} - -bool OurReader::readArray(Token& token) { - Value init(arrayValue); - currentValue().swapPayload(init); - currentValue().setOffsetStart(token.start_ - begin_); - int index = 0; - for (;;) { - skipSpaces(); - if (current_ != end_ && *current_ == ']' && - (index == 0 || - (features_.allowTrailingCommas_ && - !features_.allowDroppedNullPlaceholders_))) // empty array or trailing - // comma +bool OurReader::parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments) +{ + if (!features_.allowComments_) { - Token endArray; - readToken(endArray); - return true; + collectComments = false; } - Value& value = currentValue()[index++]; - nodes_.push(&value); - bool ok = readValue(); + + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = nullptr; + lastValue_ = nullptr; + commentsBefore_.clear(); + errors_.clear(); + while (!nodes_.empty()) nodes_.pop(); + nodes_.push(&root); + + // skip byte order mark if it exists at the beginning of the UTF-8 text. + skipBom(features_.skipBom_); + bool successful = readValue(); nodes_.pop(); - if (!ok) // error already set - return recoverFromError(tokenArrayEnd); - - Token currentToken; - // Accept Comment after last item in the array. - ok = readToken(currentToken); - while (currentToken.type_ == tokenComment && ok) { - ok = readToken(currentToken); - } - bool badTokenType = (currentToken.type_ != tokenArraySeparator && - currentToken.type_ != tokenArrayEnd); - if (!ok || badTokenType) { - return addErrorAndRecover("Missing ',' or ']' in array declaration", - currentToken, tokenArrayEnd); - } - if (currentToken.type_ == tokenArrayEnd) - break; - } - return true; -} - -bool OurReader::decodeNumber(Token& token) { - Value decoded; - if (!decodeNumber(token, decoded)) - return false; - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool OurReader::decodeNumber(Token& token, Value& decoded) { - // Attempts to parse the number as an integer. If the number is - // larger than the maximum supported value of an integer then - // we decode the number as a double. - Location current = token.start_; - const bool isNegative = *current == '-'; - if (isNegative) { - ++current; - } - - // We assume we can represent the largest and smallest integer types as - // unsigned integers with separate sign. This is only true if they can fit - // into an unsigned integer. - static_assert(Value::maxLargestInt <= Value::maxLargestUInt, - "Int must be smaller than UInt"); - - // We need to convert minLargestInt into a positive number. The easiest way - // to do this conversion is to assume our "threshold" value of minLargestInt - // divided by 10 can fit in maxLargestInt when absolute valued. This should - // be a safe assumption. - static_assert(Value::minLargestInt <= -Value::maxLargestInt, - "The absolute value of minLargestInt must be greater than or " - "equal to maxLargestInt"); - static_assert(Value::minLargestInt / 10 >= -Value::maxLargestInt, - "The absolute value of minLargestInt must be only 1 magnitude " - "larger than maxLargest Int"); - - static constexpr Value::LargestUInt positive_threshold = - Value::maxLargestUInt / 10; - static constexpr Value::UInt positive_last_digit = Value::maxLargestUInt % 10; - - // For the negative values, we have to be more careful. Since typically - // -Value::minLargestInt will cause an overflow, we first divide by 10 and - // then take the inverse. This assumes that minLargestInt is only a single - // power of 10 different in magnitude, which we check above. For the last - // digit, we take the modulus before negating for the same reason. - static constexpr auto negative_threshold = - Value::LargestUInt(-(Value::minLargestInt / 10)); - static constexpr auto negative_last_digit = - Value::UInt(-(Value::minLargestInt % 10)); - - const Value::LargestUInt threshold = - isNegative ? negative_threshold : positive_threshold; - const Value::UInt max_last_digit = - isNegative ? negative_last_digit : positive_last_digit; - - Value::LargestUInt value = 0; - while (current < token.end_) { - Char c = *current++; - if (c < '0' || c > '9') - return decodeDouble(token, decoded); - - const auto digit(static_cast(c - '0')); - if (value >= threshold) { - // We've hit or exceeded the max value divided by 10 (rounded down). If - // a) we've only just touched the limit, meaning value == threshold, - // b) this is the last digit, or - // c) it's small enough to fit in that rounding delta, we're okay. - // Otherwise treat this number as a double to avoid overflow. - if (value > threshold || current != token.end_ || - digit > max_last_digit) { - return decodeDouble(token, decoded); - } - } - value = value * 10 + digit; - } - - if (isNegative) { - // We use the same magnitude assumption here, just in case. - const auto last_digit = static_cast(value % 10); - decoded = -Value::LargestInt(value / 10) * 10 - last_digit; - } else if (value <= Value::LargestUInt(Value::maxLargestInt)) { - decoded = Value::LargestInt(value); - } else { - decoded = value; - } - - return true; -} - -bool OurReader::decodeDouble(Token& token) { - Value decoded; - if (!decodeDouble(token, decoded)) - return false; - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool OurReader::decodeDouble(Token& token, Value& decoded) { - double value = 0; - const String buffer(token.start_, token.end_); - IStringStream is(buffer); - if (!(is >> value)) { - if (value == std::numeric_limits::max()) - value = std::numeric_limits::infinity(); - else if (value == std::numeric_limits::lowest()) - value = -std::numeric_limits::infinity(); - else if (!std::isinf(value)) - return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); - } - decoded = value; - return true; -} - -bool OurReader::decodeString(Token& token) { - String decoded_string; - if (!decodeString(token, decoded_string)) - return false; - Value decoded(decoded_string); - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool OurReader::decodeString(Token& token, String& decoded) { - decoded.reserve(static_cast(token.end_ - token.start_ - 2)); - Location current = token.start_ + 1; // skip '"' - Location end = token.end_ - 1; // do not include '"' - while (current != end) { - Char c = *current++; - if (c == '"') - break; - if (c == '\\') { - if (current == end) - return addError("Empty escape sequence in string", token, current); - Char escape = *current++; - switch (escape) { - case '"': - decoded += '"'; - break; - case '/': - decoded += '/'; - break; - case '\\': - decoded += '\\'; - break; - case 'b': - decoded += '\b'; - break; - case 'f': - decoded += '\f'; - break; - case 'n': - decoded += '\n'; - break; - case 'r': - decoded += '\r'; - break; - case 't': - decoded += '\t'; - break; - case 'u': { - unsigned int unicode; - if (!decodeUnicodeCodePoint(token, current, end, unicode)) - return false; - decoded += codePointToUTF8(unicode); - } break; - default: - return addError("Bad escape sequence in string", token, current); - } - } else { - decoded += c; - } - } - return true; -} - -bool OurReader::decodeUnicodeCodePoint(Token& token, Location& current, - Location end, unsigned int& unicode) { - - if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) - return false; - if (unicode >= 0xD800 && unicode <= 0xDBFF) { - // surrogate pairs - if (end - current < 6) - return addError( - "additional six characters expected to parse unicode surrogate pair.", - token, current); - if (*(current++) == '\\' && *(current++) == 'u') { - unsigned int surrogatePair; - if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { - unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); - } else + Token token; + skipCommentTokens(token); + if (features_.failIfExtra_ && (token.type_ != tokenEndOfStream)) + { + addError("Extra non-whitespace after JSON value.", token); return false; - } else - return addError("expecting another \\u token to begin the second half of " - "a unicode surrogate pair", - token, current); - } - return true; + } + if (collectComments_ && !commentsBefore_.empty()) root.setComment(commentsBefore_, commentAfter); + if (features_.strictRoot_) + { + if (!root.isArray() && !root.isObject()) + { + // Set error location to start of doc, ideally should be first token found + // in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError("A valid JSON document must be either an array or an object value.", token); + return false; + } + } + return successful; } -bool OurReader::decodeUnicodeEscapeSequence(Token& token, Location& current, - Location end, - unsigned int& ret_unicode) { - if (end - current < 4) - return addError( - "Bad unicode escape sequence in string: four digits expected.", token, - current); - int unicode = 0; - for (int index = 0; index < 4; ++index) { - Char c = *current++; - unicode *= 16; - if (c >= '0' && c <= '9') - unicode += c - '0'; - else if (c >= 'a' && c <= 'f') - unicode += c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - unicode += c - 'A' + 10; +bool OurReader::readValue() +{ + // To preserve the old behaviour we cast size_t to int. + if (nodes_.size() > features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue()."); + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) + { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_.clear(); + } + + switch (token.type_) + { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: + { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenFalse: + { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenNull: + { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenNaN: + { + Value v(std::numeric_limits::quiet_NaN()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenPosInf: + { + Value v(std::numeric_limits::infinity()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenNegInf: + { + Value v(-std::numeric_limits::infinity()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) + { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // else, fall through ... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return addError("Syntax error: value, object or array expected.", token); + } + + if (collectComments_) + { + lastValueEnd_ = current_; + lastValueHasAComment_ = false; + lastValue_ = ¤tValue(); + } + + return successful; +} + +void OurReader::skipCommentTokens(Token& token) +{ + if (features_.allowComments_) + { + do + { + readToken(token); + } while (token.type_ == tokenComment); + } else - return addError( - "Bad unicode escape sequence in string: hexadecimal digit expected.", - token, current); - } - ret_unicode = static_cast(unicode); - return true; + { + readToken(token); + } } -bool OurReader::addError(const String& message, Token& token, Location extra) { - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = extra; - errors_.push_back(info); - return false; +bool OurReader::readToken(Token& token) +{ + skipSpaces(); + token.start_ = current_; + Char c = getNextChar(); + bool ok = true; + switch (c) + { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '\'': + if (features_.allowSingleQuotes_) + { + token.type_ = tokenString; + ok = readStringSingleQuote(); + } + else + { + // If we don't allow single quotes, this is a failure case. + ok = false; + } + break; + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + token.type_ = tokenNumber; + readNumber(false); + break; + case '-': + if (readNumber(true)) + { + token.type_ = tokenNumber; + } + else + { + token.type_ = tokenNegInf; + ok = features_.allowSpecialFloats_ && match("nfinity", 7); + } + break; + case '+': + if (readNumber(true)) + { + token.type_ = tokenNumber; + } + else + { + token.type_ = tokenPosInf; + ok = features_.allowSpecialFloats_ && match("nfinity", 7); + } + break; + case 't': + token.type_ = tokenTrue; + ok = match("rue", 3); + break; + case 'f': + token.type_ = tokenFalse; + ok = match("alse", 4); + break; + case 'n': + token.type_ = tokenNull; + ok = match("ull", 3); + break; + case 'N': + if (features_.allowSpecialFloats_) + { + token.type_ = tokenNaN; + ok = match("aN", 2); + } + else + { + ok = false; + } + break; + case 'I': + if (features_.allowSpecialFloats_) + { + token.type_ = tokenPosInf; + ok = match("nfinity", 7); + } + else + { + ok = false; + } + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; + } + if (!ok) token.type_ = tokenError; + token.end_ = current_; + return ok; } -bool OurReader::recoverFromError(TokenType skipUntilToken) { - size_t errorCount = errors_.size(); - Token skip; - for (;;) { - if (!readToken(skip)) - errors_.resize(errorCount); // discard errors caused by recovery - if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) - break; - } - errors_.resize(errorCount); - return false; +void OurReader::skipSpaces() +{ + while (current_ != end_) + { + Char c = *current_; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + ++current_; + else + break; + } } -bool OurReader::addErrorAndRecover(const String& message, Token& token, - TokenType skipUntilToken) { - addError(message, token); - return recoverFromError(skipUntilToken); +void OurReader::skipBom(bool skipBom) +{ + // The default behavior is to skip BOM. + if (skipBom) + { + if ((end_ - begin_) >= 3 && strncmp(begin_, "\xEF\xBB\xBF", 3) == 0) + { + begin_ += 3; + current_ = begin_; + } + } +} + +bool OurReader::match(const Char* pattern, int patternLength) +{ + if (end_ - current_ < patternLength) return false; + int index = patternLength; + while (index--) + if (current_[index] != pattern[index]) return false; + current_ += patternLength; + return true; +} + +bool OurReader::readComment() +{ + const Location commentBegin = current_ - 1; + const Char c = getNextChar(); + bool successful = false; + bool cStyleWithEmbeddedNewline = false; + + const bool isCStyleComment = (c == '*'); + const bool isCppStyleComment = (c == '/'); + if (isCStyleComment) + { + successful = readCStyleComment(&cStyleWithEmbeddedNewline); + } + else if (isCppStyleComment) + { + successful = readCppStyleComment(); + } + + if (!successful) return false; + + if (collectComments_) + { + CommentPlacement placement = commentBefore; + + if (!lastValueHasAComment_) + { + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) + { + if (isCppStyleComment || !cStyleWithEmbeddedNewline) + { + placement = commentAfterOnSameLine; + lastValueHasAComment_ = true; + } + } + } + + addComment(commentBegin, current_, placement); + } + return true; +} + +String OurReader::normalizeEOL(OurReader::Location begin, OurReader::Location end) +{ + String normalized; + normalized.reserve(static_cast(end - begin)); + OurReader::Location current = begin; + while (current != end) + { + char c = *current++; + if (c == '\r') + { + if (current != end && *current == '\n') + // convert dos EOL + ++current; + // convert Mac EOL + normalized += '\n'; + } + else + { + normalized += c; + } + } + return normalized; +} + +void OurReader::addComment(Location begin, Location end, CommentPlacement placement) +{ + assert(collectComments_); + const String& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) + { + assert(lastValue_ != nullptr); + lastValue_->setComment(normalized, placement); + } + else + { + commentsBefore_ += normalized; + } +} + +bool OurReader::readCStyleComment(bool* containsNewLineResult) +{ + *containsNewLineResult = false; + + while ((current_ + 1) < end_) + { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') break; + if (c == '\n') *containsNewLineResult = true; + } + + return getNextChar() == '/'; +} + +bool OurReader::readCppStyleComment() +{ + while (current_ != end_) + { + Char c = getNextChar(); + if (c == '\n') break; + if (c == '\r') + { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; +} + +bool OurReader::readNumber(bool checkInf) +{ + Location p = current_; + if (checkInf && p != end_ && *p == 'I') + { + current_ = ++p; + return false; + } + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') c = (current_ = p) < end_ ? *p++ : '\0'; + // fractional part + if (c == '.') + { + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') c = (current_ = p) < end_ ? *p++ : '\0'; + } + // exponential part + if (c == 'e' || c == 'E') + { + c = (current_ = p) < end_ ? *p++ : '\0'; + if (c == '+' || c == '-') c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') c = (current_ = p) < end_ ? *p++ : '\0'; + } + return true; +} +bool OurReader::readString() +{ + Char c = 0; + while (current_ != end_) + { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '"') + break; + } + return c == '"'; +} + +bool OurReader::readStringSingleQuote() +{ + Char c = 0; + while (current_ != end_) + { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '\'') + break; + } + return c == '\''; +} + +bool OurReader::readObject(Token& token) +{ + Token tokenName; + String name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + while (readToken(tokenName)) + { + bool initialTokenOk = true; + while (tokenName.type_ == tokenComment && initialTokenOk) initialTokenOk = readToken(tokenName); + if (!initialTokenOk) break; + if (tokenName.type_ == tokenObjectEnd && + (name.empty() || features_.allowTrailingCommas_)) // empty object or trailing comma + return true; + name.clear(); + if (tokenName.type_ == tokenString) + { + if (!decodeString(tokenName, name)) return recoverFromError(tokenObjectEnd); + } + else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) + { + Value numberName; + if (!decodeNumber(tokenName, numberName)) return recoverFromError(tokenObjectEnd); + name = numberName.asString(); + } + else + { + break; + } + if (name.length() >= (1U << 30)) throwRuntimeError("keylength >= 2^30"); + if (features_.rejectDupKeys_ && currentValue().isMember(name)) + { + String msg = "Duplicate key: '" + name + "'"; + return addErrorAndRecover(msg, tokenName, tokenObjectEnd); + } + + Token colon; + if (!readToken(colon) || colon.type_ != tokenMemberSeparator) + { + return addErrorAndRecover("Missing ':' after object member name", colon, tokenObjectEnd); + } + Value& value = currentValue()[name]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenObjectEnd); + + Token comma; + if (!readToken(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && comma.type_ != tokenComment)) + { + return addErrorAndRecover("Missing ',' or '}' in object declaration", comma, tokenObjectEnd); + } + bool finalizeTokenOk = true; + while (comma.type_ == tokenComment && finalizeTokenOk) finalizeTokenOk = readToken(comma); + if (comma.type_ == tokenObjectEnd) return true; + } + return addErrorAndRecover("Missing '}' or object member name", tokenName, tokenObjectEnd); +} + +bool OurReader::readArray(Token& token) +{ + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + int index = 0; + for (;;) + { + skipSpaces(); + if (current_ != end_ && *current_ == ']' && + (index == 0 || + (features_.allowTrailingCommas_ && !features_.allowDroppedNullPlaceholders_))) // empty array or trailing + // comma + { + Token endArray; + readToken(endArray); + return true; + } + Value& value = currentValue()[index++]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenArrayEnd); + + Token currentToken; + // Accept Comment after last item in the array. + ok = readToken(currentToken); + while (currentToken.type_ == tokenComment && ok) + { + ok = readToken(currentToken); + } + bool badTokenType = (currentToken.type_ != tokenArraySeparator && currentToken.type_ != tokenArrayEnd); + if (!ok || badTokenType) + { + return addErrorAndRecover("Missing ',' or ']' in array declaration", currentToken, tokenArrayEnd); + } + if (currentToken.type_ == tokenArrayEnd) break; + } + return true; +} + +bool OurReader::decodeNumber(Token& token) +{ + Value decoded; + if (!decodeNumber(token, decoded)) return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeNumber(Token& token, Value& decoded) +{ + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + const bool isNegative = *current == '-'; + if (isNegative) + { + ++current; + } + + // We assume we can represent the largest and smallest integer types as + // unsigned integers with separate sign. This is only true if they can fit + // into an unsigned integer. + static_assert(Value::maxLargestInt <= Value::maxLargestUInt, "Int must be smaller than UInt"); + + // We need to convert minLargestInt into a positive number. The easiest way + // to do this conversion is to assume our "threshold" value of minLargestInt + // divided by 10 can fit in maxLargestInt when absolute valued. This should + // be a safe assumption. + static_assert(Value::minLargestInt <= -Value::maxLargestInt, + "The absolute value of minLargestInt must be greater than or " + "equal to maxLargestInt"); + static_assert(Value::minLargestInt / 10 >= -Value::maxLargestInt, + "The absolute value of minLargestInt must be only 1 magnitude " + "larger than maxLargest Int"); + + static constexpr Value::LargestUInt positive_threshold = Value::maxLargestUInt / 10; + static constexpr Value::UInt positive_last_digit = Value::maxLargestUInt % 10; + + // For the negative values, we have to be more careful. Since typically + // -Value::minLargestInt will cause an overflow, we first divide by 10 and + // then take the inverse. This assumes that minLargestInt is only a single + // power of 10 different in magnitude, which we check above. For the last + // digit, we take the modulus before negating for the same reason. + static constexpr auto negative_threshold = Value::LargestUInt(-(Value::minLargestInt / 10)); + static constexpr auto negative_last_digit = Value::UInt(-(Value::minLargestInt % 10)); + + const Value::LargestUInt threshold = isNegative ? negative_threshold : positive_threshold; + const Value::UInt max_last_digit = isNegative ? negative_last_digit : positive_last_digit; + + Value::LargestUInt value = 0; + while (current < token.end_) + { + Char c = *current++; + if (c < '0' || c > '9') return decodeDouble(token, decoded); + + const auto digit(static_cast(c - '0')); + if (value >= threshold) + { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, meaning value == threshold, + // b) this is the last digit, or + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || digit > max_last_digit) + { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + + if (isNegative) + { + // We use the same magnitude assumption here, just in case. + const auto last_digit = static_cast(value % 10); + decoded = -Value::LargestInt(value / 10) * 10 - last_digit; + } + else if (value <= Value::LargestUInt(Value::maxLargestInt)) + { + decoded = Value::LargestInt(value); + } + else + { + decoded = value; + } + + return true; +} + +bool OurReader::decodeDouble(Token& token) +{ + Value decoded; + if (!decodeDouble(token, decoded)) return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeDouble(Token& token, Value& decoded) +{ + double value = 0; + const String buffer(token.start_, token.end_); + IStringStream is(buffer); + if (!(is >> value)) + { + if (value == std::numeric_limits::max()) + value = std::numeric_limits::infinity(); + else if (value == std::numeric_limits::lowest()) + value = -std::numeric_limits::infinity(); + else if (!std::isinf(value)) + return addError("'" + String(token.start_, token.end_) + "' is not a number.", token); + } + decoded = value; + return true; +} + +bool OurReader::decodeString(Token& token) +{ + String decoded_string; + if (!decodeString(token, decoded_string)) return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeString(Token& token, String& decoded) +{ + decoded.reserve(static_cast(token.end_ - token.start_ - 2)); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while (current != end) + { + Char c = *current++; + if (c == '"') break; + if (c == '\\') + { + if (current == end) return addError("Empty escape sequence in string", token, current); + Char escape = *current++; + switch (escape) + { + case '"': + decoded += '"'; + break; + case '/': + decoded += '/'; + break; + case '\\': + decoded += '\\'; + break; + case 'b': + decoded += '\b'; + break; + case 'f': + decoded += '\f'; + break; + case 'n': + decoded += '\n'; + break; + case 'r': + decoded += '\r'; + break; + case 't': + decoded += '\t'; + break; + case 'u': + { + unsigned int unicode; + if (!decodeUnicodeCodePoint(token, current, end, unicode)) return false; + decoded += codePointToUTF8(unicode); + } + break; + default: + return addError("Bad escape sequence in string", token, current); + } + } + else + { + decoded += c; + } + } + return true; +} + +bool OurReader::decodeUnicodeCodePoint(Token& token, Location& current, Location end, unsigned int& unicode) +{ + if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) + { + // surrogate pairs + if (end - current < 6) + return addError("additional six characters expected to parse unicode surrogate pair.", token, current); + if (*(current++) == '\\' && *(current++) == 'u') + { + unsigned int surrogatePair; + if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) + { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } + else + return false; + } + else + return addError( + "expecting another \\u token to begin the second half of " + "a unicode surrogate pair", + token, current); + } + return true; +} + +bool OurReader::decodeUnicodeEscapeSequence(Token& token, Location& current, Location end, unsigned int& ret_unicode) +{ + if (end - current < 4) + return addError("Bad unicode escape sequence in string: four digits expected.", token, current); + int unicode = 0; + for (int index = 0; index < 4; ++index) + { + Char c = *current++; + unicode *= 16; + if (c >= '0' && c <= '9') + unicode += c - '0'; + else if (c >= 'a' && c <= 'f') + unicode += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + unicode += c - 'A' + 10; + else + return addError("Bad unicode escape sequence in string: hexadecimal digit expected.", token, current); + } + ret_unicode = static_cast(unicode); + return true; +} + +bool OurReader::addError(const String& message, Token& token, Location extra) +{ + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back(info); + return false; +} + +bool OurReader::recoverFromError(TokenType skipUntilToken) +{ + size_t errorCount = errors_.size(); + Token skip; + for (;;) + { + if (!readToken(skip)) errors_.resize(errorCount); // discard errors caused by recovery + if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) break; + } + errors_.resize(errorCount); + return false; +} + +bool OurReader::addErrorAndRecover(const String& message, Token& token, TokenType skipUntilToken) +{ + addError(message, token); + return recoverFromError(skipUntilToken); } Value& OurReader::currentValue() { return *(nodes_.top()); } -OurReader::Char OurReader::getNextChar() { - if (current_ == end_) - return 0; - return *current_++; +OurReader::Char OurReader::getNextChar() +{ + if (current_ == end_) return 0; + return *current_++; } -void OurReader::getLocationLineAndColumn(Location location, int& line, - int& column) const { - Location current = begin_; - Location lastLineStart = current; - line = 0; - while (current < location && current != end_) { - Char c = *current++; - if (c == '\r') { - if (*current == '\n') - ++current; - lastLineStart = current; - ++line; - } else if (c == '\n') { - lastLineStart = current; - ++line; +void OurReader::getLocationLineAndColumn(Location location, int& line, int& column) const +{ + Location current = begin_; + Location lastLineStart = current; + line = 0; + while (current < location && current != end_) + { + Char c = *current++; + if (c == '\r') + { + if (*current == '\n') ++current; + lastLineStart = current; + ++line; + } + else if (c == '\n') + { + lastLineStart = current; + ++line; + } } - } - // column & line start at 1 - column = int(location - lastLineStart) + 1; - ++line; + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; } -String OurReader::getLocationLineAndColumn(Location location) const { - int line, column; - getLocationLineAndColumn(location, line, column); - char buffer[18 + 16 + 16 + 1]; - jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); - return buffer; +String OurReader::getLocationLineAndColumn(Location location) const +{ + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; + jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + return buffer; } -String OurReader::getFormattedErrorMessages() const { - String formattedMessage; - for (const auto& error : errors_) { - formattedMessage += - "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; - formattedMessage += " " + error.message_ + "\n"; - if (error.extra_) - formattedMessage += - "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; - } - return formattedMessage; -} - -std::vector OurReader::getStructuredErrors() const { - std::vector allErrors; - for (const auto& error : errors_) { - OurReader::StructuredError structured; - structured.offset_start = error.token_.start_ - begin_; - structured.offset_limit = error.token_.end_ - begin_; - structured.message = error.message_; - allErrors.push_back(structured); - } - return allErrors; -} - -class OurCharReader : public CharReader { - bool const collectComments_; - OurReader reader_; - -public: - OurCharReader(bool collectComments, OurFeatures const& features) - : collectComments_(collectComments), reader_(features) {} - bool parse(char const* beginDoc, char const* endDoc, Value* root, - String* errs) override { - bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); - if (errs) { - *errs = reader_.getFormattedErrorMessages(); +String OurReader::getFormattedErrorMessages() const +{ + String formattedMessage; + for (const auto& error : errors_) + { + formattedMessage += "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if (error.extra_) formattedMessage += "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; + } + return formattedMessage; +} + +std::vector OurReader::getStructuredErrors() const +{ + std::vector allErrors; + for (const auto& error : errors_) + { + OurReader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; +} + +class OurCharReader : public CharReader +{ + bool const collectComments_; + OurReader reader_; + + public: + OurCharReader(bool collectComments, OurFeatures const& features) + : collectComments_(collectComments), reader_(features) + { + } + bool parse(char const* beginDoc, char const* endDoc, Value* root, String* errs) override + { + bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); + if (errs) + { + *errs = reader_.getFormattedErrorMessages(); + } + return ok; } - return ok; - } }; CharReaderBuilder::CharReaderBuilder() { setDefaults(&settings_); } CharReaderBuilder::~CharReaderBuilder() = default; -CharReader* CharReaderBuilder::newCharReader() const { - bool collectComments = settings_["collectComments"].asBool(); - OurFeatures features = OurFeatures::all(); - features.allowComments_ = settings_["allowComments"].asBool(); - features.allowTrailingCommas_ = settings_["allowTrailingCommas"].asBool(); - features.strictRoot_ = settings_["strictRoot"].asBool(); - features.allowDroppedNullPlaceholders_ = - settings_["allowDroppedNullPlaceholders"].asBool(); - features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); - features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool(); +CharReader* CharReaderBuilder::newCharReader() const +{ + bool collectComments = settings_["collectComments"].asBool(); + OurFeatures features = OurFeatures::all(); + features.allowComments_ = settings_["allowComments"].asBool(); + features.allowTrailingCommas_ = settings_["allowTrailingCommas"].asBool(); + features.strictRoot_ = settings_["strictRoot"].asBool(); + features.allowDroppedNullPlaceholders_ = settings_["allowDroppedNullPlaceholders"].asBool(); + features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); + features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool(); - // Stack limit is always a size_t, so we get this as an unsigned int - // regardless of it we have 64-bit integer support enabled. - features.stackLimit_ = static_cast(settings_["stackLimit"].asUInt()); - features.failIfExtra_ = settings_["failIfExtra"].asBool(); - features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); - features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool(); - features.skipBom_ = settings_["skipBom"].asBool(); - return new OurCharReader(collectComments, features); + // Stack limit is always a size_t, so we get this as an unsigned int + // regardless of it we have 64-bit integer support enabled. + features.stackLimit_ = static_cast(settings_["stackLimit"].asUInt()); + features.failIfExtra_ = settings_["failIfExtra"].asBool(); + features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); + features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool(); + features.skipBom_ = settings_["skipBom"].asBool(); + return new OurCharReader(collectComments, features); } -bool CharReaderBuilder::validate(Json::Value* invalid) const { - static const auto& valid_keys = *new std::set{ - "collectComments", - "allowComments", - "allowTrailingCommas", - "strictRoot", - "allowDroppedNullPlaceholders", - "allowNumericKeys", - "allowSingleQuotes", - "stackLimit", - "failIfExtra", - "rejectDupKeys", - "allowSpecialFloats", - "skipBom", - }; - for (auto si = settings_.begin(); si != settings_.end(); ++si) { - auto key = si.name(); - if (valid_keys.count(key)) - continue; - if (invalid) - (*invalid)[key] = *si; - else - return false; - } - return invalid ? invalid->empty() : true; +bool CharReaderBuilder::validate(Json::Value* invalid) const +{ + static const auto& valid_keys = *new std::set{ + "collectComments", "allowComments", "allowTrailingCommas", "strictRoot", "allowDroppedNullPlaceholders", + "allowNumericKeys", "allowSingleQuotes", "stackLimit", "failIfExtra", "rejectDupKeys", + "allowSpecialFloats", "skipBom", + }; + for (auto si = settings_.begin(); si != settings_.end(); ++si) + { + auto key = si.name(); + if (valid_keys.count(key)) continue; + if (invalid) + (*invalid)[key] = *si; + else + return false; + } + return invalid ? invalid->empty() : true; } -Value& CharReaderBuilder::operator[](const String& key) { - return settings_[key]; +Value& CharReaderBuilder::operator[](const String& key) { return settings_[key]; } +// static +void CharReaderBuilder::strictMode(Json::Value* settings) +{ + //! [CharReaderBuilderStrictMode] + (*settings)["allowComments"] = false; + (*settings)["allowTrailingCommas"] = false; + (*settings)["strictRoot"] = true; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = true; + (*settings)["rejectDupKeys"] = true; + (*settings)["allowSpecialFloats"] = false; + (*settings)["skipBom"] = true; + //! [CharReaderBuilderStrictMode] } // static -void CharReaderBuilder::strictMode(Json::Value* settings) { - //! [CharReaderBuilderStrictMode] - (*settings)["allowComments"] = false; - (*settings)["allowTrailingCommas"] = false; - (*settings)["strictRoot"] = true; - (*settings)["allowDroppedNullPlaceholders"] = false; - (*settings)["allowNumericKeys"] = false; - (*settings)["allowSingleQuotes"] = false; - (*settings)["stackLimit"] = 1000; - (*settings)["failIfExtra"] = true; - (*settings)["rejectDupKeys"] = true; - (*settings)["allowSpecialFloats"] = false; - (*settings)["skipBom"] = true; - //! [CharReaderBuilderStrictMode] -} -// static -void CharReaderBuilder::setDefaults(Json::Value* settings) { - //! [CharReaderBuilderDefaults] - (*settings)["collectComments"] = true; - (*settings)["allowComments"] = true; - (*settings)["allowTrailingCommas"] = true; - (*settings)["strictRoot"] = false; - (*settings)["allowDroppedNullPlaceholders"] = false; - (*settings)["allowNumericKeys"] = false; - (*settings)["allowSingleQuotes"] = false; - (*settings)["stackLimit"] = 1000; - (*settings)["failIfExtra"] = false; - (*settings)["rejectDupKeys"] = false; - (*settings)["allowSpecialFloats"] = false; - (*settings)["skipBom"] = true; - //! [CharReaderBuilderDefaults] +void CharReaderBuilder::setDefaults(Json::Value* settings) +{ + //! [CharReaderBuilderDefaults] + (*settings)["collectComments"] = true; + (*settings)["allowComments"] = true; + (*settings)["allowTrailingCommas"] = true; + (*settings)["strictRoot"] = false; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = false; + (*settings)["rejectDupKeys"] = false; + (*settings)["allowSpecialFloats"] = false; + (*settings)["skipBom"] = true; + //! [CharReaderBuilderDefaults] } ////////////////////////////////// // global functions -bool parseFromStream(CharReader::Factory const& fact, IStream& sin, Value* root, - String* errs) { - OStringStream ssin; - ssin << sin.rdbuf(); - String doc = ssin.str(); - char const* begin = doc.data(); - char const* end = begin + doc.size(); - // Note that we do not actually need a null-terminator. - CharReaderPtr const reader(fact.newCharReader()); - return reader->parse(begin, end, root, errs); +bool parseFromStream(CharReader::Factory const& fact, IStream& sin, Value* root, String* errs) +{ + OStringStream ssin; + ssin << sin.rdbuf(); + String doc = ssin.str(); + char const* begin = doc.data(); + char const* end = begin + doc.size(); + // Note that we do not actually need a null-terminator. + CharReaderPtr const reader(fact.newCharReader()); + return reader->parse(begin, end, root, errs); } -IStream& operator>>(IStream& sin, Value& root) { - CharReaderBuilder b; - String errs; - bool ok = parseFromStream(b, sin, &root, &errs); - if (!ok) { - throwRuntimeError(errs); - } - return sin; +IStream& operator>>(IStream& sin, Value& root) +{ + CharReaderBuilder b; + String errs; + bool ok = parseFromStream(b, sin, &root, &errs); + if (!ok) + { + throwRuntimeError(errs); + } + return sin; } -} // namespace Json +} // namespace Json // ////////////////////////////////////////////////////////////////////// // End of content of file: src/lib_json/json_reader.cpp // ////////////////////////////////////////////////////////////////////// - - - - - // ////////////////////////////////////////////////////////////////////// // Beginning of content of file: src/lib_json/json_valueiterator.inl // ////////////////////////////////////////////////////////////////////// @@ -2261,7 +2379,8 @@ IStream& operator>>(IStream& sin, Value& root) { // included by json_value.cpp -namespace Json { +namespace Json +{ // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// @@ -2273,9 +2392,9 @@ namespace Json { ValueIteratorBase::ValueIteratorBase() : current_() {} -ValueIteratorBase::ValueIteratorBase( - const Value::ObjectValues::iterator& current) - : current_(current), isNull_(false) {} +ValueIteratorBase::ValueIteratorBase(const Value::ObjectValues::iterator& current) : current_(current), isNull_(false) +{ +} Value& ValueIteratorBase::deref() { return current_->second; } const Value& ValueIteratorBase::deref() const { return current_->second; } @@ -2284,81 +2403,89 @@ void ValueIteratorBase::increment() { ++current_; } void ValueIteratorBase::decrement() { --current_; } -ValueIteratorBase::difference_type -ValueIteratorBase::computeDistance(const SelfType& other) const { - // Iterator for null value are initialized using the default - // constructor, which initialize current_ to the default - // std::map::iterator. As begin() and end() are two instance - // of the default std::map::iterator, they can not be compared. - // To allow this, we handle this comparison specifically. - if (isNull_ && other.isNull_) { - return 0; - } +ValueIteratorBase::difference_type ValueIteratorBase::computeDistance(const SelfType& other) const +{ + // Iterator for null value are initialized using the default + // constructor, which initialize current_ to the default + // std::map::iterator. As begin() and end() are two instance + // of the default std::map::iterator, they can not be compared. + // To allow this, we handle this comparison specifically. + if (isNull_ && other.isNull_) + { + return 0; + } - // Usage of std::distance is not portable (does not compile with Sun Studio 12 - // RogueWave STL, - // which is the one used by default). - // Using a portable hand-made version for non random iterator instead: - // return difference_type( std::distance( current_, other.current_ ) ); - difference_type myDistance = 0; - for (Value::ObjectValues::iterator it = current_; it != other.current_; - ++it) { - ++myDistance; - } - return myDistance; + // Usage of std::distance is not portable (does not compile with Sun Studio 12 + // RogueWave STL, + // which is the one used by default). + // Using a portable hand-made version for non random iterator instead: + // return difference_type( std::distance( current_, other.current_ ) ); + difference_type myDistance = 0; + for (Value::ObjectValues::iterator it = current_; it != other.current_; ++it) + { + ++myDistance; + } + return myDistance; } -bool ValueIteratorBase::isEqual(const SelfType& other) const { - if (isNull_) { - return other.isNull_; - } - return current_ == other.current_; +bool ValueIteratorBase::isEqual(const SelfType& other) const +{ + if (isNull_) + { + return other.isNull_; + } + return current_ == other.current_; } -void ValueIteratorBase::copy(const SelfType& other) { - current_ = other.current_; - isNull_ = other.isNull_; +void ValueIteratorBase::copy(const SelfType& other) +{ + current_ = other.current_; + isNull_ = other.isNull_; } -Value ValueIteratorBase::key() const { - const Value::CZString czstring = (*current_).first; - if (czstring.data()) { - if (czstring.isStaticString()) - return Value(StaticString(czstring.data())); - return Value(czstring.data(), czstring.data() + czstring.length()); - } - return Value(czstring.index()); +Value ValueIteratorBase::key() const +{ + const Value::CZString czstring = (*current_).first; + if (czstring.data()) + { + if (czstring.isStaticString()) return Value(StaticString(czstring.data())); + return Value(czstring.data(), czstring.data() + czstring.length()); + } + return Value(czstring.index()); } -UInt ValueIteratorBase::index() const { - const Value::CZString czstring = (*current_).first; - if (!czstring.data()) - return czstring.index(); - return Value::UInt(-1); +UInt ValueIteratorBase::index() const +{ + const Value::CZString czstring = (*current_).first; + if (!czstring.data()) return czstring.index(); + return Value::UInt(-1); } -String ValueIteratorBase::name() const { - char const* keey; - char const* end; - keey = memberName(&end); - if (!keey) - return String(); - return String(keey, end); +String ValueIteratorBase::name() const +{ + char const* keey; + char const* end; + keey = memberName(&end); + if (!keey) return String(); + return String(keey, end); } -char const* ValueIteratorBase::memberName() const { - const char* cname = (*current_).first.data(); - return cname ? cname : ""; +char const* ValueIteratorBase::memberName() const +{ + const char* cname = (*current_).first.data(); + return cname ? cname : ""; } -char const* ValueIteratorBase::memberName(char const** end) const { - const char* cname = (*current_).first.data(); - if (!cname) { - *end = nullptr; - return nullptr; - } - *end = cname + (*current_).first.length(); - return cname; +char const* ValueIteratorBase::memberName(char const** end) const +{ + const char* cname = (*current_).first.data(); + if (!cname) + { + *end = nullptr; + return nullptr; + } + *end = cname + (*current_).first.length(); + return cname; } // ////////////////////////////////////////////////////////////////// @@ -2371,17 +2498,14 @@ char const* ValueIteratorBase::memberName(char const** end) const { ValueConstIterator::ValueConstIterator() = default; -ValueConstIterator::ValueConstIterator( - const Value::ObjectValues::iterator& current) - : ValueIteratorBase(current) {} +ValueConstIterator::ValueConstIterator(const Value::ObjectValues::iterator& current) : ValueIteratorBase(current) {} -ValueConstIterator::ValueConstIterator(ValueIterator const& other) - : ValueIteratorBase(other) {} +ValueConstIterator::ValueConstIterator(ValueIterator const& other) : ValueIteratorBase(other) {} -ValueConstIterator& ValueConstIterator:: -operator=(const ValueIteratorBase& other) { - copy(other); - return *this; +ValueConstIterator& ValueConstIterator::operator=(const ValueIteratorBase& other) +{ + copy(other); + return *this; } // ////////////////////////////////////////////////////////////////// @@ -2394,32 +2518,27 @@ operator=(const ValueIteratorBase& other) { ValueIterator::ValueIterator() = default; -ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current) - : ValueIteratorBase(current) {} +ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current) : ValueIteratorBase(current) {} -ValueIterator::ValueIterator(const ValueConstIterator& other) - : ValueIteratorBase(other) { - throwRuntimeError("ConstIterator to Iterator should never be allowed."); +ValueIterator::ValueIterator(const ValueConstIterator& other) : ValueIteratorBase(other) +{ + throwRuntimeError("ConstIterator to Iterator should never be allowed."); } ValueIterator::ValueIterator(const ValueIterator& other) = default; -ValueIterator& ValueIterator::operator=(const SelfType& other) { - copy(other); - return *this; +ValueIterator& ValueIterator::operator=(const SelfType& other) +{ + copy(other); + return *this; } -} // namespace Json +} // namespace Json // ////////////////////////////////////////////////////////////////////// // End of content of file: src/lib_json/json_valueiterator.inl // ////////////////////////////////////////////////////////////////////// - - - - - // ////////////////////////////////////////////////////////////////////// // Beginning of content of file: src/lib_json/json_value.cpp // ////////////////////////////////////////////////////////////////////// @@ -2433,7 +2552,7 @@ ValueIterator& ValueIterator::operator=(const SelfType& other) { #include #include #include -#endif // if !defined(JSON_IS_AMALGAMATION) +#endif // if !defined(JSON_IS_AMALGAMATION) #include #include #include @@ -2446,23 +2565,21 @@ ValueIterator& ValueIterator::operator=(const SelfType& other) { // Provide implementation equivalent of std::snprintf for older _MSC compilers #if defined(_MSC_VER) && _MSC_VER < 1900 #include -static int msvc_pre1900_c99_vsnprintf(char* outBuf, size_t size, - const char* format, va_list ap) { - int count = -1; - if (size != 0) - count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); - if (count == -1) - count = _vscprintf(format, ap); - return count; +static int msvc_pre1900_c99_vsnprintf(char* outBuf, size_t size, const char* format, va_list ap) +{ + int count = -1; + if (size != 0) count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + if (count == -1) count = _vscprintf(format, ap); + return count; } -int JSON_API msvc_pre1900_c99_snprintf(char* outBuf, size_t size, - const char* format, ...) { - va_list ap; - va_start(ap, format); - const int count = msvc_pre1900_c99_vsnprintf(outBuf, size, format, ap); - va_end(ap); - return count; +int JSON_API msvc_pre1900_c99_snprintf(char* outBuf, size_t size, const char* format, ...) +{ + va_list ap; + va_start(ap, format); + const int count = msvc_pre1900_c99_vsnprintf(outBuf, size, format, ap); + va_end(ap); + return count; } #endif @@ -2473,14 +2590,17 @@ int JSON_API msvc_pre1900_c99_snprintf(char* outBuf, size_t size, #define JSON_ASSERT_UNREACHABLE assert(false) -namespace Json { +namespace Json +{ template -static std::unique_ptr cloneUnique(const std::unique_ptr& p) { - std::unique_ptr r; - if (p) { - r = std::unique_ptr(new T(*p)); - } - return r; +static std::unique_ptr cloneUnique(const std::unique_ptr& p) +{ + std::unique_ptr r; + if (p) + { + r = std::unique_ptr(new T(*p)); + } + return r; } // This is a walkaround to avoid the static initialization of Value::null. @@ -2493,9 +2613,10 @@ static std::unique_ptr cloneUnique(const std::unique_ptr& p) { #endif // static -Value const& Value::nullSingleton() { - static Value const nullStatic; - return nullStatic; +Value const& Value::nullSingleton() +{ + static Value const nullStatic; + return nullStatic; } #if JSON_USE_NULLREF @@ -2510,26 +2631,30 @@ Value const& Value::nullRef = Value::nullSingleton(); #if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) template -static inline bool InRange(double d, T min, U max) { - // The casts can lose precision, but we are looking only for - // an approximate range. Might fail on edge cases though. ~cdunn - return d >= static_cast(min) && d <= static_cast(max); +static inline bool InRange(double d, T min, U max) +{ + // The casts can lose precision, but we are looking only for + // an approximate range. Might fail on edge cases though. ~cdunn + return d >= static_cast(min) && d <= static_cast(max); } -#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) -static inline double integerToDouble(Json::UInt64 value) { - return static_cast(Int64(value / 2)) * 2.0 + - static_cast(Int64(value & 1)); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +static inline double integerToDouble(Json::UInt64 value) +{ + return static_cast(Int64(value / 2)) * 2.0 + static_cast(Int64(value & 1)); } -template static inline double integerToDouble(T value) { - return static_cast(value); +template +static inline double integerToDouble(T value) +{ + return static_cast(value); } template -static inline bool InRange(double d, T min, U max) { - return d >= integerToDouble(min) && d <= integerToDouble(max); +static inline bool InRange(double d, T min, U max) +{ + return d >= integerToDouble(min) && d <= integerToDouble(max); } -#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) /** Duplicates the specified string value. * @param value Pointer to the string to duplicate. Must be zero-terminated if @@ -2538,78 +2663,85 @@ static inline bool InRange(double d, T min, U max) { * computed using strlen(value). * @return Pointer on the duplicate instance of string. */ -static inline char* duplicateStringValue(const char* value, size_t length) { - // Avoid an integer overflow in the call to malloc below by limiting length - // to a sane value. - if (length >= static_cast(Value::maxInt)) - length = Value::maxInt - 1; +static inline char* duplicateStringValue(const char* value, size_t length) +{ + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + if (length >= static_cast(Value::maxInt)) length = Value::maxInt - 1; - auto newString = static_cast(malloc(length + 1)); - if (newString == nullptr) { - throwRuntimeError("in Json::Value::duplicateStringValue(): " - "Failed to allocate string value buffer"); - } - memcpy(newString, value, length); - newString[length] = 0; - return newString; + auto newString = static_cast(malloc(length + 1)); + if (newString == nullptr) + { + throwRuntimeError( + "in Json::Value::duplicateStringValue(): " + "Failed to allocate string value buffer"); + } + memcpy(newString, value, length); + newString[length] = 0; + return newString; } /* Record the length as a prefix. */ -static inline char* duplicateAndPrefixStringValue(const char* value, - unsigned int length) { - // Avoid an integer overflow in the call to malloc below by limiting length - // to a sane value. - JSON_ASSERT_MESSAGE(length <= static_cast(Value::maxInt) - - sizeof(unsigned) - 1U, - "in Json::Value::duplicateAndPrefixStringValue(): " - "length too big for prefixing"); - size_t actualLength = sizeof(length) + length + 1; - auto newString = static_cast(malloc(actualLength)); - if (newString == nullptr) { - throwRuntimeError("in Json::Value::duplicateAndPrefixStringValue(): " - "Failed to allocate string value buffer"); - } - *reinterpret_cast(newString) = length; - memcpy(newString + sizeof(unsigned), value, length); - newString[actualLength - 1U] = - 0; // to avoid buffer over-run accidents by users later - return newString; +static inline char* duplicateAndPrefixStringValue(const char* value, unsigned int length) +{ + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + JSON_ASSERT_MESSAGE(length <= static_cast(Value::maxInt) - sizeof(unsigned) - 1U, + "in Json::Value::duplicateAndPrefixStringValue(): " + "length too big for prefixing"); + size_t actualLength = sizeof(length) + length + 1; + auto newString = static_cast(malloc(actualLength)); + if (newString == nullptr) + { + throwRuntimeError( + "in Json::Value::duplicateAndPrefixStringValue(): " + "Failed to allocate string value buffer"); + } + *reinterpret_cast(newString) = length; + memcpy(newString + sizeof(unsigned), value, length); + newString[actualLength - 1U] = 0; // to avoid buffer over-run accidents by users later + return newString; } -inline static void decodePrefixedString(bool isPrefixed, char const* prefixed, - unsigned* length, char const** value) { - if (!isPrefixed) { - *length = static_cast(strlen(prefixed)); - *value = prefixed; - } else { - *length = *reinterpret_cast(prefixed); - *value = prefixed + sizeof(unsigned); - } +inline static void decodePrefixedString(bool isPrefixed, char const* prefixed, unsigned* length, char const** value) +{ + if (!isPrefixed) + { + *length = static_cast(strlen(prefixed)); + *value = prefixed; + } + else + { + *length = *reinterpret_cast(prefixed); + *value = prefixed + sizeof(unsigned); + } } /** Free the string duplicated by * duplicateStringValue()/duplicateAndPrefixStringValue(). */ #if JSONCPP_USING_SECURE_MEMORY -static inline void releasePrefixedStringValue(char* value) { - unsigned length = 0; - char const* valueDecoded; - decodePrefixedString(true, value, &length, &valueDecoded); - size_t const size = sizeof(unsigned) + length + 1U; - memset(value, 0, size); - free(value); +static inline void releasePrefixedStringValue(char* value) +{ + unsigned length = 0; + char const* valueDecoded; + decodePrefixedString(true, value, &length, &valueDecoded); + size_t const size = sizeof(unsigned) + length + 1U; + memset(value, 0, size); + free(value); } -static inline void releaseStringValue(char* value, unsigned length) { - // length==0 => we allocated the strings memory - size_t size = (length == 0) ? strlen(value) : length; - memset(value, 0, size); - free(value); +static inline void releaseStringValue(char* value, unsigned length) +{ + // length==0 => we allocated the strings memory + size_t size = (length == 0) ? strlen(value) : length; + memset(value, 0, size); + free(value); } -#else // !JSONCPP_USING_SECURE_MEMORY +#else // !JSONCPP_USING_SECURE_MEMORY static inline void releasePrefixedStringValue(char* value) { free(value); } static inline void releaseStringValue(char* value, unsigned) { free(value); } -#endif // JSONCPP_USING_SECURE_MEMORY +#endif // JSONCPP_USING_SECURE_MEMORY -} // namespace Json +} // namespace Json // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// @@ -2621,9 +2753,10 @@ static inline void releaseStringValue(char* value, unsigned) { free(value); } #if !defined(JSON_IS_AMALGAMATION) #include "json_valueiterator.inl" -#endif // if !defined(JSON_IS_AMALGAMATION) +#endif // if !defined(JSON_IS_AMALGAMATION) -namespace Json { +namespace Json +{ #if JSON_USE_EXCEPTION Exception::Exception(String msg) : msg_(std::move(msg)) {} @@ -2631,20 +2764,18 @@ Exception::~Exception() noexcept = default; char const* Exception::what() const noexcept { return msg_.c_str(); } RuntimeError::RuntimeError(String const& msg) : Exception(msg) {} LogicError::LogicError(String const& msg) : Exception(msg) {} -JSONCPP_NORETURN void throwRuntimeError(String const& msg) { - throw RuntimeError(msg); +JSONCPP_NORETURN void throwRuntimeError(String const& msg) { throw RuntimeError(msg); } +JSONCPP_NORETURN void throwLogicError(String const& msg) { throw LogicError(msg); } +#else // !JSON_USE_EXCEPTION +JSONCPP_NORETURN void throwRuntimeError(String const& msg) +{ + std::cerr << msg << std::endl; + abort(); } -JSONCPP_NORETURN void throwLogicError(String const& msg) { - throw LogicError(msg); -} -#else // !JSON_USE_EXCEPTION -JSONCPP_NORETURN void throwRuntimeError(String const& msg) { - std::cerr << msg << std::endl; - abort(); -} -JSONCPP_NORETURN void throwLogicError(String const& msg) { - std::cerr << msg << std::endl; - abort(); +JSONCPP_NORETURN void throwLogicError(String const& msg) +{ + std::cerr << msg << std::endl; + abort(); } #endif @@ -2661,92 +2792,91 @@ JSONCPP_NORETURN void throwLogicError(String const& msg) { Value::CZString::CZString(ArrayIndex index) : cstr_(nullptr), index_(index) {} -Value::CZString::CZString(char const* str, unsigned length, - DuplicationPolicy allocate) - : cstr_(str) { - // allocate != duplicate - storage_.policy_ = allocate & 0x3; - storage_.length_ = length & 0x3FFFFFFF; +Value::CZString::CZString(char const* str, unsigned length, DuplicationPolicy allocate) : cstr_(str) +{ + // allocate != duplicate + storage_.policy_ = allocate & 0x3; + storage_.length_ = length & 0x3FFFFFFF; } -Value::CZString::CZString(const CZString& other) { - cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != nullptr - ? duplicateStringValue(other.cstr_, other.storage_.length_) - : other.cstr_); - storage_.policy_ = - static_cast( - other.cstr_ - ? (static_cast(other.storage_.policy_) == - noDuplication - ? noDuplication - : duplicate) - : static_cast(other.storage_.policy_)) & - 3U; - storage_.length_ = other.storage_.length_; +Value::CZString::CZString(const CZString& other) +{ + cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != nullptr + ? duplicateStringValue(other.cstr_, other.storage_.length_) + : other.cstr_); + storage_.policy_ = + static_cast( + other.cstr_ + ? (static_cast(other.storage_.policy_) == noDuplication ? noDuplication : duplicate) + : static_cast(other.storage_.policy_)) & + 3U; + storage_.length_ = other.storage_.length_; } -Value::CZString::CZString(CZString&& other) noexcept - : cstr_(other.cstr_), index_(other.index_) { - other.cstr_ = nullptr; +Value::CZString::CZString(CZString&& other) noexcept : cstr_(other.cstr_), index_(other.index_) +{ + other.cstr_ = nullptr; } -Value::CZString::~CZString() { - if (cstr_ && storage_.policy_ == duplicate) { - releaseStringValue(const_cast(cstr_), - storage_.length_ + 1U); // +1 for null terminating - // character for sake of - // completeness but not actually - // necessary - } +Value::CZString::~CZString() +{ + if (cstr_ && storage_.policy_ == duplicate) + { + releaseStringValue(const_cast(cstr_), + storage_.length_ + 1U); // +1 for null terminating + // character for sake of + // completeness but not actually + // necessary + } } -void Value::CZString::swap(CZString& other) { - std::swap(cstr_, other.cstr_); - std::swap(index_, other.index_); +void Value::CZString::swap(CZString& other) +{ + std::swap(cstr_, other.cstr_); + std::swap(index_, other.index_); } -Value::CZString& Value::CZString::operator=(const CZString& other) { - cstr_ = other.cstr_; - index_ = other.index_; - return *this; +Value::CZString& Value::CZString::operator=(const CZString& other) +{ + cstr_ = other.cstr_; + index_ = other.index_; + return *this; } -Value::CZString& Value::CZString::operator=(CZString&& other) noexcept { - cstr_ = other.cstr_; - index_ = other.index_; - other.cstr_ = nullptr; - return *this; +Value::CZString& Value::CZString::operator=(CZString&& other) noexcept +{ + cstr_ = other.cstr_; + index_ = other.index_; + other.cstr_ = nullptr; + return *this; } -bool Value::CZString::operator<(const CZString& other) const { - if (!cstr_) - return index_ < other.index_; - // return strcmp(cstr_, other.cstr_) < 0; - // Assume both are strings. - unsigned this_len = this->storage_.length_; - unsigned other_len = other.storage_.length_; - unsigned min_len = std::min(this_len, other_len); - JSON_ASSERT(this->cstr_ && other.cstr_); - int comp = memcmp(this->cstr_, other.cstr_, min_len); - if (comp < 0) - return true; - if (comp > 0) - return false; - return (this_len < other_len); +bool Value::CZString::operator<(const CZString& other) const +{ + if (!cstr_) return index_ < other.index_; + // return strcmp(cstr_, other.cstr_) < 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + unsigned min_len = std::min(this_len, other_len); + JSON_ASSERT(this->cstr_ && other.cstr_); + int comp = memcmp(this->cstr_, other.cstr_, min_len); + if (comp < 0) return true; + if (comp > 0) return false; + return (this_len < other_len); } -bool Value::CZString::operator==(const CZString& other) const { - if (!cstr_) - return index_ == other.index_; - // return strcmp(cstr_, other.cstr_) == 0; - // Assume both are strings. - unsigned this_len = this->storage_.length_; - unsigned other_len = other.storage_.length_; - if (this_len != other_len) - return false; - JSON_ASSERT(this->cstr_ && other.cstr_); - int comp = memcmp(this->cstr_, other.cstr_, this_len); - return comp == 0; +bool Value::CZString::operator==(const CZString& other) const +{ + if (!cstr_) return index_ == other.index_; + // return strcmp(cstr_, other.cstr_) == 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + if (this_len != other_len) return false; + JSON_ASSERT(this->cstr_ && other.cstr_); + int comp = memcmp(this->cstr_, other.cstr_, this_len); + return comp == 0; } ArrayIndex Value::CZString::index() const { return index_; } @@ -2754,9 +2884,7 @@ ArrayIndex Value::CZString::index() const { return index_; } // const char* Value::CZString::c_str() const { return cstr_; } const char* Value::CZString::data() const { return cstr_; } unsigned Value::CZString::length() const { return storage_.length_; } -bool Value::CZString::isStaticString() const { - return storage_.policy_ == noDuplication; -} +bool Value::CZString::isStaticString() const { return storage_.policy_ == noDuplication; } // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// @@ -2770,197 +2898,210 @@ bool Value::CZString::isStaticString() const { * memset( this, 0, sizeof(Value) ) * This optimization is used in ValueInternalMap fast allocator. */ -Value::Value(ValueType type) { - static char const emptyString[] = ""; - initBasic(type); - switch (type) { - case nullValue: - break; - case intValue: - case uintValue: - value_.int_ = 0; - break; - case realValue: - value_.real_ = 0.0; - break; - case stringValue: - // allocated_ == false, so this is safe. - value_.string_ = const_cast(static_cast(emptyString)); - break; - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues(); - break; - case booleanValue: - value_.bool_ = false; - break; - default: - JSON_ASSERT_UNREACHABLE; - } +Value::Value(ValueType type) +{ + static char const emptyString[] = ""; + initBasic(type); + switch (type) + { + case nullValue: + break; + case intValue: + case uintValue: + value_.int_ = 0; + break; + case realValue: + value_.real_ = 0.0; + break; + case stringValue: + // allocated_ == false, so this is safe. + value_.string_ = const_cast(static_cast(emptyString)); + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(); + break; + case booleanValue: + value_.bool_ = false; + break; + default: + JSON_ASSERT_UNREACHABLE; + } } -Value::Value(Int value) { - initBasic(intValue); - value_.int_ = value; +Value::Value(Int value) +{ + initBasic(intValue); + value_.int_ = value; } -Value::Value(UInt value) { - initBasic(uintValue); - value_.uint_ = value; +Value::Value(UInt value) +{ + initBasic(uintValue); + value_.uint_ = value; } #if defined(JSON_HAS_INT64) -Value::Value(Int64 value) { - initBasic(intValue); - value_.int_ = value; +Value::Value(Int64 value) +{ + initBasic(intValue); + value_.int_ = value; } -Value::Value(UInt64 value) { - initBasic(uintValue); - value_.uint_ = value; +Value::Value(UInt64 value) +{ + initBasic(uintValue); + value_.uint_ = value; } -#endif // defined(JSON_HAS_INT64) +#endif // defined(JSON_HAS_INT64) -Value::Value(double value) { - initBasic(realValue); - value_.real_ = value; +Value::Value(double value) +{ + initBasic(realValue); + value_.real_ = value; } -Value::Value(const char* value) { - initBasic(stringValue, true); - JSON_ASSERT_MESSAGE(value != nullptr, - "Null Value Passed to Value Constructor"); - value_.string_ = duplicateAndPrefixStringValue( - value, static_cast(strlen(value))); +Value::Value(const char* value) +{ + initBasic(stringValue, true); + JSON_ASSERT_MESSAGE(value != nullptr, "Null Value Passed to Value Constructor"); + value_.string_ = duplicateAndPrefixStringValue(value, static_cast(strlen(value))); } -Value::Value(const char* begin, const char* end) { - initBasic(stringValue, true); - value_.string_ = - duplicateAndPrefixStringValue(begin, static_cast(end - begin)); +Value::Value(const char* begin, const char* end) +{ + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue(begin, static_cast(end - begin)); } -Value::Value(const String& value) { - initBasic(stringValue, true); - value_.string_ = duplicateAndPrefixStringValue( - value.data(), static_cast(value.length())); +Value::Value(const String& value) +{ + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue(value.data(), static_cast(value.length())); } -Value::Value(const StaticString& value) { - initBasic(stringValue); - value_.string_ = const_cast(value.c_str()); +Value::Value(const StaticString& value) +{ + initBasic(stringValue); + value_.string_ = const_cast(value.c_str()); } -Value::Value(bool value) { - initBasic(booleanValue); - value_.bool_ = value; +Value::Value(bool value) +{ + initBasic(booleanValue); + value_.bool_ = value; } -Value::Value(const Value& other) { - dupPayload(other); - dupMeta(other); +Value::Value(const Value& other) +{ + dupPayload(other); + dupMeta(other); } -Value::Value(Value&& other) noexcept { - initBasic(nullValue); - swap(other); +Value::Value(Value&& other) noexcept +{ + initBasic(nullValue); + swap(other); } -Value::~Value() { - releasePayload(); - value_.uint_ = 0; +Value::~Value() +{ + releasePayload(); + value_.uint_ = 0; } -Value& Value::operator=(const Value& other) { - Value(other).swap(*this); - return *this; +Value& Value::operator=(const Value& other) +{ + Value(other).swap(*this); + return *this; } -Value& Value::operator=(Value&& other) noexcept { - other.swap(*this); - return *this; +Value& Value::operator=(Value&& other) noexcept +{ + other.swap(*this); + return *this; } -void Value::swapPayload(Value& other) { - std::swap(bits_, other.bits_); - std::swap(value_, other.value_); +void Value::swapPayload(Value& other) +{ + std::swap(bits_, other.bits_); + std::swap(value_, other.value_); } -void Value::copyPayload(const Value& other) { - releasePayload(); - dupPayload(other); +void Value::copyPayload(const Value& other) +{ + releasePayload(); + dupPayload(other); } -void Value::swap(Value& other) { - swapPayload(other); - std::swap(comments_, other.comments_); - std::swap(start_, other.start_); - std::swap(limit_, other.limit_); +void Value::swap(Value& other) +{ + swapPayload(other); + std::swap(comments_, other.comments_); + std::swap(start_, other.start_); + std::swap(limit_, other.limit_); } -void Value::copy(const Value& other) { - copyPayload(other); - dupMeta(other); +void Value::copy(const Value& other) +{ + copyPayload(other); + dupMeta(other); } -ValueType Value::type() const { - return static_cast(bits_.value_type_); +ValueType Value::type() const { return static_cast(bits_.value_type_); } + +int Value::compare(const Value& other) const +{ + if (*this < other) return -1; + if (*this > other) return 1; + return 0; } -int Value::compare(const Value& other) const { - if (*this < other) - return -1; - if (*this > other) - return 1; - return 0; -} - -bool Value::operator<(const Value& other) const { - int typeDelta = type() - other.type(); - if (typeDelta) - return typeDelta < 0; - switch (type()) { - case nullValue: - return false; - case intValue: - return value_.int_ < other.value_.int_; - case uintValue: - return value_.uint_ < other.value_.uint_; - case realValue: - return value_.real_ < other.value_.real_; - case booleanValue: - return value_.bool_ < other.value_.bool_; - case stringValue: { - if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { - return other.value_.string_ != nullptr; +bool Value::operator<(const Value& other) const +{ + int typeDelta = type() - other.type(); + if (typeDelta) return typeDelta < 0; + switch (type()) + { + case nullValue: + return false; + case intValue: + return value_.int_ < other.value_.int_; + case uintValue: + return value_.uint_ < other.value_.uint_; + case realValue: + return value_.real_ < other.value_.real_; + case booleanValue: + return value_.bool_ < other.value_.bool_; + case stringValue: + { + if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) + { + return other.value_.string_ != nullptr; + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, &this_str); + decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, &other_str); + unsigned min_len = std::min(this_len, other_len); + JSON_ASSERT(this_str && other_str); + int comp = memcmp(this_str, other_str, min_len); + if (comp < 0) return true; + if (comp > 0) return false; + return (this_len < other_len); + } + case arrayValue: + case objectValue: + { + auto thisSize = value_.map_->size(); + auto otherSize = other.value_.map_->size(); + if (thisSize != otherSize) return thisSize < otherSize; + return (*value_.map_) < (*other.value_.map_); + } + default: + JSON_ASSERT_UNREACHABLE; } - unsigned this_len; - unsigned other_len; - char const* this_str; - char const* other_str; - decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, - &this_str); - decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, - &other_str); - unsigned min_len = std::min(this_len, other_len); - JSON_ASSERT(this_str && other_str); - int comp = memcmp(this_str, other_str, min_len); - if (comp < 0) - return true; - if (comp > 0) - return false; - return (this_len < other_len); - } - case arrayValue: - case objectValue: { - auto thisSize = value_.map_->size(); - auto otherSize = other.value_.map_->size(); - if (thisSize != otherSize) - return thisSize < otherSize; - return (*value_.map_) < (*other.value_.map_); - } - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable + return false; // unreachable } bool Value::operator<=(const Value& other) const { return !(other < *this); } @@ -2969,825 +3110,823 @@ bool Value::operator>=(const Value& other) const { return !(*this < other); } bool Value::operator>(const Value& other) const { return other < *this; } -bool Value::operator==(const Value& other) const { - if (type() != other.type()) - return false; - switch (type()) { - case nullValue: - return true; - case intValue: - return value_.int_ == other.value_.int_; - case uintValue: - return value_.uint_ == other.value_.uint_; - case realValue: - return value_.real_ == other.value_.real_; - case booleanValue: - return value_.bool_ == other.value_.bool_; - case stringValue: { - if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { - return (value_.string_ == other.value_.string_); +bool Value::operator==(const Value& other) const +{ + if (type() != other.type()) return false; + switch (type()) + { + case nullValue: + return true; + case intValue: + return value_.int_ == other.value_.int_; + case uintValue: + return value_.uint_ == other.value_.uint_; + case realValue: + return value_.real_ == other.value_.real_; + case booleanValue: + return value_.bool_ == other.value_.bool_; + case stringValue: + { + if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) + { + return (value_.string_ == other.value_.string_); + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, &this_str); + decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, &other_str); + if (this_len != other_len) return false; + JSON_ASSERT(this_str && other_str); + int comp = memcmp(this_str, other_str, this_len); + return comp == 0; + } + case arrayValue: + case objectValue: + return value_.map_->size() == other.value_.map_->size() && (*value_.map_) == (*other.value_.map_); + default: + JSON_ASSERT_UNREACHABLE; } - unsigned this_len; - unsigned other_len; - char const* this_str; - char const* other_str; - decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, - &this_str); - decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, - &other_str); - if (this_len != other_len) - return false; - JSON_ASSERT(this_str && other_str); - int comp = memcmp(this_str, other_str, this_len); - return comp == 0; - } - case arrayValue: - case objectValue: - return value_.map_->size() == other.value_.map_->size() && - (*value_.map_) == (*other.value_.map_); - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable + return false; // unreachable } bool Value::operator!=(const Value& other) const { return !(*this == other); } -const char* Value::asCString() const { - JSON_ASSERT_MESSAGE(type() == stringValue, - "in Json::Value::asCString(): requires stringValue"); - if (value_.string_ == nullptr) - return nullptr; - unsigned this_len; - char const* this_str; - decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, - &this_str); - return this_str; +const char* Value::asCString() const +{ + JSON_ASSERT_MESSAGE(type() == stringValue, "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == nullptr) return nullptr; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, &this_str); + return this_str; } #if JSONCPP_USING_SECURE_MEMORY -unsigned Value::getCStringLength() const { - JSON_ASSERT_MESSAGE(type() == stringValue, - "in Json::Value::asCString(): requires stringValue"); - if (value_.string_ == 0) - return 0; - unsigned this_len; - char const* this_str; - decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, - &this_str); - return this_len; +unsigned Value::getCStringLength() const +{ + JSON_ASSERT_MESSAGE(type() == stringValue, "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == 0) return 0; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, &this_str); + return this_len; } #endif -bool Value::getString(char const** begin, char const** end) const { - if (type() != stringValue) - return false; - if (value_.string_ == nullptr) - return false; - unsigned length; - decodePrefixedString(this->isAllocated(), this->value_.string_, &length, - begin); - *end = *begin + length; - return true; +bool Value::getString(char const** begin, char const** end) const +{ + if (type() != stringValue) return false; + if (value_.string_ == nullptr) return false; + unsigned length; + decodePrefixedString(this->isAllocated(), this->value_.string_, &length, begin); + *end = *begin + length; + return true; } -String Value::asString() const { - switch (type()) { - case nullValue: - return ""; - case stringValue: { - if (value_.string_ == nullptr) - return ""; - unsigned this_len; - char const* this_str; - decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, - &this_str); - return String(this_str, this_len); - } - case booleanValue: - return value_.bool_ ? "true" : "false"; - case intValue: - return valueToString(value_.int_); - case uintValue: - return valueToString(value_.uint_); - case realValue: - return valueToString(value_.real_); - default: - JSON_FAIL_MESSAGE("Type is not convertible to string"); - } +String Value::asString() const +{ + switch (type()) + { + case nullValue: + return ""; + case stringValue: + { + if (value_.string_ == nullptr) return ""; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, &this_str); + return String(this_str, this_len); + } + case booleanValue: + return value_.bool_ ? "true" : "false"; + case intValue: + return valueToString(value_.int_); + case uintValue: + return valueToString(value_.uint_); + case realValue: + return valueToString(value_.real_); + default: + JSON_FAIL_MESSAGE("Type is not convertible to string"); + } } -Value::Int Value::asInt() const { - switch (type()) { - case intValue: - JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range"); - return Int(value_.int_); - case uintValue: - JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range"); - return Int(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt), - "double out of Int range"); - return Int(value_.real_); - case nullValue: - return 0; - case booleanValue: - return value_.bool_ ? 1 : 0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to Int."); +Value::Int Value::asInt() const +{ + switch (type()) + { + case intValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range"); + return Int(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range"); + return Int(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt), "double out of Int range"); + return Int(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int."); } -Value::UInt Value::asUInt() const { - switch (type()) { - case intValue: - JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range"); - return UInt(value_.int_); - case uintValue: - JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range"); - return UInt(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt), - "double out of UInt range"); - return UInt(value_.real_); - case nullValue: - return 0; - case booleanValue: - return value_.bool_ ? 1 : 0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to UInt."); +Value::UInt Value::asUInt() const +{ + switch (type()) + { + case intValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range"); + return UInt(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range"); + return UInt(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt), "double out of UInt range"); + return UInt(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt."); } #if defined(JSON_HAS_INT64) -Value::Int64 Value::asInt64() const { - switch (type()) { - case intValue: - return Int64(value_.int_); - case uintValue: - JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range"); - return Int64(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64), - "double out of Int64 range"); - return Int64(value_.real_); - case nullValue: - return 0; - case booleanValue: - return value_.bool_ ? 1 : 0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to Int64."); +Value::Int64 Value::asInt64() const +{ + switch (type()) + { + case intValue: + return Int64(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range"); + return Int64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64), "double out of Int64 range"); + return Int64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int64."); } -Value::UInt64 Value::asUInt64() const { - switch (type()) { - case intValue: - JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range"); - return UInt64(value_.int_); - case uintValue: - return UInt64(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64), - "double out of UInt64 range"); - return UInt64(value_.real_); - case nullValue: - return 0; - case booleanValue: - return value_.bool_ ? 1 : 0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to UInt64."); +Value::UInt64 Value::asUInt64() const +{ + switch (type()) + { + case intValue: + JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range"); + return UInt64(value_.int_); + case uintValue: + return UInt64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64), "double out of UInt64 range"); + return UInt64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt64."); } -#endif // if defined(JSON_HAS_INT64) +#endif // if defined(JSON_HAS_INT64) -LargestInt Value::asLargestInt() const { +LargestInt Value::asLargestInt() const +{ #if defined(JSON_NO_INT64) - return asInt(); + return asInt(); #else - return asInt64(); + return asInt64(); #endif } -LargestUInt Value::asLargestUInt() const { +LargestUInt Value::asLargestUInt() const +{ #if defined(JSON_NO_INT64) - return asUInt(); + return asUInt(); #else - return asUInt64(); + return asUInt64(); #endif } -double Value::asDouble() const { - switch (type()) { - case intValue: - return static_cast(value_.int_); - case uintValue: +double Value::asDouble() const +{ + switch (type()) + { + case intValue: + return static_cast(value_.int_); + case uintValue: #if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return static_cast(value_.uint_); -#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return integerToDouble(value_.uint_); -#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - case realValue: - return value_.real_; - case nullValue: - return 0.0; - case booleanValue: - return value_.bool_ ? 1.0 : 0.0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to double."); + return static_cast(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return integerToDouble(value_.uint_); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return value_.real_; + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0 : 0.0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to double."); } -float Value::asFloat() const { - switch (type()) { - case intValue: - return static_cast(value_.int_); - case uintValue: +float Value::asFloat() const +{ + switch (type()) + { + case intValue: + return static_cast(value_.int_); + case uintValue: #if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return static_cast(value_.uint_); -#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - // This can fail (silently?) if the value is bigger than MAX_FLOAT. - return static_cast(integerToDouble(value_.uint_)); -#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - case realValue: - return static_cast(value_.real_); - case nullValue: - return 0.0; - case booleanValue: - return value_.bool_ ? 1.0F : 0.0F; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to float."); + return static_cast(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + // This can fail (silently?) if the value is bigger than MAX_FLOAT. + return static_cast(integerToDouble(value_.uint_)); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return static_cast(value_.real_); + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0F : 0.0F; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to float."); } -bool Value::asBool() const { - switch (type()) { - case booleanValue: - return value_.bool_; - case nullValue: +bool Value::asBool() const +{ + switch (type()) + { + case booleanValue: + return value_.bool_; + case nullValue: + return false; + case intValue: + return value_.int_ != 0; + case uintValue: + return value_.uint_ != 0; + case realValue: + { + // According to JavaScript language zero or NaN is regarded as false + const auto value_classification = std::fpclassify(value_.real_); + return value_classification != FP_ZERO && value_classification != FP_NAN; + } + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to bool."); +} + +bool Value::isConvertibleTo(ValueType other) const +{ + switch (other) + { + case nullValue: + return (isNumeric() && asDouble() == 0.0) || (type() == booleanValue && !value_.bool_) || + (type() == stringValue && asString().empty()) || (type() == arrayValue && value_.map_->empty()) || + (type() == objectValue && value_.map_->empty()) || type() == nullValue; + case intValue: + return isInt() || (type() == realValue && InRange(value_.real_, minInt, maxInt)) || + type() == booleanValue || type() == nullValue; + case uintValue: + return isUInt() || (type() == realValue && InRange(value_.real_, 0, maxUInt)) || type() == booleanValue || + type() == nullValue; + case realValue: + return isNumeric() || type() == booleanValue || type() == nullValue; + case booleanValue: + return isNumeric() || type() == booleanValue || type() == nullValue; + case stringValue: + return isNumeric() || type() == booleanValue || type() == stringValue || type() == nullValue; + case arrayValue: + return type() == arrayValue || type() == nullValue; + case objectValue: + return type() == objectValue || type() == nullValue; + } + JSON_ASSERT_UNREACHABLE; return false; - case intValue: - return value_.int_ != 0; - case uintValue: - return value_.uint_ != 0; - case realValue: { - // According to JavaScript language zero or NaN is regarded as false - const auto value_classification = std::fpclassify(value_.real_); - return value_classification != FP_ZERO && value_classification != FP_NAN; - } - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to bool."); -} - -bool Value::isConvertibleTo(ValueType other) const { - switch (other) { - case nullValue: - return (isNumeric() && asDouble() == 0.0) || - (type() == booleanValue && !value_.bool_) || - (type() == stringValue && asString().empty()) || - (type() == arrayValue && value_.map_->empty()) || - (type() == objectValue && value_.map_->empty()) || - type() == nullValue; - case intValue: - return isInt() || - (type() == realValue && InRange(value_.real_, minInt, maxInt)) || - type() == booleanValue || type() == nullValue; - case uintValue: - return isUInt() || - (type() == realValue && InRange(value_.real_, 0, maxUInt)) || - type() == booleanValue || type() == nullValue; - case realValue: - return isNumeric() || type() == booleanValue || type() == nullValue; - case booleanValue: - return isNumeric() || type() == booleanValue || type() == nullValue; - case stringValue: - return isNumeric() || type() == booleanValue || type() == stringValue || - type() == nullValue; - case arrayValue: - return type() == arrayValue || type() == nullValue; - case objectValue: - return type() == objectValue || type() == nullValue; - } - JSON_ASSERT_UNREACHABLE; - return false; } /// Number of values in array or object -ArrayIndex Value::size() const { - switch (type()) { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - case stringValue: - return 0; - case arrayValue: // size of the array is highest index + 1 - if (!value_.map_->empty()) { - ObjectValues::const_iterator itLast = value_.map_->end(); - --itLast; - return (*itLast).first.index() + 1; +ArrayIndex Value::size() const +{ + switch (type()) + { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + case stringValue: + return 0; + case arrayValue: // size of the array is highest index + 1 + if (!value_.map_->empty()) + { + ObjectValues::const_iterator itLast = value_.map_->end(); + --itLast; + return (*itLast).first.index() + 1; + } + return 0; + case objectValue: + return ArrayIndex(value_.map_->size()); } - return 0; - case objectValue: - return ArrayIndex(value_.map_->size()); - } - JSON_ASSERT_UNREACHABLE; - return 0; // unreachable; + JSON_ASSERT_UNREACHABLE; + return 0; // unreachable; } -bool Value::empty() const { - if (isNull() || isArray() || isObject()) - return size() == 0U; - return false; +bool Value::empty() const +{ + if (isNull() || isArray() || isObject()) return size() == 0U; + return false; } Value::operator bool() const { return !isNull(); } -void Value::clear() { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue || - type() == objectValue, - "in Json::Value::clear(): requires complex value"); - start_ = 0; - limit_ = 0; - switch (type()) { - case arrayValue: - case objectValue: - value_.map_->clear(); - break; - default: - break; - } -} - -void Value::resize(ArrayIndex newSize) { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, - "in Json::Value::resize(): requires arrayValue"); - if (type() == nullValue) - *this = Value(arrayValue); - ArrayIndex oldSize = size(); - if (newSize == 0) - clear(); - else if (newSize > oldSize) - for (ArrayIndex i = oldSize; i < newSize; ++i) - (*this)[i]; - else { - for (ArrayIndex index = newSize; index < oldSize; ++index) { - value_.map_->erase(index); +void Value::clear() +{ + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue || type() == objectValue, + "in Json::Value::clear(): requires complex value"); + start_ = 0; + limit_ = 0; + switch (type()) + { + case arrayValue: + case objectValue: + value_.map_->clear(); + break; + default: + break; } - JSON_ASSERT(size() == newSize); - } } -Value& Value::operator[](ArrayIndex index) { - JSON_ASSERT_MESSAGE( - type() == nullValue || type() == arrayValue, - "in Json::Value::operator[](ArrayIndex): requires arrayValue"); - if (type() == nullValue) - *this = Value(arrayValue); - CZString key(index); - auto it = value_.map_->lower_bound(key); - if (it != value_.map_->end() && (*it).first == key) +void Value::resize(ArrayIndex newSize) +{ + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, "in Json::Value::resize(): requires arrayValue"); + if (type() == nullValue) *this = Value(arrayValue); + ArrayIndex oldSize = size(); + if (newSize == 0) + clear(); + else if (newSize > oldSize) + for (ArrayIndex i = oldSize; i < newSize; ++i) (*this)[i]; + else + { + for (ArrayIndex index = newSize; index < oldSize; ++index) + { + value_.map_->erase(index); + } + JSON_ASSERT(size() == newSize); + } +} + +Value& Value::operator[](ArrayIndex index) +{ + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, + "in Json::Value::operator[](ArrayIndex): requires arrayValue"); + if (type() == nullValue) *this = Value(arrayValue); + CZString key(index); + auto it = value_.map_->lower_bound(key); + if (it != value_.map_->end() && (*it).first == key) return (*it).second; + + ObjectValues::value_type defaultValue(key, nullSingleton()); + it = value_.map_->insert(it, defaultValue); return (*it).second; - - ObjectValues::value_type defaultValue(key, nullSingleton()); - it = value_.map_->insert(it, defaultValue); - return (*it).second; } -Value& Value::operator[](int index) { - JSON_ASSERT_MESSAGE( - index >= 0, - "in Json::Value::operator[](int index): index cannot be negative"); - return (*this)[ArrayIndex(index)]; +Value& Value::operator[](int index) +{ + JSON_ASSERT_MESSAGE(index >= 0, "in Json::Value::operator[](int index): index cannot be negative"); + return (*this)[ArrayIndex(index)]; } -const Value& Value::operator[](ArrayIndex index) const { - JSON_ASSERT_MESSAGE( - type() == nullValue || type() == arrayValue, - "in Json::Value::operator[](ArrayIndex)const: requires arrayValue"); - if (type() == nullValue) - return nullSingleton(); - CZString key(index); - ObjectValues::const_iterator it = value_.map_->find(key); - if (it == value_.map_->end()) - return nullSingleton(); - return (*it).second; +const Value& Value::operator[](ArrayIndex index) const +{ + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, + "in Json::Value::operator[](ArrayIndex)const: requires arrayValue"); + if (type() == nullValue) return nullSingleton(); + CZString key(index); + ObjectValues::const_iterator it = value_.map_->find(key); + if (it == value_.map_->end()) return nullSingleton(); + return (*it).second; } -const Value& Value::operator[](int index) const { - JSON_ASSERT_MESSAGE( - index >= 0, - "in Json::Value::operator[](int index) const: index cannot be negative"); - return (*this)[ArrayIndex(index)]; +const Value& Value::operator[](int index) const +{ + JSON_ASSERT_MESSAGE(index >= 0, "in Json::Value::operator[](int index) const: index cannot be negative"); + return (*this)[ArrayIndex(index)]; } -void Value::initBasic(ValueType type, bool allocated) { - setType(type); - setIsAllocated(allocated); - comments_ = Comments{}; - start_ = 0; - limit_ = 0; +void Value::initBasic(ValueType type, bool allocated) +{ + setType(type); + setIsAllocated(allocated); + comments_ = Comments{}; + start_ = 0; + limit_ = 0; } -void Value::dupPayload(const Value& other) { - setType(other.type()); - setIsAllocated(false); - switch (type()) { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - value_ = other.value_; - break; - case stringValue: - if (other.value_.string_ && other.isAllocated()) { - unsigned len; - char const* str; - decodePrefixedString(other.isAllocated(), other.value_.string_, &len, - &str); - value_.string_ = duplicateAndPrefixStringValue(str, len); - setIsAllocated(true); - } else { - value_.string_ = other.value_.string_; +void Value::dupPayload(const Value& other) +{ + setType(other.type()); + setIsAllocated(false); + switch (type()) + { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + value_ = other.value_; + break; + case stringValue: + if (other.value_.string_ && other.isAllocated()) + { + unsigned len; + char const* str; + decodePrefixedString(other.isAllocated(), other.value_.string_, &len, &str); + value_.string_ = duplicateAndPrefixStringValue(str, len); + setIsAllocated(true); + } + else + { + value_.string_ = other.value_.string_; + } + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(*other.value_.map_); + break; + default: + JSON_ASSERT_UNREACHABLE; } - break; - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues(*other.value_.map_); - break; - default: - JSON_ASSERT_UNREACHABLE; - } } -void Value::releasePayload() { - switch (type()) { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - break; - case stringValue: - if (isAllocated()) - releasePrefixedStringValue(value_.string_); - break; - case arrayValue: - case objectValue: - delete value_.map_; - break; - default: - JSON_ASSERT_UNREACHABLE; - } +void Value::releasePayload() +{ + switch (type()) + { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + break; + case stringValue: + if (isAllocated()) releasePrefixedStringValue(value_.string_); + break; + case arrayValue: + case objectValue: + delete value_.map_; + break; + default: + JSON_ASSERT_UNREACHABLE; + } } -void Value::dupMeta(const Value& other) { - comments_ = other.comments_; - start_ = other.start_; - limit_ = other.limit_; +void Value::dupMeta(const Value& other) +{ + comments_ = other.comments_; + start_ = other.start_; + limit_ = other.limit_; } // Access an object value by name, create a null member if it does not exist. // @pre Type of '*this' is object or null. // @param key is null-terminated. -Value& Value::resolveReference(const char* key) { - JSON_ASSERT_MESSAGE( - type() == nullValue || type() == objectValue, - "in Json::Value::resolveReference(): requires objectValue"); - if (type() == nullValue) - *this = Value(objectValue); - CZString actualKey(key, static_cast(strlen(key)), - CZString::noDuplication); // NOTE! - auto it = value_.map_->lower_bound(actualKey); - if (it != value_.map_->end() && (*it).first == actualKey) - return (*it).second; +Value& Value::resolveReference(const char* key) +{ + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::resolveReference(): requires objectValue"); + if (type() == nullValue) *this = Value(objectValue); + CZString actualKey(key, static_cast(strlen(key)), + CZString::noDuplication); // NOTE! + auto it = value_.map_->lower_bound(actualKey); + if (it != value_.map_->end() && (*it).first == actualKey) return (*it).second; - ObjectValues::value_type defaultValue(actualKey, nullSingleton()); - it = value_.map_->insert(it, defaultValue); - Value& value = (*it).second; - return value; + ObjectValues::value_type defaultValue(actualKey, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; } // @param key is not null-terminated. -Value& Value::resolveReference(char const* key, char const* end) { - JSON_ASSERT_MESSAGE( - type() == nullValue || type() == objectValue, - "in Json::Value::resolveReference(key, end): requires objectValue"); - if (type() == nullValue) - *this = Value(objectValue); - CZString actualKey(key, static_cast(end - key), - CZString::duplicateOnCopy); - auto it = value_.map_->lower_bound(actualKey); - if (it != value_.map_->end() && (*it).first == actualKey) - return (*it).second; +Value& Value::resolveReference(char const* key, char const* end) +{ + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::resolveReference(key, end): requires objectValue"); + if (type() == nullValue) *this = Value(objectValue); + CZString actualKey(key, static_cast(end - key), CZString::duplicateOnCopy); + auto it = value_.map_->lower_bound(actualKey); + if (it != value_.map_->end() && (*it).first == actualKey) return (*it).second; - ObjectValues::value_type defaultValue(actualKey, nullSingleton()); - it = value_.map_->insert(it, defaultValue); - Value& value = (*it).second; - return value; + ObjectValues::value_type defaultValue(actualKey, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; } -Value Value::get(ArrayIndex index, const Value& defaultValue) const { - const Value* value = &((*this)[index]); - return value == &nullSingleton() ? defaultValue : *value; +Value Value::get(ArrayIndex index, const Value& defaultValue) const +{ + const Value* value = &((*this)[index]); + return value == &nullSingleton() ? defaultValue : *value; } bool Value::isValidIndex(ArrayIndex index) const { return index < size(); } -Value const* Value::find(char const* begin, char const* end) const { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, - "in Json::Value::find(begin, end): requires " - "objectValue or nullValue"); - if (type() == nullValue) - return nullptr; - CZString actualKey(begin, static_cast(end - begin), - CZString::noDuplication); - ObjectValues::const_iterator it = value_.map_->find(actualKey); - if (it == value_.map_->end()) - return nullptr; - return &(*it).second; +Value const* Value::find(char const* begin, char const* end) const +{ + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::find(begin, end): requires " + "objectValue or nullValue"); + if (type() == nullValue) return nullptr; + CZString actualKey(begin, static_cast(end - begin), CZString::noDuplication); + ObjectValues::const_iterator it = value_.map_->find(actualKey); + if (it == value_.map_->end()) return nullptr; + return &(*it).second; } -Value* Value::demand(char const* begin, char const* end) { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, - "in Json::Value::demand(begin, end): requires " - "objectValue or nullValue"); - return &resolveReference(begin, end); +Value* Value::demand(char const* begin, char const* end) +{ + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::demand(begin, end): requires " + "objectValue or nullValue"); + return &resolveReference(begin, end); } -const Value& Value::operator[](const char* key) const { - Value const* found = find(key, key + strlen(key)); - if (!found) - return nullSingleton(); - return *found; +const Value& Value::operator[](const char* key) const +{ + Value const* found = find(key, key + strlen(key)); + if (!found) return nullSingleton(); + return *found; } -Value const& Value::operator[](const String& key) const { - Value const* found = find(key.data(), key.data() + key.length()); - if (!found) - return nullSingleton(); - return *found; +Value const& Value::operator[](const String& key) const +{ + Value const* found = find(key.data(), key.data() + key.length()); + if (!found) return nullSingleton(); + return *found; } -Value& Value::operator[](const char* key) { - return resolveReference(key, key + strlen(key)); -} +Value& Value::operator[](const char* key) { return resolveReference(key, key + strlen(key)); } -Value& Value::operator[](const String& key) { - return resolveReference(key.data(), key.data() + key.length()); -} +Value& Value::operator[](const String& key) { return resolveReference(key.data(), key.data() + key.length()); } -Value& Value::operator[](const StaticString& key) { - return resolveReference(key.c_str()); -} +Value& Value::operator[](const StaticString& key) { return resolveReference(key.c_str()); } Value& Value::append(const Value& value) { return append(Value(value)); } -Value& Value::append(Value&& value) { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, - "in Json::Value::append: requires arrayValue"); - if (type() == nullValue) { - *this = Value(arrayValue); - } - return this->value_.map_->emplace(size(), std::move(value)).first->second; +Value& Value::append(Value&& value) +{ + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, "in Json::Value::append: requires arrayValue"); + if (type() == nullValue) + { + *this = Value(arrayValue); + } + return this->value_.map_->emplace(size(), std::move(value)).first->second; } -bool Value::insert(ArrayIndex index, const Value& newValue) { - return insert(index, Value(newValue)); +bool Value::insert(ArrayIndex index, const Value& newValue) { return insert(index, Value(newValue)); } + +bool Value::insert(ArrayIndex index, Value&& newValue) +{ + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, "in Json::Value::insert: requires arrayValue"); + ArrayIndex length = size(); + if (index > length) + { + return false; + } + for (ArrayIndex i = length; i > index; i--) + { + (*this)[i] = std::move((*this)[i - 1]); + } + (*this)[index] = std::move(newValue); + return true; } -bool Value::insert(ArrayIndex index, Value&& newValue) { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, - "in Json::Value::insert: requires arrayValue"); - ArrayIndex length = size(); - if (index > length) { - return false; - } - for (ArrayIndex i = length; i > index; i--) { - (*this)[i] = std::move((*this)[i - 1]); - } - (*this)[index] = std::move(newValue); - return true; +Value Value::get(char const* begin, char const* end, Value const& defaultValue) const +{ + Value const* found = find(begin, end); + return !found ? defaultValue : *found; +} +Value Value::get(char const* key, Value const& defaultValue) const { return get(key, key + strlen(key), defaultValue); } +Value Value::get(String const& key, Value const& defaultValue) const +{ + return get(key.data(), key.data() + key.length(), defaultValue); } -Value Value::get(char const* begin, char const* end, - Value const& defaultValue) const { - Value const* found = find(begin, end); - return !found ? defaultValue : *found; +bool Value::removeMember(const char* begin, const char* end, Value* removed) +{ + if (type() != objectValue) + { + return false; + } + CZString actualKey(begin, static_cast(end - begin), CZString::noDuplication); + auto it = value_.map_->find(actualKey); + if (it == value_.map_->end()) return false; + if (removed) *removed = std::move(it->second); + value_.map_->erase(it); + return true; } -Value Value::get(char const* key, Value const& defaultValue) const { - return get(key, key + strlen(key), defaultValue); -} -Value Value::get(String const& key, Value const& defaultValue) const { - return get(key.data(), key.data() + key.length(), defaultValue); +bool Value::removeMember(const char* key, Value* removed) { return removeMember(key, key + strlen(key), removed); } +bool Value::removeMember(String const& key, Value* removed) +{ + return removeMember(key.data(), key.data() + key.length(), removed); } +void Value::removeMember(const char* key) +{ + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::removeMember(): requires objectValue"); + if (type() == nullValue) return; -bool Value::removeMember(const char* begin, const char* end, Value* removed) { - if (type() != objectValue) { - return false; - } - CZString actualKey(begin, static_cast(end - begin), - CZString::noDuplication); - auto it = value_.map_->find(actualKey); - if (it == value_.map_->end()) - return false; - if (removed) - *removed = std::move(it->second); - value_.map_->erase(it); - return true; -} -bool Value::removeMember(const char* key, Value* removed) { - return removeMember(key, key + strlen(key), removed); -} -bool Value::removeMember(String const& key, Value* removed) { - return removeMember(key.data(), key.data() + key.length(), removed); -} -void Value::removeMember(const char* key) { - JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, - "in Json::Value::removeMember(): requires objectValue"); - if (type() == nullValue) - return; - - CZString actualKey(key, unsigned(strlen(key)), CZString::noDuplication); - value_.map_->erase(actualKey); + CZString actualKey(key, unsigned(strlen(key)), CZString::noDuplication); + value_.map_->erase(actualKey); } void Value::removeMember(const String& key) { removeMember(key.c_str()); } -bool Value::removeIndex(ArrayIndex index, Value* removed) { - if (type() != arrayValue) { - return false; - } - CZString key(index); - auto it = value_.map_->find(key); - if (it == value_.map_->end()) { - return false; - } - if (removed) - *removed = it->second; - ArrayIndex oldSize = size(); - // shift left all items left, into the place of the "removed" - for (ArrayIndex i = index; i < (oldSize - 1); ++i) { - CZString keey(i); - (*value_.map_)[keey] = (*this)[i + 1]; - } - // erase the last one ("leftover") - CZString keyLast(oldSize - 1); - auto itLast = value_.map_->find(keyLast); - value_.map_->erase(itLast); - return true; +bool Value::removeIndex(ArrayIndex index, Value* removed) +{ + if (type() != arrayValue) + { + return false; + } + CZString key(index); + auto it = value_.map_->find(key); + if (it == value_.map_->end()) + { + return false; + } + if (removed) *removed = it->second; + ArrayIndex oldSize = size(); + // shift left all items left, into the place of the "removed" + for (ArrayIndex i = index; i < (oldSize - 1); ++i) + { + CZString keey(i); + (*value_.map_)[keey] = (*this)[i + 1]; + } + // erase the last one ("leftover") + CZString keyLast(oldSize - 1); + auto itLast = value_.map_->find(keyLast); + value_.map_->erase(itLast); + return true; } -bool Value::isMember(char const* begin, char const* end) const { - Value const* value = find(begin, end); - return nullptr != value; +bool Value::isMember(char const* begin, char const* end) const +{ + Value const* value = find(begin, end); + return nullptr != value; } -bool Value::isMember(char const* key) const { - return isMember(key, key + strlen(key)); -} -bool Value::isMember(String const& key) const { - return isMember(key.data(), key.data() + key.length()); +bool Value::isMember(char const* key) const { return isMember(key, key + strlen(key)); } +bool Value::isMember(String const& key) const { return isMember(key.data(), key.data() + key.length()); } + +Value::Members Value::getMemberNames() const +{ + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::getMemberNames(), value must be objectValue"); + if (type() == nullValue) return Value::Members(); + Members members; + members.reserve(value_.map_->size()); + ObjectValues::const_iterator it = value_.map_->begin(); + ObjectValues::const_iterator itEnd = value_.map_->end(); + for (; it != itEnd; ++it) + { + members.push_back(String((*it).first.data(), (*it).first.length())); + } + return members; } -Value::Members Value::getMemberNames() const { - JSON_ASSERT_MESSAGE( - type() == nullValue || type() == objectValue, - "in Json::Value::getMemberNames(), value must be objectValue"); - if (type() == nullValue) - return Value::Members(); - Members members; - members.reserve(value_.map_->size()); - ObjectValues::const_iterator it = value_.map_->begin(); - ObjectValues::const_iterator itEnd = value_.map_->end(); - for (; it != itEnd; ++it) { - members.push_back(String((*it).first.data(), (*it).first.length())); - } - return members; -} - -static bool IsIntegral(double d) { - double integral_part; - return modf(d, &integral_part) == 0.0; +static bool IsIntegral(double d) +{ + double integral_part; + return modf(d, &integral_part) == 0.0; } bool Value::isNull() const { return type() == nullValue; } bool Value::isBool() const { return type() == booleanValue; } -bool Value::isInt() const { - switch (type()) { - case intValue: +bool Value::isInt() const +{ + switch (type()) + { + case intValue: #if defined(JSON_HAS_INT64) - return value_.int_ >= minInt && value_.int_ <= maxInt; + return value_.int_ >= minInt && value_.int_ <= maxInt; #else - return true; + return true; #endif - case uintValue: - return value_.uint_ <= UInt(maxInt); - case realValue: - return value_.real_ >= minInt && value_.real_ <= maxInt && - IsIntegral(value_.real_); - default: - break; - } - return false; + case uintValue: + return value_.uint_ <= UInt(maxInt); + case realValue: + return value_.real_ >= minInt && value_.real_ <= maxInt && IsIntegral(value_.real_); + default: + break; + } + return false; } -bool Value::isUInt() const { - switch (type()) { - case intValue: +bool Value::isUInt() const +{ + switch (type()) + { + case intValue: #if defined(JSON_HAS_INT64) - return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt); + return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt); #else - return value_.int_ >= 0; + return value_.int_ >= 0; #endif - case uintValue: + case uintValue: #if defined(JSON_HAS_INT64) - return value_.uint_ <= maxUInt; + return value_.uint_ <= maxUInt; #else - return true; + return true; #endif - case realValue: - return value_.real_ >= 0 && value_.real_ <= maxUInt && - IsIntegral(value_.real_); - default: - break; - } - return false; + case realValue: + return value_.real_ >= 0 && value_.real_ <= maxUInt && IsIntegral(value_.real_); + default: + break; + } + return false; } -bool Value::isInt64() const { +bool Value::isInt64() const +{ #if defined(JSON_HAS_INT64) - switch (type()) { - case intValue: - return true; - case uintValue: - return value_.uint_ <= UInt64(maxInt64); - case realValue: - // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a - // double, so double(maxInt64) will be rounded up to 2^63. Therefore we - // require the value to be strictly less than the limit. - return value_.real_ >= double(minInt64) && - value_.real_ < double(maxInt64) && IsIntegral(value_.real_); - default: - break; - } -#endif // JSON_HAS_INT64 - return false; + switch (type()) + { + case intValue: + return true; + case uintValue: + return value_.uint_ <= UInt64(maxInt64); + case realValue: + // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a + // double, so double(maxInt64) will be rounded up to 2^63. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && value_.real_ < double(maxInt64) && IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; } -bool Value::isUInt64() const { +bool Value::isUInt64() const +{ #if defined(JSON_HAS_INT64) - switch (type()) { - case intValue: - return value_.int_ >= 0; - case uintValue: - return true; - case realValue: - // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a - // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we - // require the value to be strictly less than the limit. - return value_.real_ >= 0 && value_.real_ < maxUInt64AsDouble && - IsIntegral(value_.real_); - default: - break; - } -#endif // JSON_HAS_INT64 - return false; + switch (type()) + { + case intValue: + return value_.int_ >= 0; + case uintValue: + return true; + case realValue: + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= 0 && value_.real_ < maxUInt64AsDouble && IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; } -bool Value::isIntegral() const { - switch (type()) { - case intValue: - case uintValue: - return true; - case realValue: +bool Value::isIntegral() const +{ + switch (type()) + { + case intValue: + case uintValue: + return true; + case realValue: #if defined(JSON_HAS_INT64) - // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a - // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we - // require the value to be strictly less than the limit. - return value_.real_ >= double(minInt64) && - value_.real_ < maxUInt64AsDouble && IsIntegral(value_.real_); + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && value_.real_ < maxUInt64AsDouble && IsIntegral(value_.real_); #else - return value_.real_ >= minInt && value_.real_ <= maxUInt && - IsIntegral(value_.real_); -#endif // JSON_HAS_INT64 - default: - break; - } - return false; + return value_.real_ >= minInt && value_.real_ <= maxUInt && IsIntegral(value_.real_); +#endif // JSON_HAS_INT64 + default: + break; + } + return false; } -bool Value::isDouble() const { - return type() == intValue || type() == uintValue || type() == realValue; -} +bool Value::isDouble() const { return type() == intValue || type() == uintValue || type() == realValue; } bool Value::isNumeric() const { return isDouble(); } @@ -3797,59 +3936,53 @@ bool Value::isArray() const { return type() == arrayValue; } bool Value::isObject() const { return type() == objectValue; } -Value::Comments::Comments(const Comments& that) - : ptr_{cloneUnique(that.ptr_)} {} +Value::Comments::Comments(const Comments& that) : ptr_{cloneUnique(that.ptr_)} {} -Value::Comments::Comments(Comments&& that) noexcept - : ptr_{std::move(that.ptr_)} {} +Value::Comments::Comments(Comments&& that) noexcept : ptr_{std::move(that.ptr_)} {} -Value::Comments& Value::Comments::operator=(const Comments& that) { - ptr_ = cloneUnique(that.ptr_); - return *this; +Value::Comments& Value::Comments::operator=(const Comments& that) +{ + ptr_ = cloneUnique(that.ptr_); + return *this; } -Value::Comments& Value::Comments::operator=(Comments&& that) noexcept { - ptr_ = std::move(that.ptr_); - return *this; +Value::Comments& Value::Comments::operator=(Comments&& that) noexcept +{ + ptr_ = std::move(that.ptr_); + return *this; } -bool Value::Comments::has(CommentPlacement slot) const { - return ptr_ && !(*ptr_)[slot].empty(); +bool Value::Comments::has(CommentPlacement slot) const { return ptr_ && !(*ptr_)[slot].empty(); } + +String Value::Comments::get(CommentPlacement slot) const +{ + if (!ptr_) return {}; + return (*ptr_)[slot]; } -String Value::Comments::get(CommentPlacement slot) const { - if (!ptr_) - return {}; - return (*ptr_)[slot]; +void Value::Comments::set(CommentPlacement slot, String comment) +{ + if (slot >= CommentPlacement::numberOfCommentPlacement) return; + if (!ptr_) ptr_ = std::unique_ptr(new Array()); + (*ptr_)[slot] = std::move(comment); } -void Value::Comments::set(CommentPlacement slot, String comment) { - if (slot >= CommentPlacement::numberOfCommentPlacement) - return; - if (!ptr_) - ptr_ = std::unique_ptr(new Array()); - (*ptr_)[slot] = std::move(comment); +void Value::setComment(String comment, CommentPlacement placement) +{ + if (!comment.empty() && (comment.back() == '\n')) + { + // Always discard trailing newline, to aid indentation. + comment.pop_back(); + } + JSON_ASSERT(!comment.empty()); + JSON_ASSERT_MESSAGE(comment[0] == '\0' || comment[0] == '/', + "in Json::Value::setComment(): Comments must start with /"); + comments_.set(placement, std::move(comment)); } -void Value::setComment(String comment, CommentPlacement placement) { - if (!comment.empty() && (comment.back() == '\n')) { - // Always discard trailing newline, to aid indentation. - comment.pop_back(); - } - JSON_ASSERT(!comment.empty()); - JSON_ASSERT_MESSAGE( - comment[0] == '\0' || comment[0] == '/', - "in Json::Value::setComment(): Comments must start with /"); - comments_.set(placement, std::move(comment)); -} +bool Value::hasComment(CommentPlacement placement) const { return comments_.has(placement); } -bool Value::hasComment(CommentPlacement placement) const { - return comments_.has(placement); -} - -String Value::getComment(CommentPlacement placement) const { - return comments_.get(placement); -} +String Value::getComment(CommentPlacement placement) const { return comments_.get(placement); } void Value::setOffsetStart(ptrdiff_t start) { start_ = start; } @@ -3859,66 +3992,71 @@ ptrdiff_t Value::getOffsetStart() const { return start_; } ptrdiff_t Value::getOffsetLimit() const { return limit_; } -String Value::toStyledString() const { - StreamWriterBuilder builder; +String Value::toStyledString() const +{ + StreamWriterBuilder builder; - String out = this->hasComment(commentBefore) ? "\n" : ""; - out += Json::writeString(builder, *this); - out += '\n'; + String out = this->hasComment(commentBefore) ? "\n" : ""; + out += Json::writeString(builder, *this); + out += '\n'; - return out; + return out; } -Value::const_iterator Value::begin() const { - switch (type()) { - case arrayValue: - case objectValue: - if (value_.map_) - return const_iterator(value_.map_->begin()); - break; - default: - break; - } - return {}; +Value::const_iterator Value::begin() const +{ + switch (type()) + { + case arrayValue: + case objectValue: + if (value_.map_) return const_iterator(value_.map_->begin()); + break; + default: + break; + } + return {}; } -Value::const_iterator Value::end() const { - switch (type()) { - case arrayValue: - case objectValue: - if (value_.map_) - return const_iterator(value_.map_->end()); - break; - default: - break; - } - return {}; +Value::const_iterator Value::end() const +{ + switch (type()) + { + case arrayValue: + case objectValue: + if (value_.map_) return const_iterator(value_.map_->end()); + break; + default: + break; + } + return {}; } -Value::iterator Value::begin() { - switch (type()) { - case arrayValue: - case objectValue: - if (value_.map_) - return iterator(value_.map_->begin()); - break; - default: - break; - } - return iterator(); +Value::iterator Value::begin() +{ + switch (type()) + { + case arrayValue: + case objectValue: + if (value_.map_) return iterator(value_.map_->begin()); + break; + default: + break; + } + return iterator(); } -Value::iterator Value::end() { - switch (type()) { - case arrayValue: - case objectValue: - if (value_.map_) - return iterator(value_.map_->end()); - break; - default: - break; - } - return iterator(); +Value::iterator Value::end() +{ + switch (type()) + { + case arrayValue: + case objectValue: + if (value_.map_) return iterator(value_.map_->end()); + break; + default: + break; + } + return iterator(); } // class PathArgument @@ -3926,8 +4064,7 @@ Value::iterator Value::end() { PathArgument::PathArgument() = default; -PathArgument::PathArgument(ArrayIndex index) - : index_(index), kind_(kindIndex) {} +PathArgument::PathArgument(ArrayIndex index) : index_(index), kind_(kindIndex) {} PathArgument::PathArgument(const char* key) : key_(key), kind_(kindKey) {} @@ -3936,138 +4073,164 @@ PathArgument::PathArgument(String key) : key_(std::move(key)), kind_(kindKey) {} // class Path // ////////////////////////////////////////////////////////////////// -Path::Path(const String& path, const PathArgument& a1, const PathArgument& a2, - const PathArgument& a3, const PathArgument& a4, - const PathArgument& a5) { - InArgs in; - in.reserve(5); - in.push_back(&a1); - in.push_back(&a2); - in.push_back(&a3); - in.push_back(&a4); - in.push_back(&a5); - makePath(path, in); +Path::Path(const String& path, const PathArgument& a1, const PathArgument& a2, const PathArgument& a3, + const PathArgument& a4, const PathArgument& a5) +{ + InArgs in; + in.reserve(5); + in.push_back(&a1); + in.push_back(&a2); + in.push_back(&a3); + in.push_back(&a4); + in.push_back(&a5); + makePath(path, in); } -void Path::makePath(const String& path, const InArgs& in) { - const char* current = path.c_str(); - const char* end = current + path.length(); - auto itInArg = in.begin(); - while (current != end) { - if (*current == '[') { - ++current; - if (*current == '%') - addPathInArg(path, in, itInArg, PathArgument::kindIndex); - else { - ArrayIndex index = 0; - for (; current != end && *current >= '0' && *current <= '9'; ++current) - index = index * 10 + ArrayIndex(*current - '0'); - args_.push_back(index); - } - if (current == end || *++current != ']') - invalidPath(path, int(current - path.c_str())); - } else if (*current == '%') { - addPathInArg(path, in, itInArg, PathArgument::kindKey); - ++current; - } else if (*current == '.' || *current == ']') { - ++current; - } else { - const char* beginName = current; - while (current != end && !strchr("[.", *current)) - ++current; - args_.push_back(String(beginName, current)); +void Path::makePath(const String& path, const InArgs& in) +{ + const char* current = path.c_str(); + const char* end = current + path.length(); + auto itInArg = in.begin(); + while (current != end) + { + if (*current == '[') + { + ++current; + if (*current == '%') + addPathInArg(path, in, itInArg, PathArgument::kindIndex); + else + { + ArrayIndex index = 0; + for (; current != end && *current >= '0' && *current <= '9'; ++current) + index = index * 10 + ArrayIndex(*current - '0'); + args_.push_back(index); + } + if (current == end || *++current != ']') invalidPath(path, int(current - path.c_str())); + } + else if (*current == '%') + { + addPathInArg(path, in, itInArg, PathArgument::kindKey); + ++current; + } + else if (*current == '.' || *current == ']') + { + ++current; + } + else + { + const char* beginName = current; + while (current != end && !strchr("[.", *current)) ++current; + args_.push_back(String(beginName, current)); + } } - } } -void Path::addPathInArg(const String& /*path*/, const InArgs& in, - InArgs::const_iterator& itInArg, - PathArgument::Kind kind) { - if (itInArg == in.end()) { - // Error: missing argument %d - } else if ((*itInArg)->kind_ != kind) { - // Error: bad argument type - } else { - args_.push_back(**itInArg++); - } -} - -void Path::invalidPath(const String& /*path*/, int /*location*/) { - // Error: invalid path. -} - -const Value& Path::resolve(const Value& root) const { - const Value* node = &root; - for (const auto& arg : args_) { - if (arg.kind_ == PathArgument::kindIndex) { - if (!node->isArray() || !node->isValidIndex(arg.index_)) { - // Error: unable to resolve path (array value expected at position... ) - return Value::nullSingleton(); - } - node = &((*node)[arg.index_]); - } else if (arg.kind_ == PathArgument::kindKey) { - if (!node->isObject()) { - // Error: unable to resolve path (object value expected at position...) - return Value::nullSingleton(); - } - node = &((*node)[arg.key_]); - if (node == &Value::nullSingleton()) { - // Error: unable to resolve path (object has no member named '' at - // position...) - return Value::nullSingleton(); - } +void Path::addPathInArg(const String& /*path*/, const InArgs& in, InArgs::const_iterator& itInArg, + PathArgument::Kind kind) +{ + if (itInArg == in.end()) + { + // Error: missing argument %d } - } - return *node; -} - -Value Path::resolve(const Value& root, const Value& defaultValue) const { - const Value* node = &root; - for (const auto& arg : args_) { - if (arg.kind_ == PathArgument::kindIndex) { - if (!node->isArray() || !node->isValidIndex(arg.index_)) - return defaultValue; - node = &((*node)[arg.index_]); - } else if (arg.kind_ == PathArgument::kindKey) { - if (!node->isObject()) - return defaultValue; - node = &((*node)[arg.key_]); - if (node == &Value::nullSingleton()) - return defaultValue; + else if ((*itInArg)->kind_ != kind) + { + // Error: bad argument type } - } - return *node; -} - -Value& Path::make(Value& root) const { - Value* node = &root; - for (const auto& arg : args_) { - if (arg.kind_ == PathArgument::kindIndex) { - if (!node->isArray()) { - // Error: node is not an array at position ... - } - node = &((*node)[arg.index_]); - } else if (arg.kind_ == PathArgument::kindKey) { - if (!node->isObject()) { - // Error: node is not an object at position... - } - node = &((*node)[arg.key_]); + else + { + args_.push_back(**itInArg++); } - } - return *node; } -} // namespace Json +void Path::invalidPath(const String& /*path*/, int /*location*/) +{ + // Error: invalid path. +} + +const Value& Path::resolve(const Value& root) const +{ + const Value* node = &root; + for (const auto& arg : args_) + { + if (arg.kind_ == PathArgument::kindIndex) + { + if (!node->isArray() || !node->isValidIndex(arg.index_)) + { + // Error: unable to resolve path (array value expected at position... ) + return Value::nullSingleton(); + } + node = &((*node)[arg.index_]); + } + else if (arg.kind_ == PathArgument::kindKey) + { + if (!node->isObject()) + { + // Error: unable to resolve path (object value expected at position...) + return Value::nullSingleton(); + } + node = &((*node)[arg.key_]); + if (node == &Value::nullSingleton()) + { + // Error: unable to resolve path (object has no member named '' at + // position...) + return Value::nullSingleton(); + } + } + } + return *node; +} + +Value Path::resolve(const Value& root, const Value& defaultValue) const +{ + const Value* node = &root; + for (const auto& arg : args_) + { + if (arg.kind_ == PathArgument::kindIndex) + { + if (!node->isArray() || !node->isValidIndex(arg.index_)) return defaultValue; + node = &((*node)[arg.index_]); + } + else if (arg.kind_ == PathArgument::kindKey) + { + if (!node->isObject()) return defaultValue; + node = &((*node)[arg.key_]); + if (node == &Value::nullSingleton()) return defaultValue; + } + } + return *node; +} + +Value& Path::make(Value& root) const +{ + Value* node = &root; + for (const auto& arg : args_) + { + if (arg.kind_ == PathArgument::kindIndex) + { + if (!node->isArray()) + { + // Error: node is not an array at position ... + } + node = &((*node)[arg.index_]); + } + else if (arg.kind_ == PathArgument::kindKey) + { + if (!node->isObject()) + { + // Error: node is not an object at position... + } + node = &((*node)[arg.key_]); + } + } + return *node; +} + +} // namespace Json // ////////////////////////////////////////////////////////////////////// // End of content of file: src/lib_json/json_value.cpp // ////////////////////////////////////////////////////////////////////// - - - - - // ////////////////////////////////////////////////////////////////////// // Beginning of content of file: src/lib_json/json_writer.cpp // ////////////////////////////////////////////////////////////////////// @@ -4078,9 +4241,10 @@ Value& Path::make(Value& root) const { // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE #if !defined(JSON_IS_AMALGAMATION) -#include "json_tool.h" #include -#endif // if !defined(JSON_IS_AMALGAMATION) + +#include "json_tool.h" +#endif // if !defined(JSON_IS_AMALGAMATION) #include #include #include @@ -4120,11 +4284,11 @@ Value& Path::make(Value& root) const { #if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) #define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 -#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES +#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES -#endif //_MSC_VER +#endif //_MSC_VER -#if defined(__sun) && defined(__SVR4) // Solaris +#if defined(__sun) && defined(__SVR4) // Solaris #if !defined(isfinite) #include #define isfinite finite @@ -4134,8 +4298,7 @@ Value& Path::make(Value& root) const { #if defined(__hpux) #if !defined(isfinite) #if defined(__ia64) && !defined(finite) -#define isfinite(x) \ - ((sizeof(x) == sizeof(float) ? _Isfinitef(x) : _IsFinite(x))) +#define isfinite(x) ((sizeof(x) == sizeof(float) ? _Isfinitef(x) : _IsFinite(x))) #endif #endif #endif @@ -4157,7 +4320,8 @@ Value& Path::make(Value& root) const { #pragma warning(disable : 4996) #endif -namespace Json { +namespace Json +{ #if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) using StreamWriterPtr = std::unique_ptr; @@ -4165,28 +4329,35 @@ using StreamWriterPtr = std::unique_ptr; using StreamWriterPtr = std::auto_ptr; #endif -String valueToString(LargestInt value) { - UIntToStringBuffer buffer; - char* current = buffer + sizeof(buffer); - if (value == Value::minLargestInt) { - uintToString(LargestUInt(Value::maxLargestInt) + 1, current); - *--current = '-'; - } else if (value < 0) { - uintToString(LargestUInt(-value), current); - *--current = '-'; - } else { - uintToString(LargestUInt(value), current); - } - assert(current >= buffer); - return current; +String valueToString(LargestInt value) +{ + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + if (value == Value::minLargestInt) + { + uintToString(LargestUInt(Value::maxLargestInt) + 1, current); + *--current = '-'; + } + else if (value < 0) + { + uintToString(LargestUInt(-value), current); + *--current = '-'; + } + else + { + uintToString(LargestUInt(value), current); + } + assert(current >= buffer); + return current; } -String valueToString(LargestUInt value) { - UIntToStringBuffer buffer; - char* current = buffer + sizeof(buffer); - uintToString(value, current); - assert(current >= buffer); - return current; +String valueToString(LargestUInt value) +{ + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + uintToString(value, current); + assert(current >= buffer); + return current; } #if defined(JSON_HAS_INT64) @@ -4195,238 +4366,246 @@ String valueToString(Int value) { return valueToString(LargestInt(value)); } String valueToString(UInt value) { return valueToString(LargestUInt(value)); } -#endif // # if defined(JSON_HAS_INT64) +#endif // # if defined(JSON_HAS_INT64) -namespace { -String valueToString(double value, bool useSpecialFloats, - unsigned int precision, PrecisionType precisionType) { - // Print into the buffer. We need not request the alternative representation - // that always has a decimal point because JSON doesn't distinguish the - // concepts of reals and integers. - if (!isfinite(value)) { - static const char* const reps[2][3] = {{"NaN", "-Infinity", "Infinity"}, - {"null", "-1e+9999", "1e+9999"}}; - return reps[useSpecialFloats ? 0 : 1] - [isnan(value) ? 0 : (value < 0) ? 1 : 2]; - } - - String buffer(size_t(36), '\0'); - while (true) { - int len = jsoncpp_snprintf( - &*buffer.begin(), buffer.size(), - (precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f", - precision, value); - assert(len >= 0); - auto wouldPrint = static_cast(len); - if (wouldPrint >= buffer.size()) { - buffer.resize(wouldPrint + 1); - continue; +namespace +{ +String valueToString(double value, bool useSpecialFloats, unsigned int precision, PrecisionType precisionType) +{ + // Print into the buffer. We need not request the alternative representation + // that always has a decimal point because JSON doesn't distinguish the + // concepts of reals and integers. + if (!isfinite(value)) + { + static const char* const reps[2][3] = {{"NaN", "-Infinity", "Infinity"}, {"null", "-1e+9999", "1e+9999"}}; + return reps[useSpecialFloats ? 0 : 1][isnan(value) ? 0 : (value < 0) ? 1 : 2]; } - buffer.resize(wouldPrint); - break; - } - buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end()); + String buffer(size_t(36), '\0'); + while (true) + { + int len = + jsoncpp_snprintf(&*buffer.begin(), buffer.size(), + (precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f", precision, value); + assert(len >= 0); + auto wouldPrint = static_cast(len); + if (wouldPrint >= buffer.size()) + { + buffer.resize(wouldPrint + 1); + continue; + } + buffer.resize(wouldPrint); + break; + } - // try to ensure we preserve the fact that this was given to us as a double on - // input - if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) { - buffer += ".0"; - } + buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end()); - // strip the zero padding from the right - if (precisionType == PrecisionType::decimalPlaces) { - buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end(), precision), - buffer.end()); - } + // try to ensure we preserve the fact that this was given to us as a double on + // input + if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) + { + buffer += ".0"; + } - return buffer; + // strip the zero padding from the right + if (precisionType == PrecisionType::decimalPlaces) + { + buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end(), precision), buffer.end()); + } + + return buffer; } -} // namespace +} // namespace -String valueToString(double value, unsigned int precision, - PrecisionType precisionType) { - return valueToString(value, false, precision, precisionType); +String valueToString(double value, unsigned int precision, PrecisionType precisionType) +{ + return valueToString(value, false, precision, precisionType); } String valueToString(bool value) { return value ? "true" : "false"; } -static bool doesAnyCharRequireEscaping(char const* s, size_t n) { - assert(s || !n); +static bool doesAnyCharRequireEscaping(char const* s, size_t n) +{ + assert(s || !n); - return std::any_of(s, s + n, [](unsigned char c) { - return c == '\\' || c == '"' || c < 0x20 || c > 0x7F; - }); + return std::any_of(s, s + n, [](unsigned char c) { return c == '\\' || c == '"' || c < 0x20 || c > 0x7F; }); } -static unsigned int utf8ToCodepoint(const char*& s, const char* e) { - const unsigned int REPLACEMENT_CHARACTER = 0xFFFD; +static unsigned int utf8ToCodepoint(const char*& s, const char* e) +{ + const unsigned int REPLACEMENT_CHARACTER = 0xFFFD; - unsigned int firstByte = static_cast(*s); + unsigned int firstByte = static_cast(*s); - if (firstByte < 0x80) - return firstByte; + if (firstByte < 0x80) return firstByte; - if (firstByte < 0xE0) { - if (e - s < 2) - return REPLACEMENT_CHARACTER; + if (firstByte < 0xE0) + { + if (e - s < 2) return REPLACEMENT_CHARACTER; - unsigned int calculated = - ((firstByte & 0x1F) << 6) | (static_cast(s[1]) & 0x3F); - s += 1; - // oversized encoded characters are invalid - return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated; - } - - if (firstByte < 0xF0) { - if (e - s < 3) - return REPLACEMENT_CHARACTER; - - unsigned int calculated = ((firstByte & 0x0F) << 12) | - ((static_cast(s[1]) & 0x3F) << 6) | - (static_cast(s[2]) & 0x3F); - s += 2; - // surrogates aren't valid codepoints itself - // shouldn't be UTF-8 encoded - if (calculated >= 0xD800 && calculated <= 0xDFFF) - return REPLACEMENT_CHARACTER; - // oversized encoded characters are invalid - return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated; - } - - if (firstByte < 0xF8) { - if (e - s < 4) - return REPLACEMENT_CHARACTER; - - unsigned int calculated = ((firstByte & 0x07) << 18) | - ((static_cast(s[1]) & 0x3F) << 12) | - ((static_cast(s[2]) & 0x3F) << 6) | - (static_cast(s[3]) & 0x3F); - s += 3; - // oversized encoded characters are invalid - return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated; - } - - return REPLACEMENT_CHARACTER; -} - -static const char hex2[] = "000102030405060708090a0b0c0d0e0f" - "101112131415161718191a1b1c1d1e1f" - "202122232425262728292a2b2c2d2e2f" - "303132333435363738393a3b3c3d3e3f" - "404142434445464748494a4b4c4d4e4f" - "505152535455565758595a5b5c5d5e5f" - "606162636465666768696a6b6c6d6e6f" - "707172737475767778797a7b7c7d7e7f" - "808182838485868788898a8b8c8d8e8f" - "909192939495969798999a9b9c9d9e9f" - "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" - "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" - "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" - "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" - "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" - "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; - -static String toHex16Bit(unsigned int x) { - const unsigned int hi = (x >> 8) & 0xff; - const unsigned int lo = x & 0xff; - String result(4, ' '); - result[0] = hex2[2 * hi]; - result[1] = hex2[2 * hi + 1]; - result[2] = hex2[2 * lo]; - result[3] = hex2[2 * lo + 1]; - return result; -} - -static void appendRaw(String& result, unsigned ch) { - result += static_cast(ch); -} - -static void appendHex(String& result, unsigned ch) { - result.append("\\u").append(toHex16Bit(ch)); -} - -static String valueToQuotedStringN(const char* value, size_t length, - bool emitUTF8 = false) { - if (value == nullptr) - return ""; - - if (!doesAnyCharRequireEscaping(value, length)) - return String("\"") + value + "\""; - // We have to walk value and escape any special characters. - // Appending to String is not efficient, but this should be rare. - // (Note: forward slashes are *not* rare, but I am not escaping them.) - String::size_type maxsize = length * 2 + 3; // allescaped+quotes+NULL - String result; - result.reserve(maxsize); // to avoid lots of mallocs - result += "\""; - char const* end = value + length; - for (const char* c = value; c != end; ++c) { - switch (*c) { - case '\"': - result += "\\\""; - break; - case '\\': - result += "\\\\"; - break; - case '\b': - result += "\\b"; - break; - case '\f': - result += "\\f"; - break; - case '\n': - result += "\\n"; - break; - case '\r': - result += "\\r"; - break; - case '\t': - result += "\\t"; - break; - // case '/': - // Even though \/ is considered a legal escape in JSON, a bare - // slash is also legal, so I see no reason to escape it. - // (I hope I am not misunderstanding something.) - // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); - if (codepoint < 0x20) { - appendHex(result, codepoint); - } else { - appendRaw(result, codepoint); - } - } else { - unsigned codepoint = utf8ToCodepoint(c, end); // modifies `c` - if (codepoint < 0x20) { - appendHex(result, codepoint); - } else if (codepoint < 0x80) { - appendRaw(result, codepoint); - } else if (codepoint < 0x10000) { - // Basic Multilingual Plane - appendHex(result, codepoint); - } else { - // Extended Unicode. Encode 20 bits as a surrogate pair. - codepoint -= 0x10000; - appendHex(result, 0xd800 + ((codepoint >> 10) & 0x3ff)); - appendHex(result, 0xdc00 + (codepoint & 0x3ff)); - } - } - } break; + unsigned int calculated = ((firstByte & 0x1F) << 6) | (static_cast(s[1]) & 0x3F); + s += 1; + // oversized encoded characters are invalid + return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated; } - } - result += "\""; - return result; + + if (firstByte < 0xF0) + { + if (e - s < 3) return REPLACEMENT_CHARACTER; + + unsigned int calculated = ((firstByte & 0x0F) << 12) | ((static_cast(s[1]) & 0x3F) << 6) | + (static_cast(s[2]) & 0x3F); + s += 2; + // surrogates aren't valid codepoints itself + // shouldn't be UTF-8 encoded + if (calculated >= 0xD800 && calculated <= 0xDFFF) return REPLACEMENT_CHARACTER; + // oversized encoded characters are invalid + return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated; + } + + if (firstByte < 0xF8) + { + if (e - s < 4) return REPLACEMENT_CHARACTER; + + unsigned int calculated = ((firstByte & 0x07) << 18) | ((static_cast(s[1]) & 0x3F) << 12) | + ((static_cast(s[2]) & 0x3F) << 6) | + (static_cast(s[3]) & 0x3F); + s += 3; + // oversized encoded characters are invalid + return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated; + } + + return REPLACEMENT_CHARACTER; } -String valueToQuotedString(const char* value) { - return valueToQuotedStringN(value, strlen(value)); +static const char hex2[] = + "000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f" + "202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f" + "505152535455565758595a5b5c5d5e5f" + "606162636465666768696a6b6c6d6e6f" + "707172737475767778797a7b7c7d7e7f" + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f" + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; + +static String toHex16Bit(unsigned int x) +{ + const unsigned int hi = (x >> 8) & 0xff; + const unsigned int lo = x & 0xff; + String result(4, ' '); + result[0] = hex2[2 * hi]; + result[1] = hex2[2 * hi + 1]; + result[2] = hex2[2 * lo]; + result[3] = hex2[2 * lo + 1]; + return result; } +static void appendRaw(String& result, unsigned ch) { result += static_cast(ch); } + +static void appendHex(String& result, unsigned ch) { result.append("\\u").append(toHex16Bit(ch)); } + +static String valueToQuotedStringN(const char* value, size_t length, bool emitUTF8 = false) +{ + if (value == nullptr) return ""; + + if (!doesAnyCharRequireEscaping(value, length)) return String("\"") + value + "\""; + // We have to walk value and escape any special characters. + // Appending to String is not efficient, but this should be rare. + // (Note: forward slashes are *not* rare, but I am not escaping them.) + String::size_type maxsize = length * 2 + 3; // allescaped+quotes+NULL + String result; + result.reserve(maxsize); // to avoid lots of mallocs + result += "\""; + char const* end = value + length; + for (const char* c = value; c != end; ++c) + { + switch (*c) + { + case '\"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + // case '/': + // Even though \/ is considered a legal escape in JSON, a bare + // slash is also legal, so I see no reason to escape it. + // (I hope I am not misunderstanding something.) + // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); + if (codepoint < 0x20) + { + appendHex(result, codepoint); + } + else + { + appendRaw(result, codepoint); + } + } + else + { + unsigned codepoint = utf8ToCodepoint(c, end); // modifies `c` + if (codepoint < 0x20) + { + appendHex(result, codepoint); + } + else if (codepoint < 0x80) + { + appendRaw(result, codepoint); + } + else if (codepoint < 0x10000) + { + // Basic Multilingual Plane + appendHex(result, codepoint); + } + else + { + // Extended Unicode. Encode 20 bits as a surrogate pair. + codepoint -= 0x10000; + appendHex(result, 0xd800 + ((codepoint >> 10) & 0x3ff)); + appendHex(result, 0xdc00 + (codepoint & 0x3ff)); + } + } + } + break; + } + } + result += "\""; + return result; +} + +String valueToQuotedString(const char* value) { return valueToQuotedStringN(value, strlen(value)); } + // Class Writer // ////////////////////////////////////////////////////////////////// Writer::~Writer() = default; @@ -4444,65 +4623,70 @@ void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; } void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; } -String FastWriter::write(const Value& root) { - document_.clear(); - writeValue(root); - if (!omitEndingLineFeed_) - document_ += '\n'; - return document_; +String FastWriter::write(const Value& root) +{ + document_.clear(); + writeValue(root); + if (!omitEndingLineFeed_) document_ += '\n'; + return document_; } -void FastWriter::writeValue(const Value& value) { - switch (value.type()) { - case nullValue: - if (!dropNullPlaceholders_) - document_ += "null"; - break; - case intValue: - document_ += valueToString(value.asLargestInt()); - break; - case uintValue: - document_ += valueToString(value.asLargestUInt()); - break; - case realValue: - document_ += valueToString(value.asDouble()); - break; - case stringValue: { - // Is NULL possible for value.string_? No. - char const* str; - char const* end; - bool ok = value.getString(&str, &end); - if (ok) - document_ += valueToQuotedStringN(str, static_cast(end - str)); - break; - } - case booleanValue: - document_ += valueToString(value.asBool()); - break; - case arrayValue: { - document_ += '['; - ArrayIndex size = value.size(); - for (ArrayIndex index = 0; index < size; ++index) { - if (index > 0) - document_ += ','; - writeValue(value[index]); +void FastWriter::writeValue(const Value& value) +{ + switch (value.type()) + { + case nullValue: + if (!dropNullPlaceholders_) document_ += "null"; + break; + case intValue: + document_ += valueToString(value.asLargestInt()); + break; + case uintValue: + document_ += valueToString(value.asLargestUInt()); + break; + case realValue: + document_ += valueToString(value.asDouble()); + break; + case stringValue: + { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) document_ += valueToQuotedStringN(str, static_cast(end - str)); + break; + } + case booleanValue: + document_ += valueToString(value.asBool()); + break; + case arrayValue: + { + document_ += '['; + ArrayIndex size = value.size(); + for (ArrayIndex index = 0; index < size; ++index) + { + if (index > 0) document_ += ','; + writeValue(value[index]); + } + document_ += ']'; + } + break; + case objectValue: + { + Value::Members members(value.getMemberNames()); + document_ += '{'; + for (auto it = members.begin(); it != members.end(); ++it) + { + const String& name = *it; + if (it != members.begin()) document_ += ','; + document_ += valueToQuotedStringN(name.data(), name.length()); + document_ += yamlCompatibilityEnabled_ ? ": " : ":"; + writeValue(value[name]); + } + document_ += '}'; + } + break; } - document_ += ']'; - } break; - case objectValue: { - Value::Members members(value.getMemberNames()); - document_ += '{'; - for (auto it = members.begin(); it != members.end(); ++it) { - const String& name = *it; - if (it != members.begin()) - document_ += ','; - document_ += valueToQuotedStringN(name.data(), name.length()); - document_ += yamlCompatibilityEnabled_ ? ": " : ":"; - writeValue(value[name]); - } - document_ += '}'; - } break; - } } // Class StyledWriter @@ -4510,719 +4694,788 @@ void FastWriter::writeValue(const Value& value) { StyledWriter::StyledWriter() = default; -String StyledWriter::write(const Value& root) { - document_.clear(); - addChildValues_ = false; - indentString_.clear(); - writeCommentBeforeValue(root); - writeValue(root); - writeCommentAfterValueOnSameLine(root); - document_ += '\n'; - return document_; -} - -void StyledWriter::writeValue(const Value& value) { - switch (value.type()) { - case nullValue: - pushValue("null"); - break; - case intValue: - pushValue(valueToString(value.asLargestInt())); - break; - case uintValue: - pushValue(valueToString(value.asLargestUInt())); - break; - case realValue: - pushValue(valueToString(value.asDouble())); - break; - case stringValue: { - // Is NULL possible for value.string_? No. - char const* str; - char const* end; - bool ok = value.getString(&str, &end); - if (ok) - pushValue(valueToQuotedStringN(str, static_cast(end - str))); - else - pushValue(""); - break; - } - case booleanValue: - pushValue(valueToString(value.asBool())); - break; - case arrayValue: - writeArrayValue(value); - break; - case objectValue: { - Value::Members members(value.getMemberNames()); - if (members.empty()) - pushValue("{}"); - else { - writeWithIndent("{"); - indent(); - auto it = members.begin(); - for (;;) { - const String& name = *it; - const Value& childValue = value[name]; - writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); - document_ += " : "; - writeValue(childValue); - if (++it == members.end()) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - document_ += ','; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("}"); - } - } break; - } -} - -void StyledWriter::writeArrayValue(const Value& value) { - size_t size = value.size(); - if (size == 0) - pushValue("[]"); - else { - bool isArrayMultiLine = isMultilineArray(value); - if (isArrayMultiLine) { - writeWithIndent("["); - indent(); - bool hasChildValue = !childValues_.empty(); - ArrayIndex index = 0; - for (;;) { - const Value& childValue = value[index]; - writeCommentBeforeValue(childValue); - if (hasChildValue) - writeWithIndent(childValues_[index]); - else { - writeIndent(); - writeValue(childValue); - } - if (++index == size) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - document_ += ','; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("]"); - } else // output on a single line - { - assert(childValues_.size() == size); - document_ += "[ "; - for (size_t index = 0; index < size; ++index) { - if (index > 0) - document_ += ", "; - document_ += childValues_[index]; - } - document_ += " ]"; - } - } -} - -bool StyledWriter::isMultilineArray(const Value& value) { - ArrayIndex const size = value.size(); - bool isMultiLine = size * 3 >= rightMargin_; - childValues_.clear(); - for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { - const Value& childValue = value[index]; - isMultiLine = ((childValue.isArray() || childValue.isObject()) && - !childValue.empty()); - } - if (!isMultiLine) // check if line length > max line length - { - childValues_.reserve(size); - addChildValues_ = true; - ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' - for (ArrayIndex index = 0; index < size; ++index) { - if (hasCommentForValue(value[index])) { - isMultiLine = true; - } - writeValue(value[index]); - lineLength += static_cast(childValues_[index].length()); - } +String StyledWriter::write(const Value& root) +{ + document_.clear(); addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; + indentString_.clear(); + writeCommentBeforeValue(root); + writeValue(root); + writeCommentAfterValueOnSameLine(root); + document_ += '\n'; + return document_; } -void StyledWriter::pushValue(const String& value) { - if (addChildValues_) - childValues_.push_back(value); - else +void StyledWriter::writeValue(const Value& value) +{ + switch (value.type()) + { + case nullValue: + pushValue("null"); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: + { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str))); + else + pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: + { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else + { + writeWithIndent("{"); + indent(); + auto it = members.begin(); + for (;;) + { + const String& name = *it; + const Value& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedString(name.c_str())); + document_ += " : "; + writeValue(childValue); + if (++it == members.end()) + { + writeCommentAfterValueOnSameLine(childValue); + break; + } + document_ += ','; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } + break; + } +} + +void StyledWriter::writeArrayValue(const Value& value) +{ + size_t size = value.size(); + if (size == 0) + pushValue("[]"); + else + { + bool isArrayMultiLine = isMultilineArray(value); + if (isArrayMultiLine) + { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + ArrayIndex index = 0; + for (;;) + { + const Value& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else + { + writeIndent(); + writeValue(childValue); + } + if (++index == size) + { + writeCommentAfterValueOnSameLine(childValue); + break; + } + document_ += ','; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } + else // output on a single line + { + assert(childValues_.size() == size); + document_ += "[ "; + for (size_t index = 0; index < size; ++index) + { + if (index > 0) document_ += ", "; + document_ += childValues_[index]; + } + document_ += " ]"; + } + } +} + +bool StyledWriter::isMultilineArray(const Value& value) +{ + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) + { + const Value& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && !childValue.empty()); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) + { + if (hasCommentForValue(value[index])) + { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void StyledWriter::pushValue(const String& value) +{ + if (addChildValues_) + childValues_.push_back(value); + else + document_ += value; +} + +void StyledWriter::writeIndent() +{ + if (!document_.empty()) + { + char last = document_[document_.length() - 1]; + if (last == ' ') // already indented + return; + if (last != '\n') // Comments may add new-line + document_ += '\n'; + } + document_ += indentString_; +} + +void StyledWriter::writeWithIndent(const String& value) +{ + writeIndent(); document_ += value; } -void StyledWriter::writeIndent() { - if (!document_.empty()) { - char last = document_[document_.length() - 1]; - if (last == ' ') // already indented - return; - if (last != '\n') // Comments may add new-line - document_ += '\n'; - } - document_ += indentString_; -} - -void StyledWriter::writeWithIndent(const String& value) { - writeIndent(); - document_ += value; -} - void StyledWriter::indent() { indentString_ += String(indentSize_, ' '); } -void StyledWriter::unindent() { - assert(indentString_.size() >= indentSize_); - indentString_.resize(indentString_.size() - indentSize_); +void StyledWriter::unindent() +{ + assert(indentString_.size() >= indentSize_); + indentString_.resize(indentString_.size() - indentSize_); } -void StyledWriter::writeCommentBeforeValue(const Value& root) { - if (!root.hasComment(commentBefore)) - return; +void StyledWriter::writeCommentBeforeValue(const Value& root) +{ + if (!root.hasComment(commentBefore)) return; - document_ += '\n'; - writeIndent(); - const String& comment = root.getComment(commentBefore); - String::const_iterator iter = comment.begin(); - while (iter != comment.end()) { - document_ += *iter; - if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) - writeIndent(); - ++iter; - } - - // Comments are stripped of trailing newlines, so add one here - document_ += '\n'; -} - -void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) { - if (root.hasComment(commentAfterOnSameLine)) - document_ += " " + root.getComment(commentAfterOnSameLine); - - if (root.hasComment(commentAfter)) { document_ += '\n'; - document_ += root.getComment(commentAfter); + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); + while (iter != comment.end()) + { + document_ += *iter; + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) writeIndent(); + ++iter; + } + + // Comments are stripped of trailing newlines, so add one here document_ += '\n'; - } } -bool StyledWriter::hasCommentForValue(const Value& value) { - return value.hasComment(commentBefore) || - value.hasComment(commentAfterOnSameLine) || - value.hasComment(commentAfter); +void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) +{ + if (root.hasComment(commentAfterOnSameLine)) document_ += " " + root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) + { + document_ += '\n'; + document_ += root.getComment(commentAfter); + document_ += '\n'; + } +} + +bool StyledWriter::hasCommentForValue(const Value& value) +{ + return value.hasComment(commentBefore) || value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); } // Class StyledStreamWriter // ////////////////////////////////////////////////////////////////// StyledStreamWriter::StyledStreamWriter(String indentation) - : document_(nullptr), indentation_(std::move(indentation)), - addChildValues_(), indented_(false) {} - -void StyledStreamWriter::write(OStream& out, const Value& root) { - document_ = &out; - addChildValues_ = false; - indentString_.clear(); - indented_ = true; - writeCommentBeforeValue(root); - if (!indented_) - writeIndent(); - indented_ = true; - writeValue(root); - writeCommentAfterValueOnSameLine(root); - *document_ << "\n"; - document_ = nullptr; // Forget the stream, for safety. + : document_(nullptr), indentation_(std::move(indentation)), addChildValues_(), indented_(false) +{ } -void StyledStreamWriter::writeValue(const Value& value) { - switch (value.type()) { - case nullValue: - pushValue("null"); - break; - case intValue: - pushValue(valueToString(value.asLargestInt())); - break; - case uintValue: - pushValue(valueToString(value.asLargestUInt())); - break; - case realValue: - pushValue(valueToString(value.asDouble())); - break; - case stringValue: { - // Is NULL possible for value.string_? No. - char const* str; - char const* end; - bool ok = value.getString(&str, &end); - if (ok) - pushValue(valueToQuotedStringN(str, static_cast(end - str))); - else - pushValue(""); - break; - } - case booleanValue: - pushValue(valueToString(value.asBool())); - break; - case arrayValue: - writeArrayValue(value); - break; - case objectValue: { - Value::Members members(value.getMemberNames()); - if (members.empty()) - pushValue("{}"); - else { - writeWithIndent("{"); - indent(); - auto it = members.begin(); - for (;;) { - const String& name = *it; - const Value& childValue = value[name]; - writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); - *document_ << " : "; - writeValue(childValue); - if (++it == members.end()) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - *document_ << ","; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("}"); - } - } break; - } -} - -void StyledStreamWriter::writeArrayValue(const Value& value) { - unsigned size = value.size(); - if (size == 0) - pushValue("[]"); - else { - bool isArrayMultiLine = isMultilineArray(value); - if (isArrayMultiLine) { - writeWithIndent("["); - indent(); - bool hasChildValue = !childValues_.empty(); - unsigned index = 0; - for (;;) { - const Value& childValue = value[index]; - writeCommentBeforeValue(childValue); - if (hasChildValue) - writeWithIndent(childValues_[index]); - else { - if (!indented_) - writeIndent(); - indented_ = true; - writeValue(childValue); - indented_ = false; - } - if (++index == size) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - *document_ << ","; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("]"); - } else // output on a single line - { - assert(childValues_.size() == size); - *document_ << "[ "; - for (unsigned index = 0; index < size; ++index) { - if (index > 0) - *document_ << ", "; - *document_ << childValues_[index]; - } - *document_ << " ]"; - } - } -} - -bool StyledStreamWriter::isMultilineArray(const Value& value) { - ArrayIndex const size = value.size(); - bool isMultiLine = size * 3 >= rightMargin_; - childValues_.clear(); - for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { - const Value& childValue = value[index]; - isMultiLine = ((childValue.isArray() || childValue.isObject()) && - !childValue.empty()); - } - if (!isMultiLine) // check if line length > max line length - { - childValues_.reserve(size); - addChildValues_ = true; - ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' - for (ArrayIndex index = 0; index < size; ++index) { - if (hasCommentForValue(value[index])) { - isMultiLine = true; - } - writeValue(value[index]); - lineLength += static_cast(childValues_[index].length()); - } +void StyledStreamWriter::write(OStream& out, const Value& root) +{ + document_ = &out; addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; + indentString_.clear(); + indented_ = true; + writeCommentBeforeValue(root); + if (!indented_) writeIndent(); + indented_ = true; + writeValue(root); + writeCommentAfterValueOnSameLine(root); + *document_ << "\n"; + document_ = nullptr; // Forget the stream, for safety. } -void StyledStreamWriter::pushValue(const String& value) { - if (addChildValues_) - childValues_.push_back(value); - else +void StyledStreamWriter::writeValue(const Value& value) +{ + switch (value.type()) + { + case nullValue: + pushValue("null"); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: + { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str))); + else + pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: + { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else + { + writeWithIndent("{"); + indent(); + auto it = members.begin(); + for (;;) + { + const String& name = *it; + const Value& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedString(name.c_str())); + *document_ << " : "; + writeValue(childValue); + if (++it == members.end()) + { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } + break; + } +} + +void StyledStreamWriter::writeArrayValue(const Value& value) +{ + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else + { + bool isArrayMultiLine = isMultilineArray(value); + if (isArrayMultiLine) + { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) + { + const Value& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else + { + if (!indented_) writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + if (++index == size) + { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } + else // output on a single line + { + assert(childValues_.size() == size); + *document_ << "[ "; + for (unsigned index = 0; index < size; ++index) + { + if (index > 0) *document_ << ", "; + *document_ << childValues_[index]; + } + *document_ << " ]"; + } + } +} + +bool StyledStreamWriter::isMultilineArray(const Value& value) +{ + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) + { + const Value& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && !childValue.empty()); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) + { + if (hasCommentForValue(value[index])) + { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void StyledStreamWriter::pushValue(const String& value) +{ + if (addChildValues_) + childValues_.push_back(value); + else + *document_ << value; +} + +void StyledStreamWriter::writeIndent() +{ + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + *document_ << '\n' << indentString_; +} + +void StyledStreamWriter::writeWithIndent(const String& value) +{ + if (!indented_) writeIndent(); *document_ << value; -} - -void StyledStreamWriter::writeIndent() { - // blep intended this to look at the so-far-written string - // to determine whether we are already indented, but - // with a stream we cannot do that. So we rely on some saved state. - // The caller checks indented_. - *document_ << '\n' << indentString_; -} - -void StyledStreamWriter::writeWithIndent(const String& value) { - if (!indented_) - writeIndent(); - *document_ << value; - indented_ = false; + indented_ = false; } void StyledStreamWriter::indent() { indentString_ += indentation_; } -void StyledStreamWriter::unindent() { - assert(indentString_.size() >= indentation_.size()); - indentString_.resize(indentString_.size() - indentation_.size()); +void StyledStreamWriter::unindent() +{ + assert(indentString_.size() >= indentation_.size()); + indentString_.resize(indentString_.size() - indentation_.size()); } -void StyledStreamWriter::writeCommentBeforeValue(const Value& root) { - if (!root.hasComment(commentBefore)) - return; +void StyledStreamWriter::writeCommentBeforeValue(const Value& root) +{ + if (!root.hasComment(commentBefore)) return; - if (!indented_) - writeIndent(); - const String& comment = root.getComment(commentBefore); - String::const_iterator iter = comment.begin(); - while (iter != comment.end()) { - *document_ << *iter; - if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) - // writeIndent(); // would include newline - *document_ << indentString_; - ++iter; - } - indented_ = false; + if (!indented_) writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); + while (iter != comment.end()) + { + *document_ << *iter; + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would include newline + *document_ << indentString_; + ++iter; + } + indented_ = false; } -void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) { - if (root.hasComment(commentAfterOnSameLine)) - *document_ << ' ' << root.getComment(commentAfterOnSameLine); +void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) +{ + if (root.hasComment(commentAfterOnSameLine)) *document_ << ' ' << root.getComment(commentAfterOnSameLine); - if (root.hasComment(commentAfter)) { - writeIndent(); - *document_ << root.getComment(commentAfter); - } - indented_ = false; + if (root.hasComment(commentAfter)) + { + writeIndent(); + *document_ << root.getComment(commentAfter); + } + indented_ = false; } -bool StyledStreamWriter::hasCommentForValue(const Value& value) { - return value.hasComment(commentBefore) || - value.hasComment(commentAfterOnSameLine) || - value.hasComment(commentAfter); +bool StyledStreamWriter::hasCommentForValue(const Value& value) +{ + return value.hasComment(commentBefore) || value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); } ////////////////////////// // BuiltStyledStreamWriter /// Scoped enums are not available until C++11. -struct CommentStyle { - /// Decide whether to write comments. - enum Enum { - None, ///< Drop all comments. - Most, ///< Recover odd behavior of previous versions (not implemented yet). - All ///< Keep all comments. - }; -}; - -struct BuiltStyledStreamWriter : public StreamWriter { - BuiltStyledStreamWriter(String indentation, CommentStyle::Enum cs, - String colonSymbol, String nullSymbol, - String endingLineFeedSymbol, bool useSpecialFloats, - bool emitUTF8, unsigned int precision, - PrecisionType precisionType); - int write(Value const& root, OStream* sout) override; - -private: - void writeValue(Value const& value); - void writeArrayValue(Value const& value); - bool isMultilineArray(Value const& value); - void pushValue(String const& value); - void writeIndent(); - void writeWithIndent(String const& value); - void indent(); - void unindent(); - void writeCommentBeforeValue(Value const& root); - void writeCommentAfterValueOnSameLine(Value const& root); - static bool hasCommentForValue(const Value& value); - - using ChildValues = std::vector; - - ChildValues childValues_; - String indentString_; - unsigned int rightMargin_; - String indentation_; - CommentStyle::Enum cs_; - String colonSymbol_; - String nullSymbol_; - String endingLineFeedSymbol_; - bool addChildValues_ : 1; - bool indented_ : 1; - bool useSpecialFloats_ : 1; - bool emitUTF8_ : 1; - unsigned int precision_; - PrecisionType precisionType_; -}; -BuiltStyledStreamWriter::BuiltStyledStreamWriter( - String indentation, CommentStyle::Enum cs, String colonSymbol, - String nullSymbol, String endingLineFeedSymbol, bool useSpecialFloats, - bool emitUTF8, unsigned int precision, PrecisionType precisionType) - : rightMargin_(74), indentation_(std::move(indentation)), cs_(cs), - colonSymbol_(std::move(colonSymbol)), nullSymbol_(std::move(nullSymbol)), - endingLineFeedSymbol_(std::move(endingLineFeedSymbol)), - addChildValues_(false), indented_(false), - useSpecialFloats_(useSpecialFloats), emitUTF8_(emitUTF8), - precision_(precision), precisionType_(precisionType) {} -int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) { - sout_ = sout; - addChildValues_ = false; - indented_ = true; - indentString_.clear(); - writeCommentBeforeValue(root); - if (!indented_) - writeIndent(); - indented_ = true; - writeValue(root); - writeCommentAfterValueOnSameLine(root); - *sout_ << endingLineFeedSymbol_; - sout_ = nullptr; - return 0; -} -void BuiltStyledStreamWriter::writeValue(Value const& value) { - switch (value.type()) { - case nullValue: - pushValue(nullSymbol_); - break; - case intValue: - pushValue(valueToString(value.asLargestInt())); - break; - case uintValue: - pushValue(valueToString(value.asLargestUInt())); - break; - case realValue: - pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_, - precisionType_)); - break; - case stringValue: { - // Is NULL is possible for value.string_? No. - char const* str; - char const* end; - bool ok = value.getString(&str, &end); - if (ok) - pushValue( - valueToQuotedStringN(str, static_cast(end - str), emitUTF8_)); - else - pushValue(""); - break; - } - case booleanValue: - pushValue(valueToString(value.asBool())); - break; - case arrayValue: - writeArrayValue(value); - break; - case objectValue: { - Value::Members members(value.getMemberNames()); - if (members.empty()) - pushValue("{}"); - else { - writeWithIndent("{"); - indent(); - auto it = members.begin(); - for (;;) { - String const& name = *it; - Value const& childValue = value[name]; - writeCommentBeforeValue(childValue); - writeWithIndent( - valueToQuotedStringN(name.data(), name.length(), emitUTF8_)); - *sout_ << colonSymbol_; - writeValue(childValue); - if (++it == members.end()) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - *sout_ << ","; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("}"); - } - } break; - } -} - -void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { - unsigned size = value.size(); - if (size == 0) - pushValue("[]"); - else { - bool isMultiLine = (cs_ == CommentStyle::All) || isMultilineArray(value); - if (isMultiLine) { - writeWithIndent("["); - indent(); - bool hasChildValue = !childValues_.empty(); - unsigned index = 0; - for (;;) { - Value const& childValue = value[index]; - writeCommentBeforeValue(childValue); - if (hasChildValue) - writeWithIndent(childValues_[index]); - else { - if (!indented_) - writeIndent(); - indented_ = true; - writeValue(childValue); - indented_ = false; - } - if (++index == size) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - *sout_ << ","; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("]"); - } else // output on a single line +struct CommentStyle +{ + /// Decide whether to write comments. + enum Enum { - assert(childValues_.size() == size); - *sout_ << "["; - if (!indentation_.empty()) - *sout_ << " "; - for (unsigned index = 0; index < size; ++index) { - if (index > 0) - *sout_ << ((!indentation_.empty()) ? ", " : ","); - *sout_ << childValues_[index]; - } - if (!indentation_.empty()) - *sout_ << " "; - *sout_ << "]"; - } - } -} + None, ///< Drop all comments. + Most, ///< Recover odd behavior of previous versions (not implemented yet). + All ///< Keep all comments. + }; +}; -bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) { - ArrayIndex const size = value.size(); - bool isMultiLine = size * 3 >= rightMargin_; - childValues_.clear(); - for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { - Value const& childValue = value[index]; - isMultiLine = ((childValue.isArray() || childValue.isObject()) && - !childValue.empty()); - } - if (!isMultiLine) // check if line length > max line length - { - childValues_.reserve(size); - addChildValues_ = true; - ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' - for (ArrayIndex index = 0; index < size; ++index) { - if (hasCommentForValue(value[index])) { - isMultiLine = true; - } - writeValue(value[index]); - lineLength += static_cast(childValues_[index].length()); - } +struct BuiltStyledStreamWriter : public StreamWriter +{ + BuiltStyledStreamWriter(String indentation, CommentStyle::Enum cs, String colonSymbol, String nullSymbol, + String endingLineFeedSymbol, bool useSpecialFloats, bool emitUTF8, unsigned int precision, + PrecisionType precisionType); + int write(Value const& root, OStream* sout) override; + + private: + void writeValue(Value const& value); + void writeArrayValue(Value const& value); + bool isMultilineArray(Value const& value); + void pushValue(String const& value); + void writeIndent(); + void writeWithIndent(String const& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(Value const& root); + void writeCommentAfterValueOnSameLine(Value const& root); + static bool hasCommentForValue(const Value& value); + + using ChildValues = std::vector; + + ChildValues childValues_; + String indentString_; + unsigned int rightMargin_; + String indentation_; + CommentStyle::Enum cs_; + String colonSymbol_; + String nullSymbol_; + String endingLineFeedSymbol_; + bool addChildValues_ : 1; + bool indented_ : 1; + bool useSpecialFloats_ : 1; + bool emitUTF8_ : 1; + unsigned int precision_; + PrecisionType precisionType_; +}; +BuiltStyledStreamWriter::BuiltStyledStreamWriter(String indentation, CommentStyle::Enum cs, String colonSymbol, + String nullSymbol, String endingLineFeedSymbol, bool useSpecialFloats, + bool emitUTF8, unsigned int precision, PrecisionType precisionType) + : rightMargin_(74), + indentation_(std::move(indentation)), + cs_(cs), + colonSymbol_(std::move(colonSymbol)), + nullSymbol_(std::move(nullSymbol)), + endingLineFeedSymbol_(std::move(endingLineFeedSymbol)), + addChildValues_(false), + indented_(false), + useSpecialFloats_(useSpecialFloats), + emitUTF8_(emitUTF8), + precision_(precision), + precisionType_(precisionType) +{ +} +int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) +{ + sout_ = sout; addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; + indented_ = true; + indentString_.clear(); + writeCommentBeforeValue(root); + if (!indented_) writeIndent(); + indented_ = true; + writeValue(root); + writeCommentAfterValueOnSameLine(root); + *sout_ << endingLineFeedSymbol_; + sout_ = nullptr; + return 0; +} +void BuiltStyledStreamWriter::writeValue(Value const& value) +{ + switch (value.type()) + { + case nullValue: + pushValue(nullSymbol_); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_, precisionType_)); + break; + case stringValue: + { + // Is NULL is possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str), emitUTF8_)); + else + pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: + { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else + { + writeWithIndent("{"); + indent(); + auto it = members.begin(); + for (;;) + { + String const& name = *it; + Value const& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedStringN(name.data(), name.length(), emitUTF8_)); + *sout_ << colonSymbol_; + writeValue(childValue); + if (++it == members.end()) + { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } + break; + } } -void BuiltStyledStreamWriter::pushValue(String const& value) { - if (addChildValues_) - childValues_.push_back(value); - else +void BuiltStyledStreamWriter::writeArrayValue(Value const& value) +{ + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else + { + bool isMultiLine = (cs_ == CommentStyle::All) || isMultilineArray(value); + if (isMultiLine) + { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) + { + Value const& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else + { + if (!indented_) writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + if (++index == size) + { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } + else // output on a single line + { + assert(childValues_.size() == size); + *sout_ << "["; + if (!indentation_.empty()) *sout_ << " "; + for (unsigned index = 0; index < size; ++index) + { + if (index > 0) *sout_ << ((!indentation_.empty()) ? ", " : ","); + *sout_ << childValues_[index]; + } + if (!indentation_.empty()) *sout_ << " "; + *sout_ << "]"; + } + } +} + +bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) +{ + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) + { + Value const& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && !childValue.empty()); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) + { + if (hasCommentForValue(value[index])) + { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void BuiltStyledStreamWriter::pushValue(String const& value) +{ + if (addChildValues_) + childValues_.push_back(value); + else + *sout_ << value; +} + +void BuiltStyledStreamWriter::writeIndent() +{ + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + + if (!indentation_.empty()) + { + // In this case, drop newlines too. + *sout_ << '\n' << indentString_; + } +} + +void BuiltStyledStreamWriter::writeWithIndent(String const& value) +{ + if (!indented_) writeIndent(); *sout_ << value; -} - -void BuiltStyledStreamWriter::writeIndent() { - // blep intended this to look at the so-far-written string - // to determine whether we are already indented, but - // with a stream we cannot do that. So we rely on some saved state. - // The caller checks indented_. - - if (!indentation_.empty()) { - // In this case, drop newlines too. - *sout_ << '\n' << indentString_; - } -} - -void BuiltStyledStreamWriter::writeWithIndent(String const& value) { - if (!indented_) - writeIndent(); - *sout_ << value; - indented_ = false; + indented_ = false; } void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; } -void BuiltStyledStreamWriter::unindent() { - assert(indentString_.size() >= indentation_.size()); - indentString_.resize(indentString_.size() - indentation_.size()); +void BuiltStyledStreamWriter::unindent() +{ + assert(indentString_.size() >= indentation_.size()); + indentString_.resize(indentString_.size() - indentation_.size()); } -void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { - if (cs_ == CommentStyle::None) - return; - if (!root.hasComment(commentBefore)) - return; +void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) +{ + if (cs_ == CommentStyle::None) return; + if (!root.hasComment(commentBefore)) return; - if (!indented_) - writeIndent(); - const String& comment = root.getComment(commentBefore); - String::const_iterator iter = comment.begin(); - while (iter != comment.end()) { - *sout_ << *iter; - if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) - // writeIndent(); // would write extra newline - *sout_ << indentString_; - ++iter; - } - indented_ = false; + if (!indented_) writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); + while (iter != comment.end()) + { + *sout_ << *iter; + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would write extra newline + *sout_ << indentString_; + ++iter; + } + indented_ = false; } -void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine( - Value const& root) { - if (cs_ == CommentStyle::None) - return; - if (root.hasComment(commentAfterOnSameLine)) - *sout_ << " " + root.getComment(commentAfterOnSameLine); +void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) +{ + if (cs_ == CommentStyle::None) return; + if (root.hasComment(commentAfterOnSameLine)) *sout_ << " " + root.getComment(commentAfterOnSameLine); - if (root.hasComment(commentAfter)) { - writeIndent(); - *sout_ << root.getComment(commentAfter); - } + if (root.hasComment(commentAfter)) + { + writeIndent(); + *sout_ << root.getComment(commentAfter); + } } // static -bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) { - return value.hasComment(commentBefore) || - value.hasComment(commentAfterOnSameLine) || - value.hasComment(commentAfter); +bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) +{ + return value.hasComment(commentBefore) || value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); } /////////////// @@ -5233,110 +5486,114 @@ StreamWriter::~StreamWriter() = default; StreamWriter::Factory::~Factory() = default; StreamWriterBuilder::StreamWriterBuilder() { setDefaults(&settings_); } StreamWriterBuilder::~StreamWriterBuilder() = default; -StreamWriter* StreamWriterBuilder::newStreamWriter() const { - const String indentation = settings_["indentation"].asString(); - const String cs_str = settings_["commentStyle"].asString(); - const String pt_str = settings_["precisionType"].asString(); - const bool eyc = settings_["enableYAMLCompatibility"].asBool(); - const bool dnp = settings_["dropNullPlaceholders"].asBool(); - const bool usf = settings_["useSpecialFloats"].asBool(); - const bool emitUTF8 = settings_["emitUTF8"].asBool(); - unsigned int pre = settings_["precision"].asUInt(); - CommentStyle::Enum cs = CommentStyle::All; - if (cs_str == "All") { - cs = CommentStyle::All; - } else if (cs_str == "None") { - cs = CommentStyle::None; - } else { - throwRuntimeError("commentStyle must be 'All' or 'None'"); - } - PrecisionType precisionType(significantDigits); - if (pt_str == "significant") { - precisionType = PrecisionType::significantDigits; - } else if (pt_str == "decimal") { - precisionType = PrecisionType::decimalPlaces; - } else { - throwRuntimeError("precisionType must be 'significant' or 'decimal'"); - } - String colonSymbol = " : "; - if (eyc) { - colonSymbol = ": "; - } else if (indentation.empty()) { - colonSymbol = ":"; - } - String nullSymbol = "null"; - if (dnp) { - nullSymbol.clear(); - } - if (pre > 17) - pre = 17; - String endingLineFeedSymbol; - return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol, - endingLineFeedSymbol, usf, emitUTF8, pre, - precisionType); -} - -bool StreamWriterBuilder::validate(Json::Value* invalid) const { - static const auto& valid_keys = *new std::set{ - "indentation", - "commentStyle", - "enableYAMLCompatibility", - "dropNullPlaceholders", - "useSpecialFloats", - "emitUTF8", - "precision", - "precisionType", - }; - for (auto si = settings_.begin(); si != settings_.end(); ++si) { - auto key = si.name(); - if (valid_keys.count(key)) - continue; - if (invalid) - (*invalid)[key] = *si; +StreamWriter* StreamWriterBuilder::newStreamWriter() const +{ + const String indentation = settings_["indentation"].asString(); + const String cs_str = settings_["commentStyle"].asString(); + const String pt_str = settings_["precisionType"].asString(); + const bool eyc = settings_["enableYAMLCompatibility"].asBool(); + const bool dnp = settings_["dropNullPlaceholders"].asBool(); + const bool usf = settings_["useSpecialFloats"].asBool(); + const bool emitUTF8 = settings_["emitUTF8"].asBool(); + unsigned int pre = settings_["precision"].asUInt(); + CommentStyle::Enum cs = CommentStyle::All; + if (cs_str == "All") + { + cs = CommentStyle::All; + } + else if (cs_str == "None") + { + cs = CommentStyle::None; + } else - return false; - } - return invalid ? invalid->empty() : true; + { + throwRuntimeError("commentStyle must be 'All' or 'None'"); + } + PrecisionType precisionType(significantDigits); + if (pt_str == "significant") + { + precisionType = PrecisionType::significantDigits; + } + else if (pt_str == "decimal") + { + precisionType = PrecisionType::decimalPlaces; + } + else + { + throwRuntimeError("precisionType must be 'significant' or 'decimal'"); + } + String colonSymbol = " : "; + if (eyc) + { + colonSymbol = ": "; + } + else if (indentation.empty()) + { + colonSymbol = ":"; + } + String nullSymbol = "null"; + if (dnp) + { + nullSymbol.clear(); + } + if (pre > 17) pre = 17; + String endingLineFeedSymbol; + return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol, endingLineFeedSymbol, usf, emitUTF8, + pre, precisionType); } -Value& StreamWriterBuilder::operator[](const String& key) { - return settings_[key]; +bool StreamWriterBuilder::validate(Json::Value* invalid) const +{ + static const auto& valid_keys = *new std::set{ + "indentation", "commentStyle", "enableYAMLCompatibility", "dropNullPlaceholders", "useSpecialFloats", + "emitUTF8", "precision", "precisionType", + }; + for (auto si = settings_.begin(); si != settings_.end(); ++si) + { + auto key = si.name(); + if (valid_keys.count(key)) continue; + if (invalid) + (*invalid)[key] = *si; + else + return false; + } + return invalid ? invalid->empty() : true; } + +Value& StreamWriterBuilder::operator[](const String& key) { return settings_[key]; } // static -void StreamWriterBuilder::setDefaults(Json::Value* settings) { - //! [StreamWriterBuilderDefaults] - (*settings)["commentStyle"] = "All"; - (*settings)["indentation"] = "\t"; - (*settings)["enableYAMLCompatibility"] = false; - (*settings)["dropNullPlaceholders"] = false; - (*settings)["useSpecialFloats"] = false; - (*settings)["emitUTF8"] = false; - (*settings)["precision"] = 17; - (*settings)["precisionType"] = "significant"; - //! [StreamWriterBuilderDefaults] +void StreamWriterBuilder::setDefaults(Json::Value* settings) +{ + //! [StreamWriterBuilderDefaults] + (*settings)["commentStyle"] = "All"; + (*settings)["indentation"] = "\t"; + (*settings)["enableYAMLCompatibility"] = false; + (*settings)["dropNullPlaceholders"] = false; + (*settings)["useSpecialFloats"] = false; + (*settings)["emitUTF8"] = false; + (*settings)["precision"] = 17; + (*settings)["precisionType"] = "significant"; + //! [StreamWriterBuilderDefaults] } -String writeString(StreamWriter::Factory const& factory, Value const& root) { - OStringStream sout; - StreamWriterPtr const writer(factory.newStreamWriter()); - writer->write(root, &sout); - return sout.str(); +String writeString(StreamWriter::Factory const& factory, Value const& root) +{ + OStringStream sout; + StreamWriterPtr const writer(factory.newStreamWriter()); + writer->write(root, &sout); + return sout.str(); } -OStream& operator<<(OStream& sout, Value const& root) { - StreamWriterBuilder builder; - StreamWriterPtr const writer(builder.newStreamWriter()); - writer->write(root, &sout); - return sout; +OStream& operator<<(OStream& sout, Value const& root) +{ + StreamWriterBuilder builder; + StreamWriterPtr const writer(builder.newStreamWriter()); + writer->write(root, &sout); + return sout; } -} // namespace Json +} // namespace Json // ////////////////////////////////////////////////////////////////////// // End of content of file: src/lib_json/json_writer.cpp // ////////////////////////////////////////////////////////////////////// - - - - - diff --git a/src/communication/sub/src/main.cpp b/src/communication/sub/src/main.cpp new file mode 100644 index 0000000..29f0ef8 --- /dev/null +++ b/src/communication/sub/src/main.cpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include + +#include "sub/control_state.hpp" +#include "sub/json.h" +#include "sub/mqtt_receiver.hpp" +#include "sub/sub_node.hpp" + +using sub_node_pkg::ControlState; +using sub_node_pkg::MqttConfig; +using sub_node_pkg::MqttReceiver; +using sub_node_pkg::SubNode; + +static bool load_config(const std::string& path, MqttConfig& cfg) +{ + Json::Reader reader; + Json::Value root; + + std::ifstream in(path, std::ios::binary); + if (!in.is_open()) + { + std::cerr << "Failed to open config: " << path << std::endl; + return false; + } + + if (!reader.parse(in, root)) + { + std::cerr << "Failed to parse config json.\n"; + return false; + } + + const auto& mqtt = root["mqtt"]; + + cfg.inter_net_address = mqtt["inter_net_address"].asString(); + cfg.inter_net_port = mqtt["inter_net_port"].asInt(); + cfg.external_net_address = mqtt["external_net_address"].asString(); + cfg.external_net_port = mqtt["external_net_port"].asInt(); + + cfg.mqtt_user = mqtt.get("username", "").asString(); + cfg.mqtt_password = mqtt.get("password", "").asString(); + + // ⚠️ 注意:这里读的是 template,而不是完整 topic + cfg.remote_topic_template = mqtt["remote_topic"].asString(); + + // 基本校验:必须包含 {vid} + if (cfg.remote_topic_template.find("{vid}") == std::string::npos) + { + std::cerr << "remote_topic must contain '{vid}' placeholder\n"; + return false; + } + + // Paho MQTT C 要求 tcp:// + cfg.address = "tcp://" + cfg.external_net_address + ":" + std::to_string(cfg.external_net_port); + + return true; +} + +int main(int argc, char** argv) +{ + // 1) load config + MqttConfig cfg; + if (!load_config("config.json", cfg)) + { + return 1; + } + + std::cout << "MQTT address: " << cfg.address << "\n"; + std::cout << "MQTT topic template: " << cfg.remote_topic_template << "\n"; + + // 2) shared state + ControlState state; + { + std::lock_guard lock(state.mtx); + state.ctrl.steering = 32767; // 你原默认 + } + + // 3) start mqtt receiver + MqttReceiver mqtt(state, cfg); + mqtt.start(); + + // 4) ROS2 spin + rclcpp::init(argc, argv); + auto node = std::make_shared(state, "sub_node"); + rclcpp::spin(node); + rclcpp::shutdown(); + + // 5) stop mqtt + mqtt.stop(); + return 0; +} diff --git a/src/communication/sub/src/md5.cpp b/src/communication/sub/src/md5.cpp index eb7f064..f16b17c 100644 --- a/src/communication/sub/src/md5.cpp +++ b/src/communication/sub/src/md5.cpp @@ -1,7 +1,9 @@ -#include "md5.h" +#include "sub/md5.h" + #include -#include + #include +#include // 重命名自定义的 byte 类型 typedef unsigned char uchar; @@ -66,41 +68,34 @@ Rotation is separate from addition to prevent recomputation. } const uchar MD5::PADDING[64] = {0x80}; -const char MD5::HEX[16] = { - '0', '1', '2', '3', - '4', '5', '6', '7', - '8', '9', 'a', 'b', - 'c', 'd', 'e', 'f'}; +const char MD5::HEX[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; /* Default construct. */ -MD5::MD5() -{ - reset(); -} +MD5::MD5() { reset(); } /* Construct a MD5 object with a input buffer. */ -MD5::MD5(const void *input, size_t length) +MD5::MD5(const void* input, size_t length) { reset(); update(input, length); } /* Construct a MD5 object with a string. */ -MD5::MD5(const string &str) +MD5::MD5(const string& str) { reset(); update(str); } /* Construct a MD5 object with a file. */ -MD5::MD5(ifstream &in) +MD5::MD5(ifstream& in) { reset(); update(in); } /* Return the message-digest */ -const uchar *MD5::digest() +const uchar* MD5::digest() { if (!_finished) { @@ -124,21 +119,21 @@ void MD5::reset() } /* Updating the context with a input buffer. */ -void MD5::update(const void *input, size_t length) +void MD5::update(const void* input, size_t length) { - const uchar *uinput = static_cast(input); + const uchar* uinput = static_cast(input); update(uinput, length); } /* Updating the context with a string. */ -void MD5::update(const string &str) +void MD5::update(const string& str) { - const uchar *uinput = reinterpret_cast(str.c_str()); + const uchar* uinput = reinterpret_cast(str.c_str()); update(uinput, str.length()); } /* Updating the context with a file. */ -void MD5::update(ifstream &in) +void MD5::update(ifstream& in) { if (!in) { @@ -153,7 +148,7 @@ void MD5::update(ifstream &in) length = in.gcount(); if (length > 0) { - const uchar *uinput = reinterpret_cast(buffer); + const uchar* uinput = reinterpret_cast(buffer); update(uinput, length); } } @@ -164,7 +159,7 @@ void MD5::update(ifstream &in) operation, processing another message block, and updating the context. */ -void MD5::update(const uchar *input, size_t length) +void MD5::update(const uchar* input, size_t length) { uint32 i, index, partLen; @@ -325,7 +320,7 @@ void MD5::transform(const uchar block[64]) /* Encodes input (ulong) into output (byte). Assumes length is a multiple of 4. */ -void MD5::encode(const uint32 *input, uchar *output, size_t length) +void MD5::encode(const uint32* input, uchar* output, size_t length) { for (size_t i = 0, j = 0; j < length; ++i, j += 4) { @@ -339,17 +334,17 @@ void MD5::encode(const uint32 *input, uchar *output, size_t length) /* Decodes input (byte) into output (ulong). Assumes length is a multiple of 4. */ -void MD5::decode(const uchar *input, uint32 *output, size_t length) +void MD5::decode(const uchar* input, uint32* output, size_t length) { for (size_t i = 0, j = 0; j < length; ++i, j += 4) { - output[i] = ((uint32)input[j]) | (((uint32)input[j + 1]) << 8) | - (((uint32)input[j + 2]) << 16) | (((uint32)input[j + 3]) << 24); + output[i] = ((uint32)input[j]) | (((uint32)input[j + 1]) << 8) | (((uint32)input[j + 2]) << 16) | + (((uint32)input[j + 3]) << 24); } } /* Convert byte array to hex string. */ -string MD5::bytesToHexString(const uchar *input, size_t length) +string MD5::bytesToHexString(const uchar* input, size_t length) { string str; str.reserve(length << 1); @@ -365,7 +360,4 @@ string MD5::bytesToHexString(const uchar *input, size_t length) } /* Convert digest to string value */ -string MD5::toString() -{ - return bytesToHexString(digest(), 16); -} \ No newline at end of file +string MD5::toString() { return bytesToHexString(digest(), 16); } \ No newline at end of file diff --git a/src/communication/sub/src/mqtt_receiver.cpp b/src/communication/sub/src/mqtt_receiver.cpp new file mode 100644 index 0000000..2ca2e50 --- /dev/null +++ b/src/communication/sub/src/mqtt_receiver.cpp @@ -0,0 +1,373 @@ +#include "sub/mqtt_receiver.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sub/json.h" + +namespace sub_node_pkg +{ + +static std::string expand_topic(const std::string& tpl, const std::string& vid) +{ + std::string t = tpl; + auto pos = t.find("{vid}"); + if (pos != std::string::npos) t.replace(pos, 5, vid); + return t; +} + +static int64_t now_ms() +{ + using namespace std::chrono; + return duration_cast(steady_clock::now().time_since_epoch()).count(); +} + +MqttReceiver::MqttReceiver(ControlState& state, MqttConfig cfg) : state_(state), cfg_(std::move(cfg)) {} + +MqttReceiver::~MqttReceiver() { stop(); } + +std::string MqttReceiver::generateClientId() +{ + // 毫秒时间戳 + 4位随机数 + auto millis = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) + .count(); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(1000, 9999); + + std::ostringstream oss; + oss << "client_" << millis << "_" << std::setw(4) << std::setfill('0') << dis(gen); + return oss.str(); +} + +bool MqttReceiver::start() +{ + if (running_) return true; + running_ = true; + th_ = std::thread(&MqttReceiver::runLoop, this); + return true; +} + +void MqttReceiver::stop() +{ + if (!running_) return; + running_ = false; + + if (th_.joinable()) th_.join(); + + if (client_) + { + MQTTClient_disconnect(client_, 2000); + MQTTClient_destroy(&client_); + client_ = nullptr; + } + state_.mqtt_connected = false; +} + +void MqttReceiver::onDelivered(void* context, MQTTClient_deliveryToken dt) +{ + (void)dt; + (void)context; + // 可选:打印确认 +} + +void MqttReceiver::onConnLost(void* context, char* cause) +{ + auto* self = static_cast(context); + self->state_.mqtt_connected = false; + std::cerr << "[MQTT] Connection lost, cause: " << (cause ? cause : "(null)") << std::endl; +} + +int MqttReceiver::onMessageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* message) +{ + (void)topicLen; + auto* self = static_cast(context); + return self->handleMessage(topicName, message); +} + +void MqttReceiver::sendResponse(const std::string& topic, long long seqNo, int code, const std::string& msg) +{ + Json::Value response; + response["type"] = "response"; + response["seqNo"] = static_cast(seqNo); + Json::Value data; + data["code"] = code; + data["msg"] = msg; + response["data"] = data; + + Json::FastWriter writer; + const std::string payload = writer.write(response); + + MQTTClient_message pubmsg = MQTTClient_message_initializer; + pubmsg.payload = (void*)payload.data(); + pubmsg.payloadlen = static_cast(payload.size()); + pubmsg.qos = 1; + pubmsg.retained = 0; + + MQTTClient_deliveryToken token; + + std::lock_guard lk(publish_mtx_); + if (!client_ || MQTTClient_isConnected(client_) == 0) + { + return; + } + + int rc = MQTTClient_publishMessage(client_, topic.c_str(), &pubmsg, &token); + if (rc != MQTTCLIENT_SUCCESS) + { + std::cerr << "[MQTT] publish failed rc=" << rc << std::endl; + return; + } + MQTTClient_waitForCompletion(client_, token, 10000L); +} + +int MqttReceiver::handleMessage(char* topicName, MQTTClient_message* message) +{ + // 拷贝 payload(message->payload 不一定以 \0 结尾) + std::string payload; + payload.assign(static_cast(message->payload), static_cast(message->payloadlen)); + + Json::Reader reader; + Json::Value root; + if (!reader.parse(payload, root)) + { + std::cerr << "[MQTT] JSON parse failed\n"; + MQTTClient_freeMessage(&message); + MQTTClient_free(topicName); + return 1; + } + + // type 必须是 request + if (!root.isMember("type") || root["type"].asString() != "request") + { + MQTTClient_freeMessage(&message); + MQTTClient_free(topicName); + return 1; + } + + if (!root.isMember("seqNo") || !root["seqNo"].isInt64()) + { + MQTTClient_freeMessage(&message); + MQTTClient_free(topicName); + return 1; + } + const long long seqNo = root["seqNo"].asInt64(); + + if (!root.isMember("data") || !root["data"].isObject()) + { + MQTTClient_freeMessage(&message); + MQTTClient_free(topicName); + return 1; + } + + const Json::Value data = root["data"]; + const std::string command = data.get("command", "").asString(); + + // 收到控制消息,更新时间(用于“控制超时”) + state_.last_msg_ms = now_ms(); + + if (command == "mode" || command == "gear" || command == "sweepCtrl" || command == "gather") + { + const int value = data.get("value", 0).asInt(); + std::lock_guard lock(state_.mtx); + + if (command == "mode") + state_.ctrl.mode = value; + else if (command == "gear") + state_.ctrl.gear = value; + else if (command == "sweepCtrl") + state_.ctrl.sweepCtrl = value; + else if (command == "gather") + state_.get_route = value; + } + else if (command == "drive") + { + // 你原协议:value = "throttle,brake,steering" + const std::string driveValues = data.get("value", "").asString(); + + std::stringstream ss(driveValues); + std::vector values; + std::string tok; + while (std::getline(ss, tok, ',')) values.push_back(tok); + + if (values.size() >= 3) + { + int throttle = std::stoi(values[0]); + int brake = std::stoi(values[1]); + int steering = std::stoi(values[2]); + + // 你原逻辑:steering = 32768 - steering + steering = 32768 - steering; + + std::lock_guard lock(state_.mtx); + state_.ctrl.throttle = throttle; + state_.ctrl.brake = brake; + state_.ctrl.steering = steering; + } + } + + // 发送 response(同步,避免 detached thread) + std::string resp_topic; + { + std::lock_guard lock(state_.mtx); + if (!state_.identity_ready) + { + MQTTClient_freeMessage(&message); + MQTTClient_free(topicName); + return 1; + } + resp_topic = expand_topic(cfg_.remote_topic_template, state_.vid); + } + + sendResponse(resp_topic, seqNo, 200, "Success"); + + MQTTClient_freeMessage(&message); + MQTTClient_free(topicName); + return 1; +} + +void MqttReceiver::runLoop() +{ + // 生成 client id(如未提供) + if (cfg_.client_id.empty()) cfg_.client_id = generateClientId(); + + const std::string& address = cfg_.address; + if (address.rfind("tcp://", 0) != 0) + { + std::cerr << "[MQTT] address must be 'tcp://ip:port', got: " << address << std::endl; + } + + int rc = MQTTClient_create(&client_, address.c_str(), cfg_.client_id.c_str(), MQTTCLIENT_PERSISTENCE_NONE, nullptr); + if (rc != MQTTCLIENT_SUCCESS) + { + std::cerr << "[MQTT] create failed rc=" << rc << std::endl; + state_.mqtt_connected = false; + return; + } + + rc = MQTTClient_setCallbacks(client_, this, &MqttReceiver::onConnLost, &MqttReceiver::onMessageArrived, + &MqttReceiver::onDelivered); + if (rc != MQTTCLIENT_SUCCESS) + { + std::cerr << "[MQTT] setCallbacks failed rc=" << rc << std::endl; + MQTTClient_destroy(&client_); + client_ = nullptr; + state_.mqtt_connected = false; + return; + } + + conn_opts_.keepAliveInterval = 20; + conn_opts_.cleansession = 1; + conn_opts_.username = cfg_.mqtt_user.c_str(); + conn_opts_.password = cfg_.mqtt_password.c_str(); + + auto get_sub_topic = [&]() -> std::string + { + std::lock_guard lock(state_.mtx); + if (!state_.identity_ready) return {}; + return expand_topic(cfg_.remote_topic_template, state_.vid); + }; + + auto do_connect_and_sub = [&]() -> bool + { + int rc2 = MQTTClient_connect(client_, &conn_opts_); + if (rc2 != MQTTCLIENT_SUCCESS) + { + std::cerr << "[MQTT] connect failed rc=" << rc2 << "\n"; + state_.mqtt_connected = false; + return false; + } + else + { + std::cout << "[MQTT] Connected to broker\n"; + } + + std::string topic = get_sub_topic(); + if (topic.empty()) + { + state_.mqtt_connected = false; + return false; + } + + rc2 = MQTTClient_subscribe(client_, topic.c_str(), 1); + if (rc2 != MQTTCLIENT_SUCCESS) + { + std::cerr << "[MQTT] subscribe failed rc=" << rc2 << "\n"; + state_.mqtt_connected = false; + return false; + } + + state_.mqtt_connected = true; + std::cout << "[MQTT] Connected & subscribed: " << topic << "\n"; + return true; + }; + + // 首次连接(identity 可能还没 ready,所以这里可能失败,没关系) + do_connect_and_sub(); + + bool last_ready = false; + std::string last_topic; + + while (running_) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if (!client_) continue; + + // 1) 检测 identity ready 以及当前应订阅的 topic + bool ready = false; + std::string topic; + { + std::lock_guard lock(state_.mtx); + ready = state_.identity_ready; + if (ready) topic = expand_topic(cfg_.remote_topic_template, state_.vid); + } + + // 2) identity 从 false->true(或 vid 变化)时,主动订阅一次 + if (ready && (!last_ready || topic != last_topic)) + { + std::cout << "[MQTT] Identity ready/topic changed, subscribing: " << topic << "\n"; + + // 已连接就直接 subscribe;未连接则走 do_connect_and_sub + if (MQTTClient_isConnected(client_) != 0) + { + int rc_sub = MQTTClient_subscribe(client_, topic.c_str(), 1); + if (rc_sub == MQTTCLIENT_SUCCESS) + { + state_.mqtt_connected = true; + std::cout << "[MQTT] Subscribed: " << topic << "\n"; + last_topic = topic; + } + else + { + state_.mqtt_connected = false; + std::cerr << "[MQTT] subscribe failed rc=" << rc_sub << "\n"; + } + } + else + { + do_connect_and_sub(); + last_topic = topic; // 记录期望值,避免频繁刷屏 + } + } + + last_ready = ready; + + // 3) 断线重连逻辑保留 + if (MQTTClient_isConnected(client_) == 0) + { + state_.mqtt_connected = false; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + do_connect_and_sub(); + } + } +} + +} // namespace sub_node_pkg diff --git a/src/communication/sub/src/sub_node.cpp b/src/communication/sub/src/sub_node.cpp index 4f558ce..b1bc3c1 100644 --- a/src/communication/sub/src/sub_node.cpp +++ b/src/communication/sub/src/sub_node.cpp @@ -1,478 +1,76 @@ -#include "MQTTClient.h" -#include "rclcpp/rclcpp.hpp" -#include "sweeper_interfaces/msg/sub.hpp" -#include "sweeper_interfaces/msg/mc_ctrl.hpp" -#include "json.h" -#include "sub_node.hpp" -#include -#include "md5.h" -#include -#include -#include +#include "sub/sub_node.hpp" + #include -#include -#include -#include -#include -#include -#include -#include -#include -using namespace std; -// 全局变量声明 -std::string command; -std::string sub_topic; -int get_route = 0; -int horn = 0; -int isDisconn = 0; +#include "sub/control_mapper.hpp" -MQTTClient client; -MQTTClient_deliveryToken token_d_m; -MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; -MQTTClient_message pubmsg_d_m = MQTTClient_message_initializer; - -// mqtt相关 -std::string mqtt_vid; -std::string mqtt_inter_net_address; -int mqtt_inter_net_port; -std::string mqtt_external_net_address; -int mqtt_external_net_port; -std::string mqtt_user; -std::string mqtt_password; -std::string mqtt_topic_remote; - -// 新增:声明未定义的常量 -std::string ADDRESS; -std::string CLIENTID_SUB; // 客户端ID,提供默认值 - -std::chrono::steady_clock::time_point last_message_time = std::chrono::steady_clock::now(); -constexpr auto QOS = 1; -constexpr auto TIMEOUT = 10000L; -volatile MQTTClient_deliveryToken deliveredtoken; -char sub_buff[500]; - -car_ctrl car_ctrl_mes; - -std::string generate_mqtt_client_id() +namespace sub_node_pkg { - // 获取当前时间戳(以毫秒为单位) - auto now = std::chrono::system_clock::now(); - auto millis = std::chrono::duration_cast(now.time_since_epoch()).count(); - // 生成一个 4 位随机数 - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> dis(1000, 9999); - int random_num = dis(gen); +SubNode::SubNode(ControlState& state, const std::string& name) : Node(name), state_(state) +{ + RCLCPP_INFO(this->get_logger(), "%s node started.", name.c_str()); - // 拼接成 client ID - std::ostringstream oss; - oss << "client_" << millis << "_" << std::setw(4) << std::setfill('0') << random_num; + pub_mc_ = this->create_publisher("remote_mc_ctrl", 10); + pub_gather_ = this->create_publisher("gather", 10); + // identity(transient_local,确保晚启动也能拿到) + identity_sub_ = this->create_subscription( + "/vehicle/identity", rclcpp::QoS(1).transient_local().reliable(), + std::bind(&SubNode::identityCallback, this, std::placeholders::_1)); - return oss.str(); + timer_ = this->create_wall_timer(std::chrono::milliseconds(100), std::bind(&SubNode::timerCallback, this)); } -void sendResponse(MQTTClient client, const std::string &topic, long long seqNo, int code, std::string msg) +void SubNode::timerCallback() { - // 使用JSON库构建响应数据 - Json::Value responseData; - responseData["type"] = "response"; - responseData["seqNo"] = static_cast(seqNo); + // 读快照(避免与 MQTT 线程 data race) + CarCtrl ctrl_snapshot; + int get_route_snapshot = 0; + bool mqtt_connected = state_.mqtt_connected.load(); + const int64_t last_ms = state_.last_msg_ms.load(); - Json::Value data; - data["code"] = code; - data["msg"] = msg; + { + std::lock_guard lock(state_.mtx); + ctrl_snapshot = state_.ctrl; + get_route_snapshot = state_.get_route; + } - responseData["data"] = data; + // gather 发布 + sweeper_interfaces::msg::Sub gather_msg; + gather_msg.get_route = get_route_snapshot; + pub_gather_->publish(gather_msg); - // 使用JSON writer生成字符串 - Json::FastWriter writer; - std::string responseJson = writer.write(responseData); + // 生成控制 + sweeper_interfaces::msg::McCtrl mc; + fillMcCtrlFromCarCtrl(ctrl_snapshot, mc); - // 发布MQTT消息 - pubmsg_d_m.payload = (void *)responseJson.c_str(); - pubmsg_d_m.payloadlen = responseJson.length(); - pubmsg_d_m.qos = QOS; - pubmsg_d_m.retained = 0; + // 可选:控制超时保护(例如 500ms 内未收到控制,强制安全态) + // 你原来把“500ms无消息”当掉线,我这里改成“控制超时保护”,语义更准确 + const int64_t now = + std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()) + .count(); + constexpr int64_t CONTROL_TIMEOUT_MS = 500; - MQTTClient_publishMessage(client, topic.c_str(), &pubmsg_d_m, &token_d_m); - MQTTClient_waitForCompletion(client, token_d_m, TIMEOUT); - printf("Message with delivery token %d delivered\n", token_d_m); + if (!mqtt_connected || (last_ms > 0 && (now - last_ms) > CONTROL_TIMEOUT_MS)) + { + // 强制安全态 + mc.brake = 1; + mc.gear = 0; + mc.rpm = 0; + mc.angle = 0; + mc.angle_speed = 120; + } + + pub_mc_->publish(mc); } -// 修正:将getSubTopic()函数移到使用它的mqtt_sub()函数之前 -const char *getSubTopic() +void SubNode::identityCallback(const sweeper_interfaces::msg::VehicleIdentity::SharedPtr msg) { - return sub_topic.c_str(); + std::lock_guard lock(state_.mtx); + state_.vid = msg->vid; + state_.identity_ready = msg->ready; + + RCLCPP_INFO(get_logger(), "Identity: VID=%s ready=%d", msg->vid.c_str(), msg->ready); } -void delivered(void *context, MQTTClient_deliveryToken dt) -{ - (void)context; - printf("Message with token value %d delivery confirmed\n", dt); - deliveredtoken = dt; -} - -int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message) -{ - (void)context; - (void)topicLen; - // printf("Message arrived\n"); - last_message_time = std::chrono::steady_clock::now(); - // printf("topic: %s\n", topicName); - // printf("message: %.*s\n", message->payloadlen, (char *)message->payload); - - memset(sub_buff, 0, sizeof(sub_buff)); - memcpy(&sub_buff, (char *)message->payload, message->payloadlen); - - Json::Reader reader; - Json::Value root; - - if (!reader.parse(sub_buff, root)) - { - printf("recv json fail\n"); - MQTTClient_freeMessage(&message); - MQTTClient_free(topicName); - return 1; - } - - isDisconn = 0; - // std::cout << root << std::endl; - - // Check type - if (!root.isMember("type") || root["type"].asString() != "request") - { - std::cout << "Message type is not 'request', ignoring.\n"; - MQTTClient_freeMessage(&message); - MQTTClient_free(topicName); - return 1; - } - - // Extract seqNo - if (!root.isMember("seqNo") || !root["seqNo"].isInt64()) - { - std::cout << "Invalid or missing seqNo.\n"; - MQTTClient_freeMessage(&message); - MQTTClient_free(topicName); - return 1; - } - - long long seqNo = root["seqNo"].asInt64(); - - // Check data - if (!root.isMember("data") || !root["data"].isObject()) - { - std::cout << "Invalid message: missing 'data' field.\n"; - MQTTClient_freeMessage(&message); - MQTTClient_free(topicName); - return 1; - } - - Json::Value data = root["data"]; - std::string command = data["command"].asString(); - int value = data["value"].asInt(); - - // Handle command - if (command == "mode") - car_ctrl_mes.mode = value; - else if (command == "gear") - car_ctrl_mes.gear = value; - else if (command == "drive") - { - std::string driveValues = data["value"].asString(); - std::stringstream ss(driveValues); - std::vector values; - std::string token; - while (std::getline(ss, token, ',')) - { - values.push_back(token); - } - if (values.size() >= 3) - { - int throttle = std::stoi(values[0]); - int brake = std::stoi(values[1]); - int steering = std::stoi(values[2]); - steering = 32768 - steering; - car_ctrl_mes.throttle = throttle; - car_ctrl_mes.brake = brake; - car_ctrl_mes.steering = steering; - } - } - else if (command == "sweepCtrl") - car_ctrl_mes.sweepCtrl = value; - - else if (command == "gather") - get_route = value; - - // Generate response - // sendResponse(client, mqtt_topic_remote, seqNo, 200, "Success"); - // sendResponse作为线程函数,发送响应 - // 创建线程发送响应 - std::thread responseThread(sendResponse, client, mqtt_topic_remote, seqNo, 200, "Success"); - responseThread.detach(); - - MQTTClient_freeMessage(&message); - MQTTClient_free(topicName); - return 1; -} - -void connlost(void *context, char *cause) -{ - (void)context; - isDisconn = 1; - printf("\nConnection lost\n"); - printf("cause: %s\n", cause); -} - -void *mqtt_sub(void *arg) -{ - (void)arg; - // 使用已定义的getSubTopic()函数 - const char *SUB_TOPIC = getSubTopic(); - - int rc; - const char *username = mqtt_user.c_str(); - const char *password = mqtt_password.c_str(); - - if ((rc = MQTTClient_create(&client, ADDRESS.c_str(), CLIENTID_SUB.c_str(), MQTTCLIENT_PERSISTENCE_NONE, NULL)) != MQTTCLIENT_SUCCESS) - { - printf("Failed to create client, return code %d\n", rc); - MQTTClient_destroy(&client); - return NULL; - } - - if ((rc = MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered)) != MQTTCLIENT_SUCCESS) - { - printf("Failed to set callbacks, return code %d\n", rc); - MQTTClient_destroy(&client); - return NULL; - } - - conn_opts.keepAliveInterval = 20; - conn_opts.cleansession = 1; - conn_opts.username = username; - conn_opts.password = password; - - if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) - { - printf("Failed to connect, return code %d\n", rc); - MQTTClient_destroy(&client); - return NULL; - } - - printf("Subscribing to topic %s\nfor client %s using QoS%d\n\n", SUB_TOPIC, CLIENTID_SUB.c_str(), QOS); - if ((rc = MQTTClient_subscribe(client, SUB_TOPIC, QOS)) != MQTTCLIENT_SUCCESS) - { - printf("Failed to subscribe, return code %d\n", rc); - MQTTClient_destroy(&client); - return NULL; - } - - while (1) - { - usleep(100); - // 检查连接状态,如果连接丢失,则尝试重新连接 - if (MQTTClient_isConnected(client) == 0) - { - printf("MQTT connection lost, trying to reconnect...\n"); - - if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) - { - isDisconn = 1; - printf("Failed to reconnect, return code %d\n", rc); - // 处理连接失败的情况,例如等待一段时间后再次尝试 - } - else - { - isDisconn = 0; - printf("Reconnected to MQTT server.\n"); - // 重新订阅主题 - if ((rc = MQTTClient_subscribe(client, SUB_TOPIC, QOS)) != MQTTCLIENT_SUCCESS) - { - printf("Failed to resubscribe, return code %d\n", rc); - } - } - } - else - { - isDisconn = 0; - auto current_time = std::chrono::steady_clock::now(); - if (std::chrono::duration_cast(current_time - last_message_time).count() > 500) - { - isDisconn = 1; - // printf("Heartbeat timeout: No message received in 500ms.\n"); - // 执行心跳超时的处理逻辑 - } - } - } - - if ((rc = MQTTClient_unsubscribe(client, SUB_TOPIC)) != MQTTCLIENT_SUCCESS) - { - printf("Failed to unsubscribe, return code %d\n", rc); - MQTTClient_destroy(&client); - return NULL; - } - - if ((rc = MQTTClient_disconnect(client, 10000)) != MQTTCLIENT_SUCCESS) - { - printf("Failed to disconnect, return code %d\n", rc); - MQTTClient_destroy(&client); - return NULL; - } - - MQTTClient_destroy(&client); - return NULL; -} - -class sub_node : public rclcpp::Node -{ -public: - // 构造函数,有一个参数为节点名称 - sub_node(std::string name) : Node(name) - { - RCLCPP_INFO(this->get_logger(), "%s节点已经启动.", name.c_str()); - - timer_ = this->create_wall_timer(std::chrono::milliseconds(100), std::bind(&sub_node::timer_callback, this)); - // 发布控制指令消息的发布器 - pub_mc = this->create_publisher("remote_mc_ctrl", 10); - pub_gather = this->create_publisher("gather", 10); - } - -private: - void timer_callback() - { - sweeper_interfaces::msg::Sub message; - message.get_route = get_route; - pub_gather->publish(message); - - sweeper_interfaces::msg::McCtrl msg; - // if (isDisconn == 0) - { - // mcu 部分 - if (car_ctrl_mes.mode == 3) // 台架端远程驾驶模式 - { - msg.brake = 0; - - if (car_ctrl_mes.gear == 0) // N档 - msg.gear = 0; - else if (car_ctrl_mes.gear == 1) // D档 - msg.gear = 2; - else if (car_ctrl_mes.gear == 2) // R档 - msg.gear = 1; - - msg.rpm = car_ctrl_mes.throttle / 65535 * 1000; - - // 转向 - // 0~32200 -> -40~0 33200~65535 -> 0~40 - int steering = 0; - if (car_ctrl_mes.steering < 32200) - { - const int originalWidth1 = 32200; - const int targetWidth1 = 40; - - double unitLength1 = targetWidth1 / originalWidth1; - - steering = -40 + static_cast(car_ctrl_mes.steering * unitLength1); - } - else if (car_ctrl_mes.steering > 33200) - { - const int originalWidth2 = 32355; // 注意这里是从 33200 到 65535,共计 32355 个数 - const int targetWidth2 = 40; - - double unitLength2 = targetWidth2 / originalWidth2; - - steering = static_cast((car_ctrl_mes.steering - 32200) * unitLength2); - } - else - { - steering = 0; // 朝向前方 - } - msg.angle = steering; - msg.angle_speed = 120; - } - else - { - msg.brake = 1; - msg.gear = 0; - msg.rpm = 0; - - // eps部分 - msg.angle = 0; - msg.angle_speed = 120; - } - - msg.sweep = car_ctrl_mes.sweepCtrl; - - pub_mc->publish(msg); - - // RCLCPP_INFO_STREAM(this->get_logger(), "Publishing ControlMsg:" << "\n 档位: " << [&]() - // { - // switch(msg.gear) { - // case 0: return "空挡"; - // case 1: return "后退"; - // case 2: return "前进"; - // default: return "未知档位"; // 添加默认分支 - // } }() << "\n 油门转速: " << msg.rpm << " rpm" - // << "\n 转向角度: " << msg.angle << "°" - // << "\n 刹车: " << (msg.brake == 0 ? "开(释放)" : "关(刹住)") << "\n 清扫: " << (msg.sweep ? "开启清扫" : "关闭清扫")); - } - } - - // 声明定时器指针 - rclcpp::TimerBase::SharedPtr timer_; - // 声明话题发布者指针 - rclcpp::Publisher::SharedPtr pub_gather; - rclcpp::Publisher::SharedPtr pub_mc; -}; - -void init_main() -{ - Json::Reader reader; - Json::Value root; - std::ifstream in("config.json", std::ios::binary); - if (!in.is_open()) - { - std::cout << "read config file error" << std::endl; - return; - } - if (reader.parse(in, root)) - { - mqtt_vid = root["mqtt"]["vid"].asString(); - mqtt_inter_net_address = root["mqtt"]["inter_net_address"].asString(); - mqtt_inter_net_port = root["mqtt"]["inter_net_port"].asInt(); - mqtt_external_net_address = root["mqtt"]["external_net_address"].asString(); - mqtt_external_net_port = root["mqtt"]["external_net_port"].asInt(); - mqtt_user = root["mqtt"]["mqtt_user"].asString(); - mqtt_password = root["mqtt"]["mqtt_password"].asString(); - mqtt_topic_remote = root["mqtt"]["remote_topic"].asString(); - // 添加对sub_topic的初始化 - sub_topic = mqtt_topic_remote; - CLIENTID_SUB = generate_mqtt_client_id(); - // ADDRESS = mqtt_inter_net_address + ":" + std::to_string(mqtt_inter_net_port); - ADDRESS = mqtt_external_net_address + ":" + std::to_string(mqtt_external_net_port); - cout << "ADDRESS: " << ADDRESS << endl; - cout << "CLIENTID_SUB: " << CLIENTID_SUB << endl; - cout << "mqtt_vid: " << mqtt_vid << endl; - } - in.close(); // 关闭文件流 -} - -pthread_t mqtt_sub_thread_t; - -int main(int argc, char **argv) -{ - init_main(); - memset(&car_ctrl_mes, 0, sizeof(car_ctrl_mes)); - car_ctrl_mes.steering = 32767; - pthread_create(&mqtt_sub_thread_t, NULL, mqtt_sub, NULL); - - rclcpp::init(argc, argv); - /*创建对应节点的共享指针对象*/ - auto node = std::make_shared("sub_node"); - /* 运行节点,并检测退出信号*/ - rclcpp::spin(node); - rclcpp::shutdown(); - - return 0; -} \ No newline at end of file +} // namespace sub_node_pkg