From 2b0256ea6ad49f1efad35aa21287ed84ed3b0eb1 Mon Sep 17 00:00:00 2001 From: Ko van der Sloot Date: Thu, 5 Dec 2024 08:40:42 +0100 Subject: [PATCH 1/7] Squashed commit of the following: modernizing, expanded tests, cleanup cleaning up XmlTools. update FileUtils to use filesystem::remove updating enum_types handling. reworking LogStream C++ code quality (C++17) --- .github/workflows/ticcutils.yml | 4 +- include/ticcutils/CommandLine.h | 14 +-- include/ticcutils/Configuration.h | 4 +- include/ticcutils/FileUtils.h | 4 +- include/ticcutils/LogBuffer.h | 8 +- include/ticcutils/LogStream.h | 48 +++++----- include/ticcutils/UniHash.h | 8 +- include/ticcutils/Unicode.h | 2 +- include/ticcutils/XMLtools.h | 143 ++++++++++++++++------------- include/ticcutils/enum_flags.h | 3 +- src/FdStream.cxx | 2 +- src/FileUtils.cxx | 4 +- src/LogStream.cxx | 128 ++++++++------------------ src/Makefile.am | 2 +- src/ServerBase.cxx | 4 +- src/XMLtools.cxx | 148 ++++++++++++++++++++---------- src/runtest.cxx | 97 +++++++++++++++----- src/testlogstream.cxx | 10 +- 18 files changed, 346 insertions(+), 287 deletions(-) diff --git a/.github/workflows/ticcutils.yml b/.github/workflows/ticcutils.yml index 644ff62..f8f871a 100644 --- a/.github/workflows/ticcutils.yml +++ b/.github/workflows/ticcutils.yml @@ -5,7 +5,9 @@ on: schedule: - cron: "0 22 3 * 5" # run test once a month push: - branches: [master] + branches: + - master + - develop paths: - configure.ac - 'src/**' diff --git a/include/ticcutils/CommandLine.h b/include/ticcutils/CommandLine.h index 19929e8..e5bf190 100644 --- a/include/ticcutils/CommandLine.h +++ b/include/ticcutils/CommandLine.h @@ -107,7 +107,7 @@ namespace TiCC { class CL_Options { friend std::ostream& operator<<( std::ostream&, const CL_Options& ); public: - typedef std::vector::const_iterator const_iterator; + using const_iterator = std::vector::const_iterator; CL_Options(); CL_Options( const std::string&, const std::string& ); void allow_args( const std::string& = "", const std::string& = "" ); @@ -126,14 +126,6 @@ namespace TiCC { } void add_short_options( const std::string& s ); void add_long_options( const std::string& s ); - void set_short_options( const std::string& s ){ - // misnomer, kept for backward compatability - add_short_options( s ); - } - void set_long_options( const std::string& s ){ - // misnomer, kept for backward compatability - add_long_options( s ); - } const std::string& prog_name() const { /// return the stored name of the calling program (normally argv[0]) return _prog_name; @@ -334,8 +326,8 @@ namespace TiCC { bool extract_internal( const std::string&, std::string& ); std::vector Opts; std::vector MassOpts; - CL_Options( const CL_Options& ); - CL_Options& operator=( const CL_Options& ); + CL_Options( const CL_Options& ) = delete; + CL_Options& operator=( const CL_Options& ) = delete; std::set valid_chars; std::set valid_chars_par; std::set valid_chars_opt; diff --git a/include/ticcutils/Configuration.h b/include/ticcutils/Configuration.h index 3329b0e..b6ef9f3 100644 --- a/include/ticcutils/Configuration.h +++ b/include/ticcutils/Configuration.h @@ -46,8 +46,8 @@ namespace TiCC { /// There is an implicit \b global section when the section is unnamed. class Configuration { public: - typedef std::map ssMap; - typedef std::map sssMap; + using ssMap = std::map; + using sssMap = std::map; Configuration(); void merge( const Configuration&, bool = false ); bool fill( const std::string& ); diff --git a/include/ticcutils/FileUtils.h b/include/ticcutils/FileUtils.h index 9b15174..0406f71 100644 --- a/include/ticcutils/FileUtils.h +++ b/include/ticcutils/FileUtils.h @@ -70,8 +70,8 @@ namespace TiCC { std::string _temp_name; std::ofstream *_os; bool _keep; - tmp_stream( const tmp_stream& ); - tmp_stream operator=( const tmp_stream& ); + tmp_stream( const tmp_stream& ) = delete; + tmp_stream operator=( const tmp_stream& ) = delete; }; } diff --git a/include/ticcutils/LogBuffer.h b/include/ticcutils/LogBuffer.h index 7fea411..2186183 100644 --- a/include/ticcutils/LogBuffer.h +++ b/include/ticcutils/LogBuffer.h @@ -78,13 +78,11 @@ template > std::string ass_mess; void buffer_out(); // prohibit copying and assignment - basic_log_buffer( const basic_log_buffer& ); - basic_log_buffer& operator=( const basic_log_buffer& ); + basic_log_buffer( const basic_log_buffer& ) = delete; + basic_log_buffer& operator=( const basic_log_buffer& ) = delete; }; -typedef basic_log_buffer > LogBuffer; -typedef basic_log_buffer > wLogBuffer; - +using LogBuffer = basic_log_buffer>; template basic_log_buffer::~basic_log_buffer(){ diff --git a/include/ticcutils/LogStream.h b/include/ticcutils/LogStream.h index f3610a9..83b2608 100644 --- a/include/ticcutils/LogStream.h +++ b/include/ticcutils/LogStream.h @@ -43,34 +43,28 @@ namespace TiCC { public: explicit LogStream(); explicit LogStream( int ); - explicit LogStream( const std::string&, LogFlag = StampBoth ); - explicit LogStream( const char *cs, LogFlag lf = StampBoth ): - LogStream( std::string( cs ), lf ) {}; LogStream( std::ostream&, - const std::string& = "", LogFlag = StampBoth ); - LogStream( const LogStream&, const std::string&, LogFlag ); - LogStream( const LogStream&, const std::string& ); LogStream( const LogStream * ); + LogStream *create( const std::string&, std::ios_base::openmode = std::ios::out ); bool set_single_threaded_mode(); bool single_threaded() const { return single_threaded_mode; }; - void setthreshold( LogLevel t ){ buf.Threshold( t ); }; - LogLevel getthreshold() const { return buf.Threshold(); }; - void setlevel( LogLevel l ){ buf.Level( l ); }; - LogLevel getlevel() const{ return buf.Level(); }; + void set_threshold( LogLevel t ){ buf.Threshold( t ); }; + LogLevel get_threshold() const { return buf.Threshold(); }; + void set_level( LogLevel l ){ buf.Level( l ); }; + LogLevel get_level() const{ return buf.Level(); }; void associate( std::ostream& os ) { buf.AssocStream( os ); }; - // std::ostream& associate() const { return buf.AssocStream(); }; - void setstamp( LogFlag f ){ buf.StampFlag( f ); }; - LogFlag getstamp() const { return buf.StampFlag(); }; - void message( const std::string& s ){ buf.Message( s ); }; - void addmessage( const std::string& ); - void addmessage( const int ); - const std::string& message() const { return buf.Message(); }; + void set_stamp( LogFlag f ){ buf.StampFlag( f ); }; + LogFlag get_stamp() const { return buf.StampFlag(); }; + void set_message( const std::string& s ){ buf.Message( s ); }; + void add_message( const std::string& ); + void add_message( const int ); + const std::string& get_message() const { return buf.Message(); }; static bool Problems(); private: LogBuffer buf; // prohibit assignment - LogStream& operator=( const LogStream& ); + LogStream& operator=( const LogStream& ) = delete; bool IsBlocking(); bool single_threaded_mode; }; @@ -79,7 +73,7 @@ namespace TiCC { bool IsActive( LogStream * ); /// \brief create a LogStream - class Log{ + class Log { public: explicit Log( LogStream * ); explicit Log( LogStream& l ); @@ -88,8 +82,8 @@ namespace TiCC { private: LogStream *my_stream; LogLevel my_level; - Log( const Log& ); - Log& operator=( const Log& ); + Log( const Log& ) = delete; + Log& operator=( const Log& ) = delete; }; /// \brief create a debugging LogStream @@ -102,8 +96,8 @@ namespace TiCC { private: LogStream *my_stream; LogLevel my_level; - Dbg( const Dbg& ); - Dbg& operator=( const Dbg& ); + Dbg( const Dbg& ) = delete; + Dbg& operator=( const Dbg& ) = delete; }; /// \brief a debugging LogStream for heavy output @@ -116,8 +110,8 @@ namespace TiCC { private: LogStream *my_stream; LogLevel my_level; - xDbg( const xDbg& ); - xDbg& operator=( const xDbg& ); + xDbg( const xDbg& ) = delete; + xDbg& operator=( const xDbg& ) = delete; }; /// \brief a debugging LogStream for extreme output @@ -130,8 +124,8 @@ namespace TiCC { private: LogStream *my_stream; LogLevel my_level; - xxDbg( const xxDbg& ); - xxDbg& operator=( const xxDbg& ); + xxDbg( const xxDbg& ) = delete; + xxDbg& operator=( const xxDbg& ) = delete; }; } diff --git a/include/ticcutils/UniHash.h b/include/ticcutils/UniHash.h index e2c3c70..7a40026 100644 --- a/include/ticcutils/UniHash.h +++ b/include/ticcutils/UniHash.h @@ -56,8 +56,8 @@ namespace Hash { private: const icu::UnicodeString _value; unsigned int _ID; - UniInfo( const UniInfo& ) =delete; - UniInfo& operator=( const UniInfo& ) =delete; + UniInfo( const UniInfo& ) = delete; + UniInfo& operator=( const UniInfo& ) = delete; }; /// \brief The UnicodeHash class is used to enumerate Unicode strings. @@ -85,8 +85,8 @@ namespace Hash { unsigned int _num_of_tokens; std::vector _rev_index; Tries::UniTrie _tree; - UnicodeHash( const UnicodeHash& ) =delete; - UnicodeHash& operator=( const UnicodeHash& ) =delete; + UnicodeHash( const UnicodeHash& ) = delete; + UnicodeHash& operator=( const UnicodeHash& ) = delete; }; } diff --git a/include/ticcutils/Unicode.h b/include/ticcutils/Unicode.h index b2ae42e..f88b10c 100644 --- a/include/ticcutils/Unicode.h +++ b/include/ticcutils/Unicode.h @@ -84,7 +84,7 @@ namespace TiCC { bool set_debug( bool b ){ bool r = _debug; _debug = b; return r; }; private: // inhibit copies! - UnicodeRegexMatcher( const UnicodeRegexMatcher& ) =delete; + UnicodeRegexMatcher( const UnicodeRegexMatcher& ) = delete; UnicodeRegexMatcher& operator=( const UnicodeRegexMatcher& ) = delete; RegexPattern *_pattern; RegexMatcher *_matcher; diff --git a/include/ticcutils/XMLtools.h b/include/ticcutils/XMLtools.h index 90f3763..492e267 100644 --- a/include/ticcutils/XMLtools.h +++ b/include/ticcutils/XMLtools.h @@ -27,46 +27,56 @@ #ifndef TICC_XML_TOOLS_H #define TICC_XML_TOOLS_H +#include #include #include #include -#include +#include #include "libxml/xmlstring.h" #include "libxml/globals.h" #include "libxml/parser.h" namespace TiCC { - inline const xmlChar *to_xmlChar( const char *in ){ - return reinterpret_cast(in); + xmlDoc *create_xmlDocument( const std::string& ); + xmlNode *getRoot( xmlDoc *); + bool isNCName( const std::string& ); + std::string create_NCName( const std::string& ); + + inline const xmlChar *to_xmlChar( const std::string& in ){ + return reinterpret_cast(in.c_str()); } - inline const char *to_char( const xmlChar *in ){ + inline const std::string to_string( const xmlChar *in ){ return reinterpret_cast(in); } + inline const std::string to_string( const xmlChar *in, size_t len ){ + return std::string( reinterpret_cast(in), len ); + } + inline xmlNode *XmlNewNode( const std::string& elem ){ - return xmlNewNode( 0, to_xmlChar(elem.c_str()) ); + return xmlNewNode( 0, to_xmlChar(elem) ); } inline xmlNode *XmlNewNode( xmlNs *ns, const std::string& elem ){ - return xmlNewNode( ns, to_xmlChar(elem.c_str()) ); + return xmlNewNode( ns, to_xmlChar(elem) ); } inline xmlNode *XmlNewComment( const std::string& elem ){ - return xmlNewComment( to_xmlChar(elem.c_str()) ); + return xmlNewComment( to_xmlChar(elem) ); } inline xmlNode *XmlNewChild( xmlNode *node, const std::string& elem ){ - xmlNode *chld = xmlNewNode( 0, to_xmlChar(elem.c_str()) ); + xmlNode *chld = xmlNewNode( 0, to_xmlChar(elem) ); return xmlAddChild( node, chld ); } inline xmlNode *XmlNewChild( xmlNode *node, xmlNs *ns, const std::string& elem ){ - xmlNode *chld = xmlNewNode( ns, to_xmlChar(elem.c_str()) ); + xmlNode *chld = xmlNewNode( ns, to_xmlChar(elem) ); return xmlAddChild( node, chld ); } @@ -76,13 +86,13 @@ namespace TiCC { if ( val.empty() ) return xmlNewTextChild( node, 0, - to_xmlChar(elem.c_str()), + to_xmlChar(elem), 0 ); else return xmlNewTextChild( node, 0, - to_xmlChar(elem.c_str()), - to_xmlChar(val.c_str()) ); + to_xmlChar(elem), + to_xmlChar(val) ); } inline xmlNode *XmlNewTextChild( xmlNode *node, @@ -90,25 +100,52 @@ namespace TiCC { const std::string& elem, const std::string& val ){ if ( val.empty() ) - return xmlNewTextChild( node, ns, - to_xmlChar(elem.c_str()) - , 0 ); + return xmlNewTextChild( node, + ns, + to_xmlChar(elem) + ,0 ); else - return xmlNewTextChild( node, ns, - to_xmlChar(elem.c_str()), - to_xmlChar(val.c_str()) ); + return xmlNewTextChild( node, + ns, + to_xmlChar(elem), + to_xmlChar(val) ); } inline void XmlAddContent( xmlNode *node, const std::string& cont ){ - xmlNodeAddContent( node, to_xmlChar(cont.c_str()) ); + xmlNodeAddContent( node, to_xmlChar(cont) ); } inline xmlAttr *XmlSetAttribute( xmlNode *node, const std::string& att, const std::string& val ){ return xmlSetProp( node, - to_xmlChar(att.c_str()), - to_xmlChar(val.c_str()) ); + to_xmlChar(att), + to_xmlChar(val) ); + } + + inline std::string Name( const xmlNode *node ){ + std::string result; + if ( node ){ + result = to_string(node->name); + } + return result; + } + + inline std::string TextValue( const xmlNode *node ){ + /// extract the string content of an xmlNode + /*! + \param node The xmlNode to extract from + \return the string value of node + */ + std::string result; + if ( node ){ + xmlChar *tmp = xmlNodeGetContent( node ); + if ( tmp ){ + result = to_string(tmp ); + xmlFree( tmp ); + } + } + return result; } inline std::string getAttribute( const xmlNode *node, @@ -116,8 +153,9 @@ namespace TiCC { if ( node ){ const xmlAttr *a = node->properties; while ( a ){ - if ( att == to_char(a->name) ) - return to_char(a->children->content); + if ( att == to_string(a->name) ){ + return TextValue(a->children); + } a = a->next; } } @@ -129,8 +167,7 @@ namespace TiCC { if ( node ){ const xmlAttr *a = node->properties; while ( a ){ - result[to_char(a->name) ] - = to_char(a->children->content); + result[to_string(a->name)] = TextValue(a->children); a = a->next; } } @@ -146,48 +183,23 @@ namespace TiCC { std::map getNSvalues( const xmlNode * ); std::map getDefinedNS( const xmlNode * ); - std::string serialize( const xmlNode& node ); + const std::string serialize( const xmlNode& ); + const std::string serialize( const xmlNode* ); + const std::string serialize( const xmlDoc& ); + const std::string serialize( const xmlDoc* ); - inline std::string Name( const xmlNode *node ){ - std::string result; - if ( node ){ - result = to_char(node->name); - } - return result; + inline std::ostream& operator << ( std::ostream& os, const xmlDoc& doc ){ + os << serialize(doc); + return os; } - inline std::string XmlContent( const xmlNode *node ){ - std::string result; - if ( node ){ - xmlChar *tmp = xmlNodeListGetString( node->doc, node->children, 1 ); - if ( tmp ){ - result = std::string( to_char(tmp) ); - xmlFree( tmp ); - } + inline std::ostream& operator << ( std::ostream& os, const xmlDoc* doc ){ + if ( doc ){ + os << serialize(*doc); } - return result; - } - - /// \brief XmlDoc is a C++ wrapper around libxml2::xmlDoc - class XmlDoc { - friend std::ostream& operator << ( std::ostream& , const XmlDoc& ); - public: - explicit XmlDoc( const std::string& ); - ~XmlDoc(){ - xmlFreeDoc( the_doc ); + else { + os << "No xmlDoc"; } - void setRoot( xmlNode* ); - xmlNode *getRoot() const; - xmlNode *MakeRoot( const std::string& ); - const std::string toString() const; - private: - XmlDoc( const XmlDoc& ) =delete; // no copies please - XmlDoc& operator= ( const XmlDoc& ) =delete; // no copies please - xmlDoc *the_doc; - }; - - inline std::ostream& operator << ( std::ostream& os, const XmlDoc& doc ){ - os << doc.toString(); return os; } @@ -197,7 +209,12 @@ namespace TiCC { } inline std::ostream& operator << ( std::ostream& os, const xmlNode *node ){ - os << serialize( *node ); + if ( node ){ + os << serialize( *node ); + } + else { + os << "No xmlNode"; + } return os; } diff --git a/include/ticcutils/enum_flags.h b/include/ticcutils/enum_flags.h index ea2e563..bad413f 100644 --- a/include/ticcutils/enum_flags.h +++ b/include/ticcutils/enum_flags.h @@ -39,7 +39,6 @@ namespace TiCC { inline ENUMTYPE operator ~ (ENUMTYPE a) { return ENUMTYPE(~((std::underlying_type::type)a)); } \ inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((std::underlying_type::type)a) ^ ((std::underlying_type::type)b)); } \ inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((std::underlying_type::type &)a) ^= ((std::underlying_type::type)b)); } \ - inline std::ostream& operator<<( std::ostream& os, const ENUMTYPE& f ){ os << (std::underlying_type::type &)f; return os; } \ - + inline bool operator % ( const ENUMTYPE &a, ENUMTYPE b) { return (a & b) == b; } } #endif diff --git a/src/FdStream.cxx b/src/FdStream.cxx index 2b42d29..2a471d6 100644 --- a/src/FdStream.cxx +++ b/src/FdStream.cxx @@ -206,7 +206,7 @@ bool nb_putline( ostream& os, const string& what, int& timeout ){ unsigned int i=0; int count = 0; bool result = true; - typedef void (*sig_hndl)(int); + using sig_hndl = void (*)(int); sig_hndl sig; // specify that the SIGPIPE signal is to be ignored sig=signal(SIGPIPE,SIG_IGN); diff --git a/src/FileUtils.cxx b/src/FileUtils.cxx index a6be931..68e81f4 100644 --- a/src/FileUtils.cxx +++ b/src/FileUtils.cxx @@ -63,7 +63,7 @@ namespace TiCC { } bool isFile( const string& name ){ - /// check if 'name' an accessible file + /// check if 'name' a regular file try{ filesystem::path the_path(name); return filesystem::is_regular_file(the_path); @@ -357,7 +357,7 @@ namespace TiCC { close(); delete _os; if ( !_keep ){ - remove( _temp_name.c_str() ); + filesystem::remove( _temp_name ); } } diff --git a/src/LogStream.cxx b/src/LogStream.cxx index cb477c9..5ae5385 100755 --- a/src/LogStream.cxx +++ b/src/LogStream.cxx @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -40,12 +41,12 @@ //#define LSDEBUG using std::ostream; +using std::ofstream; using std::streambuf; using std::cerr; using std::endl; using std::bad_cast; using std::string; -using std::to_string; namespace TiCC { LogStream::LogStream( int ) : @@ -64,22 +65,10 @@ namespace TiCC { /// create a LogStream connected to cerr } - LogStream::LogStream( const string& message, LogFlag stamp ) : - ostream( &buf ), - buf( cerr, message, stamp ), - single_threaded_mode(false) { - /// create a LogStream connected to cerr - /*! - \param message the prefix message - \param stamp the stamping flag to use - */ - } - LogStream::LogStream( ostream& as, - const string& message, LogFlag stamp ) : ostream( &buf ), - buf( as, message, stamp ), + buf( as, "", stamp ), single_threaded_mode(false){ /// create a LogStream connected to an output stream /*! @@ -89,49 +78,6 @@ namespace TiCC { */ } - LogStream::LogStream( const LogStream& ls, - const string& message, - LogFlag stamp ): - ostream( &buf ), - buf( ls.buf.AssocStream(), - ls.buf.Message(), - stamp ), - single_threaded_mode( ls.single_threaded_mode ){ - /// create a LogStream connected to a LogStream - /*! - \param ls a LogStream to connect to - \param message the prefix message to append to the parents message - \param stamp the stamping flag to use - - the new Stream takes all properties from the parent, except for the - message and the stamp flag - */ - buf.Level( ls.buf.Level() ); - buf.Threshold( ls.buf.Threshold() ); - addmessage( message ); - } - - LogStream::LogStream( const LogStream& ls, - const string& message ): - ostream( &buf ), - buf( ls.buf.AssocStream(), - ls.buf.Message(), - ls.buf.StampFlag() ), - single_threaded_mode( ls.single_threaded_mode ){ - /// create a LogStream connected to a LogStream - /*! - \param ls a LogStream to connect to - \param message the prefix message to append to the parents message - - the new Stream takes all properties from the parent, except for the - message - */ - buf.Level( ls.buf.Level() ); - buf.Threshold( ls.buf.Threshold() ); - addmessage( message ); - } - - LogStream::LogStream( const LogStream *ls ): ostream( &buf ), buf( ls->buf.AssocStream(), @@ -147,7 +93,13 @@ namespace TiCC { buf.Threshold( ls->buf.Threshold() ); } - void LogStream::addmessage( const string& s ){ + LogStream *LogStream::create( const string& filename, + std::ios_base::openmode mode ){ + ofstream os( filename, mode ); + return new LogStream( os ); + } + + void LogStream::add_message( const string& s ){ /// append a string to the current messsage if ( !s.empty() ){ string tmp = buf.Message(); @@ -156,10 +108,10 @@ namespace TiCC { } } - void LogStream::addmessage( const int i ){ + void LogStream::add_message( const int i ){ /// append a number to the current messsage - string m = "-" + to_string(i); - addmessage( m ); + string m = "-" + std::to_string(i); + add_message( m ); } static bool static_init = false; @@ -309,10 +261,10 @@ namespace TiCC { /// is the current level below the threshold? if ( !bad() ){ #ifdef LSDEBUG - cerr << "IsBlocking( " << getlevel() << "," << getthreshold() << ")" - << " ==> " << ( getlevel() < getthreshold() ) << endl; + cerr << "IsBlocking( " << get_level() << "," << get_threshold() << ")" + << " ==> " << ( get_level() < get_threshold() ) << endl; #endif - return getlevel() < getthreshold(); + return get_level() < get_threshold(); } else { return true; @@ -336,9 +288,9 @@ namespace TiCC { throw( "LogStreams FATAL error: No Stream supplied! " ); } if ( os->single_threaded() || init_mutex() ){ - my_level = os->getthreshold(); + my_level = os->get_threshold(); my_stream = os; - os->setthreshold( LogNormal ); + os->set_threshold( LogNormal ); } } @@ -346,15 +298,15 @@ namespace TiCC { /// create a Log object on the LogStream with Normal threshold if ( os.single_threaded() || init_mutex() ){ my_stream = &os; - my_level = os.getthreshold(); - os.setthreshold( LogNormal ); + my_level = os.get_threshold(); + os.set_threshold( LogNormal ); } } Log::~Log(){ /// destroy the Log object my_stream->flush(); - my_stream->setthreshold( my_level ); + my_stream->set_threshold( my_level ); if ( !my_stream->single_threaded() ){ mutex_release(); } @@ -363,7 +315,7 @@ namespace TiCC { LogStream& Log::operator *(){ /// return the LogStream from the Log object #ifdef DARE_TO_OPTIMIZE - if ( my_stream->getlevel() >= my_stream->getthreshold() ){ + if ( my_stream->get_level() >= my_stream->get_threshold() ){ return *my_stream; } else { @@ -381,8 +333,8 @@ namespace TiCC { } if ( os->single_threaded() || init_mutex() ){ my_stream = os; - my_level = os->getthreshold(); - os->setthreshold( LogDebug ); + my_level = os->get_threshold(); + os->set_threshold( LogDebug ); } } @@ -390,15 +342,15 @@ namespace TiCC { /// create a Dbg object on the LogStream with Debug threshold if ( os.single_threaded() || init_mutex() ){ my_stream = &os; - my_level = os.getthreshold(); - os.setthreshold( LogDebug ); + my_level = os.get_threshold(); + os.set_threshold( LogDebug ); } } Dbg::~Dbg(){ /// destroy the Dbg object my_stream->flush(); - my_stream->setthreshold( my_level ); + my_stream->set_threshold( my_level ); if ( !my_stream->single_threaded() ){ mutex_release(); } @@ -407,7 +359,7 @@ namespace TiCC { LogStream& Dbg::operator *() { /// return the LogStream from the Dbg object #ifdef DARE_TO_OPTIMIZE - if ( my_stream->getlevel() >= my_stream->getthreshold() ){ + if ( my_stream->get_level() >= my_stream->get_threshold() ){ return *my_stream; } else { @@ -425,8 +377,8 @@ namespace TiCC { } if ( os->single_threaded() || init_mutex() ){ my_stream = os; - my_level = os->getthreshold(); - os->setthreshold( LogHeavy ); + my_level = os->get_threshold(); + os->set_threshold( LogHeavy ); } } @@ -434,15 +386,15 @@ namespace TiCC { /// create a xDbg object on the LogStream with Heavy threshold if ( os.single_threaded() || init_mutex() ){ my_stream = &os; - my_level = os.getthreshold(); - os.setthreshold( LogHeavy ); + my_level = os.get_threshold(); + os.set_threshold( LogHeavy ); } } xDbg::~xDbg(){ /// destroy the xDbg object my_stream->flush(); - my_stream->setthreshold( my_level ); + my_stream->set_threshold( my_level ); if ( !my_stream->single_threaded() ){ mutex_release(); } @@ -451,7 +403,7 @@ namespace TiCC { LogStream& xDbg::operator *(){ /// return the LogStream from the xDbg object #ifdef DARE_TO_OPTIMIZE - if ( my_stream->getlevel() >= my_stream->getthreshold() ){ + if ( my_stream->get_level() >= my_stream->get_threshold() ){ return *my_stream; } else { @@ -469,8 +421,8 @@ namespace TiCC { } if ( os->single_threaded() || init_mutex() ){ my_stream = os; - my_level = os->getthreshold(); - os->setthreshold( LogExtreme ); + my_level = os->get_threshold(); + os->set_threshold( LogExtreme ); } } @@ -478,15 +430,15 @@ namespace TiCC { /// create a xxDbg object on the LogStream with Extreme threshold if ( os.single_threaded() || init_mutex() ){ my_stream = &os; - my_level = os.getthreshold(); - os.setthreshold( LogExtreme ); + my_level = os.get_threshold(); + os.set_threshold( LogExtreme ); } } xxDbg::~xxDbg(){ /// destroy the xxDbg object my_stream->flush(); - my_stream->setthreshold( my_level ); + my_stream->set_threshold( my_level ); if ( !my_stream->single_threaded() ){ mutex_release(); } @@ -495,7 +447,7 @@ namespace TiCC { LogStream& xxDbg::operator *(){ /// return the LogStream from the xxDbg object #ifdef DARE_TO_OPTIMIZE - if ( my_stream->getlevel() >= my_stream->getthreshold() ){ + if ( my_stream->get_level() >= my_stream->get_threshold() ){ return *my_stream; } else { diff --git a/src/Makefile.am b/src/Makefile.am index 2b8aaa3..a3fab6f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,7 +4,7 @@ AM_CXXFLAGS = -g -O3 -std=c++17 -W -Wall -pedantic LDADD = libticcutils.la lib_LTLIBRARIES = libticcutils.la -libticcutils_la_LDFLAGS = -version-info 9:0:0 +libticcutils_la_LDFLAGS = -version-info 10:0:0 libticcutils_la_SOURCES = LogStream.cxx StringOps.cxx \ Configuration.cxx Timer.cxx XMLtools.cxx zipper.cxx \ diff --git a/src/ServerBase.cxx b/src/ServerBase.cxx index f755494..b60da36 100644 --- a/src/ServerBase.cxx +++ b/src/ServerBase.cxx @@ -94,7 +94,7 @@ namespace TiCCServer { ServerBase::ServerBase( const Configuration *config, void *callback_data ): - _my_log("BasicServer"), + _my_log(), _do_daemon( true ), _debug( false ), _max_conn( 25 ), @@ -159,7 +159,7 @@ namespace TiCCServer { else { _name = _protocol + "-server"; } - _my_log.message( _name ); + _my_log.set_message( _name ); value = _config->lookUp( "debug" ); if ( !value.empty() ){ if ( value == "no" ){ diff --git a/src/XMLtools.cxx b/src/XMLtools.cxx index c848b36..c0b06e8 100644 --- a/src/XMLtools.cxx +++ b/src/XMLtools.cxx @@ -38,53 +38,107 @@ using namespace std; namespace TiCC { - XmlDoc::XmlDoc( const string& elem ){ - /// create an XmlDoc with a root node - /*! - \param elem the tag of the root node - */ - the_doc = xmlNewDoc( to_xmlChar("1.0") ); - MakeRoot( elem ); + xmlDoc *create_xmlDocument( const std::string& root_name ){ + xmlDoc *result= xmlNewDoc( TiCC::to_xmlChar("1.0") ); + xmlNode *root = xmlNewDocNode( result, + 0, + TiCC::to_xmlChar(root_name), + 0 ); + xmlDocSetRootElement( result, root ); + return result; } - const string XmlDoc::toString() const { - /// serialize a complete XmlDoc to an UTF-8 string + xmlNode *getRoot( xmlDoc *doc ){ + return xmlDocGetRootElement(doc); + } + + const string serialize( const xmlDoc& doc ) { + /// serialize a complete xmlDoc to an UTF-8 string xmlChar *buf; int size; - xmlDocDumpFormatMemoryEnc( the_doc, &buf, &size, "UTF-8", 1 ); - const string result = string( to_char(buf), size ); + xmlDocDumpFormatMemoryEnc( const_cast(&doc), &buf, &size, "UTF-8", 1 ); + const string result = to_string( buf, size ); xmlFree( buf ); return result; } - xmlNode *XmlDoc::getRoot() const { - /// return the root node - if ( the_doc ){ - return xmlDocGetRootElement(the_doc); - } - return 0; - } - - void XmlDoc::setRoot( xmlNode *node ){ - /// set the root node - if ( the_doc ){ - xmlDocSetRootElement(the_doc, node ); + bool isNCName( const string& s ){ + /// test if a string is a valid NCName value + /*! + \param s the inputstring + \return true if \e s may be used as an NCName (e.g. for xml:id) + */ + int test = xmlValidateNCName( to_xmlChar(s), 0 ); + if ( test != 0 ){ + return false; } + return true; } - xmlNode *XmlDoc::MakeRoot( const string& elem ){ - /// create a root node with tag \e elem + string create_NCName( const string& s ){ + /// create a valid NCName /*! - \param elem the tag of the new root node - \return the newly created node + \param s a string to be used as template + \return a string that is a valid NCname + + Make sure these prerequisites are met: + An xsd:NCName value must start with either a letter or underscore ( _ ) + and may contain only letters, digits, underscores ( _ ), hyphens ( - ), + and periods ( . ). */ - xmlNode *root; - root = xmlNewDocNode( the_doc, - 0, - to_xmlChar(elem.c_str()), - 0 ); - xmlDocSetRootElement( the_doc, root ); - return root; + if ( isNCName( s ) ){ + return s; + } + else { + string result = s; + while ( !result.empty() + && ( result.front() == '.' + || result.front() == '-' + || !isalpha(result.front() ) ) ){ + if ( result.front() == '_' ){ + break; + } + result.erase(result.begin()); + } + if ( result.empty() ){ + throw runtime_error( "unable to create a valid NCName from '" + + s + "', would be empty" ); + } + if ( isNCName( result ) ){ + return result; + } + else { + auto it = result.begin(); + while ( it != result.end() ){ + if ( *it == ' ' ){ + // replace spaces by '_' + *it = '_'; + ++it; + } + else if ( *it == '-' + || *it == '_' + || *it == '.' ){ + // not alphanumeric, but allowed + ++it; + } + else if ( !isalnum(*it) ){ + it = result.erase(it); + } + else { + ++it; + } + } + if ( result.empty() ){ + throw runtime_error( "unable to create a valid NCName from '" + + s + "', (empty result)" ); + } + else if ( !isNCName( result ) ){ + throw runtime_error( "unable to create a valid NCName from '" + + s + "'" ); + } + return result; + } + } } string getNS( const xmlNode *node, string& prefix ){ @@ -99,9 +153,9 @@ namespace TiCC { const xmlNs *p = node->ns; if ( p ){ if ( p->prefix ){ - prefix = to_char(p->prefix); + prefix = to_string(p->prefix); } - result = to_char(p->href); + result = to_string(p->href); } return result; } @@ -118,9 +172,9 @@ namespace TiCC { string pre; string val; if ( p->prefix ){ - pre = to_char(p->prefix); + pre = to_string(p->prefix); } - val = to_char(p->href); + val = to_string(p->href); result[pre] = val; p = p->next; } @@ -139,9 +193,9 @@ namespace TiCC { string pre; string val; if ( p->prefix ){ - pre = to_char(p->prefix); + pre = to_string(p->prefix); } - val = to_char(p->href); + val = to_string(p->href); result[pre] = val; p = p->next; } @@ -159,7 +213,7 @@ namespace TiCC { \return a list of all matching nodes */ list nodes; - xmlXPathObject* result = xmlXPathEval( to_xmlChar(xpath.c_str()), ctxt); + xmlXPathObject* result = xmlXPathEval( to_xmlChar(xpath), ctxt); if ( result ){ if (result->type != XPATH_NODESET) { xmlXPathFreeObject(result); @@ -202,13 +256,13 @@ namespace TiCC { if ( key.empty() ){ // the anonymous namespace xmlXPathRegisterNs( ctxt, - to_xmlChar(defaultP.c_str()), - to_xmlChar(value.c_str()) ); + to_xmlChar(defaultP), + to_xmlChar(value) ); } else { xmlXPathRegisterNs( ctxt, - to_xmlChar(key.c_str()), - to_xmlChar(value.c_str()) ); + to_xmlChar(key), + to_xmlChar(value) ); } } } @@ -303,11 +357,11 @@ namespace TiCC { return xPath( root, xpath ); } - string serialize( const xmlNode& node ){ + const string serialize( const xmlNode& node ){ /// serialize an xmlNode to a string (XML fragment) xmlBuffer *buf = xmlBufferCreate(); xmlNodeDump( buf, 0, const_cast(&node), 0, 0 ); - string result = to_char(xmlBufferContent( buf )); + const string result = to_string(xmlBufferContent( buf )); xmlBufferFree( buf ); return result; } diff --git a/src/runtest.cxx b/src/runtest.cxx index a7a6c39..5305b9b 100644 --- a/src/runtest.cxx +++ b/src/runtest.cxx @@ -46,13 +46,14 @@ #include "ticcutils/Unicode.h" #include "ticcutils/json.hpp" #include "ticcutils/enum_flags.h" +#include "ticcutils/XMLtools.h" using namespace std; using namespace TiCC; using namespace icu; void helper(){ - throw runtime_error("fout"); + throw runtime_error("expected_error"); } int helper2(){ @@ -846,30 +847,35 @@ void test_configuration( const string& path ){ void test_logstream( const string& path ){ ofstream uit( "/tmp/testls.1" ); LogStream ls( uit ); - ls.setstamp( NoStamp ); - *Log( ls ) << "test 1 level=" << ls.getlevel() << " threshold=" << ls.getthreshold() << endl; + ls.set_stamp( NoStamp ); + *Log( ls ) << "test 1 level=" << ls.get_level() << " threshold=" + << ls.get_threshold() << endl; *Dbg( ls ) << "debug 1" << endl; *xDbg( ls ) << "x_debug 1" << endl; *xxDbg( ls ) << "xx_debug 1" << endl; - ls.setlevel( LogSilent ); - *Log( ls ) << "test 2 level=" << ls.getlevel() << " threshold=" << ls.getthreshold() << endl; + ls.set_level( LogSilent ); + *Log( ls ) << "test 2 level=" << ls.get_level() << " threshold=" + << ls.get_threshold() << endl; *Dbg( ls ) << "debug 2" << endl; *xDbg( ls ) << "x_debug 2" << endl; *xxDbg( ls ) << "xx_debug 2" << endl; - ls.setlevel( LogDebug ); - *Log( ls ) << "test 3 level=" << ls.getlevel() << " threshold=" << ls.getthreshold() << endl; + ls.set_level( LogDebug ); + *Log( ls ) << "test 3 level=" << ls.get_level() << " threshold=" + << ls.get_threshold() << endl; *Dbg( ls ) << "debug 3" << endl; *xDbg( ls ) << "x_debug 3" << endl; *xxDbg( ls ) << "xx_debug 3" << endl; - ls.setlevel( LogExtreme ); - *Log( ls ) << "test 4 level=" << ls.getlevel() << " threshold=" << ls.getthreshold() << endl; + ls.set_level( LogExtreme ); + *Log( ls ) << "test 4 level=" << ls.get_level() << " threshold=" + << ls.get_threshold() << endl; *Dbg( ls ) << "debug 4" << endl; *xDbg( ls ) << "x_debug 4" << endl; *xxDbg( ls ) << "xx_debug 4" << endl; - ls.setlevel( LogHeavy ); - *Log( ls ) << "test 5 level=" << ls.getlevel() << " threshold=" << ls.getthreshold() << endl; - ls.addmessage( "AHA:" ); - ls.setstamp( StampMessage ); + ls.set_level( LogHeavy ); + *Log( ls ) << "test 5 level=" << ls.get_level() << " threshold=" + << ls.get_threshold() << endl; + ls.add_message( "AHA:" ); + ls.set_stamp( StampMessage ); *Dbg( ls ) << "debug 5" << endl; *xDbg( ls ) << "x_debug 5" << endl; *xxDbg( ls ) << "xx_debug 5" << endl; @@ -1136,11 +1142,25 @@ enum class class_flags { nope = 0, ok = 1, warning = 1<<1, error = 1<<2 }; DEFINE_ENUM_FLAG_OPERATORS(flags); DEFINE_ENUM_FLAG_OPERATORS(class_flags); +std::ostream& operator<<( std::ostream& os, const flags& f ){ + os << int(f); + return os; +} + +std::ostream& operator<<( std::ostream& os, const class_flags& f ){ + os << int(f); + return os; +} + void test_enum_flags() { { flags f = flags::Two|flags::Four; + cerr << f << endl; + cerr << (f & (flags::Two|flags::Four) ) << endl; + cerr << (f & flags::Two) << endl; // cppcheck-suppress knownConditionTrueFalse assertTrue( f == 6 ); + assertTrue( f % (flags::Two|flags::Four) ); f = ~f; // cppcheck-suppress knownConditionTrueFalse assertEqual( f, -7 ); @@ -1153,17 +1173,23 @@ void test_enum_flags() { { // DEFINE_ENUM_FLAGS works for both 'enum' and 'enum class' // BUT: the assertion macro's have a problem with the latter - // needs work. Now we need an explixit cast + // needs work. Now we need an explicit cast class_flags f = class_flags::warning|class_flags::error; std::stringstream ss; ss << f; assertEqual( ss.str(), "6" ); // cppcheck-suppress knownConditionTrueFalse assertTrue( (int)f == 6 ); + assertTrue( f%(class_flags::warning|class_flags::error) ); + assertTrue( f%class_flags::warning ); + assertTrue( f%class_flags::error ); + assertFalse( f%class_flags::ok ); f = ~f; assertEqual( int(f), -7 ); f &= class_flags::ok; assertEqual( (int)f, 1 ); + assertTrue( (f%class_flags::ok) ); + assertFalse( (f%class_flags::warning) ); } } @@ -1191,7 +1217,7 @@ private: \ template \ static constexpr std::false_type check(...); \ \ - typedef decltype(check(0)) type; \ + using type = decltype(check(0)); \ \ public: \ static constexpr bool value = type::value; \ @@ -1199,6 +1225,7 @@ public: \ ADD_FUN_CHECK( string_fun ) ADD_FUN_CHECK( unistring_fun ) +ADD_FUN_CHECK( int_fun ) void test_templates(){ @@ -1211,18 +1238,37 @@ void test_templates(){ bool unistring_fun( const icu::UnicodeString& ){ return true; } + int int_fun( const int& i, int j ){ + return i+j; + } }; - bool test_string = has_string_fun::value; - assertEqual( test_string, true ); - test_string = has_unistring_fun::value; - assertEqual( test_string, true ); - test_string = has_string_fun::value; - assertEqual( test_string, true ); - test_string = has_unistring_fun::value; - assertEqual( test_string, false ); - test_string = has_string_fun::value; - assertEqual( test_string, false ); + bool test_val = has_string_fun::value; + assertEqual( test_val, true ); + test_val = has_unistring_fun::value; + assertEqual( test_val, true ); + test_val = has_string_fun::value; + assertEqual( test_val, true ); + test_val = has_unistring_fun::value; + assertEqual( test_val, false ); + test_val = has_string_fun::value; + assertEqual( test_val, false ); + test_val = has_int_fun::value; + assertEqual( test_val, true ); + test_val = has_int_fun::value; + assertEqual( test_val, true ); + test_val = has_int_fun::value; + assertEqual( test_val, true ); +} + +void test_ncname(){ + assertFalse( isNCName("123") ); + assertTrue( isNCName("_123") ); + assertEqual( create_NCName( "12?name" ), "name" ); + assertEqual( create_NCName("aap!noot"), "aapnoot" ); + assertEqual( create_NCName("A#12!3"), "A123" ); + assertEqual( create_NCName(".-_!A#12!3"), "_A123" ); + assertEqual( create_NCName("_appel-taart.met slagroom_"), "_appel-taart.met_slagroom_" ); } int main( const int argc, const char* argv[] ){ @@ -1264,6 +1310,7 @@ int main( const int argc, const char* argv[] ){ test_lowercase(); test_unicodehash(); test_realpath(); + test_ncname(); string testdir; bool dummy; opts1.is_present( 'd', testdir, dummy ); diff --git a/src/testlogstream.cxx b/src/testlogstream.cxx index 7f5dd7f..9381282 100644 --- a/src/testlogstream.cxx +++ b/src/testlogstream.cxx @@ -42,7 +42,8 @@ using namespace TiCC; class Sub1 { public: explicit Sub1( LogStream& log ){ - ls = new LogStream( log, "-SUB1" ); + ls = new LogStream( &log ); + ls->set_message( "-SUB1" ); *Log(ls) << "created a sub1 " << endl; } ~Sub1(){ delete ls; }; @@ -75,7 +76,9 @@ class Sub2 { class Sub3 { public: explicit Sub3( Sub2& s ){ - ls = new LogStream( s.ls, "-SUB3", StampMessage ); + ls = new LogStream( s.ls ); + ls->set_stamp( StampMessage ); + ls->set_message( "-SUB3-" ); *Log(ls) << "created a sub3 " << endl; } ~Sub3(){ delete ls; }; @@ -91,7 +94,8 @@ class Sub3 { }; int main(){ - LogStream the_log( "main-log" ); + LogStream the_log; + the_log.set_message( "main-log" ); Sub1 sub1( the_log ); Sub2 sub2( &the_log ); assert( IsActive( the_log ) ); From 345b693f217718c86cc7d7bae5344c2c561885a9 Mon Sep 17 00:00:00 2001 From: Ko van der Sloot Date: Sun, 15 Dec 2024 12:31:24 +0100 Subject: [PATCH 2/7] update NEWS --- NEWS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS b/NEWS index f6d1cd3..fed4051 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,11 @@ +16 dec 2024 0.36 +[Ko van der Sloot] +* code rework and cleanup + - LogStream interface + - Enum type utils + - XmlTools +* bumped library version +* added more tests 12 sep 2024 0.35 [Ko van der Sloot] * C++17 is requied now From 6a624299655203ec1cb2b93bfa2b643a3309fb9f Mon Sep 17 00:00:00 2001 From: Ko van der Sloot Date: Sun, 15 Dec 2024 12:49:35 +0100 Subject: [PATCH 3/7] added a newline --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index fed4051..e976a67 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,7 @@ - XmlTools * bumped library version * added more tests + 12 sep 2024 0.35 [Ko van der Sloot] * C++17 is requied now From c7058db0ec52a2616815224b25699de137b8fa64 Mon Sep 17 00:00:00 2001 From: Ko van der Sloot Date: Mon, 16 Jun 2025 23:31:01 +0200 Subject: [PATCH 4/7] fix CI --- .github/workflows/ticcutils.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ticcutils.yml b/.github/workflows/ticcutils.yml index f8f871a..2fa776b 100644 --- a/.github/workflows/ticcutils.yml +++ b/.github/workflows/ticcutils.yml @@ -36,7 +36,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - compiler: [g++-12, clang++] + compiler: [g++-12, clang++ -std-c++17] steps: - uses: actions/checkout@v4.1.1 From 36f796407136e19b70878ac272446e4c6ee1341b Mon Sep 17 00:00:00 2001 From: Ko van der Sloot Date: Mon, 16 Jun 2025 23:33:01 +0200 Subject: [PATCH 5/7] stupid typo --- .github/workflows/ticcutils.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ticcutils.yml b/.github/workflows/ticcutils.yml index 2fa776b..e102385 100644 --- a/.github/workflows/ticcutils.yml +++ b/.github/workflows/ticcutils.yml @@ -36,7 +36,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - compiler: [g++-12, clang++ -std-c++17] + compiler: [g++-12, clang++ -std=c++17] steps: - uses: actions/checkout@v4.1.1 From d88ba652bbe1cce812f25cbb99e7fd247aeaa998 Mon Sep 17 00:00:00 2001 From: Ko van der Sloot Date: Mon, 17 Nov 2025 09:02:34 +0100 Subject: [PATCH 6/7] update CI --- .github/workflows/ticcutils.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ticcutils.yml b/.github/workflows/ticcutils.yml index e102385..27adee6 100644 --- a/.github/workflows/ticcutils.yml +++ b/.github/workflows/ticcutils.yml @@ -36,13 +36,24 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - compiler: [g++-12, clang++ -std=c++17] + compiler: [g++ -std=c++17, clang++ -std=c++17] steps: - uses: actions/checkout@v4.1.1 - uses: LanguageMachines/ticcactions/cpp-build-env@v1 - uses: LanguageMachines/ticcactions/cpp-dependencies@v1 + + - name: show software version + run: | + clang++ -v + g++ -v + + - uses: LanguageMachines/ticcactions/setup-cppcheck@v1 + - name: Static Code-check + if: ${{ env.action_status == '' }} + run: cppcheck ${{ env.cpc_opts }} . + - uses: LanguageMachines/ticcactions/cpp-safe-build@v1 - uses: LanguageMachines/ticcactions/irc-nick@v1 From 4a0d8c0b245a4b56c9601bc1eda9252ae676691d Mon Sep 17 00:00:00 2001 From: Ko van der Sloot Date: Sat, 22 Nov 2025 16:47:31 +0100 Subject: [PATCH 7/7] Squashed commit of the following: Date: Sat Nov 22 16:07:08 2025 +0100 updated as suggested by https://github.com/LanguageMachines/ticcutils/pull/30 entering 2025 updated nlohmann json.hpp --- NEWS | 3 + README.md | 2 +- codemeta.json | 2 +- configure.ac | 2 +- include/ticcutils/CommandLine.h | 2 +- include/ticcutils/Configuration.h | 2 +- include/ticcutils/FdStream.h | 2 +- include/ticcutils/FileUtils.h | 2 +- include/ticcutils/LogBuffer.h | 2 +- include/ticcutils/LogStream.h | 2 +- include/ticcutils/PrettyPrint.h | 2 +- include/ticcutils/ServerBase.h | 2 +- include/ticcutils/SocketBasics.h | 2 +- include/ticcutils/StringOps.h | 2 +- include/ticcutils/Timer.h | 2 +- include/ticcutils/UniHash.h | 15 +- include/ticcutils/UniTrie.h | 2 +- include/ticcutils/Unicode.h | 2 +- include/ticcutils/UnitTest.h | 2 +- include/ticcutils/Version.h | 2 +- include/ticcutils/XMLtools.h | 2 +- include/ticcutils/enum_flags.h | 4 +- include/ticcutils/json.hpp | 5868 +++++++++++++++++------------ include/ticcutils/json_fwd.hpp | 33 +- include/ticcutils/zipper.h | 2 +- src/CommandLine.cxx | 2 +- src/Configuration.cxx | 2 +- src/FdStream.cxx | 2 +- src/FileUtils.cxx | 2 +- src/LogStream.cxx | 2 +- src/Makefile.am | 2 +- src/ServerBase.cxx | 2 +- src/SocketBasics.cxx | 2 +- src/StringOps.cxx | 2 +- src/Timer.cxx | 2 +- src/UniHash.cxx | 63 +- src/Unicode.cxx | 2 +- src/XMLtools.cxx | 2 +- src/runtest.cxx | 17 +- src/testlogstream.cxx | 2 +- src/zipper.cxx | 2 +- 41 files changed, 3542 insertions(+), 2529 deletions(-) diff --git a/NEWS b/NEWS index e976a67..74cfbdd 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +22 nov 2015 0.37 +[Ko van der Sloot] + 16 dec 2024 0.36 [Ko van der Sloot] * code rework and cleanup diff --git a/README.md b/README.md index df423e8..4674ac0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ TiCC utils ============== - TiCC utils 0.22 (c) ILK/CLST 1998 - 2024 + TiCC utils 0.22 (c) ILK/CLST 1998 - 2025 by Ko van der Sloot Tilburg centre for Cognition and Communication, Tilburg University. diff --git a/codemeta.json b/codemeta.json index 1461f78..92abc81 100644 --- a/codemeta.json +++ b/codemeta.json @@ -7,7 +7,7 @@ "@type": "SoftwareSourceCode", "identifier": "ticcutils", "name": "ticcutils", - "version": "0.36", + "version": "0.37", "description": "This module contains useful functions for general use in the TiCC software stack and beyond.", "license": "https://spdx.org/licenses/GPL-3.0", "url": "https://github.com/LanguageMachines/ticcutils", diff --git a/configure.ac b/configure.ac index 68345d8..afedde9 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) -AC_INIT([ticcutils],[0.36],[lamasoftware@science.ru.nl]) #also adapt in codemeta.json! +AC_INIT([ticcutils],[0.37],[lamasoftware@science.ru.nl]) #also adapt in codemeta.json! AM_INIT_AUTOMAKE([foreign]) AC_CONFIG_SRCDIR([.]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/include/ticcutils/CommandLine.h b/include/ticcutils/CommandLine.h index e5bf190..0c760f9 100644 --- a/include/ticcutils/CommandLine.h +++ b/include/ticcutils/CommandLine.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/Configuration.h b/include/ticcutils/Configuration.h index b6ef9f3..9da329b 100644 --- a/include/ticcutils/Configuration.h +++ b/include/ticcutils/Configuration.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/FdStream.h b/include/ticcutils/FdStream.h index be32276..fdd3857 100644 --- a/include/ticcutils/FdStream.h +++ b/include/ticcutils/FdStream.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/FileUtils.h b/include/ticcutils/FileUtils.h index 0406f71..34f887b 100644 --- a/include/ticcutils/FileUtils.h +++ b/include/ticcutils/FileUtils.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/LogBuffer.h b/include/ticcutils/LogBuffer.h index 2186183..27e7f76 100644 --- a/include/ticcutils/LogBuffer.h +++ b/include/ticcutils/LogBuffer.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/LogStream.h b/include/ticcutils/LogStream.h index 83b2608..8170028 100644 --- a/include/ticcutils/LogStream.h +++ b/include/ticcutils/LogStream.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/PrettyPrint.h b/include/ticcutils/PrettyPrint.h index 29d5402..f7d985d 100644 --- a/include/ticcutils/PrettyPrint.h +++ b/include/ticcutils/PrettyPrint.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/ServerBase.h b/include/ticcutils/ServerBase.h index eee5028..299f082 100644 --- a/include/ticcutils/ServerBase.h +++ b/include/ticcutils/ServerBase.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/SocketBasics.h b/include/ticcutils/SocketBasics.h index 9924678..967d2b2 100644 --- a/include/ticcutils/SocketBasics.h +++ b/include/ticcutils/SocketBasics.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/StringOps.h b/include/ticcutils/StringOps.h index 2371448..431d71f 100644 --- a/include/ticcutils/StringOps.h +++ b/include/ticcutils/StringOps.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/Timer.h b/include/ticcutils/Timer.h index e28935a..587a6bc 100644 --- a/include/ticcutils/Timer.h +++ b/include/ticcutils/Timer.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/UniHash.h b/include/ticcutils/UniHash.h index 7a40026..a2f95c3 100644 --- a/include/ticcutils/UniHash.h +++ b/include/ticcutils/UniHash.h @@ -1,3 +1,4 @@ + /* Copyright (c) 2006 - 2024 CLST - Radboud University @@ -29,7 +30,7 @@ #include #include -#include "ticcutils/UniTrie.h" +#include #include "ticcutils/Unicode.h" namespace Hash { @@ -60,13 +61,20 @@ namespace Hash { UniInfo& operator=( const UniInfo& ) = delete; }; + // Custom hasher for icu::UnicodeString + struct UnicodeStringHash { + std::size_t operator()(const icu::UnicodeString& k) const { + return k.hashCode(); + } + }; + /// \brief The UnicodeHash class is used to enumerate Unicode strings. /// /// Every string gets an UNIQUE id assigned. /// /// It also keeps a reverse index from the id back to the string. /// - /// Internally it uses a UniTrie for fast inserting en retrieving + /// Internally it uses a std::unordered_map for fast inserting en retrieving class UnicodeHash { friend std::ostream& operator << ( std::ostream&, const UnicodeHash& ); public: @@ -84,7 +92,8 @@ namespace Hash { private: unsigned int _num_of_tokens; std::vector _rev_index; - Tries::UniTrie _tree; + // Replaced UniTrie with unordered_map + std::unordered_map _map; UnicodeHash( const UnicodeHash& ) = delete; UnicodeHash& operator=( const UnicodeHash& ) = delete; }; diff --git a/include/ticcutils/UniTrie.h b/include/ticcutils/UniTrie.h index d291578..c267039 100644 --- a/include/ticcutils/UniTrie.h +++ b/include/ticcutils/UniTrie.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/Unicode.h b/include/ticcutils/Unicode.h index f88b10c..c7ed744 100644 --- a/include/ticcutils/Unicode.h +++ b/include/ticcutils/Unicode.h @@ -2,7 +2,7 @@ #define TICC_UNICODE_H /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/UnitTest.h b/include/ticcutils/UnitTest.h index 0048512..2d4d22e 100644 --- a/include/ticcutils/UnitTest.h +++ b/include/ticcutils/UnitTest.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/Version.h b/include/ticcutils/Version.h index 70bf5c7..e1648e4 100644 --- a/include/ticcutils/Version.h +++ b/include/ticcutils/Version.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/XMLtools.h b/include/ticcutils/XMLtools.h index 492e267..317b804 100644 --- a/include/ticcutils/XMLtools.h +++ b/include/ticcutils/XMLtools.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/include/ticcutils/enum_flags.h b/include/ticcutils/enum_flags.h index bad413f..73c2a21 100644 --- a/include/ticcutils/enum_flags.h +++ b/include/ticcutils/enum_flags.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University @@ -40,5 +40,5 @@ namespace TiCC { inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((std::underlying_type::type)a) ^ ((std::underlying_type::type)b)); } \ inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((std::underlying_type::type &)a) ^= ((std::underlying_type::type)b)); } \ inline bool operator % ( const ENUMTYPE &a, ENUMTYPE b) { return (a & b) == b; } -} +} //namespace TICC #endif diff --git a/include/ticcutils/json.hpp b/include/ticcutils/json.hpp index a858728..ceb7a9f 100644 --- a/include/ticcutils/json.hpp +++ b/include/ticcutils/json.hpp @@ -1,9 +1,9 @@ // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT /****************************************************************************\ @@ -34,10 +34,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -47,10 +47,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -59,20 +59,24 @@ #ifndef JSON_SKIP_LIBRARY_VERSION_CHECK #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) - #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3 + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0 #warning "Already included a different version of the library!" #endif #endif #endif #define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum) #ifndef JSON_DIAGNOSTICS #define JSON_DIAGNOSTICS 0 #endif +#ifndef JSON_DIAGNOSTIC_POSITIONS + #define JSON_DIAGNOSTIC_POSITIONS 0 +#endif + #ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 #endif @@ -83,6 +87,12 @@ #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS #endif +#if JSON_DIAGNOSTIC_POSITIONS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS +#endif + #if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp #else @@ -94,14 +104,15 @@ #endif // Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) #define NLOHMANN_JSON_ABI_TAGS \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \ NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ - NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS) // Construct the namespace version component #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ @@ -149,10 +160,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -172,10 +183,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -192,10 +203,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -208,10 +219,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -220,10 +231,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -233,10 +244,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -320,15 +331,16 @@ NLOHMANN_JSON_NAMESPACE_END // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-FileCopyrightText: 2016-2021 Evan Nemerson // SPDX-License-Identifier: MIT /* Hedley - https://nemequ.github.io/hedley * Created by Evan Nemerson + * SPDX-License-Identifier: CC0-1.0 */ #if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) @@ -2383,16 +2395,27 @@ JSON_HEDLEY_DIAGNOSTIC_POP #endif // C++ language standard detection -// if the user manually specified the used c++ version this is skipped -#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) - #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) +// if the user manually specified the used C++ version, this is skipped +#if !defined(JSON_HAS_CPP_26) && !defined(JSON_HAS_CPP_23) && !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus > 202302L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202302L) + #define JSON_HAS_CPP_26 + #define JSON_HAS_CPP_23 + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202002L) + #define JSON_HAS_CPP_23 #define JSON_HAS_CPP_20 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #elif (defined(__cplusplus) && __cplusplus > 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG > 201703L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 201402L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #elif (defined(__cplusplus) && __cplusplus > 201103L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) #define JSON_HAS_CPP_14 #endif // the cpp 11 flag is always specified because it is the minimal required version @@ -2475,7 +2498,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #endif #ifndef JSON_HAS_RANGES - // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has a syntax error #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 #define JSON_HAS_RANGES 0 #elif defined(__cpp_lib_ranges) @@ -2552,7 +2575,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define JSON_ASSERT(x) assert(x) #endif -// allow to access some private functions (needed by the test suite) +// allow accessing some private functions (needed by the test suite) #if defined(JSON_TESTS_PRIVATE) #define JSON_PRIVATE_UNLESS_TESTED public #else @@ -2568,7 +2591,9 @@ JSON_HEDLEY_DIAGNOSTIC_POP template \ inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [e](const std::pair& ej_pair) -> bool \ @@ -2580,7 +2605,9 @@ JSON_HEDLEY_DIAGNOSTIC_POP template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [&j](const std::pair& ej_pair) -> bool \ @@ -2743,42 +2770,146 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; #define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); -#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1) : nlohmann_json_default_obj.v1; /*! @brief macro @def NLOHMANN_DEFINE_TYPE_INTRUSIVE @since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE @since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } -#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } // inspired from https://stackoverflow.com/a/26745591 -// allows to call any std function as if (e.g. with begin): +// allows calling any std function as if (e.g., with begin): // using std::begin; begin(x); // // it allows using the detected idiom to retrieve the return type @@ -2939,10 +3070,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -2972,10 +3103,10 @@ inline void replace_substring(StringType& s, const StringType& f, const StringType& t) { JSON_ASSERT(!f.empty()); - for (auto pos = s.find(f); // find first occurrence of f + for (auto pos = s.find(f); // find the first occurrence of f pos != StringType::npos; // make sure f was found s.replace(pos, f.size(), t), // replace with t, and - pos = s.find(f, pos + t.size())) // find next occurrence of f + pos = s.find(f, pos + t.size())) // find the next occurrence of f {} } @@ -3002,7 +3133,7 @@ inline StringType escape(StringType s) * Note the order of escaping "~1" to "/" and "~0" to "~" is important. */ template -static void unescape(StringType& s) +inline void unescape(StringType& s) { replace_substring(s, StringType{"~1"}, StringType{"/"}); replace_substring(s, StringType{"~0"}, StringType{"~"}); @@ -3014,10 +3145,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3056,10 +3187,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-FileCopyrightText: 2018 The Abseil Authors // SPDX-License-Identifier: MIT @@ -3219,7 +3350,7 @@ struct static_const #endif template -inline constexpr std::array make_array(Args&& ... args) +constexpr std::array make_array(Args&& ... args) { return std::array {{static_cast(std::forward(args))...}}; } @@ -3230,27 +3361,29 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // numeric_limits +#include // char_traits +#include // tuple #include // false_type, is_constructible, is_integral, is_same, true_type #include // declval -#include // tuple -#include // char_traits - +#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L + #include // byte +#endif // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3293,7 +3426,7 @@ struct iterator_traits template struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> - : iterator_types + : iterator_types { }; @@ -3315,10 +3448,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3335,10 +3468,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3359,10 +3492,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT #ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ @@ -3453,12 +3586,12 @@ namespace detail // Note to maintainers: // -// Every trait in this file expects a non CV-qualified type. +// Every trait in this file expects a non-CV-qualified type. // The only exceptions are in the 'aliases for detected' section -// (i.e. those of the form: decltype(T::member_function(std::declval()))) +// (i.e., those of the form: decltype(T::member_function(std::declval()))) // // In this case, T has to be properly CV-qualified to constraint the function arguments -// (e.g. to_json(BasicJsonType&, const T&)) +// (e.g., to_json(BasicJsonType&, const T&)) template struct is_basic_json : std::false_type {}; @@ -3466,7 +3599,7 @@ NLOHMANN_BASIC_JSON_TPL_DECLARATION struct is_basic_json : std::true_type {}; // used by exceptions create() member functions -// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t +// true_type for the pointer to possibly cv-qualified basic_json or std::nullptr_t // false_type otherwise template struct is_basic_json_context : @@ -3624,7 +3757,7 @@ struct char_traits : std::char_traits static constexpr int_type eof() noexcept { - return static_cast(EOF); + return static_cast(std::char_traits::eof()); } }; @@ -3648,9 +3781,33 @@ struct char_traits : std::char_traits static constexpr int_type eof() noexcept { - return static_cast(EOF); + return static_cast(std::char_traits::eof()); + } +}; + +#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L +template<> +struct char_traits : std::char_traits +{ + using char_type = std::byte; + using int_type = uint64_t; + + static int_type to_int_type(char_type c) noexcept + { + return static_cast(std::to_integer(c)); + } + + static char_type to_char_type(int_type i) noexcept + { + return std::byte(static_cast(i)); + } + + static constexpr int_type eof() noexcept + { + return static_cast(std::char_traits::eof()); } }; +#endif /////////////////// // is_ functions // @@ -3668,25 +3825,25 @@ template struct negation : std::integral_constant < bool, !B::value > { // Reimplementation of is_constructible and is_default_constructible, due to them being broken for // std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). -// This causes compile errors in e.g. clang 3.5 or gcc 4.9. +// This causes compile errors in e.g., Clang 3.5 or GCC 4.9. template struct is_default_constructible : std::is_default_constructible {}; template struct is_default_constructible> - : conjunction, is_default_constructible> {}; + : conjunction, is_default_constructible> {}; template struct is_default_constructible> - : conjunction, is_default_constructible> {}; + : conjunction, is_default_constructible> {}; template struct is_default_constructible> - : conjunction...> {}; + : conjunction...> {}; template struct is_default_constructible> - : conjunction...> {}; + : conjunction...> {}; template struct is_constructible : std::is_constructible {}; @@ -3748,7 +3905,7 @@ using range_value_t = value_type_t>>; // The following implementation of is_complete_type is taken from // https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ -// and is written by Xiang Fan who agreed to using it in this library. +// and is written by Xiang Fan who agreed to use it in this library. template struct is_complete_type : std::false_type {}; @@ -3884,8 +4041,8 @@ is_detected::value&& // special case for types like std::filesystem::path whose iterator's value_type are themselves // c.f. https://github.com/nlohmann/json/pull/3073 !std::is_same>::value&& - is_complete_type < - detected_t>::value >> +is_complete_type < +detected_t>::value >> { using value_type = range_value_t; @@ -3972,20 +4129,35 @@ struct is_specialization_of> : std::true_type {}; template using is_json_pointer = is_specialization_of<::nlohmann::json_pointer, uncvref_t>; +// checks if B is a json_pointer +template +struct is_json_pointer_of : std::false_type {}; + +template +struct is_json_pointer_of> : std::true_type {}; + +template +struct is_json_pointer_of&> : std::true_type {}; + // checks if A and B are comparable using Compare functor template struct is_comparable : std::false_type {}; +// We exclude json_pointer here, because the checks using Compare(A, B) will +// use json_pointer::operator string_t() which triggers a deprecation warning +// for GCC. See https://github.com/nlohmann/json/issues/4621. The call to +// is_json_pointer_of can be removed once the deprecated function has been +// removed. template -struct is_comparable()(std::declval(), std::declval())), -decltype(std::declval()(std::declval(), std::declval())) +struct is_comparable < Compare, A, B, enable_if_t < !is_json_pointer_of::value +&& std::is_constructible ()(std::declval(), std::declval()))>::value +&& std::is_constructible ()(std::declval(), std::declval()))>::value >> : std::true_type {}; template using detect_is_transparent = typename T::is_transparent; -// type trait to check if KeyType can be used as object key (without a BasicJsonType) +// type trait to check if KeyType can be used as an object key (without a BasicJsonType) // see is_usable_as_basic_json_key_type below template> @@ -3999,7 +4171,7 @@ using is_usable_as_key_type = typename std::conditional < std::true_type, std::false_type >::type; -// type trait to check if KeyType can be used as object key +// type trait to check if KeyType can be used as an object key // true if: // - KeyType is comparable with BasicJsonType::object_t::key_type // - if ExcludeObjectKeyType is true, KeyType is not BasicJsonType::object_t::key_type @@ -4008,12 +4180,12 @@ using is_usable_as_key_type = typename std::conditional < template> using is_usable_as_basic_json_key_type = typename std::conditional < - is_usable_as_key_type::value - && !is_json_iterator_of::value, - std::true_type, - std::false_type >::type; + is_usable_as_key_type::value + && !is_json_iterator_of::value, + std::true_type, + std::false_type >::type; template using detect_erase_with_key_type = decltype(std::declval().erase(std::declval())); @@ -4042,7 +4214,7 @@ struct is_ordered_map template static one test( decltype(&C::capacity) ) ; template static two test(...); - enum { value = sizeof(test(nullptr)) == sizeof(char) }; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + enum { value = sizeof(test(nullptr)) == sizeof(char) }; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-use-enum-class) }; // to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324) @@ -4147,7 +4319,7 @@ struct value_in_range_of_impl1 }; template -inline constexpr bool value_in_range_of(T val) +constexpr bool value_in_range_of(T val) { return value_in_range_of_impl1::test(val); } @@ -4163,7 +4335,7 @@ namespace impl { template -inline constexpr bool is_c_string() +constexpr bool is_c_string() { using TUnExt = typename std::remove_extent::type; using TUnCVExt = typename std::remove_cv::type; @@ -4191,7 +4363,7 @@ namespace impl { template -inline constexpr bool is_transparent() +constexpr bool is_transparent() { return is_detected::value; } @@ -4210,10 +4382,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -4358,6 +4530,18 @@ inline OutStringType concat(Args && ... args) NLOHMANN_JSON_NAMESPACE_END +// With -Wweak-vtables, Clang will complain about the exception classes as they +// have no out-of-line virtual method definitions and their vtable will be +// emitted in every translation unit. This issue cannot be fixed with a +// header-only library as there is no implementation file to move these +// functions to. As a result, we suppress this warning here to avoid client +// code stumbling over this. See https://github.com/nlohmann/json/issues/4087 +// for a discussion. +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wweak-vtables" +#endif + NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { @@ -4452,16 +4636,34 @@ class exception : public std::exception { return concat(a, '/', detail::escape(b)); }); - return concat('(', str, ") "); + + return concat('(', str, ") ", get_byte_positions(leaf_element)); #else - static_cast(leaf_element); - return ""; + return get_byte_positions(leaf_element); #endif } private: /// an exception object as storage for error messages std::runtime_error m; +#if JSON_DIAGNOSTIC_POSITIONS + template + static std::string get_byte_positions(const BasicJsonType* leaf_element) + { + if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos)) + { + return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") "); + } + return ""; + } +#else + template + static std::string get_byte_positions(const BasicJsonType* leaf_element) + { + static_cast(leaf_element); + return ""; + } +#endif }; /// @brief exception indicating a parse error @@ -4589,6 +4791,10 @@ class other_error : public exception } // namespace detail NLOHMANN_JSON_NAMESPACE_END +#if defined(__clang__) + #pragma clang diagnostic pop +#endif + // #include // #include @@ -4596,10 +4802,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -4620,10 +4826,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -4640,7 +4846,7 @@ namespace std_fs = std::experimental::filesystem; } // namespace detail NLOHMANN_JSON_NAMESPACE_END #elif JSON_HAS_FILESYSTEM -#include +#include // NOLINT(build/c++17) NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { @@ -4656,6 +4862,15 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// include after macro_scope.hpp +#ifdef JSON_HAS_CPP_17 + #include // optional +#endif + +#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM + #include // u8string_view +#endif + NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { @@ -4670,6 +4885,21 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) n = nullptr; } +#ifdef JSON_HAS_CPP_17 +template +void from_json(const BasicJsonType& j, std::optional& opt) +{ + if (j.is_null()) + { + opt = std::nullopt; + } + else + { + opt.emplace(j.template get()); + } +} +#endif // JSON_HAS_CPP_17 + // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& @@ -4817,6 +5047,54 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines } } +template +auto from_json(const BasicJsonType& j, T (&arr)[N1][N2]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get(), void()) +{ + for (std::size_t i1 = 0; i1 < N1; ++i1) + { + for (std::size_t i2 = 0; i2 < N2; ++i2) + { + arr[i1][i2] = j.at(i1).at(i2).template get(); + } + } +} + +template +auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get(), void()) +{ + for (std::size_t i1 = 0; i1 < N1; ++i1) + { + for (std::size_t i2 = 0; i2 < N2; ++i2) + { + for (std::size_t i3 = 0; i3 < N3; ++i3) + { + arr[i1][i2][i3] = j.at(i1).at(i2).at(i3).template get(); + } + } + } +} + +template +auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3][N4]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get(), void()) +{ + for (std::size_t i1 = 0; i1 < N1; ++i1) + { + for (std::size_t i2 = 0; i2 < N2; ++i2) + { + for (std::size_t i3 = 0; i3 < N3; ++i3) + { + for (std::size_t i4 = 0; i4 < N4; ++i4) + { + arr[i1][i2][i3][i4] = j.at(i1).at(i2).at(i3).at(i4).template get(); + } + } + } + } +} + template inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { @@ -4902,7 +5180,7 @@ void()) template < typename BasicJsonType, typename T, std::size_t... Idx > std::array from_json_inplace_array_impl(BasicJsonType&& j, - identity_tag> /*unused*/, index_sequence /*unused*/) + identity_tag> /*unused*/, index_sequence /*unused*/) { return { { std::forward(j).at(Idx).template get()... } }; } @@ -4953,7 +5231,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) } // overload for arithmetic types, not chosen for basic_json template arguments -// (BooleanType, etc..); note: Is it really necessary to provide explicit +// (BooleanType, etc.); note: Is it really necessary to provide explicit // overloads for boolean_t etc. in case of a custom BooleanType which is not // an arithmetic type? template < typename BasicJsonType, typename ArithmeticType, @@ -5006,6 +5284,12 @@ std::tuple from_json_tuple_impl_base(BasicJsonType&& j, index_sequence< return std::make_tuple(std::forward(j).at(Idx).template get()...); } +template +std::tuple<> from_json_tuple_impl_base(BasicJsonType& /*unused*/, index_sequence<> /*unused*/) +{ + return {}; +} + template < typename BasicJsonType, class A1, class A2 > std::pair from_json_tuple_impl(BasicJsonType&& j, identity_tag> /*unused*/, priority_tag<0> /*unused*/) { @@ -5091,7 +5375,15 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); } - p = *j.template get_ptr(); + const auto& s = *j.template get_ptr(); + // Checking for C++20 standard or later can be insufficient in case the + // library support for char8_t is either incomplete or was disabled + // altogether. Use the __cpp_lib_char8_t feature test instead. +#if defined(__cpp_lib_char8_t) && (__cpp_lib_char8_t >= 201907L) + p = std_fs::path(std::u8string_view(reinterpret_cast(s.data()), s.size())); +#else + p = std_fs::u8path(s); // accepts UTF-8 encoded std::string in C++17, deprecated in C++20 +#endif } #endif @@ -5126,17 +5418,24 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT +// #include +// JSON_HAS_CPP_17 +#ifdef JSON_HAS_CPP_17 + #include // optional +#endif + #include // copy #include // begin, end -#include // string +#include // allocator_traits +#include // basic_string, char_traits #include // tuple, get #include // is_same, is_constructible, is_floating_point, is_enum, underlying_type #include // move, forward, declval, pair @@ -5146,17 +5445,16 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // size_t -#include // input_iterator_tag -#include // string, to_string +#include // forward_iterator_tag #include // tuple_size, get, tuple_element #include // move @@ -5168,20 +5466,53 @@ NLOHMANN_JSON_NAMESPACE_END // #include -// #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t +#include // string, to_string + +// #include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { -template -void int_to_string( string_type& target, std::size_t value ) +template +void int_to_string(StringType& target, std::size_t value) { // For ADL using std::to_string; target = to_string(value); } + +template +StringType to_string(std::size_t value) +{ + StringType result; + int_to_string(result, value); + return result; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + template class iteration_proxy_value { public: @@ -5189,7 +5520,7 @@ template class iteration_proxy_value using value_type = iteration_proxy_value; using pointer = value_type *; using reference = value_type &; - using iterator_category = std::input_iterator_tag; + using iterator_category = std::forward_iterator_tag; using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; private: @@ -5369,7 +5700,7 @@ namespace std #endif template class tuple_size<::nlohmann::detail::iteration_proxy_value> // NOLINT(cert-dcl58-cpp) - : public std::integral_constant {}; + : public std::integral_constant {}; template class tuple_element> // NOLINT(cert-dcl58-cpp) @@ -5390,8 +5721,6 @@ class tuple_element> inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy> = true; #endif -// #include - // #include // #include @@ -5637,6 +5966,22 @@ struct external_constructor // to_json // ///////////// +#ifdef JSON_HAS_CPP_17 +template::value, int> = 0> +void to_json(BasicJsonType& j, const std::optional& opt) noexcept +{ + if (opt.has_value()) + { + j = *opt; + } + else + { + j = nullptr; + } +} +#endif + template::value, int> = 0> inline void to_json(BasicJsonType& j, T b) noexcept @@ -5783,6 +6128,13 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence< j = { std::get(t)... }; } +template +inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/) +{ + using array_t = typename BasicJsonType::array_t; + j = array_t(); +} + template::value, int > = 0> inline void to_json(BasicJsonType& j, const T& t) { @@ -5790,10 +6142,21 @@ inline void to_json(BasicJsonType& j, const T& t) } #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM +#if defined(__cpp_lib_char8_t) +template +inline void to_json(BasicJsonType& j, const std::basic_string& s) +{ + using OtherAllocator = typename std::allocator_traits::template rebind_alloc; + j = std::basic_string, OtherAllocator>(s.begin(), s.end(), s.get_allocator()); +} +#endif + template inline void to_json(BasicJsonType& j, const std_fs::path& p) { - j = p.string(); + // Returns either a std::string or a std::u8string depending whether library + // support for char8_t is enabled. + j = p.u8string(); } #endif @@ -5868,10 +6231,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -5980,10 +6343,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -6113,10 +6476,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -6133,16 +6496,19 @@ NLOHMANN_JSON_NAMESPACE_END #include // char_traits, string #include // make_pair, move #include // vector +#ifdef __cpp_lib_byteswap + #include //byteswap +#endif // #include // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -6153,6 +6519,7 @@ NLOHMANN_JSON_NAMESPACE_END #include // begin, end, iterator_traits, random_access_iterator_tag, distance, next #include // shared_ptr, make_shared, addressof #include // accumulate +#include // streambuf #include // string, char_traits #include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer #include // pair, declval @@ -6162,6 +6529,8 @@ NLOHMANN_JSON_NAMESPACE_END #include // istream #endif // JSON_NO_IO +// #include + // #include // #include @@ -6209,6 +6578,13 @@ class file_input_adapter return std::fgetc(m_file); } + // returns the number of characters successfully read + template + std::size_t get_elements(T* dest, std::size_t count = 1) + { + return fread(dest, 1, sizeof(T) * count, m_file); + } + private: /// the file pointer to read from std::FILE* m_file; @@ -6242,7 +6618,7 @@ class input_stream_adapter : is(&i), sb(i.rdbuf()) {} - // delete because of pointer members + // deleted because of pointer members input_stream_adapter(const input_stream_adapter&) = delete; input_stream_adapter& operator=(input_stream_adapter&) = delete; input_stream_adapter& operator=(input_stream_adapter&&) = delete; @@ -6256,7 +6632,7 @@ class input_stream_adapter // std::istream/std::streambuf use std::char_traits::to_int_type, to // ensure that std::char_traits::eof() and the character 0xFF do not - // end up as the same value, e.g. 0xFFFFFFFF. + // end up as the same value, e.g., 0xFFFFFFFF. std::char_traits::int_type get_character() { auto res = sb->sbumpc(); @@ -6268,6 +6644,17 @@ class input_stream_adapter return res; } + template + std::size_t get_elements(T* dest, std::size_t count = 1) + { + auto res = static_cast(sb->sgetn(reinterpret_cast(dest), static_cast(count * sizeof(T)))); + if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T))) + { + is->clear(is->rdstate() | std::ios::eofbit); + } + return res; + } + private: /// the associated input stream std::istream* is = nullptr; @@ -6299,6 +6686,26 @@ class iterator_input_adapter return char_traits::eof(); } + // for general iterators, we cannot really do something better than falling back to processing the range one-by-one + template + std::size_t get_elements(T* dest, std::size_t count = 1) + { + auto* ptr = reinterpret_cast(dest); + for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index) + { + if (JSON_HEDLEY_LIKELY(current != end)) + { + ptr[read_index] = static_cast(*current); + std::advance(current, 1); + } + else + { + return read_index; + } + } + return count * sizeof(T); + } + private: IteratorType current; IteratorType end; @@ -6447,7 +6854,7 @@ class wide_string_input_adapter typename std::char_traits::int_type get_character() noexcept { - // check if buffer needs to be filled + // check if the buffer needs to be filled if (utf8_bytes_index == utf8_bytes_filled) { fill_buffer(); @@ -6462,6 +6869,13 @@ class wide_string_input_adapter return utf8_bytes[utf8_bytes_index++]; } + // parsing binary with wchar doesn't make sense, but since the parsing mode can be runtime, we need something here + template + std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1) + { + JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr)); + } + private: BaseInputAdapter base_adapter; @@ -6497,7 +6911,7 @@ template struct is_iterator_of_multibyte { using value_type = typename std::iterator_traits::value_type; - enum + enum // NOLINT(cppcoreguidelines-use-enum-class) { value = sizeof(value_type) > 1 }; @@ -6558,10 +6972,17 @@ typename container_input_adapter_factory_impl::container_input_adapter_factory::create(container); } +// specialization for std::string +using string_input_adapter_type = decltype(input_adapter(std::declval())); + #ifndef JSON_NO_IO // Special cases with fast paths inline file_input_adapter input_adapter(std::FILE* file) { + if (file == nullptr) + { + JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + } return file_input_adapter(file); } @@ -6588,9 +7009,13 @@ template < typename CharT, int >::type = 0 > contiguous_bytes_input_adapter input_adapter(CharT b) { + if (b == nullptr) + { + JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + } auto length = std::strlen(reinterpret_cast(b)); const auto* ptr = reinterpret_cast(b); - return input_adapter(ptr, ptr + length); + return input_adapter(ptr, ptr + length); // cppcheck-suppress[nullPointerArithmeticRedundantCheck] } template @@ -6636,2383 +7061,2653 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT #include #include // string +#include // enable_if_t #include // move #include // vector // #include -// #include - -// #include - +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann +// SPDX-License-Identifier: MIT -NLOHMANN_JSON_NAMESPACE_BEGIN -/*! -@brief SAX interface -This class describes the SAX interface used by @ref nlohmann::json::sax_parse. -Each function is called in different situations while the input is parsed. The -boolean return value informs the parser whether to continue processing the -input. -*/ -template -struct json_sax -{ - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - - /*! - @brief a null value was read - @return whether parsing should proceed - */ - virtual bool null() = 0; - - /*! - @brief a boolean value was read - @param[in] val boolean value - @return whether parsing should proceed - */ - virtual bool boolean(bool val) = 0; - - /*! - @brief an integer number was read - @param[in] val integer value - @return whether parsing should proceed - */ - virtual bool number_integer(number_integer_t val) = 0; - - /*! - @brief an unsigned integer number was read - @param[in] val unsigned integer value - @return whether parsing should proceed - */ - virtual bool number_unsigned(number_unsigned_t val) = 0; - - /*! - @brief a floating-point number was read - @param[in] val floating-point value - @param[in] s raw token value - @return whether parsing should proceed - */ - virtual bool number_float(number_float_t val, const string_t& s) = 0; +#include // array +#include // localeconv +#include // size_t +#include // snprintf +#include // strtof, strtod, strtold, strtoll, strtoull +#include // initializer_list +#include // char_traits, string +#include // move +#include // vector - /*! - @brief a string value was read - @param[in] val string value - @return whether parsing should proceed - @note It is safe to move the passed string value. - */ - virtual bool string(string_t& val) = 0; +// #include - /*! - @brief a binary value was read - @param[in] val binary value - @return whether parsing should proceed - @note It is safe to move the passed binary value. - */ - virtual bool binary(binary_t& val) = 0; +// #include - /*! - @brief the beginning of an object was read - @param[in] elements number of object elements or -1 if unknown - @return whether parsing should proceed - @note binary formats may report the number of elements - */ - virtual bool start_object(std::size_t elements) = 0; +// #include - /*! - @brief an object key was read - @param[in] val object key - @return whether parsing should proceed - @note It is safe to move the passed string. - */ - virtual bool key(string_t& val) = 0; +// #include - /*! - @brief the end of an object was read - @return whether parsing should proceed - */ - virtual bool end_object() = 0; - /*! - @brief the beginning of an array was read - @param[in] elements number of array elements or -1 if unknown - @return whether parsing should proceed - @note binary formats may report the number of elements - */ - virtual bool start_array(std::size_t elements) = 0; +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ - /*! - @brief the end of an array was read - @return whether parsing should proceed - */ - virtual bool end_array() = 0; +/////////// +// lexer // +/////////// - /*! - @brief a parse error occurred - @param[in] position the position in the input where the error occurs - @param[in] last_token the last read token - @param[in] ex an exception object describing the error - @return whether parsing should proceed (must return false) - */ - virtual bool parse_error(std::size_t position, - const std::string& last_token, - const detail::exception& ex) = 0; +template +class lexer_base +{ + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value + value_integer, ///< a signed integer -- use get_number_integer() for actual value + value_float, ///< an floating point number -- use get_number_float() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input, ///< indicating the end of the input buffer + literal_or_value ///< a literal or the begin of a value (only for diagnostics) + }; - json_sax() = default; - json_sax(const json_sax&) = default; - json_sax(json_sax&&) noexcept = default; - json_sax& operator=(const json_sax&) = default; - json_sax& operator=(json_sax&&) noexcept = default; - virtual ~json_sax() = default; + /// return name of values of type token_type (only used for errors) + JSON_HEDLEY_RETURNS_NON_NULL + JSON_HEDLEY_CONST + static const char* token_type_name(const token_type t) noexcept + { + switch (t) + { + case token_type::uninitialized: + return ""; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case token_type::value_unsigned: + case token_type::value_integer: + case token_type::value_float: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return ""; + case token_type::end_of_input: + return "end of input"; + case token_type::literal_or_value: + return "'[', '{', or a literal"; + // LCOV_EXCL_START + default: // catch non-enum values + return "unknown token"; + // LCOV_EXCL_STOP + } + } }; - -namespace detail -{ /*! -@brief SAX implementation to create a JSON value from SAX events - -This class implements the @ref json_sax interface and processes the SAX events -to create a JSON value which makes it basically a DOM parser. The structure or -hierarchy of the JSON value is managed by the stack `ref_stack` which contains -a pointer to the respective array or object for each recursion depth. - -After successful parsing, the value that is passed by reference to the -constructor contains the parsed value. +@brief lexical analysis -@tparam BasicJsonType the JSON type +This class organizes the lexical analysis during JSON deserialization. */ -template -class json_sax_dom_parser +template +class lexer : public lexer_base { - public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; + using char_type = typename InputAdapterType::char_type; + using char_int_type = typename char_traits::int_type; - /*! - @param[in,out] r reference to a JSON value that is manipulated while - parsing - @param[in] allow_exceptions_ whether parse errors yield exceptions - */ - explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true) - : root(r), allow_exceptions(allow_exceptions_) + public: + using token_type = typename lexer_base::token_type; + + explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept + : ia(std::move(adapter)) + , ignore_comments(ignore_comments_) + , decimal_point_char(static_cast(get_decimal_point())) {} - // make class move-only - json_sax_dom_parser(const json_sax_dom_parser&) = delete; - json_sax_dom_parser(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; - json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - ~json_sax_dom_parser() = default; + // deleted because of pointer members + lexer(const lexer&) = delete; + lexer(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + lexer& operator=(lexer&) = delete; + lexer& operator=(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~lexer() = default; - bool null() + private: + ///////////////////// + // locales + ///////////////////// + + /// return the locale-dependent decimal point + JSON_HEDLEY_PURE + static char get_decimal_point() noexcept { - handle_value(nullptr); - return true; + const auto* loc = localeconv(); + JSON_ASSERT(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); } - bool boolean(bool val) - { - handle_value(val); - return true; - } - - bool number_integer(number_integer_t val) - { - handle_value(val); - return true; - } - - bool number_unsigned(number_unsigned_t val) - { - handle_value(val); - return true; - } + ///////////////////// + // scan functions + ///////////////////// - bool number_float(number_float_t val, const string_t& /*unused*/) - { - handle_value(val); - return true; - } + /*! + @brief get codepoint from 4 hex characters following `\u` - bool string(string_t& val) - { - handle_value(val); - return true; - } + For input "\u c1 c2 c3 c4" the codepoint is: + (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 + = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) - bool binary(binary_t& val) - { - handle_value(std::move(val)); - return true; - } + Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' + must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The + conversion is done by subtracting the offset (0x30, 0x37, and 0x57) + between the ASCII value of the character and the desired integer value. - bool start_object(std::size_t len) + @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or + non-hex character) + */ + int get_codepoint() { - ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); + // this function only makes sense after reading `\u` + JSON_ASSERT(current == 'u'); + int codepoint = 0; - if (JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) + const auto factors = { 12u, 8u, 4u, 0u }; + for (const auto factor : factors) { - JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); + get(); + + if (current >= '0' && current <= '9') + { + codepoint += static_cast((static_cast(current) - 0x30u) << factor); + } + else if (current >= 'A' && current <= 'F') + { + codepoint += static_cast((static_cast(current) - 0x37u) << factor); + } + else if (current >= 'a' && current <= 'f') + { + codepoint += static_cast((static_cast(current) - 0x57u) << factor); + } + else + { + return -1; + } } - return true; + JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF); + return codepoint; } - bool key(string_t& val) - { - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(ref_stack.back()->is_object()); + /*! + @brief check if the next byte(s) are inside a given range - // add null at given key and store the reference for later - object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val)); - return true; - } + Adds the current byte and, for each passed range, reads a new byte and + checks if it is inside the range. If a violation was detected, set up an + error message and return false. Otherwise, return true. - bool end_object() - { - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(ref_stack.back()->is_object()); + @param[in] ranges list of integers; interpreted as list of pairs of + inclusive lower and upper bound, respectively - ref_stack.back()->set_parents(); - ref_stack.pop_back(); - return true; - } + @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, + 1, 2, or 3 pairs. This precondition is enforced by an assertion. - bool start_array(std::size_t len) + @return true if and only if no range violation was detected + */ + bool next_byte_in_range(std::initializer_list ranges) { - ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); + JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); + add(current); - if (JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) + for (auto range = ranges.begin(); range != ranges.end(); ++range) { - JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + get(); + if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions) + { + add(current); + } + else + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; + } } return true; } - bool end_array() - { - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(ref_stack.back()->is_array()); - - ref_stack.back()->set_parents(); - ref_stack.pop_back(); - return true; - } + /*! + @brief scan a string literal - template - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, - const Exception& ex) - { - errored = true; - static_cast(ex); - if (allow_exceptions) - { - JSON_THROW(ex); - } - return false; - } + This function scans a string according to Sect. 7 of RFC 8259. While + scanning, bytes are escaped and copied into buffer token_buffer. Then the + function returns successfully, token_buffer is *not* null-terminated (as it + may contain \0 bytes), and token_buffer.size() is the number of bytes in the + string. - constexpr bool is_errored() const - { - return errored; - } + @return token_type::value_string if string could be successfully scanned, + token_type::parse_error otherwise - private: - /*! - @invariant If the ref stack is empty, then the passed value will be the new - root. - @invariant If the ref stack contains a value, then it is an array or an - object to which we can add elements + @note In case of errors, variable error_message contains a textual + description. */ - template - JSON_HEDLEY_RETURNS_NON_NULL - BasicJsonType* handle_value(Value&& v) + token_type scan_string() { - if (ref_stack.empty()) - { - root = BasicJsonType(std::forward(v)); - return &root; - } + // reset token_buffer (ignore opening quote) + reset(); - JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + // we entered the function by reading an open quote + JSON_ASSERT(current == '\"'); - if (ref_stack.back()->is_array()) + while (true) { - ref_stack.back()->m_data.m_value.array->emplace_back(std::forward(v)); - return &(ref_stack.back()->m_data.m_value.array->back()); - } - - JSON_ASSERT(ref_stack.back()->is_object()); - JSON_ASSERT(object_element); - *object_element = BasicJsonType(std::forward(v)); - return object_element; - } + // get the next character + switch (get()) + { + // end of file while parsing the string + case char_traits::eof(): + { + error_message = "invalid string: missing closing quote"; + return token_type::parse_error; + } - /// the parsed JSON value - BasicJsonType& root; - /// stack to model hierarchy of values - std::vector ref_stack {}; - /// helper to hold the reference for the next object element - BasicJsonType* object_element = nullptr; - /// whether a syntax error occurred - bool errored = false; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; -}; + // closing quote + case '\"': + { + return token_type::value_string; + } -template -class json_sax_dom_callback_parser -{ - public: - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - using parser_callback_t = typename BasicJsonType::parser_callback_t; - using parse_event_t = typename BasicJsonType::parse_event_t; + // escapes + case '\\': + { + switch (get()) + { + // quotation mark + case '\"': + add('\"'); + break; + // reverse solidus + case '\\': + add('\\'); + break; + // solidus + case '/': + add('/'); + break; + // backspace + case 'b': + add('\b'); + break; + // form feed + case 'f': + add('\f'); + break; + // line feed + case 'n': + add('\n'); + break; + // carriage return + case 'r': + add('\r'); + break; + // tab + case 't': + add('\t'); + break; - json_sax_dom_callback_parser(BasicJsonType& r, - const parser_callback_t cb, - const bool allow_exceptions_ = true) - : root(r), callback(cb), allow_exceptions(allow_exceptions_) - { - keep_stack.push_back(true); - } - - // make class move-only - json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; - json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; - json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - ~json_sax_dom_callback_parser() = default; + // unicode escapes + case 'u': + { + const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; // start with codepoint1 - bool null() - { - handle_value(nullptr); - return true; - } + if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } - bool boolean(bool val) - { - handle_value(val); - return true; - } + // check if code point is a high surrogate + if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) + { + // expect next \uxxxx entry + if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u')) + { + const int codepoint2 = get_codepoint(); - bool number_integer(number_integer_t val) - { - handle_value(val); - return true; - } + if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } - bool number_unsigned(number_unsigned_t val) - { - handle_value(val); - return true; - } + // check if codepoint2 is a low surrogate + if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) + { + // overwrite codepoint + codepoint = static_cast( + // high surrogate occupies the most significant 22 bits + (static_cast(codepoint1) << 10u) + // low surrogate occupies the least significant 15 bits + + static_cast(codepoint2) + // there is still the 0xD800, 0xDC00, and 0x10000 noise + // in the result, so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00u); + } + else + { + error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; + return token_type::parse_error; + } + } - bool number_float(number_float_t val, const string_t& /*unused*/) - { - handle_value(val); - return true; - } + // the result of the above calculation yields a proper codepoint + JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); - bool string(string_t& val) - { - handle_value(val); - return true; - } + // translate codepoint into bytes + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + add(static_cast(codepoint)); + } + else if (codepoint <= 0x7FF) + { + // 2-byte characters: 110xxxxx 10xxxxxx + add(static_cast(0xC0u | (static_cast(codepoint) >> 6u))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } + else if (codepoint <= 0xFFFF) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + add(static_cast(0xE0u | (static_cast(codepoint) >> 12u))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } + else + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + add(static_cast(0xF0u | (static_cast(codepoint) >> 18u))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 12u) & 0x3Fu))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } - bool binary(binary_t& val) - { - handle_value(std::move(val)); - return true; - } + break; + } - bool start_object(std::size_t len) - { - // check callback for object start - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); - keep_stack.push_back(keep); + // other characters after escape + default: + error_message = "invalid string: forbidden character after backslash"; + return token_type::parse_error; + } - auto val = handle_value(BasicJsonType::value_t::object, true); - ref_stack.push_back(val.second); + break; + } - // check object limit - if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } + // invalid control characters + case 0x00: + { + error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; + return token_type::parse_error; + } - return true; - } + case 0x01: + { + error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; + return token_type::parse_error; + } - bool key(string_t& val) - { - BasicJsonType k = BasicJsonType(val); + case 0x02: + { + error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; + return token_type::parse_error; + } - // check callback for key - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); - key_keep_stack.push_back(keep); + case 0x03: + { + error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; + return token_type::parse_error; + } - // add discarded value at given key and store the reference for later - if (keep && ref_stack.back()) - { - object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded); - } + case 0x04: + { + error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; + return token_type::parse_error; + } - return true; - } + case 0x05: + { + error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; + return token_type::parse_error; + } - bool end_object() - { - if (ref_stack.back()) - { - if (!callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) - { - // discard object - *ref_stack.back() = discarded; - } - else - { - ref_stack.back()->set_parents(); - } - } + case 0x06: + { + error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; + return token_type::parse_error; + } - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); + case 0x07: + { + error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; + return token_type::parse_error; + } - if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) - { - // remove discarded value - for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) - { - if (it->is_discarded()) + case 0x08: { - ref_stack.back()->erase(it); - break; + error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; + return token_type::parse_error; } - } - } - return true; - } + case 0x09: + { + error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; + return token_type::parse_error; + } - bool start_array(std::size_t len) - { - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); - keep_stack.push_back(keep); + case 0x0A: + { + error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; + return token_type::parse_error; + } - auto val = handle_value(BasicJsonType::value_t::array, true); - ref_stack.push_back(val.second); + case 0x0B: + { + error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; + return token_type::parse_error; + } - // check array limit - if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); - } - - return true; - } - - bool end_array() - { - bool keep = true; - - if (ref_stack.back()) - { - keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); - if (keep) - { - ref_stack.back()->set_parents(); - } - else - { - // discard array - *ref_stack.back() = discarded; - } - } - - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - - // remove discarded value - if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) - { - ref_stack.back()->m_data.m_value.array->pop_back(); - } - - return true; - } + case 0x0C: + { + error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; + return token_type::parse_error; + } - template - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, - const Exception& ex) - { - errored = true; - static_cast(ex); - if (allow_exceptions) - { - JSON_THROW(ex); - } - return false; - } + case 0x0D: + { + error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; + return token_type::parse_error; + } - constexpr bool is_errored() const - { - return errored; - } + case 0x0E: + { + error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; + return token_type::parse_error; + } - private: - /*! - @param[in] v value to add to the JSON value we build during parsing - @param[in] skip_callback whether we should skip calling the callback - function; this is required after start_array() and - start_object() SAX events, because otherwise we would call the - callback function with an empty array or object, respectively. + case 0x0F: + { + error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; + return token_type::parse_error; + } - @invariant If the ref stack is empty, then the passed value will be the new - root. - @invariant If the ref stack contains a value, then it is an array or an - object to which we can add elements + case 0x10: + { + error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; + return token_type::parse_error; + } - @return pair of boolean (whether value should be kept) and pointer (to the - passed value in the ref_stack hierarchy; nullptr if not kept) - */ - template - std::pair handle_value(Value&& v, const bool skip_callback = false) - { - JSON_ASSERT(!keep_stack.empty()); + case 0x11: + { + error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; + return token_type::parse_error; + } - // do not handle this value if we know it would be added to a discarded - // container - if (!keep_stack.back()) - { - return {false, nullptr}; - } + case 0x12: + { + error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; + return token_type::parse_error; + } - // create value - auto value = BasicJsonType(std::forward(v)); + case 0x13: + { + error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; + return token_type::parse_error; + } - // check callback - const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); + case 0x14: + { + error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; + return token_type::parse_error; + } - // do not handle this value if we just learnt it shall be discarded - if (!keep) - { - return {false, nullptr}; - } + case 0x15: + { + error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; + return token_type::parse_error; + } - if (ref_stack.empty()) - { - root = std::move(value); - return {true, & root}; - } + case 0x16: + { + error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; + return token_type::parse_error; + } - // skip this value if we already decided to skip the parent - // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) - if (!ref_stack.back()) - { - return {false, nullptr}; - } + case 0x17: + { + error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; + return token_type::parse_error; + } - // we now only expect arrays and objects - JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + case 0x18: + { + error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; + return token_type::parse_error; + } - // array - if (ref_stack.back()->is_array()) - { - ref_stack.back()->m_data.m_value.array->emplace_back(std::move(value)); - return {true, & (ref_stack.back()->m_data.m_value.array->back())}; - } + case 0x19: + { + error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; + return token_type::parse_error; + } - // object - JSON_ASSERT(ref_stack.back()->is_object()); - // check if we should store an element for the current key - JSON_ASSERT(!key_keep_stack.empty()); - const bool store_element = key_keep_stack.back(); - key_keep_stack.pop_back(); + case 0x1A: + { + error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; + return token_type::parse_error; + } - if (!store_element) - { - return {false, nullptr}; - } + case 0x1B: + { + error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; + return token_type::parse_error; + } - JSON_ASSERT(object_element); - *object_element = std::move(value); - return {true, object_element}; - } + case 0x1C: + { + error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; + return token_type::parse_error; + } - /// the parsed JSON value - BasicJsonType& root; - /// stack to model hierarchy of values - std::vector ref_stack {}; - /// stack to manage which values to keep - std::vector keep_stack {}; // NOLINT(readability-redundant-member-init) - /// stack to manage which object keys to keep - std::vector key_keep_stack {}; // NOLINT(readability-redundant-member-init) - /// helper to hold the reference for the next object element - BasicJsonType* object_element = nullptr; - /// whether a syntax error occurred - bool errored = false; - /// callback function - const parser_callback_t callback = nullptr; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; - /// a discarded value for the callback - BasicJsonType discarded = BasicJsonType::value_t::discarded; -}; + case 0x1D: + { + error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; + return token_type::parse_error; + } -template -class json_sax_acceptor -{ - public: - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; + case 0x1E: + { + error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; + return token_type::parse_error; + } - bool null() - { - return true; - } + case 0x1F: + { + error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; + return token_type::parse_error; + } - bool boolean(bool /*unused*/) - { - return true; - } + // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) + case 0x20: + case 0x21: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + { + add(current); + break; + } + + // U+0080..U+07FF: bytes C2..DF 80..BF + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: + { + if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) + { + return token_type::parse_error; + } + break; + } + + // U+0800..U+0FFF: bytes E0 A0..BF 80..BF + case 0xE0: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF + // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xEE: + case 0xEF: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+D000..U+D7FF: bytes ED 80..9F 80..BF + case 0xED: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF + case 0xF0: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF + case 0xF1: + case 0xF2: + case 0xF3: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF + case 0xF4: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // the remaining bytes (80..C1 and F5..FF) are ill-formed + default: + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return token_type::parse_error; + } + } + } + } + + /*! + * @brief scan a comment + * @return whether comment could be scanned successfully + */ + bool scan_comment() + { + switch (get()) + { + // single-line comments skip input until a newline or EOF is read + case '/': + { + while (true) + { + switch (get()) + { + case '\n': + case '\r': + case char_traits::eof(): + case '\0': + return true; + + default: + break; + } + } + } + + // multi-line comments skip input until */ is read + case '*': + { + while (true) + { + switch (get()) + { + case char_traits::eof(): + case '\0': + { + error_message = "invalid comment; missing closing '*/'"; + return false; + } + + case '*': + { + switch (get()) + { + case '/': + return true; + + default: + { + unget(); + continue; + } + } + } + + default: + continue; + } + } + } + + // unexpected character after reading '/' + default: + { + error_message = "invalid comment; expecting '/' or '*' after '/'"; + return false; + } + } + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(float& f, const char* str, char** endptr) noexcept + { + f = std::strtof(str, endptr); + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(double& f, const char* str, char** endptr) noexcept + { + f = std::strtod(str, endptr); + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(long double& f, const char* str, char** endptr) noexcept + { + f = std::strtold(str, endptr); + } + + /*! + @brief scan a number literal + + This function scans a string according to Sect. 6 of RFC 8259. + + The function is realized with a deterministic finite state machine derived + from the grammar described in RFC 8259. Starting in state "init", the + input is read and used to determined the next state. Only state "done" + accepts the number. State "error" is a trap state to model errors. In the + table below, "anything" means any character but the ones listed before. + + state | 0 | 1-9 | e E | + | - | . | anything + ---------|----------|----------|----------|---------|---------|----------|----------- + init | zero | any1 | [error] | [error] | minus | [error] | [error] + minus | zero | any1 | [error] | [error] | [error] | [error] | [error] + zero | done | done | exponent | done | done | decimal1 | done + any1 | any1 | any1 | exponent | done | done | decimal1 | done + decimal1 | decimal2 | decimal2 | [error] | [error] | [error] | [error] | [error] + decimal2 | decimal2 | decimal2 | exponent | done | done | done | done + exponent | any2 | any2 | [error] | sign | sign | [error] | [error] + sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] + any2 | any2 | any2 | done | done | done | done | done + + The state machine is realized with one label per state (prefixed with + "scan_number_") and `goto` statements between them. The state machine + contains cycles, but any cycle can be left when EOF is read. Therefore, + the function is guaranteed to terminate. + + During scanning, the read bytes are stored in token_buffer. This string is + then converted to a signed integer, an unsigned integer, or a + floating-point number. + + @return token_type::value_unsigned, token_type::value_integer, or + token_type::value_float if number could be successfully scanned, + token_type::parse_error otherwise + + @note The scanner is independent of the current locale. Internally, the + locale's decimal point is used instead of `.` to work with the + locale-dependent converters. + */ + token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated. + { + // reset token_buffer to store the number's bytes + reset(); + + // the type of the parsed number; initially set to unsigned; will be + // changed if minus sign, decimal point, or exponent is read + token_type number_type = token_type::value_unsigned; - bool number_integer(number_integer_t /*unused*/) - { - return true; - } + // state (init): we just found out we need to scan a number + switch (current) + { + case '-': + { + add(current); + goto scan_number_minus; + } - bool number_unsigned(number_unsigned_t /*unused*/) - { - return true; - } + case '0': + { + add(current); + goto scan_number_zero; + } - bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) - { - return true; - } + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } - bool string(string_t& /*unused*/) - { - return true; - } + // all other characters are rejected outside scan_number() + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } - bool binary(binary_t& /*unused*/) - { - return true; - } +scan_number_minus: + // state: we just parsed a leading minus sign + number_type = token_type::value_integer; + switch (get()) + { + case '0': + { + add(current); + goto scan_number_zero; + } - bool start_object(std::size_t /*unused*/ = static_cast(-1)) - { - return true; - } + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } - bool key(string_t& /*unused*/) - { - return true; - } + default: + { + error_message = "invalid number; expected digit after '-'"; + return token_type::parse_error; + } + } - bool end_object() - { - return true; - } +scan_number_zero: + // state: we just parse a zero (maybe with a leading minus sign) + switch (get()) + { + case '.': + { + add(decimal_point_char); + decimal_point_position = token_buffer.size() - 1; + goto scan_number_decimal1; + } - bool start_array(std::size_t /*unused*/ = static_cast(-1)) - { - return true; - } + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } - bool end_array() - { - return true; - } + default: + goto scan_number_done; + } - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) - { - return false; - } -}; +scan_number_any1: + // state: we just parsed a number 0-9 (maybe with a leading minus sign) + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END + case '.': + { + add(decimal_point_char); + decimal_point_position = token_buffer.size() - 1; + goto scan_number_decimal1; + } -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + default: + goto scan_number_done; + } +scan_number_decimal1: + // state: we just parsed a decimal point + number_type = token_type::value_float; + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } -#include // array -#include // localeconv -#include // size_t -#include // snprintf -#include // strtof, strtod, strtold, strtoll, strtoull -#include // initializer_list -#include // char_traits, string -#include // move -#include // vector + default: + { + error_message = "invalid number; expected digit after '.'"; + return token_type::parse_error; + } + } + +scan_number_decimal2: + // we just parsed at least one number after a decimal point + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_exponent: + // we just parsed an exponent + number_type = token_type::value_float; + switch (get()) + { + case '+': + case '-': + { + add(current); + goto scan_number_sign; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = + "invalid number; expected '+', '-', or digit after exponent"; + return token_type::parse_error; + } + } -// #include +scan_number_sign: + // we just parsed an exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } -// #include + default: + { + error_message = "invalid number; expected digit after exponent sign"; + return token_type::parse_error; + } + } -// #include +scan_number_any2: + // we just parsed a number after the exponent or exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } -// #include + default: + goto scan_number_done; + } +scan_number_done: + // unget the character after the number (we only read it to know that + // we are done scanning a number) + unget(); -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ + char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg) + errno = 0; -/////////// -// lexer // -/////////// + // try to parse integers first and fall back to floats + if (number_type == token_type::value_unsigned) + { + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); -template -class lexer_base -{ - public: - /// token types for the parser - enum class token_type - { - uninitialized, ///< indicating the scanner is uninitialized - literal_true, ///< the `true` literal - literal_false, ///< the `false` literal - literal_null, ///< the `null` literal - value_string, ///< a string -- use get_string() for actual value - value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value - value_integer, ///< a signed integer -- use get_number_integer() for actual value - value_float, ///< an floating point number -- use get_number_float() for actual value - begin_array, ///< the character for array begin `[` - begin_object, ///< the character for object begin `{` - end_array, ///< the character for array end `]` - end_object, ///< the character for object end `}` - name_separator, ///< the name separator `:` - value_separator, ///< the value separator `,` - parse_error, ///< indicating a parse error - end_of_input, ///< indicating the end of the input buffer - literal_or_value ///< a literal or the begin of a value (only for diagnostics) - }; + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); - /// return name of values of type token_type (only used for errors) - JSON_HEDLEY_RETURNS_NON_NULL - JSON_HEDLEY_CONST - static const char* token_type_name(const token_type t) noexcept - { - switch (t) - { - case token_type::uninitialized: - return ""; - case token_type::literal_true: - return "true literal"; - case token_type::literal_false: - return "false literal"; - case token_type::literal_null: - return "null literal"; - case token_type::value_string: - return "string literal"; - case token_type::value_unsigned: - case token_type::value_integer: - case token_type::value_float: - return "number literal"; - case token_type::begin_array: - return "'['"; - case token_type::begin_object: - return "'{'"; - case token_type::end_array: - return "']'"; - case token_type::end_object: - return "'}'"; - case token_type::name_separator: - return "':'"; - case token_type::value_separator: - return "','"; - case token_type::parse_error: - return ""; - case token_type::end_of_input: - return "end of input"; - case token_type::literal_or_value: - return "'[', '{', or a literal"; - // LCOV_EXCL_START - default: // catch non-enum values - return "unknown token"; - // LCOV_EXCL_STOP + if (errno != ERANGE) + { + value_unsigned = static_cast(x); + if (value_unsigned == x) + { + return token_type::value_unsigned; + } + } } - } -}; -/*! -@brief lexical analysis - -This class organizes the lexical analysis during JSON deserialization. -*/ -template -class lexer : public lexer_base -{ - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using char_type = typename InputAdapterType::char_type; - using char_int_type = typename char_traits::int_type; + else if (number_type == token_type::value_integer) + { + const auto x = std::strtoll(token_buffer.data(), &endptr, 10); - public: - using token_type = typename lexer_base::token_type; + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); - explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept - : ia(std::move(adapter)) - , ignore_comments(ignore_comments_) - , decimal_point_char(static_cast(get_decimal_point())) - {} + if (errno != ERANGE) + { + value_integer = static_cast(x); + if (value_integer == x) + { + return token_type::value_integer; + } + } + } - // delete because of pointer members - lexer(const lexer&) = delete; - lexer(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - lexer& operator=(lexer&) = delete; - lexer& operator=(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - ~lexer() = default; + // this code is reached if we parse a floating-point number or if an + // integer conversion above failed + strtof(value_float, token_buffer.data(), &endptr); - private: - ///////////////////// - // locales - ///////////////////// + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); - /// return the locale-dependent decimal point - JSON_HEDLEY_PURE - static char get_decimal_point() noexcept - { - const auto* loc = localeconv(); - JSON_ASSERT(loc != nullptr); - return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); + return token_type::value_float; } - ///////////////////// - // scan functions - ///////////////////// - /*! - @brief get codepoint from 4 hex characters following `\u` - - For input "\u c1 c2 c3 c4" the codepoint is: - (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 - = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) - - Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' - must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The - conversion is done by subtracting the offset (0x30, 0x37, and 0x57) - between the ASCII value of the character and the desired integer value. - - @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or - non-hex character) + @param[in] literal_text the literal text to expect + @param[in] length the length of the passed literal text + @param[in] return_type the token type to return on success */ - int get_codepoint() + JSON_HEDLEY_NON_NULL(2) + token_type scan_literal(const char_type* literal_text, const std::size_t length, + token_type return_type) { - // this function only makes sense after reading `\u` - JSON_ASSERT(current == 'u'); - int codepoint = 0; - - const auto factors = { 12u, 8u, 4u, 0u }; - for (const auto factor : factors) + JSON_ASSERT(char_traits::to_char_type(current) == literal_text[0]); + for (std::size_t i = 1; i < length; ++i) { - get(); - - if (current >= '0' && current <= '9') - { - codepoint += static_cast((static_cast(current) - 0x30u) << factor); - } - else if (current >= 'A' && current <= 'F') - { - codepoint += static_cast((static_cast(current) - 0x37u) << factor); - } - else if (current >= 'a' && current <= 'f') - { - codepoint += static_cast((static_cast(current) - 0x57u) << factor); - } - else + if (JSON_HEDLEY_UNLIKELY(char_traits::to_char_type(get()) != literal_text[i])) { - return -1; + error_message = "invalid literal"; + return token_type::parse_error; } } - - JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF); - return codepoint; + return return_type; } - /*! - @brief check if the next byte(s) are inside a given range + ///////////////////// + // input management + ///////////////////// - Adds the current byte and, for each passed range, reads a new byte and - checks if it is inside the range. If a violation was detected, set up an - error message and return false. Otherwise, return true. + /// reset token_buffer; current character is beginning of token + void reset() noexcept + { + token_buffer.clear(); + token_string.clear(); + decimal_point_position = std::string::npos; + token_string.push_back(char_traits::to_char_type(current)); + } - @param[in] ranges list of integers; interpreted as list of pairs of - inclusive lower and upper bound, respectively + /* + @brief get next character from the input - @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, - 1, 2, or 3 pairs. This precondition is enforced by an assertion. + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a + `char_traits::eof()` in that case. Stores the scanned characters + for use in error messages. - @return true if and only if no range violation was detected + @return character read from the input */ - bool next_byte_in_range(std::initializer_list ranges) + char_int_type get() { - JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); - add(current); + ++position.chars_read_total; + ++position.chars_read_current_line; - for (auto range = ranges.begin(); range != ranges.end(); ++range) + if (next_unget) { - get(); - if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions) - { - add(current); - } - else - { - error_message = "invalid string: ill-formed UTF-8 byte"; - return false; - } + // only reset the next_unget variable and work with current + next_unget = false; + } + else + { + current = ia.get_character(); } - return true; - } + if (JSON_HEDLEY_LIKELY(current != char_traits::eof())) + { + token_string.push_back(char_traits::to_char_type(current)); + } - /*! - @brief scan a string literal + if (current == '\n') + { + ++position.lines_read; + position.chars_read_current_line = 0; + } - This function scans a string according to Sect. 7 of RFC 8259. While - scanning, bytes are escaped and copied into buffer token_buffer. Then the - function returns successfully, token_buffer is *not* null-terminated (as it - may contain \0 bytes), and token_buffer.size() is the number of bytes in the - string. + return current; + } - @return token_type::value_string if string could be successfully scanned, - token_type::parse_error otherwise + /*! + @brief unget current character (read it again on next get) - @note In case of errors, variable error_message contains a textual - description. + We implement unget by setting variable next_unget to true. The input is not + changed - we just simulate ungetting by modifying chars_read_total, + chars_read_current_line, and token_string. The next call to get() will + behave as if the unget character is read again. */ - token_type scan_string() + void unget() { - // reset token_buffer (ignore opening quote) - reset(); + next_unget = true; - // we entered the function by reading an open quote - JSON_ASSERT(current == '\"'); + --position.chars_read_total; - while (true) + // in case we "unget" a newline, we have to also decrement the lines_read + if (position.chars_read_current_line == 0) { - // get next character - switch (get()) + if (position.lines_read > 0) { - // end of file while parsing string - case char_traits::eof(): - { - error_message = "invalid string: missing closing quote"; - return token_type::parse_error; - } - - // closing quote - case '\"': - { - return token_type::value_string; - } - - // escapes - case '\\': - { - switch (get()) - { - // quotation mark - case '\"': - add('\"'); - break; - // reverse solidus - case '\\': - add('\\'); - break; - // solidus - case '/': - add('/'); - break; - // backspace - case 'b': - add('\b'); - break; - // form feed - case 'f': - add('\f'); - break; - // line feed - case 'n': - add('\n'); - break; - // carriage return - case 'r': - add('\r'); - break; - // tab - case 't': - add('\t'); - break; - - // unicode escapes - case 'u': - { - const int codepoint1 = get_codepoint(); - int codepoint = codepoint1; // start with codepoint1 - - if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) - { - error_message = "invalid string: '\\u' must be followed by 4 hex digits"; - return token_type::parse_error; - } + --position.lines_read; + } + } + else + { + --position.chars_read_current_line; + } - // check if code point is a high surrogate - if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) - { - // expect next \uxxxx entry - if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u')) - { - const int codepoint2 = get_codepoint(); + if (JSON_HEDLEY_LIKELY(current != char_traits::eof())) + { + JSON_ASSERT(!token_string.empty()); + token_string.pop_back(); + } + } - if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) - { - error_message = "invalid string: '\\u' must be followed by 4 hex digits"; - return token_type::parse_error; - } + /// add a character to token_buffer + void add(char_int_type c) + { + token_buffer.push_back(static_cast(c)); + } - // check if codepoint2 is a low surrogate - if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) - { - // overwrite codepoint - codepoint = static_cast( - // high surrogate occupies the most significant 22 bits - (static_cast(codepoint1) << 10u) - // low surrogate occupies the least significant 15 bits - + static_cast(codepoint2) - // there is still the 0xD800, 0xDC00 and 0x10000 noise - // in the result, so we have to subtract with: - // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 - - 0x35FDC00u); - } - else - { - error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; - return token_type::parse_error; - } - } - else - { - error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; - return token_type::parse_error; - } - } - else - { - if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) - { - error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; - return token_type::parse_error; - } - } + public: + ///////////////////// + // value getters + ///////////////////// - // result of the above calculation yields a proper codepoint - JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); + /// return integer value + constexpr number_integer_t get_number_integer() const noexcept + { + return value_integer; + } - // translate codepoint into bytes - if (codepoint < 0x80) - { - // 1-byte characters: 0xxxxxxx (ASCII) - add(static_cast(codepoint)); - } - else if (codepoint <= 0x7FF) - { - // 2-byte characters: 110xxxxx 10xxxxxx - add(static_cast(0xC0u | (static_cast(codepoint) >> 6u))); - add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); - } - else if (codepoint <= 0xFFFF) - { - // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx - add(static_cast(0xE0u | (static_cast(codepoint) >> 12u))); - add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); - add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); - } - else - { - // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - add(static_cast(0xF0u | (static_cast(codepoint) >> 18u))); - add(static_cast(0x80u | ((static_cast(codepoint) >> 12u) & 0x3Fu))); - add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); - add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); - } + /// return unsigned integer value + constexpr number_unsigned_t get_number_unsigned() const noexcept + { + return value_unsigned; + } - break; - } + /// return floating-point value + constexpr number_float_t get_number_float() const noexcept + { + return value_float; + } - // other characters after escape - default: - error_message = "invalid string: forbidden character after backslash"; - return token_type::parse_error; - } + /// return current string value (implicitly resets the token; useful only once) + string_t& get_string() + { + // translate decimal points from locale back to '.' (#4084) + if (decimal_point_char != '.' && decimal_point_position != std::string::npos) + { + token_buffer[decimal_point_position] = '.'; + } + return token_buffer; + } - break; - } + ///////////////////// + // diagnostics + ///////////////////// - // invalid control characters - case 0x00: - { - error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; - return token_type::parse_error; - } + /// return position of last read token + constexpr position_t get_position() const noexcept + { + return position; + } - case 0x01: - { - error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; - return token_type::parse_error; - } + /// return the last read token (for errors only). Will never contain EOF + /// (an arbitrary value that is not a valid char value, often -1), because + /// 255 may legitimately occur. May contain NUL, which should be escaped. + std::string get_token_string() const + { + // escape control characters + std::string result; + for (const auto c : token_string) + { + if (static_cast(c) <= '\x1F') + { + // escape control characters + std::array cs{{}}; + static_cast((std::snprintf)(cs.data(), cs.size(), "", static_cast(c))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + result += cs.data(); + } + else + { + // add character as is + result.push_back(static_cast(c)); + } + } - case 0x02: - { - error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; - return token_type::parse_error; - } + return result; + } - case 0x03: - { - error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; - return token_type::parse_error; - } + /// return syntax error message + JSON_HEDLEY_RETURNS_NON_NULL + constexpr const char* get_error_message() const noexcept + { + return error_message; + } - case 0x04: - { - error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; - return token_type::parse_error; - } + ///////////////////// + // actual scanner + ///////////////////// - case 0x05: - { - error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; - return token_type::parse_error; - } + /*! + @brief skip the UTF-8 byte order mark + @return true iff there is no BOM or the correct BOM has been skipped + */ + bool skip_bom() + { + if (get() == 0xEF) + { + // check if we completely parse the BOM + return get() == 0xBB && get() == 0xBF; + } - case 0x06: - { - error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; - return token_type::parse_error; - } + // the first character is not the beginning of the BOM; unget it to + // process is later + unget(); + return true; + } - case 0x07: - { - error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; - return token_type::parse_error; - } + void skip_whitespace() + { + do + { + get(); + } + while (current == ' ' || current == '\t' || current == '\n' || current == '\r'); + } - case 0x08: - { - error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; - return token_type::parse_error; - } + token_type scan() + { + // initially, skip the BOM + if (position.chars_read_total == 0 && !skip_bom()) + { + error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given"; + return token_type::parse_error; + } - case 0x09: - { - error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; - return token_type::parse_error; - } + // read the next character and ignore whitespace + skip_whitespace(); - case 0x0A: - { - error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; - return token_type::parse_error; - } + // ignore comments + while (ignore_comments && current == '/') + { + if (!scan_comment()) + { + return token_type::parse_error; + } - case 0x0B: - { - error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; - return token_type::parse_error; - } + // skip following whitespace + skip_whitespace(); + } - case 0x0C: - { - error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; - return token_type::parse_error; - } + switch (current) + { + // structural characters + case '[': + return token_type::begin_array; + case ']': + return token_type::end_array; + case '{': + return token_type::begin_object; + case '}': + return token_type::end_object; + case ':': + return token_type::name_separator; + case ',': + return token_type::value_separator; - case 0x0D: - { - error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; - return token_type::parse_error; - } + // literals + case 't': + { + std::array true_literal = {{static_cast('t'), static_cast('r'), static_cast('u'), static_cast('e')}}; + return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true); + } + case 'f': + { + std::array false_literal = {{static_cast('f'), static_cast('a'), static_cast('l'), static_cast('s'), static_cast('e')}}; + return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false); + } + case 'n': + { + std::array null_literal = {{static_cast('n'), static_cast('u'), static_cast('l'), static_cast('l')}}; + return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null); + } - case 0x0E: - { - error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; - return token_type::parse_error; - } + // string + case '\"': + return scan_string(); - case 0x0F: - { - error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; - return token_type::parse_error; - } + // number + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scan_number(); - case 0x10: - { - error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; - return token_type::parse_error; - } + // end of input (the null byte is needed when parsing from + // string literals) + case '\0': + case char_traits::eof(): + return token_type::end_of_input; - case 0x11: - { - error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; - return token_type::parse_error; - } + // error + default: + error_message = "invalid literal"; + return token_type::parse_error; + } + } - case 0x12: - { - error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; - return token_type::parse_error; - } + private: + /// input adapter + InputAdapterType ia; - case 0x13: - { - error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; - return token_type::parse_error; - } + /// whether comments should be ignored (true) or signaled as errors (false) + const bool ignore_comments = false; - case 0x14: - { - error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; - return token_type::parse_error; - } + /// the current character + char_int_type current = char_traits::eof(); - case 0x15: - { - error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; - return token_type::parse_error; - } + /// whether the next get() call should just return current + bool next_unget = false; - case 0x16: - { - error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; - return token_type::parse_error; - } + /// the start position of the current token + position_t position {}; - case 0x17: - { - error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; - return token_type::parse_error; - } + /// raw input token string (for error messages) + std::vector token_string {}; - case 0x18: - { - error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; - return token_type::parse_error; - } + /// buffer for variable-length tokens (numbers, strings) + string_t token_buffer {}; - case 0x19: - { - error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; - return token_type::parse_error; - } + /// a description of occurred lexer errors + const char* error_message = ""; - case 0x1A: - { - error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; - return token_type::parse_error; - } + // number values + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; - case 0x1B: - { - error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; - return token_type::parse_error; - } + /// the decimal point + const char_int_type decimal_point_char = '.'; + /// the position of the decimal point in the input + std::size_t decimal_point_position = std::string::npos; +}; - case 0x1C: - { - error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; - return token_type::parse_error; - } +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END - case 0x1D: - { - error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; - return token_type::parse_error; - } +// #include - case 0x1E: - { - error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; - return token_type::parse_error; - } +// #include - case 0x1F: - { - error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; - return token_type::parse_error; - } +NLOHMANN_JSON_NAMESPACE_BEGIN - // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) - case 0x20: - case 0x21: - case 0x23: - case 0x24: - case 0x25: - case 0x26: - case 0x27: - case 0x28: - case 0x29: - case 0x2A: - case 0x2B: - case 0x2C: - case 0x2D: - case 0x2E: - case 0x2F: - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3A: - case 0x3B: - case 0x3C: - case 0x3D: - case 0x3E: - case 0x3F: - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - case 0x48: - case 0x49: - case 0x4A: - case 0x4B: - case 0x4C: - case 0x4D: - case 0x4E: - case 0x4F: - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - case 0x58: - case 0x59: - case 0x5A: - case 0x5B: - case 0x5D: - case 0x5E: - case 0x5F: - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6A: - case 0x6B: - case 0x6C: - case 0x6D: - case 0x6E: - case 0x6F: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - case 0x78: - case 0x79: - case 0x7A: - case 0x7B: - case 0x7C: - case 0x7D: - case 0x7E: - case 0x7F: - { - add(current); - break; - } +/*! +@brief SAX interface - // U+0080..U+07FF: bytes C2..DF 80..BF - case 0xC2: - case 0xC3: - case 0xC4: - case 0xC5: - case 0xC6: - case 0xC7: - case 0xC8: - case 0xC9: - case 0xCA: - case 0xCB: - case 0xCC: - case 0xCD: - case 0xCE: - case 0xCF: - case 0xD0: - case 0xD1: - case 0xD2: - case 0xD3: - case 0xD4: - case 0xD5: - case 0xD6: - case 0xD7: - case 0xD8: - case 0xD9: - case 0xDA: - case 0xDB: - case 0xDC: - case 0xDD: - case 0xDE: - case 0xDF: - { - if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) - { - return token_type::parse_error; - } - break; - } +This class describes the SAX interface used by @ref nlohmann::json::sax_parse. +Each function is called in different situations while the input is parsed. The +boolean return value informs the parser whether to continue processing the +input. +*/ +template +struct json_sax +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + /*! + @brief a null value was read + @return whether parsing should proceed + */ + virtual bool null() = 0; - // U+0800..U+0FFF: bytes E0 A0..BF 80..BF - case 0xE0: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } + /*! + @brief a boolean value was read + @param[in] val boolean value + @return whether parsing should proceed + */ + virtual bool boolean(bool val) = 0; - // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF - // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF - case 0xE1: - case 0xE2: - case 0xE3: - case 0xE4: - case 0xE5: - case 0xE6: - case 0xE7: - case 0xE8: - case 0xE9: - case 0xEA: - case 0xEB: - case 0xEC: - case 0xEE: - case 0xEF: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } + /*! + @brief an integer number was read + @param[in] val integer value + @return whether parsing should proceed + */ + virtual bool number_integer(number_integer_t val) = 0; - // U+D000..U+D7FF: bytes ED 80..9F 80..BF - case 0xED: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } + /*! + @brief an unsigned integer number was read + @param[in] val unsigned integer value + @return whether parsing should proceed + */ + virtual bool number_unsigned(number_unsigned_t val) = 0; - // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF - case 0xF0: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } + /*! + @brief a floating-point number was read + @param[in] val floating-point value + @param[in] s raw token value + @return whether parsing should proceed + */ + virtual bool number_float(number_float_t val, const string_t& s) = 0; - // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF - case 0xF1: - case 0xF2: - case 0xF3: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } + /*! + @brief a string value was read + @param[in] val string value + @return whether parsing should proceed + @note It is safe to move the passed string value. + */ + virtual bool string(string_t& val) = 0; - // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF - case 0xF4: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } + /*! + @brief a binary value was read + @param[in] val binary value + @return whether parsing should proceed + @note It is safe to move the passed binary value. + */ + virtual bool binary(binary_t& val) = 0; - // remaining bytes (80..C1 and F5..FF) are ill-formed - default: - { - error_message = "invalid string: ill-formed UTF-8 byte"; - return token_type::parse_error; - } - } - } - } + /*! + @brief the beginning of an object was read + @param[in] elements number of object elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_object(std::size_t elements) = 0; /*! - * @brief scan a comment - * @return whether comment could be scanned successfully - */ - bool scan_comment() - { - switch (get()) - { - // single-line comments skip input until a newline or EOF is read - case '/': - { - while (true) - { - switch (get()) - { - case '\n': - case '\r': - case char_traits::eof(): - case '\0': - return true; + @brief an object key was read + @param[in] val object key + @return whether parsing should proceed + @note It is safe to move the passed string. + */ + virtual bool key(string_t& val) = 0; - default: - break; - } - } - } + /*! + @brief the end of an object was read + @return whether parsing should proceed + */ + virtual bool end_object() = 0; - // multi-line comments skip input until */ is read - case '*': - { - while (true) - { - switch (get()) - { - case char_traits::eof(): - case '\0': - { - error_message = "invalid comment; missing closing '*/'"; - return false; - } + /*! + @brief the beginning of an array was read + @param[in] elements number of array elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_array(std::size_t elements) = 0; - case '*': - { - switch (get()) - { - case '/': - return true; + /*! + @brief the end of an array was read + @return whether parsing should proceed + */ + virtual bool end_array() = 0; + + /*! + @brief a parse error occurred + @param[in] position the position in the input where the error occurs + @param[in] last_token the last read token + @param[in] ex an exception object describing the error + @return whether parsing should proceed (must return false) + */ + virtual bool parse_error(std::size_t position, + const std::string& last_token, + const detail::exception& ex) = 0; + + json_sax() = default; + json_sax(const json_sax&) = default; + json_sax(json_sax&&) noexcept = default; + json_sax& operator=(const json_sax&) = default; + json_sax& operator=(json_sax&&) noexcept = default; + virtual ~json_sax() = default; +}; + +namespace detail +{ +constexpr std::size_t unknown_size() +{ + return (std::numeric_limits::max)(); +} + +/*! +@brief SAX implementation to create a JSON value from SAX events + +This class implements the @ref json_sax interface and processes the SAX events +to create a JSON value which makes it basically a DOM parser. The structure or +hierarchy of the JSON value is managed by the stack `ref_stack` which contains +a pointer to the respective array or object for each recursion depth. + +After successful parsing, the value that is passed by reference to the +constructor contains the parsed value. + +@tparam BasicJsonType the JSON type +*/ +template +class json_sax_dom_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using lexer_t = lexer; + + /*! + @param[in,out] r reference to a JSON value that is manipulated while + parsing + @param[in] allow_exceptions_ whether parse errors yield exceptions + */ + explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr) + : root(r), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_) + {} + + // make class move-only + json_sax_dom_parser(const json_sax_dom_parser&) = delete; + json_sax_dom_parser(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; + json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~json_sax_dom_parser() = default; + + bool null() + { + handle_value(nullptr); + return true; + } - default: - { - unget(); - continue; - } - } - } + bool boolean(bool val) + { + handle_value(val); + return true; + } - default: - continue; - } - } - } + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } - // unexpected character after reading '/' - default: - { - error_message = "invalid comment; expecting '/' or '*' after '/'"; - return false; - } - } + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; } - JSON_HEDLEY_NON_NULL(2) - static void strtof(float& f, const char* str, char** endptr) noexcept + bool number_float(number_float_t val, const string_t& /*unused*/) { - f = std::strtof(str, endptr); + handle_value(val); + return true; } - JSON_HEDLEY_NON_NULL(2) - static void strtof(double& f, const char* str, char** endptr) noexcept + bool string(string_t& val) { - f = std::strtod(str, endptr); + handle_value(val); + return true; } - JSON_HEDLEY_NON_NULL(2) - static void strtof(long double& f, const char* str, char** endptr) noexcept + bool binary(binary_t& val) { - f = std::strtold(str, endptr); + handle_value(std::move(val)); + return true; } - /*! - @brief scan a number literal + bool start_object(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); - This function scans a string according to Sect. 6 of RFC 8259. +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the object here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + // Lexer has read the first character of the object, so + // subtract 1 from the position to get the correct start position. + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; + } +#endif - The function is realized with a deterministic finite state machine derived - from the grammar described in RFC 8259. Starting in state "init", the - input is read and used to determined the next state. Only state "done" - accepts the number. State "error" is a trap state to model errors. In the - table below, "anything" means any character but the ones listed before. + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); + } - state | 0 | 1-9 | e E | + | - | . | anything - ---------|----------|----------|----------|---------|---------|----------|----------- - init | zero | any1 | [error] | [error] | minus | [error] | [error] - minus | zero | any1 | [error] | [error] | [error] | [error] | [error] - zero | done | done | exponent | done | done | decimal1 | done - any1 | any1 | any1 | exponent | done | done | decimal1 | done - decimal1 | decimal2 | decimal2 | [error] | [error] | [error] | [error] | [error] - decimal2 | decimal2 | decimal2 | exponent | done | done | done | done - exponent | any2 | any2 | [error] | sign | sign | [error] | [error] - sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] - any2 | any2 | any2 | done | done | done | done | done + return true; + } - The state machine is realized with one label per state (prefixed with - "scan_number_") and `goto` statements between them. The state machine - contains cycles, but any cycle can be left when EOF is read. Therefore, - the function is guaranteed to terminate. + bool key(string_t& val) + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); - During scanning, the read bytes are stored in token_buffer. This string is - then converted to a signed integer, an unsigned integer, or a - floating-point number. + // add null at the given key and store the reference for later + object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val)); + return true; + } - @return token_type::value_unsigned, token_type::value_integer, or - token_type::value_float if number could be successfully scanned, - token_type::parse_error otherwise + bool end_object() + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); - @note The scanner is independent of the current locale. Internally, the - locale's decimal point is used instead of `.` to work with the - locale-dependent converters. - */ - token_type scan_number() // lgtm [cpp/use-of-goto] +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing brace, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); + } +#endif + + ref_stack.back()->set_parents(); + ref_stack.pop_back(); + return true; + } + + bool start_array(std::size_t len) { - // reset token_buffer to store the number's bytes - reset(); + ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); - // the type of the parsed number; initially set to unsigned; will be - // changed if minus sign, decimal point or exponent is read - token_type number_type = token_type::value_unsigned; +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the array here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; + } +#endif - // state (init): we just found out we need to scan a number - switch (current) + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) { - case '-': - { - add(current); - goto scan_number_minus; - } + JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + } - case '0': - { - add(current); - goto scan_number_zero; - } + return true; + } - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any1; - } + bool end_array() + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_array()); - // all other characters are rejected outside scan_number() - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing bracket, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); } +#endif -scan_number_minus: - // state: we just parsed a leading minus sign - number_type = token_type::value_integer; - switch (get()) + ref_stack.back()->set_parents(); + ref_stack.pop_back(); + return true; + } + + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast(ex); + if (allow_exceptions) { - case '0': - { - add(current); - goto scan_number_zero; - } + JSON_THROW(ex); + } + return false; + } - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': + constexpr bool is_errored() const + { + return errored; + } + + private: + +#if JSON_DIAGNOSTIC_POSITIONS + void handle_diagnostic_positions_for_json_value(BasicJsonType& v) + { + if (m_lexer_ref) + { + // Lexer has read past the current field value, so set the end position to the current position. + // The start position will be set below based on the length of the string representation + // of the value. + v.end_position = m_lexer_ref->get_position(); + + switch (v.type()) { - add(current); - goto scan_number_any1; + case value_t::boolean: + { + // 4 and 5 are the string length of "true" and "false" + v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5); + break; + } + + case value_t::null: + { + // 4 is the string length of "null" + v.start_position = v.end_position - 4; + break; + } + + case value_t::string: + { + // include the length of the quotes, which is 2 + v.start_position = v.end_position - v.m_data.m_value.string->size() - 2; + break; + } + + // As we handle the start and end positions for values created during parsing, + // we do not expect the following value type to be called. Regardless, set the positions + // in case this is created manually or through a different constructor. Exclude from lcov + // since the exact condition of this switch is esoteric. + // LCOV_EXCL_START + case value_t::discarded: + { + v.end_position = std::string::npos; + v.start_position = v.end_position; + break; + } + // LCOV_EXCL_STOP + case value_t::binary: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + { + v.start_position = v.end_position - m_lexer_ref->get_string().size(); + break; + } + case value_t::object: + case value_t::array: + { + // object and array are handled in start_object() and start_array() handlers + // skip setting the values here. + break; + } + default: // LCOV_EXCL_LINE + // Handle all possible types discretely, default handler should never be reached. + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE } + } + } +#endif + + /*! + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + */ + template + JSON_HEDLEY_RETURNS_NON_NULL + BasicJsonType* handle_value(Value&& v) + { + if (ref_stack.empty()) + { + root = BasicJsonType(std::forward(v)); - default: - { - error_message = "invalid number; expected digit after '-'"; - return token_type::parse_error; - } +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(root); +#endif + + return &root; } -scan_number_zero: - // state: we just parse a zero (maybe with a leading minus sign) - switch (get()) + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + if (ref_stack.back()->is_array()) { - case '.': - { - add(decimal_point_char); - goto scan_number_decimal1; - } + ref_stack.back()->m_data.m_value.array->emplace_back(std::forward(v)); - case 'e': - case 'E': - { - add(current); - goto scan_number_exponent; - } +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(ref_stack.back()->m_data.m_value.array->back()); +#endif - default: - goto scan_number_done; + return &(ref_stack.back()->m_data.m_value.array->back()); } -scan_number_any1: - // state: we just parsed a number 0-9 (maybe with a leading minus sign) - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any1; - } + JSON_ASSERT(ref_stack.back()->is_object()); + JSON_ASSERT(object_element); + *object_element = BasicJsonType(std::forward(v)); - case '.': - { - add(decimal_point_char); - goto scan_number_decimal1; - } +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(*object_element); +#endif - case 'e': - case 'E': - { - add(current); - goto scan_number_exponent; - } + return object_element; + } - default: - goto scan_number_done; - } + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; + /// the lexer reference to obtain the current position + lexer_t* m_lexer_ref = nullptr; +}; -scan_number_decimal1: - // state: we just parsed a decimal point - number_type = token_type::value_float; - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_decimal2; - } +template +class json_sax_dom_callback_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using parser_callback_t = typename BasicJsonType::parser_callback_t; + using parse_event_t = typename BasicJsonType::parse_event_t; + using lexer_t = lexer; - default: - { - error_message = "invalid number; expected digit after '.'"; - return token_type::parse_error; - } - } + json_sax_dom_callback_parser(BasicJsonType& r, + parser_callback_t cb, + const bool allow_exceptions_ = true, + lexer_t* lexer_ = nullptr) + : root(r), callback(std::move(cb)), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_) + { + keep_stack.push_back(true); + } -scan_number_decimal2: - // we just parsed at least one number after a decimal point - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_decimal2; - } + // make class move-only + json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~json_sax_dom_callback_parser() = default; - case 'e': - case 'E': - { - add(current); - goto scan_number_exponent; - } + bool null() + { + handle_value(nullptr); + return true; + } - default: - goto scan_number_done; - } + bool boolean(bool val) + { + handle_value(val); + return true; + } -scan_number_exponent: - // we just parsed an exponent - number_type = token_type::value_float; - switch (get()) - { - case '+': - case '-': - { - add(current); - goto scan_number_sign; - } + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any2; - } + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } - default: - { - error_message = - "invalid number; expected '+', '-', or digit after exponent"; - return token_type::parse_error; - } - } + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } -scan_number_sign: - // we just parsed an exponent sign - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any2; - } + bool string(string_t& val) + { + handle_value(val); + return true; + } + + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } + + bool start_object(std::size_t len) + { + // check callback for object start + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); + keep_stack.push_back(keep); + + auto val = handle_value(BasicJsonType::value_t::object, true); + ref_stack.push_back(val.second); + + if (ref_stack.back()) + { - default: +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the object here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) { - error_message = "invalid number; expected digit after exponent sign"; - return token_type::parse_error; + // Lexer has read the first character of the object, so + // subtract 1 from the position to get the correct start position. + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; } - } +#endif -scan_number_any2: - // we just parsed a number after the exponent or exponent sign - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': + // check object limit + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) { - add(current); - goto scan_number_any2; + JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); } - - default: - goto scan_number_done; } + return true; + } -scan_number_done: - // unget the character after the number (we only read it to know that - // we are done scanning a number) - unget(); + bool key(string_t& val) + { + BasicJsonType k = BasicJsonType(val); - char* endptr = nullptr; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - errno = 0; + // check callback for the key + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); + key_keep_stack.push_back(keep); - // try to parse integers first and fall back to floats - if (number_type == token_type::value_unsigned) + // add discarded value at the given key and store the reference for later + if (keep && ref_stack.back()) { - const auto x = std::strtoull(token_buffer.data(), &endptr, 10); + object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded); + } - // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + return true; + } - if (errno == 0) - { - value_unsigned = static_cast(x); - if (value_unsigned == x) - { - return token_type::value_unsigned; - } - } - } - else if (number_type == token_type::value_integer) + bool end_object() + { + if (ref_stack.back()) { - const auto x = std::strtoll(token_buffer.data(), &endptr, 10); - - // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + if (!callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) + { + // discard object + *ref_stack.back() = discarded; - if (errno == 0) +#if JSON_DIAGNOSTIC_POSITIONS + // Set start/end positions for discarded object. + handle_diagnostic_positions_for_json_value(*ref_stack.back()); +#endif + } + else { - value_integer = static_cast(x); - if (value_integer == x) + +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) { - return token_type::value_integer; + // Lexer's position is past the closing brace, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); } +#endif + + ref_stack.back()->set_parents(); } } - // this code is reached if we parse a floating-point number or if an - // integer conversion above failed - strtof(value_float, token_buffer.data(), &endptr); - - // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); - - return token_type::value_float; - } + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); - /*! - @param[in] literal_text the literal text to expect - @param[in] length the length of the passed literal text - @param[in] return_type the token type to return on success - */ - JSON_HEDLEY_NON_NULL(2) - token_type scan_literal(const char_type* literal_text, const std::size_t length, - token_type return_type) - { - JSON_ASSERT(char_traits::to_char_type(current) == literal_text[0]); - for (std::size_t i = 1; i < length; ++i) + if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) { - if (JSON_HEDLEY_UNLIKELY(char_traits::to_char_type(get()) != literal_text[i])) + // remove discarded value + for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) { - error_message = "invalid literal"; - return token_type::parse_error; + if (it->is_discarded()) + { + ref_stack.back()->erase(it); + break; + } } } - return return_type; - } - - ///////////////////// - // input management - ///////////////////// - /// reset token_buffer; current character is beginning of token - void reset() noexcept - { - token_buffer.clear(); - token_string.clear(); - token_string.push_back(char_traits::to_char_type(current)); + return true; } - /* - @brief get next character from the input - - This function provides the interface to the used input adapter. It does - not throw in case the input reached EOF, but returns a - `char_traits::eof()` in that case. Stores the scanned characters - for use in error messages. - - @return character read from the input - */ - char_int_type get() + bool start_array(std::size_t len) { - ++position.chars_read_total; - ++position.chars_read_current_line; + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); + keep_stack.push_back(keep); - if (next_unget) - { - // just reset the next_unget variable and work with current - next_unget = false; - } - else - { - current = ia.get_character(); - } + auto val = handle_value(BasicJsonType::value_t::array, true); + ref_stack.push_back(val.second); - if (JSON_HEDLEY_LIKELY(current != char_traits::eof())) + if (ref_stack.back()) { - token_string.push_back(char_traits::to_char_type(current)); - } - if (current == '\n') - { - ++position.lines_read; - position.chars_read_current_line = 0; +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the array here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + // Lexer has read the first character of the array, so + // subtract 1 from the position to get the correct start position. + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; + } +#endif + + // check array limit + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + } } - return current; + return true; } - /*! - @brief unget current character (read it again on next get) - - We implement unget by setting variable next_unget to true. The input is not - changed - we just simulate ungetting by modifying chars_read_total, - chars_read_current_line, and token_string. The next call to get() will - behave as if the unget character is read again. - */ - void unget() + bool end_array() { - next_unget = true; - - --position.chars_read_total; + bool keep = true; - // in case we "unget" a newline, we have to also decrement the lines_read - if (position.chars_read_current_line == 0) + if (ref_stack.back()) { - if (position.lines_read > 0) + keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); + if (keep) { - --position.lines_read; - } - } - else - { - --position.chars_read_current_line; - } - if (JSON_HEDLEY_LIKELY(current != char_traits::eof())) - { - JSON_ASSERT(!token_string.empty()); - token_string.pop_back(); - } - } +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing bracket, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); + } +#endif - /// add a character to token_buffer - void add(char_int_type c) - { - token_buffer.push_back(static_cast(c)); - } + ref_stack.back()->set_parents(); + } + else + { + // discard array + *ref_stack.back() = discarded; - public: - ///////////////////// - // value getters - ///////////////////// +#if JSON_DIAGNOSTIC_POSITIONS + // Set start/end positions for discarded array. + handle_diagnostic_positions_for_json_value(*ref_stack.back()); +#endif + } + } - /// return integer value - constexpr number_integer_t get_number_integer() const noexcept - { - return value_integer; - } + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); - /// return unsigned integer value - constexpr number_unsigned_t get_number_unsigned() const noexcept - { - return value_unsigned; - } + // remove discarded value + if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->pop_back(); + } - /// return floating-point value - constexpr number_float_t get_number_float() const noexcept - { - return value_float; + return true; } - /// return current string value (implicitly resets the token; useful only once) - string_t& get_string() + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) { - return token_buffer; + errored = true; + static_cast(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; } - ///////////////////// - // diagnostics - ///////////////////// - - /// return position of last read token - constexpr position_t get_position() const noexcept + constexpr bool is_errored() const { - return position; + return errored; } - /// return the last read token (for errors only). Will never contain EOF - /// (an arbitrary value that is not a valid char value, often -1), because - /// 255 may legitimately occur. May contain NUL, which should be escaped. - std::string get_token_string() const + private: + +#if JSON_DIAGNOSTIC_POSITIONS + void handle_diagnostic_positions_for_json_value(BasicJsonType& v) { - // escape control characters - std::string result; - for (const auto c : token_string) + if (m_lexer_ref) { - if (static_cast(c) <= '\x1F') - { - // escape control characters - std::array cs{{}}; - static_cast((std::snprintf)(cs.data(), cs.size(), "", static_cast(c))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - result += cs.data(); - } - else + // Lexer has read past the current field value, so set the end position to the current position. + // The start position will be set below based on the length of the string representation + // of the value. + v.end_position = m_lexer_ref->get_position(); + + switch (v.type()) { - // add character as is - result.push_back(static_cast(c)); + case value_t::boolean: + { + // 4 and 5 are the string length of "true" and "false" + v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5); + break; + } + + case value_t::null: + { + // 4 is the string length of "null" + v.start_position = v.end_position - 4; + break; + } + + case value_t::string: + { + // include the length of the quotes, which is 2 + v.start_position = v.end_position - v.m_data.m_value.string->size() - 2; + break; + } + + case value_t::discarded: + { + v.end_position = std::string::npos; + v.start_position = v.end_position; + break; + } + + case value_t::binary: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + { + v.start_position = v.end_position - m_lexer_ref->get_string().size(); + break; + } + + case value_t::object: + case value_t::array: + { + // object and array are handled in start_object() and start_array() handlers + // skip setting the values here. + break; + } + default: // LCOV_EXCL_LINE + // Handle all possible types discretely, default handler should never be reached. + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE } } - - return result; } +#endif - /// return syntax error message - JSON_HEDLEY_RETURNS_NON_NULL - constexpr const char* get_error_message() const noexcept - { - return error_message; - } + /*! + @param[in] v value to add to the JSON value we build during parsing + @param[in] skip_callback whether we should skip calling the callback + function; this is required after start_array() and + start_object() SAX events, because otherwise we would call the + callback function with an empty array or object, respectively. - ///////////////////// - // actual scanner - ///////////////////// + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements - /*! - @brief skip the UTF-8 byte order mark - @return true iff there is no BOM or the correct BOM has been skipped + @return pair of boolean (whether value should be kept) and pointer (to the + passed value in the ref_stack hierarchy; nullptr if not kept) */ - bool skip_bom() + template + std::pair handle_value(Value&& v, const bool skip_callback = false) { - if (get() == 0xEF) + JSON_ASSERT(!keep_stack.empty()); + + // do not handle this value if we know it would be added to a discarded + // container + if (!keep_stack.back()) { - // check if we completely parse the BOM - return get() == 0xBB && get() == 0xBF; + return {false, nullptr}; } - // the first character is not the beginning of the BOM; unget it to - // process is later - unget(); - return true; - } + // create value + auto value = BasicJsonType(std::forward(v)); - void skip_whitespace() - { - do +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(value); +#endif + + // check callback + const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); + + // do not handle this value if we just learnt it shall be discarded + if (!keep) { - get(); + return {false, nullptr}; } - while (current == ' ' || current == '\t' || current == '\n' || current == '\r'); - } - token_type scan() - { - // initially, skip the BOM - if (position.chars_read_total == 0 && !skip_bom()) + if (ref_stack.empty()) { - error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given"; - return token_type::parse_error; + root = std::move(value); + return {true, & root}; } - // read next character and ignore whitespace - skip_whitespace(); - - // ignore comments - while (ignore_comments && current == '/') + // skip this value if we already decided to skip the parent + // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) + if (!ref_stack.back()) { - if (!scan_comment()) - { - return token_type::parse_error; - } + return {false, nullptr}; + } - // skip following whitespace - skip_whitespace(); + // we now only expect arrays and objects + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + // array + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->emplace_back(std::move(value)); + return {true, & (ref_stack.back()->m_data.m_value.array->back())}; } - switch (current) + // object + JSON_ASSERT(ref_stack.back()->is_object()); + // check if we should store an element for the current key + JSON_ASSERT(!key_keep_stack.empty()); + const bool store_element = key_keep_stack.back(); + key_keep_stack.pop_back(); + + if (!store_element) { - // structural characters - case '[': - return token_type::begin_array; - case ']': - return token_type::end_array; - case '{': - return token_type::begin_object; - case '}': - return token_type::end_object; - case ':': - return token_type::name_separator; - case ',': - return token_type::value_separator; + return {false, nullptr}; + } - // literals - case 't': - { - std::array true_literal = {{static_cast('t'), static_cast('r'), static_cast('u'), static_cast('e')}}; - return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true); - } - case 'f': - { - std::array false_literal = {{static_cast('f'), static_cast('a'), static_cast('l'), static_cast('s'), static_cast('e')}}; - return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false); - } - case 'n': - { - std::array null_literal = {{static_cast('n'), static_cast('u'), static_cast('l'), static_cast('l')}}; - return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null); - } + JSON_ASSERT(object_element); + *object_element = std::move(value); + return {true, object_element}; + } - // string - case '\"': - return scan_string(); + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// stack to manage which values to keep + std::vector keep_stack {}; // NOLINT(readability-redundant-member-init) + /// stack to manage which object keys to keep + std::vector key_keep_stack {}; // NOLINT(readability-redundant-member-init) + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// callback function + const parser_callback_t callback = nullptr; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; + /// a discarded value for the callback + BasicJsonType discarded = BasicJsonType::value_t::discarded; + /// the lexer reference to obtain the current position + lexer_t* m_lexer_ref = nullptr; +}; - // number - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return scan_number(); +template +class json_sax_acceptor +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; - // end of input (the null byte is needed when parsing from - // string literals) - case '\0': - case char_traits::eof(): - return token_type::end_of_input; + bool null() + { + return true; + } + + bool boolean(bool /*unused*/) + { + return true; + } - // error - default: - error_message = "invalid literal"; - return token_type::parse_error; - } + bool number_integer(number_integer_t /*unused*/) + { + return true; } - private: - /// input adapter - InputAdapterType ia; + bool number_unsigned(number_unsigned_t /*unused*/) + { + return true; + } - /// whether comments should be ignored (true) or signaled as errors (false) - const bool ignore_comments = false; + bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) + { + return true; + } - /// the current character - char_int_type current = char_traits::eof(); + bool string(string_t& /*unused*/) + { + return true; + } - /// whether the next get() call should just return current - bool next_unget = false; + bool binary(binary_t& /*unused*/) + { + return true; + } - /// the start position of the current token - position_t position {}; + bool start_object(std::size_t /*unused*/ = detail::unknown_size()) + { + return true; + } - /// raw input token string (for error messages) - std::vector token_string {}; + bool key(string_t& /*unused*/) + { + return true; + } - /// buffer for variable-length tokens (numbers, strings) - string_t token_buffer {}; + bool end_object() + { + return true; + } - /// a description of occurred lexer errors - const char* error_message = ""; + bool start_array(std::size_t /*unused*/ = detail::unknown_size()) + { + return true; + } - // number values - number_integer_t value_integer = 0; - number_unsigned_t value_unsigned = 0; - number_float_t value_float = 0; + bool end_array() + { + return true; + } - /// the decimal point - const char_int_type decimal_point_char = '.'; + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) + { + return false; + } }; } // namespace detail NLOHMANN_JSON_NAMESPACE_END +// #include + // #include // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -9196,7 +9891,7 @@ enum class cbor_tag_handler_t @note from https://stackoverflow.com/a/1001328/266378 */ -static inline bool little_endianness(int num = 1) noexcept +inline bool little_endianness(int num = 1) noexcept { return *reinterpret_cast(&num) == 1; } @@ -9208,7 +9903,7 @@ static inline bool little_endianness(int num = 1) noexcept /*! @brief deserialization of CBOR, MessagePack, and UBJSON values */ -template> +template> class binary_reader { using number_integer_t = typename BasicJsonType::number_integer_t; @@ -9315,7 +10010,7 @@ class binary_reader std::int32_t document_size{}; get_number(input_format_t::bson, document_size); - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { return false; } @@ -9471,7 +10166,13 @@ class binary_reader return get_number(input_format_t::bson, value) && sax->number_integer(value); } - default: // anything else not supported (yet) + case 0x11: // uint64 + { + std::uint64_t value{}; + return get_number(input_format_t::bson, value) && sax->number_unsigned(value); + } + + default: // anything else is not supported (yet) { std::array cr{{}}; static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) @@ -9537,7 +10238,7 @@ class binary_reader std::int32_t document_size{}; get_number(input_format_t::bson, document_size); - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { return false; } @@ -9797,7 +10498,7 @@ class binary_reader } case 0x9F: // array (indefinite length) - return get_cbor_array(static_cast(-1), tag_handler); + return get_cbor_array(detail::unknown_size(), tag_handler); // map (0x00..0x17 pairs of data items follow) case 0xA0: @@ -9851,7 +10552,7 @@ class binary_reader } case 0xBF: // map (indefinite length) - return get_cbor_object(static_cast(-1), tag_handler); + return get_cbor_object(detail::unknown_size(), tag_handler); case 0xC6: // tagged item case 0xC7: @@ -9868,7 +10569,7 @@ class binary_reader case 0xD2: case 0xD3: case 0xD4: - case 0xD8: // tagged item (1 bytes follow) + case 0xD8: // tagged item (1 byte follows) case 0xD9: // tagged item (2 bytes follow) case 0xDA: // tagged item (4 bytes follow) case 0xDB: // tagged item (8 bytes follow) @@ -9920,7 +10621,7 @@ class binary_reader case cbor_tag_handler_t::store: { binary_t b; - // use binary subtype and store in binary container + // use binary subtype and store in a binary container switch (current) { case 0xD8: @@ -9989,7 +10690,7 @@ class binary_reader const auto byte1 = static_cast(byte1_raw); const auto byte2 = static_cast(byte2_raw); - // code from RFC 7049, Appendix D, Figure 3: + // Code from RFC 7049, Appendix D, Figure 3: // As half-precision floating-point numbers were only added // to IEEE 754 in 2008, today's programming platforms often // still only have limited support for them. It is very @@ -10239,7 +10940,7 @@ class binary_reader } /*! - @param[in] len the length of the array or static_cast(-1) for an + @param[in] len the length of the array or detail::unknown_size() for an array of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether array creation completed @@ -10252,7 +10953,7 @@ class binary_reader return false; } - if (len != static_cast(-1)) + if (len != detail::unknown_size()) { for (std::size_t i = 0; i < len; ++i) { @@ -10277,7 +10978,7 @@ class binary_reader } /*! - @param[in] len the length of the object or static_cast(-1) for an + @param[in] len the length of the object or detail::unknown_size() for an object of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether object creation completed @@ -10293,7 +10994,7 @@ class binary_reader if (len != 0) { string_t key; - if (len != static_cast(-1)) + if (len != detail::unknown_size()) { for (std::size_t i = 0; i < len; ++i) { @@ -11296,7 +11997,7 @@ class binary_reader { break; } - if (is_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array + if (is_ndarray) // ndarray dimensional vector can only contain integers and cannot embed another array { return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "ndarray dimensional vector is not allowed", "size"), nullptr)); } @@ -11329,8 +12030,16 @@ class binary_reader result = 1; for (auto i : dim) { + // Pre-multiplication overflow check: if i > 0 and result > SIZE_MAX/i, then result*i would overflow. + // This check must happen before multiplication since overflow detection after the fact is unreliable + // as modular arithmetic can produce any value, not just 0 or SIZE_MAX. + if (JSON_HEDLEY_UNLIKELY(i > 0 && result > (std::numeric_limits::max)() / i)) + { + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message(input_format, "excessive ndarray size caused overflow", "size"), nullptr)); + } result *= i; - if (result == 0 || result == npos) // because dim elements shall not have zeros, result = 0 means overflow happened; it also can't be npos as it is used to initialize size in get_ubjson_size_type() + // Additional post-multiplication check to catch any edge cases the pre-check might miss + if (result == 0 || result == npos) { return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message(input_format, "excessive ndarray size caused overflow", "size"), nullptr)); } @@ -11456,6 +12165,16 @@ class binary_reader case 'Z': // null return sax->null(); + case 'B': // byte + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint8_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + case 'U': { std::uint8_t number{}; @@ -11536,7 +12255,7 @@ class binary_reader const auto byte1 = static_cast(byte1_raw); const auto byte2 = static_cast(byte2_raw); - // code from RFC 7049, Appendix D, Figure 3: + // Code from RFC 7049, Appendix D, Figure 3: // As half-precision floating-point numbers were only added // to IEEE 754 in 2008, today's programming platforms often // still only have limited support for them. It is very @@ -11656,7 +12375,7 @@ class binary_reader return false; } - if (size_and_type.second == 'C') + if (size_and_type.second == 'C' || size_and_type.second == 'B') { size_and_type.second = 'U'; } @@ -11678,6 +12397,13 @@ class binary_reader return (sax->end_array() && sax->end_object()); } + // If BJData type marker is 'B' decode as binary + if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B') + { + binary_t result; + return get_binary(input_format, size_and_type.first, result) && sax->binary(result); + } + if (size_and_type.first != npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) @@ -11711,7 +12437,7 @@ class binary_reader } else { - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { return false; } @@ -11789,7 +12515,7 @@ class binary_reader } else { - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { return false; } @@ -11817,7 +12543,7 @@ class binary_reader bool get_ubjson_high_precision_number() { - // get size of following number string + // get the size of the following number string std::size_t size{}; bool no_ndarray = true; auto res = get_ubjson_size_value(size, no_ndarray); @@ -11900,6 +12626,29 @@ class binary_reader return current = ia.get_character(); } + /*! + @brief get_to read into a primitive type + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns false instead + + @return bool, whether the read was successful + */ + template + bool get_to(T& dest, const input_format_t format, const char* context) + { + auto new_chars_read = ia.get_elements(&dest); + chars_read += new_chars_read; + if (JSON_HEDLEY_UNLIKELY(new_chars_read < sizeof(T))) + { + // in case of failure, advance position by 1 to report the failing location + ++chars_read; + sax->parse_error(chars_read, "", parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context), nullptr)); + return false; + } + return true; + } + /*! @return character read from the input after ignoring all 'N' entries */ @@ -11914,6 +12663,33 @@ class binary_reader return current; } + template + static void byte_swap(NumberType& number) + { + constexpr std::size_t sz = sizeof(number); +#ifdef __cpp_lib_byteswap + if constexpr (sz == 1) + { + return; + } + else if constexpr(std::is_integral_v) + { + number = std::byteswap(number); + return; + } + else + { +#endif + auto* ptr = reinterpret_cast(&number); + for (std::size_t i = 0; i < sz / 2; ++i) + { + std::swap(ptr[i], ptr[sz - i - 1]); + } +#ifdef __cpp_lib_byteswap + } +#endif + } + /* @brief read a number from the input @@ -11932,29 +12708,16 @@ class binary_reader template bool get_number(const input_format_t format, NumberType& result) { - // step 1: read input into array with system's byte order - std::array vec{}; - for (std::size_t i = 0; i < sizeof(NumberType); ++i) - { - get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number"))) - { - return false; - } + // read in the original format - // reverse byte order prior to conversion if necessary - if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata)) - { - vec[sizeof(NumberType) - i - 1] = static_cast(current); - } - else - { - vec[i] = static_cast(current); // LCOV_EXCL_LINE - } + if (JSON_HEDLEY_UNLIKELY(!get_to(result, format, "number"))) + { + return false; + } + if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata)) + { + byte_swap(result); } - - // step 2: convert array into number of type T and return - std::memcpy(&result, vec.data(), sizeof(NumberType)); return true; } @@ -12019,7 +12782,7 @@ class binary_reader success = false; break; } - result.push_back(static_cast(current)); + result.push_back(static_cast(current)); } return success; } @@ -12093,7 +12856,7 @@ class binary_reader } private: - static JSON_INLINE_VARIABLE constexpr std::size_t npos = static_cast(-1); + static JSON_INLINE_VARIABLE constexpr std::size_t npos = detail::unknown_size(); /// input adapter InputAdapterType ia; @@ -12119,6 +12882,7 @@ class binary_reader #define JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ \ make_array( \ + bjd_type{'B', "byte"}, \ bjd_type{'C', "char"}, \ bjd_type{'D', "double"}, \ bjd_type{'I', "int16"}, \ @@ -12161,10 +12925,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -12238,12 +13002,14 @@ class parser public: /// a parser reading from an input adapter explicit parser(InputAdapterType&& adapter, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions_ = true, - const bool skip_comments = false) - : callback(cb) - , m_lexer(std::move(adapter), skip_comments) + const bool ignore_comments = false, + const bool ignore_trailing_commas_ = false) + : callback(std::move(cb)) + , m_lexer(std::move(adapter), ignore_comments) , allow_exceptions(allow_exceptions_) + , ignore_trailing_commas(ignore_trailing_commas_) { // read first token get_token(); @@ -12263,7 +13029,7 @@ class parser { if (callback) { - json_sax_dom_callback_parser sdp(result, callback, allow_exceptions); + json_sax_dom_callback_parser sdp(result, callback, allow_exceptions, &m_lexer); sax_parse_internal(&sdp); // in strict mode, input must be completely read @@ -12275,7 +13041,7 @@ class parser exception_message(token_type::end_of_input, "value"), nullptr)); } - // in case of an error, return discarded value + // in case of an error, return a discarded value if (sdp.is_errored()) { result = value_t::discarded; @@ -12291,7 +13057,7 @@ class parser } else { - json_sax_dom_parser sdp(result, allow_exceptions); + json_sax_dom_parser sdp(result, allow_exceptions, &m_lexer); sax_parse_internal(&sdp); // in strict mode, input must be completely read @@ -12302,7 +13068,7 @@ class parser parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr)); } - // in case of an error, return discarded value + // in case of an error, return a discarded value if (sdp.is_errored()) { result = value_t::discarded; @@ -12363,7 +13129,7 @@ class parser { case token_type::begin_object: { - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { return false; } @@ -12408,7 +13174,7 @@ class parser case token_type::begin_array: { - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { return false; } @@ -12505,7 +13271,7 @@ class parser case token_type::parse_error: { - // using "uninitialized" to avoid "expected" message + // using "uninitialized" to avoid an "expected" message return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr)); @@ -12553,11 +13319,17 @@ class parser if (states.back()) // array { // comma -> next value + // or end of array (ignore_trailing_commas = true) if (get_token() == token_type::value_separator) { // parse a new value get_token(); - continue; + + // if ignore_trailing_commas and last_token is ], we can continue to "closing ]" + if (!(ignore_trailing_commas && last_token == token_type::end_array)) + { + continue; + } } // closing ] @@ -12586,32 +13358,39 @@ class parser // states.back() is false -> object // comma -> next value + // or end of object (ignore_trailing_commas = true) if (get_token() == token_type::value_separator) { - // parse key - if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string)) - { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); - } + get_token(); - if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) + // if ignore_trailing_commas and last_token is }, we can continue to "closing }" + if (!(ignore_trailing_commas && last_token == token_type::end_object)) { - return false; - } + // parse key + if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); + } - // parse separator (:) - if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) - { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); - } + if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) + { + return false; + } - // parse values - get_token(); - continue; + // parse separator (:) + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); + } + + // parse values + get_token(); + continue; + } } // closing } @@ -12682,6 +13461,8 @@ class parser lexer_t m_lexer; /// whether to throw exceptions in case of errors const bool allow_exceptions = true; + /// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false) + const bool ignore_trailing_commas = false; }; } // namespace detail @@ -12690,10 +13471,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -12703,10 +13484,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -12725,9 +13506,9 @@ namespace detail @brief an iterator for primitive JSON types This class models an iterator for primitive JSON types (boolean, number, -string). It's only purpose is to allow the iterator/const_iterator classes +string). Its only purpose is to allow the iterator/const_iterator classes to "iterate" over primitive values. Internally, the iterator is modeled by -a `difference_type` variable. Value begin_value (`0`) models the begin, +a `difference_type` variable. Value begin_value (`0`) models the begin and end_value (`1`) models past the end. */ class primitive_iterator_t @@ -12862,10 +13643,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -12892,7 +13673,7 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { -// forward declare, to be able to friend it later on +// forward declare to be able to friend it later on template class iteration_proxy; template class iteration_proxy_value; @@ -13332,7 +14113,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > bool operator==(const IterImpl& other) const @@ -13343,7 +14124,11 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); } - JSON_ASSERT(m_object != nullptr); + // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 + if (m_object == nullptr) + { + return true; + } switch (m_object->m_data.m_type) { @@ -13368,7 +14153,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: not equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > bool operator!=(const IterImpl& other) const @@ -13378,7 +14163,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: smaller - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ bool operator<(const iter_impl& other) const { @@ -13388,7 +14173,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); } - JSON_ASSERT(m_object != nullptr); + // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 + if (m_object == nullptr) + { + // the iterators are both value-initialized and are to be considered equal, but this function checks for smaller, so we return false + return false; + } switch (m_object->m_data.m_type) { @@ -13413,7 +14203,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: less than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ bool operator<=(const iter_impl& other) const { @@ -13422,7 +14212,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: greater than - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ bool operator>(const iter_impl& other) const { @@ -13431,7 +14221,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: greater than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) The iterator is initialized; i.e. `m_object != nullptr`, or (2) both iterators are value-initialized. */ bool operator>=(const iter_impl& other) const { @@ -13624,10 +14414,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -13759,10 +14549,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -13801,10 +14591,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -14034,7 +14824,7 @@ class json_pointer } const char* p = s.c_str(); - char* p_end = nullptr; + char* p_end = nullptr; // NOLINT(misc-const-correctness) errno = 0; // strtoull doesn't reset errno const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int) if (p == p_end // invalid input or empty string @@ -14091,7 +14881,7 @@ class json_pointer { if (reference_token == "0") { - // start a new array if reference token is 0 + // start a new array if the reference token is 0 result = &result->operator[](0); } else @@ -14120,7 +14910,7 @@ class json_pointer The following code is only reached if there exists a reference token _and_ the current value is primitive. In this case, we have an error situation, because primitive values may only occur as - single value; that is, with an empty list of reference tokens. + a single value; that is, with an empty list of reference tokens. */ case detail::value_t::string: case detail::value_t::boolean: @@ -14164,7 +14954,7 @@ class json_pointer // convert null values to arrays or objects before continuing if (ptr->is_null()) { - // check if reference token is a number + // check if the reference token is a number const bool nums = std::all_of(reference_token.begin(), reference_token.end(), [](const unsigned char x) @@ -14172,7 +14962,7 @@ class json_pointer return std::isdigit(x); }); - // change value to array for numbers or "-" or to object otherwise + // change value to an array for numbers or "-" or to object otherwise *ptr = (nums || reference_token == "-") ? detail::value_t::array : detail::value_t::object; @@ -14415,7 +15205,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9'))) { - // first char should be between '1' and '9' + // the first char should be between '1' and '9' return false; } for (std::size_t i = 1; i < reference_token.size(); i++) @@ -14479,7 +15269,7 @@ class json_pointer return result; } - // check if nonempty reference string begins with slash + // check if a nonempty reference string begins with slash if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) { JSON_THROW(detail::parse_error::create(107, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr)); @@ -14553,10 +15343,10 @@ class json_pointer } else { - // iterate array and use index as reference string + // iterate array and use index as a reference string for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i) { - flatten(detail::concat(reference_string, '/', std::to_string(i)), + flatten(detail::concat(reference_string, '/', std::to_string(i)), value.m_data.m_value.array->operator[](i), result); } } @@ -14575,7 +15365,7 @@ class json_pointer // iterate object and use keys as reference string for (const auto& element : *value.m_data.m_value.object) { - flatten(detail::concat(reference_string, '/', detail::escape(element.first)), element.second, result); + flatten(detail::concat(reference_string, '/', detail::escape(element.first)), element.second, result); } } break; @@ -14591,7 +15381,7 @@ class json_pointer case detail::value_t::discarded: default: { - // add primitive value with its reference string + // add a primitive value with its reference string result[reference_string] = value; break; } @@ -14627,17 +15417,17 @@ class json_pointer JSON_THROW(detail::type_error::create(315, "values in object must be primitive", &element.second)); } - // assign value to reference pointed to by JSON pointer; Note that if - // the JSON pointer is "" (i.e., points to the whole value), function - // get_and_create returns a reference to result itself. An assignment - // will then create a primitive value. + // Assign the value to the reference pointed to by JSON pointer. Note + // that if the JSON pointer is "" (i.e., points to the whole value), + // function get_and_create returns a reference to the result itself. + // An assignment will then create a primitive value. json_pointer(element.first).get_and_create(result) = element.second; } return result; } - // can't use conversion operator because of ambiguity + // can't use the conversion operator because of ambiguity json_pointer convert() const& { json_pointer result; @@ -14732,7 +15522,7 @@ class json_pointer }; #if !JSON_HAS_THREE_WAY_COMPARISON -// functions cannot be defined inside class due to ODR violations +// functions cannot be defined inside the class due to ODR violations template inline bool operator==(const json_pointer& lhs, const json_pointer& rhs) noexcept @@ -14796,10 +15586,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -14881,6 +15671,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + // #include // #include @@ -14888,10 +15680,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -14914,10 +15706,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -15068,6 +15860,13 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { +/// how to encode BJData +enum class bjdata_version_t +{ + draft2, + draft3, +}; + /////////////////// // binary writer // /////////////////// @@ -15652,7 +16451,7 @@ class binary_writer case value_t::binary: { // step 0: determine if the binary type has a set subtype to - // determine whether or not to use the ext or fixext types + // determine whether to use the ext or fixext types const bool use_ext = j.m_data.m_value.binary->has_subtype(); // step 1: write control byte and the byte string length @@ -15775,11 +16574,14 @@ class binary_writer @param[in] use_type whether to use '$' prefixes (optimized format) @param[in] add_prefix whether prefixes need to be used for this value @param[in] use_bjdata whether write in BJData format, default is false + @param[in] bjdata_version which BJData version to use, default is draft2 */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true, - const bool use_bjdata = false) + const bool use_bjdata = false, const bjdata_version_t bjdata_version = bjdata_version_t::draft2) { + const bool bjdata_draft3 = use_bjdata && bjdata_version == bjdata_version_t::draft3; + switch (j.type()) { case value_t::null: @@ -15869,7 +16671,7 @@ class binary_writer for (const auto& el : *j.m_data.m_value.array) { - write_ubjson(el, use_count, use_type, prefix_required, use_bjdata); + write_ubjson(el, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } if (!use_count) @@ -15887,11 +16689,11 @@ class binary_writer oa->write_character(to_char_type('[')); } - if (use_type && !j.m_data.m_value.binary->empty()) + if (use_type && (bjdata_draft3 || !j.m_data.m_value.binary->empty())) { JSON_ASSERT(use_count); oa->write_character(to_char_type('$')); - oa->write_character('U'); + oa->write_character(bjdata_draft3 ? 'B' : 'U'); } if (use_count) @@ -15910,7 +16712,7 @@ class binary_writer { for (size_t i = 0; i < j.m_data.m_value.binary->size(); ++i) { - oa->write_character(to_char_type('U')); + oa->write_character(to_char_type(bjdata_draft3 ? 'B' : 'U')); oa->write_character(j.m_data.m_value.binary->data()[i]); } } @@ -15927,7 +16729,7 @@ class binary_writer { if (use_bjdata && j.m_data.m_value.object->size() == 3 && j.m_data.m_value.object->find("_ArrayType_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArraySize_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArrayData_") != j.m_data.m_value.object->end()) { - if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) + if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type, bjdata_version)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) { break; } @@ -15971,7 +16773,7 @@ class binary_writer oa->write_characters( reinterpret_cast(el.first.c_str()), el.first.size()); - write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata); + write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } if (!use_count) @@ -16003,9 +16805,9 @@ class binary_writer if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) { JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); - static_cast(j); } + static_cast(j); return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; } @@ -16127,7 +16929,8 @@ class binary_writer } else { - JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BSON as it does not fit int64"), &j)); + write_bson_entry_header(name, 0x11 /* uint64 */); + write_number(static_cast(j.m_data.m_value.number_unsigned), true); } } @@ -16581,7 +17384,7 @@ class binary_writer { return 'L'; } - // anything else is treated as high-precision number + // anything else is treated as a high-precision number return 'H'; // LCOV_EXCL_LINE } @@ -16619,7 +17422,7 @@ class binary_writer { return 'M'; } - // anything else is treated as high-precision number + // anything else is treated as a high-precision number return 'H'; // LCOV_EXCL_LINE } @@ -16655,10 +17458,11 @@ class binary_writer /*! @return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid */ - bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type) + bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type, const bjdata_version_t bjdata_version) { std::map bjdtype = {{"uint8", 'U'}, {"int8", 'i'}, {"uint16", 'u'}, {"int16", 'I'}, - {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, {"char", 'C'} + {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, + {"char", 'C'}, {"byte", 'B'} }; string_t key = "_ArrayType_"; @@ -16688,10 +17492,10 @@ class binary_writer oa->write_character('#'); key = "_ArraySize_"; - write_ubjson(value.at(key), use_count, use_type, true, true); + write_ubjson(value.at(key), use_count, use_type, true, true, bjdata_version); key = "_ArrayData_"; - if (dtype == 'U' || dtype == 'C') + if (dtype == 'U' || dtype == 'C' || dtype == 'B') { for (const auto& el : value.at(key)) { @@ -16784,11 +17588,11 @@ class binary_writer template void write_number(const NumberType n, const bool OutputIsLittleEndian = false) { - // step 1: write number to array of length NumberType + // step 1: write the number to an array of length NumberType std::array vec{}; std::memcpy(vec.data(), &n, sizeof(NumberType)); - // step 2: write array to output (with possible reordering) + // step 2: write the array to output (with possible reordering) if (is_little_endian != OutputIsLittleEndian) { // reverse byte order prior to conversion if necessary @@ -16804,9 +17608,9 @@ class binary_writer #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif - if (static_cast(n) >= static_cast(std::numeric_limits::lowest()) && - static_cast(n) <= static_cast((std::numeric_limits::max)()) && - static_cast(static_cast(n)) == static_cast(n)) + if (!std::isfinite(n) || ((static_cast(n) >= static_cast(std::numeric_limits::lowest()) && + static_cast(n) <= static_cast((std::numeric_limits::max)()) && + static_cast(static_cast(n)) == static_cast(n)))) { oa->write_character(format == detail::input_format_t::cbor ? get_cbor_float_prefix(static_cast(n)) @@ -16841,8 +17645,21 @@ class binary_writer enable_if_t < std::is_signed::value && std::is_unsigned::value > * = nullptr > static CharType to_char_type(std::uint8_t x) noexcept { - static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); + // The std::is_trivial trait is deprecated in C++26. The replacement is to use + // std::is_trivially_copyable and std::is_trivially_default_constructible. + // However, some older library implementations support std::is_trivial + // but not all the std::is_trivially_* traits. + // Since detecting full support across all libraries is difficult, + // we use std::is_trivial unless we are using a standard where it has been deprecated. + // For more details, see: https://github.com/nlohmann/json/pull/4775#issuecomment-2884361627 +#ifdef JSON_HAS_CPP_26 + static_assert(std::is_trivially_copyable::value, "CharType must be trivially copyable"); + static_assert(std::is_trivially_default_constructible::value, "CharType must be trivially default constructible"); +#else static_assert(std::is_trivial::value, "CharType must be trivial"); +#endif + + static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); CharType result; std::memcpy(&result, &x, sizeof(x)); return result; @@ -16882,11 +17699,11 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2008-2009 Björn Hoehrmann -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2008, 2009 Björn Hoehrmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -16907,11 +17724,11 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2009 Florian Loitsch -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -17038,7 +17855,7 @@ struct diyfp // f * 2^e // p_lo = p0_lo + (Q << 32) // // But in this particular case here, the full p_lo is not required. - // Effectively we only need to add the highest bit in p_lo to p_hi (and + // Effectively, we only need to add the highest bit in p_lo to p_hi (and // Q_hi + 1 does not overflow). Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up @@ -17128,7 +17945,7 @@ boundaries compute_boundaries(FloatType value) // Compute the boundaries m- and m+ of the floating-point value // v = f * 2^e. // - // Determine v- and v+, the floating-point predecessor and successor if v, + // Determine v- and v+, the floating-point predecessor and successor of v, // respectively. // // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) @@ -17147,10 +17964,10 @@ boundaries compute_boundaries(FloatType value) // v- m- v m+ v+ const bool lower_boundary_is_closer = F == 0 && E > 1; - const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_plus = diyfp((2 * v.f) + 1, v.e - 1); const diyfp m_minus = lower_boundary_is_closer - ? diyfp(4 * v.f - 1, v.e - 2) // (B) - : diyfp(2 * v.f - 1, v.e - 1); // (A) + ? diyfp((4 * v.f) - 1, v.e - 2) // (B) + : diyfp((2 * v.f) - 1, v.e - 1); // (A) // Determine the normalized w+ = m+. const diyfp w_plus = diyfp::normalize(m_plus); @@ -17283,7 +18100,7 @@ inline cached_power get_cached_power_for_binary_exponent(int e) // (A smaller distance gamma-alpha would require a larger table.) // NB: - // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. + // Actually, this function returns c, such that -60 <= e_c + e + 64 <= -34. constexpr int kCachedPowersMinDecExp = -300; constexpr int kCachedPowersDecStep = 8; @@ -17380,7 +18197,7 @@ inline cached_power get_cached_power_for_binary_exponent(int e) JSON_ASSERT(e >= -1500); JSON_ASSERT(e <= 1500); const int f = kAlpha - e - 1; - const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); + const int k = ((f * 78913) / (1 << 18)) + static_cast(f > 0); const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; JSON_ASSERT(index >= 0); @@ -17595,8 +18412,8 @@ inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, decimal_exponent += n; - // We may now just stop. But instead look if the buffer could be - // decremented to bring V closer to w. + // We may now just stop. But instead, it looks as if the buffer + // could be decremented to bring V closer to w. // // pow10 = 10^n is now 1 ulp in the decimal representation V. // The rounding procedure works with diyfp's with an implicit @@ -17858,15 +18675,15 @@ inline char* append_exponent(char* buf, int e) } else if (k < 100) { - *buf++ = static_cast('0' + k / 10); + *buf++ = static_cast('0' + (k / 10)); k %= 10; *buf++ = static_cast('0' + k); } else { - *buf++ = static_cast('0' + k / 100); + *buf++ = static_cast('0' + (k / 100)); k %= 100; - *buf++ = static_cast('0' + k / 10); + *buf++ = static_cast('0' + (k / 10)); k %= 10; *buf++ = static_cast('0' + k); } @@ -18003,7 +18820,7 @@ char* to_chars(char* first, const char* last, FloatType value) // Compute v = buffer * 10^decimal_exponent. // The decimal digits are stored in the buffer, which needs to be interpreted // as an unsigned decimal integer. - // len is the length of the buffer, i.e. the number of decimal digits. + // len is the length of the buffer, i.e., the number of decimal digits. int len = 0; int decimal_exponent = 0; dtoa_impl::grisu2(first, len, decimal_exponent, value); @@ -18084,7 +18901,7 @@ class serializer , error_handler(error_handler_) {} - // delete because of pointer members + // deleted because of pointer members serializer(const serializer&) = delete; serializer& operator=(const serializer&) = delete; serializer(serializer&&) = delete; @@ -18582,7 +19399,7 @@ class serializer break; } - default: // decode found yet incomplete multi-byte code point + default: // decode found yet incomplete multibyte code point { if (!ensure_ascii) { @@ -18652,7 +19469,7 @@ class serializer @param[in] x unsigned integer number to count its digits @return number of decimal digits */ - inline unsigned int count_digits(number_unsigned_t x) noexcept + unsigned int count_digits(number_unsigned_t x) noexcept { unsigned int n_digits = 1; for (;;) @@ -18771,7 +19588,7 @@ class serializer // jump to the end to generate the string from backward, // so we later avoid reversing the result - buffer_ptr += n_chars; + buffer_ptr += static_cast(n_chars); // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu // See: https://www.youtube.com/watch?v=o4-CwDo2zpg @@ -18836,7 +19653,7 @@ class serializer void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) { - // get number of digits for a float -> text -> float round-trip + // get the number of digits for a float -> text -> float round-trip static constexpr auto d = std::numeric_limits::max_digits10; // the actual conversion @@ -18845,10 +19662,10 @@ class serializer // negative value indicates an error JSON_ASSERT(len > 0); - // check if buffer was large enough + // check if the buffer was large enough JSON_ASSERT(static_cast(len) < number_buffer.size()); - // erase thousands separator + // erase thousands separators if (thousands_sep != '\0') { // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081 @@ -18935,7 +19752,7 @@ class serializer ? (byte & 0x3fu) | (codep << 6u) : (0xFFu >> type) & (byte); - const std::size_t index = 256u + static_cast(state) * 16u + static_cast(type); + const std::size_t index = 256u + (static_cast(state) * 16u) + static_cast(type); JSON_ASSERT(index < utf8d.size()); state = utf8d[index]; return state; @@ -18956,12 +19773,12 @@ class serializer * Helper function for dump_integer * * This function takes a negative signed integer and returns its absolute - * value as unsigned integer. The plus/minus shuffling is necessary as we can - * not directly remove the sign of an arbitrary signed integer as the + * value as an unsigned integer. The plus/minus shuffling is necessary as we + * cannot directly remove the sign of an arbitrary signed integer as the * absolute values of INT_MIN and INT_MAX are usually not the same. See * #1708 for details. */ - inline number_unsigned_t remove_sign(number_integer_t x) noexcept + number_unsigned_t remove_sign(number_integer_t x) noexcept { JSON_ASSERT(x < 0 && x < (std::numeric_limits::max)()); // NOLINT(misc-redundant-expression) return static_cast(-(x + 1)) + 1; @@ -19003,10 +19820,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -19031,7 +19848,7 @@ NLOHMANN_JSON_NAMESPACE_BEGIN /// for use within nlohmann::basic_json template , class Allocator = std::allocator>> - struct ordered_map : std::vector, Allocator> + struct ordered_map : std::vector, Allocator> { using key_type = Key; using mapped_type = T; @@ -19231,7 +20048,7 @@ template , // Since we cannot move const Keys, we re-construct them in place. // We start at first and re-construct (viz. copy) the elements from - // the back of the vector. Example for first iteration: + // the back of the vector. Example for the first iteration: // ,--------. // v | destroy e and re-construct with h @@ -19346,7 +20163,7 @@ template , template using require_input_iter = typename std::enable_if::iterator_category, - std::input_iterator_tag>::value>::type; + std::input_iterator_tag>::value>::type; template> void insert(InputIt first, InputIt last) @@ -19417,9 +20234,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec friend class ::nlohmann::detail::binary_writer; template friend class ::nlohmann::detail::binary_reader; - template + template friend class ::nlohmann::detail::json_sax_dom_parser; - template + template friend class ::nlohmann::detail::json_sax_dom_callback_parser; friend class ::nlohmann::detail::exception; @@ -19436,11 +20253,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec InputAdapterType adapter, detail::parser_callback_tcb = nullptr, const bool allow_exceptions = true, - const bool ignore_comments = false + const bool ignore_comments = false, + const bool ignore_trailing_commas = false ) { return ::nlohmann::detail::parser(std::move(adapter), - std::move(cb), allow_exceptions, ignore_comments); + std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); } private: @@ -19473,6 +20291,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using error_handler_t = detail::error_handler_t; /// how to treat CBOR tags using cbor_tag_handler_t = detail::cbor_tag_handler_t; + /// how to encode BJData + using bjdata_version_t = detail::bjdata_version_t; /// helper type for initializer lists of basic_json values using initializer_list_t = std::initializer_list>; @@ -19552,7 +20372,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { basic_json result; - result["copyright"] = "(C) 2013-2023 Niels Lohmann"; + result["copyright"] = "(C) 2013-2025 Niels Lohmann"; result["name"] = "JSON for Modern C++"; result["url"] = "https://github.com/nlohmann/json"; result["version"]["string"] = @@ -19817,7 +20637,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec object = nullptr; // silence warning, see #821 if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) { - JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.11.3", nullptr)); // LCOV_EXCL_LINE + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE } break; } @@ -19863,7 +20683,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec (t == value_t::binary && binary == nullptr) ) { - //not initialized (e.g. due to exception in the ctor) + // not initialized (e.g., due to exception in the ctor) return; } if (t == value_t::array || t == value_t::object) @@ -19888,7 +20708,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec while (!stack.empty()) { - // move the last item to local variable to be processed + // move the last item to a local variable to be processed basic_json current_item(std::move(stack.back())); stack.pop_back(); @@ -19910,7 +20730,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec current_item.m_data.m_value.object->clear(); } - // it's now safe that current_item get destructed + // it's now safe that current_item gets destructed // since it doesn't have any children } } @@ -20053,10 +20873,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return it; } - reference set_parent(reference j, std::size_t old_capacity = static_cast(-1)) + reference set_parent(reference j, std::size_t old_capacity = detail::unknown_size()) { #if JSON_DIAGNOSTICS - if (old_capacity != static_cast(-1)) + if (old_capacity != detail::unknown_size()) { // see https://github.com/nlohmann/json/issues/2838 JSON_ASSERT(type() == value_t::array); @@ -20136,8 +20956,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::enable_if_t < !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape) - JSONSerializer::to_json(std::declval(), - std::forward(val)))) + JSONSerializer::to_json(std::declval(), + std::forward(val)))) { JSONSerializer::to_json(*this, std::forward(val)); set_parents(); @@ -20150,6 +20970,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::enable_if_t < detail::is_basic_json::value&& !std::is_same::value, int > = 0 > basic_json(const BasicJsonType& val) +#if JSON_DIAGNOSTIC_POSITIONS + : start_position(val.start_pos()), + end_position(val.end_pos()) +#endif { using other_boolean_t = typename BasicJsonType::boolean_t; using other_number_float_t = typename BasicJsonType::number_float_t; @@ -20196,6 +21020,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } JSON_ASSERT(m_data.m_type == val.type()); + set_parents(); assert_invariant(); } @@ -20213,20 +21038,20 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { // The cast is to ensure op[size_type] is called, bearing in mind size_type may not be int; // (many string types can be constructed from 0 via its null-pointer guise, so we get a - // broken call to op[key_type], the wrong semantics and a 4804 warning on Windows) + // broken call to op[key_type], the wrong semantics, and a 4804 warning on Windows) return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[static_cast(0)].is_string(); }); // adjust type if type deduction is not wanted if (!type_deduction) { - // if array is wanted, do not create an object though possible + // if an array is wanted, do not create an object though possible if (manual_type == value_t::array) { is_an_object = false; } - // if object is wanted but impossible, throw an exception + // if an object is wanted but impossible, throw an exception if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) { JSON_THROW(type_error::create(301, "cannot create object from initializer list", nullptr)); @@ -20235,7 +21060,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_an_object) { - // the initializer list is a list of pairs -> create object + // the initializer list is a list of pairs -> create an object m_data.m_type = value_t::object; m_data.m_value = value_t::object; @@ -20249,7 +21074,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } else { - // the initializer list describes an array -> create array + // the initializer list describes an array -> create an array m_data.m_type = value_t::array; m_data.m_value.array = create(init.begin(), init.end()); } @@ -20332,21 +21157,21 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < class InputIT, typename std::enable_if < std::is_same::value || std::is_same::value, int >::type = 0 > - basic_json(InputIT first, InputIT last) + basic_json(InputIT first, InputIT last) // NOLINT(performance-unnecessary-value-param) { JSON_ASSERT(first.m_object != nullptr); JSON_ASSERT(last.m_object != nullptr); - // make sure iterator fits the current value + // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(201, "iterators are not compatible", nullptr)); } - // copy type from first iterator + // copy type from the first iterator m_data.m_type = first.m_object->m_data.m_type; - // check if iterator range is complete for primitive values + // check if the iterator range is complete for primitive values switch (m_data.m_type) { case value_t::boolean: @@ -20447,6 +21272,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(const basic_json& other) : json_base_class_t(other) +#if JSON_DIAGNOSTIC_POSITIONS + , start_position(other.start_position) + , end_position(other.end_position) +#endif { m_data.m_type = other.m_data.m_type; // check of passed value is valid @@ -20516,22 +21345,31 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(basic_json&& other) noexcept : json_base_class_t(std::forward(other)), - m_data(std::move(other.m_data)) + m_data(std::move(other.m_data)) // cppcheck-suppress[accessForwarded] TODO check +#if JSON_DIAGNOSTIC_POSITIONS + , start_position(other.start_position) // cppcheck-suppress[accessForwarded] TODO check + , end_position(other.end_position) // cppcheck-suppress[accessForwarded] TODO check +#endif { - // check that passed value is valid - other.assert_invariant(false); + // check that the passed value is valid + other.assert_invariant(false); // cppcheck-suppress[accessForwarded] // invalidate payload other.m_data.m_type = value_t::null; other.m_data.m_value = {}; +#if JSON_DIAGNOSTIC_POSITIONS + other.start_position = std::string::npos; + other.end_position = std::string::npos; +#endif + set_parents(); assert_invariant(); } /// @brief copy assignment /// @sa https://json.nlohmann.me/api/basic_json/operator=/ - basic_json& operator=(basic_json other) noexcept ( + basic_json& operator=(basic_json other) noexcept ( // NOLINT(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value&& std::is_nothrow_move_constructible::value&& @@ -20539,12 +21377,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec std::is_nothrow_move_assignable::value ) { - // check that passed value is valid + // check that the passed value is valid other.assert_invariant(); using std::swap; swap(m_data.m_type, other.m_data.m_type); swap(m_data.m_value, other.m_data.m_value); + +#if JSON_DIAGNOSTIC_POSITIONS + swap(start_position, other.start_position); + swap(end_position, other.end_position); +#endif + json_base_class_t::operator=(std::move(other)); set_parents(); @@ -20766,13 +21610,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// get a pointer to the value (integer number) number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept { - return is_number_integer() ? &m_data.m_value.number_integer : nullptr; + return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr; } /// get a pointer to the value (integer number) constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept { - return is_number_integer() ? &m_data.m_value.number_integer : nullptr; + return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr; } /// get a pointer to the value (unsigned number) @@ -20907,7 +21751,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::has_from_json::value, int > = 0 > ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept( - JSONSerializer::from_json(std::declval(), std::declval()))) + JSONSerializer::from_json(std::declval(), std::declval()))) { auto ret = ValueType(); JSONSerializer::from_json(*this, ret); @@ -20949,7 +21793,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::has_non_default_from_json::value, int > = 0 > ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept( - JSONSerializer::from_json(std::declval()))) + JSONSerializer::from_json(std::declval()))) { return JSONSerializer::from_json(*this); } @@ -21099,7 +21943,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::has_from_json::value, int > = 0 > ValueType & get_to(ValueType& v) const noexcept(noexcept( - JSONSerializer::from_json(std::declval(), v))) + JSONSerializer::from_json(std::declval(), v))) { JSONSerializer::from_json(*this, v); return v; @@ -21249,9 +22093,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } JSON_CATCH (std::out_of_range&) { - // create better exception explanation + // create a better exception explanation JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); - } + } // cppcheck-suppress[missingReturn] } else { @@ -21272,9 +22116,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } JSON_CATCH (std::out_of_range&) { - // create better exception explanation + // create a better exception explanation JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); - } + } // cppcheck-suppress[missingReturn] } else { @@ -21362,7 +22206,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ reference operator[](size_type idx) { - // implicitly convert null value to an empty array + // implicitly convert a null value to an empty array if (is_null()) { m_data.m_type = value_t::array; @@ -21373,7 +22217,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // operator[] only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { - // fill up array with null values if given idx is outside range + // fill up the array with null values if given idx is outside the range if (idx >= m_data.m_value.array->size()) { #if JSON_DIAGNOSTICS @@ -21419,9 +22263,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief access specified object element /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ - reference operator[](typename object_t::key_type key) + reference operator[](typename object_t::key_type key) // NOLINT(performance-unnecessary-value-param) { - // implicitly convert null value to an empty object + // implicitly convert a null value to an empty object if (is_null()) { m_data.m_type = value_t::object; @@ -21474,7 +22318,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::is_usable_as_basic_json_key_type::value, int > = 0 > reference operator[](KeyType && key) { - // implicitly convert null value to an empty object + // implicitly convert a null value to an empty object if (is_null()) { m_data.m_type = value_t::object; @@ -21531,7 +22375,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // value only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - // if key is found, return value and given default value otherwise + // If 'key' is found, return its value. Otherwise, return `default_value'. const auto it = find(key); if (it != end()) { @@ -21556,7 +22400,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // value only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - // if key is found, return value and given default value otherwise + // If 'key' is found, return its value. Otherwise, return `default_value'. const auto it = find(key); if (it != end()) { @@ -21582,7 +22426,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // value only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - // if key is found, return value and given default value otherwise + // If 'key' is found, return its value. Otherwise, return `default_value'. const auto it = find(std::forward(key)); if (it != end()) { @@ -21609,7 +22453,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // value only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - // if key is found, return value and given default value otherwise + // If 'key' is found, return its value. Otherwise, return `default_value'. const auto it = find(std::forward(key)); if (it != end()) { @@ -21632,7 +22476,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // value only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - // if pointer resolves a value, return it or use default value + // If the pointer resolves to a value, return it. Otherwise, return + // 'default_value'. JSON_TRY { return ptr.get_checked(this).template get(); @@ -21657,7 +22502,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // value only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - // if pointer resolves a value, return it or use default value + // If the pointer resolves to a value, return it. Otherwise, return + // 'default_value'. JSON_TRY { return ptr.get_checked(this).template get(); @@ -21729,9 +22575,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < class IteratorType, detail::enable_if_t < std::is_same::value || std::is_same::value, int > = 0 > - IteratorType erase(IteratorType pos) + IteratorType erase(IteratorType pos) // NOLINT(performance-unnecessary-value-param) { - // make sure iterator fits the current value + // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); @@ -21799,9 +22645,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < class IteratorType, detail::enable_if_t < std::is_same::value || std::is_same::value, int > = 0 > - IteratorType erase(IteratorType first, IteratorType last) + IteratorType erase(IteratorType first, IteratorType last) // NOLINT(performance-unnecessary-value-param) { - // make sure iterator fits the current value + // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) { JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value", this)); @@ -22396,7 +23242,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); } - // transform null object into an array + // transform a null object into an array if (is_null()) { m_data.m_type = value_t::array; @@ -22404,7 +23250,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec assert_invariant(); } - // add element to array (move semantics) + // add the element to the array (move semantics) const auto old_capacity = m_data.m_value.array->capacity(); m_data.m_value.array->push_back(std::move(val)); set_parent(m_data.m_value.array->back(), old_capacity); @@ -22429,7 +23275,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); } - // transform null object into an array + // transform a null object into an array if (is_null()) { m_data.m_type = value_t::array; @@ -22437,7 +23283,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec assert_invariant(); } - // add element to array + // add the element to the array const auto old_capacity = m_data.m_value.array->capacity(); m_data.m_value.array->push_back(val); set_parent(m_data.m_value.array->back(), old_capacity); @@ -22461,7 +23307,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); } - // transform null object into an object + // transform a null object into an object if (is_null()) { m_data.m_type = value_t::object; @@ -22469,7 +23315,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec assert_invariant(); } - // add element to object + // add the element to the object auto res = m_data.m_value.object->insert(val); set_parent(res.first->second); } @@ -22517,7 +23363,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(type_error::create(311, detail::concat("cannot use emplace_back() with ", type_name()), this)); } - // transform null object into an array + // transform a null object into an array if (is_null()) { m_data.m_type = value_t::array; @@ -22525,7 +23371,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec assert_invariant(); } - // add element to array (perfect forwarding) + // add the element to the array (perfect forwarding) const auto old_capacity = m_data.m_value.array->capacity(); m_data.m_value.array->emplace_back(std::forward(args)...); return set_parent(m_data.m_value.array->back(), old_capacity); @@ -22542,7 +23388,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(type_error::create(311, detail::concat("cannot use emplace() with ", type_name()), this)); } - // transform null object into an object + // transform a null object into an object if (is_null()) { m_data.m_type = value_t::object; @@ -22550,11 +23396,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec assert_invariant(); } - // add element to array (perfect forwarding) + // add the element to the array (perfect forwarding) auto res = m_data.m_value.object->emplace(std::forward(args)...); set_parent(res.first->second); - // create result iterator and set iterator to the result of emplace + // create a result iterator and set iterator to the result of emplace auto it = begin(); it.m_it.object_iterator = res.first; @@ -22566,7 +23412,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @note: This uses std::distance to support GCC 4.8, /// see https://github.com/nlohmann/json/pull/1257 template - iterator insert_iterator(const_iterator pos, Args&& ... args) + iterator insert_iterator(const_iterator pos, Args&& ... args) // NOLINT(performance-unnecessary-value-param) { iterator result(this); JSON_ASSERT(m_data.m_value.array != nullptr); @@ -22585,7 +23431,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, const basic_json& val) + iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) @@ -22605,14 +23451,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, basic_json&& val) + iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param) { return insert(pos, val); } /// @brief inserts copies of element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) @@ -22632,7 +23478,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts range of elements into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, const_iterator first, const_iterator last) + iterator insert(const_iterator pos, const_iterator first, const_iterator last) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) @@ -22663,7 +23509,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts elements from initializer list into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, initializer_list_t ilist) + iterator insert(const_iterator pos, initializer_list_t ilist) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) @@ -22683,7 +23529,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts range of elements into object /// @sa https://json.nlohmann.me/api/basic_json/insert/ - void insert(const_iterator first, const_iterator last) + void insert(const_iterator first, const_iterator last) // NOLINT(performance-unnecessary-value-param) { // insert only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) @@ -22704,6 +23550,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + set_parents(); } /// @brief updates a JSON object from another object, overwriting existing keys @@ -22715,9 +23562,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief updates a JSON object from another object, overwriting existing keys /// @sa https://json.nlohmann.me/api/basic_json/update/ - void update(const_iterator first, const_iterator last, bool merge_objects = false) + void update(const_iterator first, const_iterator last, bool merge_objects = false) // NOLINT(performance-unnecessary-value-param) { - // implicitly convert null value to an empty object + // implicitly convert a null value to an empty object if (is_null()) { m_data.m_type = value_t::object; @@ -23277,7 +24124,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ friend std::ostream& operator<<(std::ostream& o, const basic_json& j) { - // read width member and use it as indentation parameter if nonzero + // read width member and use it as the indentation parameter if nonzero const bool pretty_print = o.width() > 0; const auto indentation = pretty_print ? o.width() : 0; @@ -23316,12 +24163,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(InputType&& i, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions = true, - const bool ignore_comments = false) + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) { basic_json result; - parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions, ignore_comments).parse(true, result); + parser(detail::input_adapter(std::forward(i)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved,accessForwarded] return result; } @@ -23331,24 +24179,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(IteratorType first, IteratorType last, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions = true, - const bool ignore_comments = false) + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) { basic_json result; - parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); + parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved] return result; } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) static basic_json parse(detail::span_input_adapter&& i, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions = true, - const bool ignore_comments = false) + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) { basic_json result; - parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); + parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved] return result; } @@ -23356,26 +24206,29 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/accept/ template static bool accept(InputType&& i, - const bool ignore_comments = false) + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) { - return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments).accept(true); + return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); } /// @brief check if the input is valid JSON /// @sa https://json.nlohmann.me/api/basic_json/accept/ template static bool accept(IteratorType first, IteratorType last, - const bool ignore_comments = false) + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) { - return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments).accept(true); + return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) static bool accept(detail::span_input_adapter&& i, - const bool ignore_comments = false) + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) { - return parser(i.get(), nullptr, false, ignore_comments).accept(true); + return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); } /// @brief generate SAX events @@ -23385,11 +24238,28 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static bool sax_parse(InputType&& i, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, - const bool ignore_comments = false) + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) { +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-pointer-compare" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + if (sax == nullptr) + { + JSON_THROW(other_error::create(502, "SAX handler must not be null", nullptr)); + } +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif auto ia = detail::input_adapter(std::forward(i)); return format == input_format_t::json - ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); } @@ -23400,11 +24270,28 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, - const bool ignore_comments = false) + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) { +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-pointer-compare" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + if (sax == nullptr) + { + JSON_THROW(other_error::create(502, "SAX handler must not be null", nullptr)); + } +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif auto ia = detail::input_adapter(std::move(first), std::move(last)); return format == input_format_t::json - ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); } @@ -23419,12 +24306,29 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, - const bool ignore_comments = false) + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) { +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-pointer-compare" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + if (sax == nullptr) + { + JSON_THROW(other_error::create(502, "SAX handler must not be null", nullptr)); + } +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif auto ia = i.get(); return format == input_format_t::json // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); } @@ -23479,8 +24383,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: - default: return "number"; + default: + return "invalid"; } } @@ -23527,6 +24432,23 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json* m_parent = nullptr; #endif +#if JSON_DIAGNOSTIC_POSITIONS + /// the start position of the value + std::size_t start_position = std::string::npos; + /// the end position of the value + std::size_t end_position = std::string::npos; + public: + constexpr std::size_t start_pos() const noexcept + { + return start_position; + } + + constexpr std::size_t end_pos() const noexcept + { + return end_position; + } +#endif + ////////////////////////////////////////// // binary serialization/deserialization // ////////////////////////////////////////// @@ -23612,27 +24534,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static std::vector to_bjdata(const basic_json& j, const bool use_size = false, - const bool use_type = false) + const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { std::vector result; - to_bjdata(j, result, use_size, use_type); + to_bjdata(j, result, use_size, use_type, version); return result; } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BSON serialization of a given JSON value @@ -23668,9 +24593,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23684,9 +24609,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23709,10 +24634,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23725,9 +24650,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23740,9 +24665,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23763,10 +24688,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23779,9 +24704,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23794,9 +24719,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23817,10 +24742,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23833,9 +24758,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23848,9 +24773,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23863,9 +24788,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23878,9 +24803,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23901,10 +24826,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } /// @} @@ -24005,7 +24930,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // the valid JSON Patch operations enum class patch_operations {add, remove, replace, move, copy, test, invalid}; - const auto get_op = [](const std::string & op) + const auto get_op = [](const string_t& op) { if (op == "add") { @@ -24036,7 +24961,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec }; // wrapper for "add" operation; add value at ptr - const auto operation_add = [&result](json_pointer & ptr, basic_json val) + const auto operation_add = [&result](json_pointer & ptr, const basic_json & val) { // adding to the root of the target document means replacing it if (ptr.empty()) @@ -24052,7 +24977,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec result.at(top_pointer); } - // get reference to parent of JSON pointer ptr + // get reference to the parent of the JSON pointer ptr const auto last_path = ptr.back(); ptr.pop_back(); // parent must exist when performing patch add per RFC6902 specs @@ -24090,7 +25015,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec break; } - // if there exists a parent it cannot be primitive + // if there exists a parent, it cannot be primitive case value_t::string: // LCOV_EXCL_LINE case value_t::boolean: // LCOV_EXCL_LINE case value_t::number_integer: // LCOV_EXCL_LINE @@ -24106,7 +25031,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // wrapper for "remove" operation; remove value at ptr const auto operation_remove = [this, & result](json_pointer & ptr) { - // get reference to parent of JSON pointer ptr + // get reference to the parent of the JSON pointer ptr const auto last_path = ptr.back(); ptr.pop_back(); basic_json& parent = result.at(ptr); @@ -24142,8 +25067,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec for (const auto& val : json_patch) { // wrapper to get a value for an operation - const auto get_value = [&val](const std::string & op, - const std::string & member, + const auto get_value = [&val](const string_t& op, + const string_t& member, bool string_type) -> basic_json & { // find value @@ -24152,14 +25077,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // context-sensitive error message const auto error_msg = (op == "op") ? "operation" : detail::concat("operation '", op, '\''); // NOLINT(bugprone-unused-local-non-trivial-variable) - // check if desired value is present + // check if the desired value is present if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end())) { // NOLINTNEXTLINE(performance-inefficient-string-concatenation) JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have member '", member, "'"), &val)); } - // check if result is of type string + // check if the result is of type string if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) { // NOLINTNEXTLINE(performance-inefficient-string-concatenation) @@ -24177,8 +25102,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // collect mandatory members - const auto op = get_value("op", "op", true).template get(); - const auto path = get_value(op, "path", true).template get(); + const auto op = get_value("op", "op", true).template get(); + const auto path = get_value(op, "path", true).template get(); json_pointer ptr(path); switch (get_op(op)) @@ -24204,7 +25129,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case patch_operations::move: { - const auto from_path = get_value("move", "from", true).template get(); + const auto from_path = get_value("move", "from", true).template get(); json_pointer from_ptr(from_path); // the "from" location must exist - use at() @@ -24221,7 +25146,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case patch_operations::copy: { - const auto from_path = get_value("copy", "from", true).template get(); + const auto from_path = get_value("copy", "from", true).template get(); const json_pointer from_ptr(from_path); // the "from" location must exist - use at() @@ -24248,7 +25173,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // ignore out of range errors: success remains false } - // throw an exception if test fails + // throw an exception if the test fails if (JSON_HEDLEY_UNLIKELY(!success)) { JSON_THROW(other_error::create(501, detail::concat("unsuccessful: ", val.dump()), &val)); @@ -24281,12 +25206,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/diff/ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json diff(const basic_json& source, const basic_json& target, - const std::string& path = "") + const string_t& path = "") { // the patch basic_json result(value_t::array); - // if the values are the same, return empty patch + // if the values are the same, return an empty patch if (source == target) { return result; @@ -24311,7 +25236,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec while (i < source.size() && i < target.size()) { // recursive call to compare array values at index i - auto temp_diff = diff(source[i], target[i], detail::concat(path, '/', std::to_string(i))); + auto temp_diff = diff(source[i], target[i], detail::concat(path, '/', detail::to_string(i))); result.insert(result.end(), temp_diff.begin(), temp_diff.end()); ++i; } @@ -24328,7 +25253,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec result.insert(result.begin() + end_index, object( { {"op", "remove"}, - {"path", detail::concat(path, '/', std::to_string(i))} + {"path", detail::concat(path, '/', detail::to_string(i))} })); ++i; } @@ -24339,7 +25264,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec result.push_back( { {"op", "add"}, - {"path", detail::concat(path, "/-")}, + {"path", detail::concat(path, "/-")}, {"value", target[i]} }); ++i; @@ -24354,7 +25279,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec for (auto it = source.cbegin(); it != source.cend(); ++it) { // escape the key name to be used in a JSON patch - const auto path_key = detail::concat(path, '/', detail::escape(it.key())); + const auto path_key = detail::concat(path, '/', detail::escape(it.key())); if (target.find(it.key()) != target.end()) { @@ -24378,7 +25303,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (source.find(it.key()) == source.end()) { // found a key that is not in this -> add it - const auto path_key = detail::concat(path, '/', detail::escape(it.key())); + const auto path_key = detail::concat(path, '/', detail::escape(it.key())); result.push_back( { {"op", "add"}, {"path", path_key}, @@ -24400,7 +25325,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case value_t::discarded: default: { - // both primitive type: replace value + // both primitive types: replace value result.push_back( { {"op", "replace"}, {"path", path}, {"value", target} @@ -24468,26 +25393,44 @@ inline namespace json_literals /// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json/ JSON_HEDLEY_NON_NULL(1) #if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) - inline nlohmann::json operator ""_json(const char* s, std::size_t n) + inline nlohmann::json operator""_json(const char* s, std::size_t n) #else - inline nlohmann::json operator "" _json(const char* s, std::size_t n) + // GCC 4.8 requires a space between "" and suffix + inline nlohmann::json operator"" _json(const char* s, std::size_t n) #endif { return nlohmann::json::parse(s, s + n); } +#if defined(__cpp_char8_t) +JSON_HEDLEY_NON_NULL(1) +inline nlohmann::json operator""_json(const char8_t* s, std::size_t n) +{ + return nlohmann::json::parse(reinterpret_cast(s), + reinterpret_cast(s) + n); +} +#endif + /// @brief user-defined string literal for JSON pointer /// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json_pointer/ JSON_HEDLEY_NON_NULL(1) #if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) - inline nlohmann::json::json_pointer operator ""_json_pointer(const char* s, std::size_t n) + inline nlohmann::json::json_pointer operator""_json_pointer(const char* s, std::size_t n) #else - inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) + // GCC 4.8 requires a space between "" and suffix + inline nlohmann::json::json_pointer operator"" _json_pointer(const char* s, std::size_t n) #endif { return nlohmann::json::json_pointer(std::string(s, n)); } +#if defined(__cpp_char8_t) +inline nlohmann::json::json_pointer operator""_json_pointer(const char8_t* s, std::size_t n) +{ + return nlohmann::json::json_pointer(std::string(reinterpret_cast(s), n)); +} +#endif + } // namespace json_literals } // namespace literals NLOHMANN_JSON_NAMESPACE_END @@ -24548,21 +25491,22 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC #if JSON_USE_GLOBAL_UDLS #if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) - using nlohmann::literals::json_literals::operator ""_json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers) - using nlohmann::literals::json_literals::operator ""_json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers) + using nlohmann::literals::json_literals::operator""_json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers) + using nlohmann::literals::json_literals::operator""_json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers) #else - using nlohmann::literals::json_literals::operator "" _json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers) - using nlohmann::literals::json_literals::operator "" _json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers) + // GCC 4.8 requires a space between "" and suffix + using nlohmann::literals::json_literals::operator"" _json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers) + using nlohmann::literals::json_literals::operator"" _json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers) #endif #endif // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -24593,6 +25537,8 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC #undef JSON_HAS_CPP_14 #undef JSON_HAS_CPP_17 #undef JSON_HAS_CPP_20 + #undef JSON_HAS_CPP_23 + #undef JSON_HAS_CPP_26 #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef JSON_HAS_THREE_WAY_COMPARISON @@ -24604,10 +25550,10 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT diff --git a/include/ticcutils/json_fwd.hpp b/include/ticcutils/json_fwd.hpp index 29a6036..e6e423c 100644 --- a/include/ticcutils/json_fwd.hpp +++ b/include/ticcutils/json_fwd.hpp @@ -1,9 +1,9 @@ // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT #ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ @@ -18,10 +18,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -30,20 +30,24 @@ #ifndef JSON_SKIP_LIBRARY_VERSION_CHECK #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) - #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3 + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0 #warning "Already included a different version of the library!" #endif #endif #endif #define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum) #ifndef JSON_DIAGNOSTICS #define JSON_DIAGNOSTICS 0 #endif +#ifndef JSON_DIAGNOSTIC_POSITIONS + #define JSON_DIAGNOSTIC_POSITIONS 0 +#endif + #ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 #endif @@ -54,6 +58,12 @@ #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS #endif +#if JSON_DIAGNOSTIC_POSITIONS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS +#endif + #if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp #else @@ -65,14 +75,15 @@ #endif // Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) #define NLOHMANN_JSON_ABI_TAGS \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \ NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ - NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS) // Construct the namespace version component #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ diff --git a/include/ticcutils/zipper.h b/include/ticcutils/zipper.h index 6dd2bb8..1e8179c 100644 --- a/include/ticcutils/zipper.h +++ b/include/ticcutils/zipper.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx index 8aa3e47..66bde6f 100644 --- a/src/CommandLine.cxx +++ b/src/CommandLine.cxx @@ -1,6 +1,6 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/src/Configuration.cxx b/src/Configuration.cxx index 332a481..d249b3c 100644 --- a/src/Configuration.cxx +++ b/src/Configuration.cxx @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/src/FdStream.cxx b/src/FdStream.cxx index 2a471d6..4bfd5af 100644 --- a/src/FdStream.cxx +++ b/src/FdStream.cxx @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/src/FileUtils.cxx b/src/FileUtils.cxx index 68e81f4..3bcf980 100644 --- a/src/FileUtils.cxx +++ b/src/FileUtils.cxx @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/src/LogStream.cxx b/src/LogStream.cxx index 5ae5385..3b9995c 100755 --- a/src/LogStream.cxx +++ b/src/LogStream.cxx @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/src/Makefile.am b/src/Makefile.am index a3fab6f..64f50fa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ AM_CPPFLAGS = -I@top_srcdir@/include -AM_CXXFLAGS = -g -O3 -std=c++17 -W -Wall -pedantic +AM_CXXFLAGS = -g -O3 -std=c++17 -Wextra -Wall -Wpedantic LDADD = libticcutils.la diff --git a/src/ServerBase.cxx b/src/ServerBase.cxx index b60da36..86e78a8 100644 --- a/src/ServerBase.cxx +++ b/src/ServerBase.cxx @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/src/SocketBasics.cxx b/src/SocketBasics.cxx index efb9976..462bcdc 100755 --- a/src/SocketBasics.cxx +++ b/src/SocketBasics.cxx @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/src/StringOps.cxx b/src/StringOps.cxx index b0f113b..de71931 100644 --- a/src/StringOps.cxx +++ b/src/StringOps.cxx @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/src/Timer.cxx b/src/Timer.cxx index 5b1bcf6..30bdb4c 100644 --- a/src/Timer.cxx +++ b/src/Timer.cxx @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/src/UniHash.cxx b/src/UniHash.cxx index 35cf3ca..e6a3273 100644 --- a/src/UniHash.cxx +++ b/src/UniHash.cxx @@ -1,3 +1,4 @@ + /* Copyright (c) 2006 - 2024 CLST - Radboud University @@ -26,14 +27,13 @@ #include "ticcutils/UniHash.h" #include "ticcutils/Unicode.h" +// normalizer2.h is included via Unicode.h using namespace std; using namespace icu; namespace Hash { - using namespace Tries; - UniInfo::UniInfo( const UnicodeString& value, const unsigned int index ): _value(value),_ID(index){ @@ -62,6 +62,10 @@ namespace Hash { UnicodeHash::~UnicodeHash(){ /// destroy a UnicodeHash + // We must explicitly delete the pointers stored in the map + for ( auto const& pair : _map ) { + delete pair.second; + } } unsigned int UnicodeHash::hash( const UnicodeString& value ){ @@ -72,16 +76,36 @@ namespace Hash { when a new hash is inserted, the reverse index is also updated the UnicodeString will be NFC normalized first. */ - static TiCC::UnicodeNormalizer nfc_norm; - UnicodeString val = nfc_norm.normalize( value ); - UniInfo *info = _tree.Retrieve( val ); - if ( !info ){ - info = new UniInfo( val, ++_num_of_tokens ); - info = reinterpret_cast(_tree.Store( val, info )); + UErrorCode status = U_ZERO_ERROR; + const Normalizer2 *n2 = Normalizer2::getNFCInstance(status); + const UnicodeString *pVal = &value; + UnicodeString norm_storage; + + // Optimization: Only normalize if strictly necessary + if ( U_SUCCESS(status) && !n2->isNormalized(value, status) ){ + n2->normalize(value, norm_storage, status); + pVal = &norm_storage; } + + // Map lookup + auto it = _map.find( *pVal ); + UniInfo *info = nullptr; + + if ( it == _map.end() ){ + // Not found, insert new + info = new UniInfo( *pVal, ++_num_of_tokens ); + _map.insert({ *pVal, info }); + } else { + info = it->second; + } + unsigned int idx = info->index(); if ( idx >= _rev_index.size() ){ - _rev_index.resize( _rev_index.size() + 1000 ); + // Ensure capacity before resizing + if ( idx >= _rev_index.capacity() ) { + _rev_index.reserve( idx + 10000 ); + } + _rev_index.resize( idx + 1000, nullptr ); } _rev_index[idx] = info; return idx; @@ -93,11 +117,20 @@ namespace Hash { \param value the string to lookup \return the hash value, or 0 when not found */ - static TiCC::UnicodeNormalizer nfc_norm; - UnicodeString val = nfc_norm.normalize( value ); - const UniInfo *info = _tree.Retrieve( val ); - if ( info ){ - return info->index(); + UErrorCode status = U_ZERO_ERROR; + const Normalizer2 *n2 = Normalizer2::getNFCInstance(status); + const UnicodeString *pVal = &value; + UnicodeString norm_storage; + + // Optimization: Only normalize if strictly necessary + if ( U_SUCCESS(status) && !n2->isNormalized(value, status) ){ + n2->normalize(value, norm_storage, status); + pVal = &norm_storage; + } + + auto it = _map.find( *pVal ); + if ( it != _map.end() ){ + return it->second->index(); } return 0; } @@ -115,7 +148,7 @@ namespace Hash { ostream& operator << ( ostream& os, const UnicodeHash& S ){ /// output the content of a whole UnicodeHash structure (Debugging only) - return os << &S._tree; + return os << "UnicodeHash map size: " << S._map.size(); } } diff --git a/src/Unicode.cxx b/src/Unicode.cxx index 5d5a205..e987fba 100644 --- a/src/Unicode.cxx +++ b/src/Unicode.cxx @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/src/XMLtools.cxx b/src/XMLtools.cxx index c0b06e8..e3ccd40 100644 --- a/src/XMLtools.cxx +++ b/src/XMLtools.cxx @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/src/runtest.cxx b/src/runtest.cxx index 5305b9b..0ca3d06 100644 --- a/src/runtest.cxx +++ b/src/runtest.cxx @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University @@ -1139,8 +1139,18 @@ enum flags { No = 0, One = 1, Two= 2, Four = 4}; enum class class_flags { nope = 0, ok = 1, warning = 1<<1, error = 1<<2 }; -DEFINE_ENUM_FLAG_OPERATORS(flags); -DEFINE_ENUM_FLAG_OPERATORS(class_flags); +DEFINE_ENUM_FLAG_OPERATORS(flags) +DEFINE_ENUM_FLAG_OPERATORS(class_flags) + +std::ostream& operator<<( std::ostream& os, const flags& f ){ + os << int(f); + return os; +} + +std::ostream& operator<<( std::ostream& os, const class_flags& f ){ + os << int(f); + return os; +} std::ostream& operator<<( std::ostream& os, const flags& f ){ os << int(f); @@ -1271,6 +1281,7 @@ void test_ncname(){ assertEqual( create_NCName("_appel-taart.met slagroom_"), "_appel-taart.met_slagroom_" ); } + int main( const int argc, const char* argv[] ){ cerr << BuildInfo() << endl; Timer t1; diff --git a/src/testlogstream.cxx b/src/testlogstream.cxx index 9381282..b2af1d9 100644 --- a/src/testlogstream.cxx +++ b/src/testlogstream.cxx @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University diff --git a/src/zipper.cxx b/src/zipper.cxx index 76689a8..baca709 100644 --- a/src/zipper.cxx +++ b/src/zipper.cxx @@ -1,5 +1,5 @@ /* - Copyright (c) 2006 - 2024 + Copyright (c) 2006 - 2025 CLST - Radboud University ILK - Tilburg University