forked from Alepha/Alepha
UA
This commit is contained in:
282
UA_Key.h
Normal file
282
UA_Key.h
Normal file
@ -0,0 +1,282 @@
|
||||
static_assert( __cplusplus > 2020'99 );
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Alepha/Alepha.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
/*!
|
||||
* @file
|
||||
* @todo validate levels for externals sources like vector constructor, operator /
|
||||
* @todo better exceptions
|
||||
* @todo switch both isValid to use regex if appropriate
|
||||
* @todo factory method for Level from a string, then possibly use it in UA_Key constructor
|
||||
*/
|
||||
|
||||
namespace Alepha::Hydrogen ::detail:: UA_Key_m
|
||||
{
|
||||
inline namespace exports
|
||||
{
|
||||
using namespace std::literals::string_literals;
|
||||
|
||||
/*! @brief Used as a compound key to address an element in a (possibly) multi level Universal Aggregate)
|
||||
* @detail Format is that it must start with a name (string) and then is followed by a series of names or vector indices.
|
||||
* Names are separated with a period if consecutive and indices are contained in brackets.
|
||||
* Names are alphanumeric starting with an alpha. Indices are non negative ints.
|
||||
* A Name is used a key for a map in the UniversalAggregate
|
||||
* Correct examples: key
|
||||
* key[1]
|
||||
* key.key2
|
||||
* key[3][4]key2[2]
|
||||
* key.key.key
|
||||
* Bad examples: 3key starts with a period
|
||||
* key.[2] period not supported between key and bracket
|
||||
* key[2].key period not supported between key and bracket
|
||||
*/
|
||||
class UA_Key
|
||||
{
|
||||
private:
|
||||
/*! @brief Determines if a stringview represents a valid name according to our definition
|
||||
* @details Currently required to start with an alpha and contain only alphanumerics.
|
||||
*/
|
||||
bool
|
||||
isValidAsName( const std::string_view sv ) const
|
||||
{
|
||||
return true
|
||||
and ( not sv.empty() )
|
||||
and ( std::isalpha( sv.at( 0 ) ) )
|
||||
and ( std::find_if_not( sv.cbegin() + 1, sv.cend(), []( const unsigned char c ){ return std::isalnum( c ); } ) == sv.end() );
|
||||
}
|
||||
|
||||
/*! @brief Determines if a stringview represents a valid index (i.e., valid size_t representation, no check is done against an actual vector)
|
||||
* @details Currently required to represent a non negative integer.
|
||||
*/
|
||||
bool
|
||||
isValidAsIndex( const std::string_view sv ) const
|
||||
{
|
||||
return true
|
||||
and ( not sv.empty() )
|
||||
and ( std::find_if_not( sv.cbegin(), sv.cend(), []( const unsigned char c ){ return std::isdigit( c ); } ) == sv.end() );
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Type for a UA key for a map */
|
||||
using NameType= std::string;
|
||||
/*! @brief Type for a UA index for a vector */
|
||||
using IndexType= size_t;
|
||||
|
||||
/*! @brief A representation of either a name for a UA map lookup or an index for a vector */
|
||||
class Level: public std::variant< size_t, std::string >
|
||||
{
|
||||
friend std::ostream &
|
||||
operator << ( std::ostream &os, const Level &lv )
|
||||
{
|
||||
std::visit( [&]( const auto &val ) { os << val; }, lv );
|
||||
return os;
|
||||
}
|
||||
};
|
||||
|
||||
/*! @brief Stores the parsed levels */
|
||||
std::vector< Level > levels;
|
||||
|
||||
/*! @brief The default empty constructor is fine */
|
||||
UA_Key()= default;
|
||||
|
||||
/*!@brief construct from a vector of Level */
|
||||
explicit
|
||||
UA_Key( const std::vector< Level > &lev )
|
||||
: levels( lev )
|
||||
{
|
||||
}
|
||||
|
||||
/*! @brief construct from another UA_Key with option to skip some of the levels at the front source UA_Key */
|
||||
explicit
|
||||
UA_Key( const UA_Key &ua, const IndexType skip )
|
||||
: levels( ua.levels.cbegin() + std::min( skip, ua.levels.size() ), ua.levels.cend() )
|
||||
{
|
||||
}
|
||||
|
||||
/*!@brief construct from a stringview
|
||||
* @details see class documentation for format explanation\n
|
||||
*/
|
||||
explicit
|
||||
UA_Key( const std::string_view sv )
|
||||
{
|
||||
size_t levelStart= 0; // index to the first char of the level being parsed
|
||||
bool inIndex= false; // set to true when parsing of this level is after an open bracket
|
||||
bool postIndex= false; // set to true when parsing of this level is after a closed bracket - next char must be a period or open bracket
|
||||
while( levelStart < sv.size() )
|
||||
{
|
||||
char term; // which terminating char is encountered
|
||||
auto pos= sv.find_first_of( "[].", levelStart );
|
||||
if( pos == std::string::npos )
|
||||
{
|
||||
if( inIndex )
|
||||
{
|
||||
throw std::runtime_error( "Unable to parse UA_Key(1) from " + std::string( sv ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
term= '.';
|
||||
pos= sv.size();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
term= sv.at( pos );
|
||||
}
|
||||
const std::string_view candidate= sv.substr( levelStart, pos - levelStart );
|
||||
levelStart= pos + 1;
|
||||
if( postIndex )
|
||||
{
|
||||
if( candidate.size() != 0 or term == ']' )
|
||||
{
|
||||
throw std::runtime_error( "Unable to parse UA_Key(2) from " + std::string( sv ) );
|
||||
}
|
||||
postIndex= false;
|
||||
inIndex= ( term == '[' );
|
||||
}
|
||||
else if( ( term == '.' or term == '[' ) and not inIndex and isValidAsName( candidate ) )
|
||||
{
|
||||
|
||||
levels.push_back( Level( std::string( candidate ) ) );
|
||||
inIndex= ( term == '[' );
|
||||
}
|
||||
else if( term == ']' and inIndex and isValidAsIndex( candidate ) )
|
||||
{
|
||||
levels.push_back( Level( ::boost::lexical_cast< size_t >( candidate ) ) );
|
||||
inIndex= false;
|
||||
postIndex= true;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error( "Unable to parse UA_Key(3) from " + std::string( sv ) );
|
||||
}
|
||||
}
|
||||
if( inIndex )
|
||||
{
|
||||
throw std::runtime_error( "Unable to parse UA_Key(4) from " + std::string( sv ) );
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief default == and != are fine. Other comparisons are not useful for now.
|
||||
* @details Perhaps an imposed ordering could be used if sorting were to be needed.
|
||||
*/
|
||||
bool operator == ( const UA_Key & ) const= default;
|
||||
|
||||
/*! @brief returns true if the lowest level is a vector index.
|
||||
* @note, false will be returned if there are no levels
|
||||
*/
|
||||
bool
|
||||
isVector() const
|
||||
{
|
||||
return true
|
||||
and ( not levels.empty() )
|
||||
and ( std::holds_alternative< size_t >( levels.back() ) )
|
||||
;
|
||||
}
|
||||
|
||||
/*! @brief returns a UA_Key formed by removing the last level from a copy of this UA_Key
|
||||
* @note, current behavior mimics that of vector.pop_back() in that if the this UA_Key has no levels, the behavior is undefined
|
||||
*/
|
||||
UA_Key
|
||||
parent() const
|
||||
{
|
||||
UA_Key ret= *this;
|
||||
ret.levels.pop_back();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*! @brief returns a reference to the last level
|
||||
* @note, current behavior mimics that of vector.back() in that if the this UA_Key has no levels, the behavior is undefined
|
||||
*/
|
||||
Level &
|
||||
back()
|
||||
{
|
||||
if( levels.empty() )
|
||||
{
|
||||
throw std::runtime_error( "back attempted on UA_Key with 0 Levels " );
|
||||
}
|
||||
return levels.back();
|
||||
}
|
||||
|
||||
/*! @brief returns a const reference to the last level
|
||||
* @note, current behavior mimics that of vector.back() in that if the this UA_Key has no levels, the behavior is undefined
|
||||
*/
|
||||
const Level &
|
||||
back() const
|
||||
{
|
||||
return levels.back();
|
||||
}
|
||||
|
||||
/*! @brief appends a level to a copy of this UA_Key
|
||||
*
|
||||
*/
|
||||
UA_Key
|
||||
operator / ( const Level &lv )
|
||||
{
|
||||
UA_Key ret= *this;
|
||||
ret.levels.push_back( lv );
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*! @brief outputs a character representation of the levels
|
||||
* @note, as long as the current restriction against whitespace on a string parses into a UA_Key remains, the operator << will produce an output identical to the input
|
||||
*/
|
||||
friend std::ostream &
|
||||
operator << ( std::ostream &os, const UA_Key &uak )
|
||||
{
|
||||
bool first= true;
|
||||
for( const auto lv: uak.levels )
|
||||
{
|
||||
std::visit
|
||||
(
|
||||
[&]< typename T >( const T &t )
|
||||
{
|
||||
if constexpr( std::is_same_v< std::string, T > )
|
||||
{
|
||||
if( not first )
|
||||
{
|
||||
os << '.';
|
||||
}
|
||||
os << std::get< std::string >( lv );
|
||||
}
|
||||
else if constexpr( std::is_same_v< size_t, T > )
|
||||
{
|
||||
os << '[' << std::get< size_t >( lv ) << ']';
|
||||
}
|
||||
},
|
||||
lv
|
||||
);
|
||||
first= false;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
/*! @brief istream operator. Reads a string
|
||||
* @details result is the same as if the istream is read into a string and the string were used to construct a UA_Key and then copied ot uak uak.
|
||||
*/
|
||||
friend std::istream &
|
||||
operator >> ( std::istream &is, UA_Key &uak )
|
||||
{
|
||||
std::string input_string;
|
||||
is >> input_string;
|
||||
uak= UA_Key( input_string );
|
||||
return is;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace Alepha::Hydrogen::inline exports::inline UA_Key_m
|
||||
{
|
||||
using namespace detail::UA_Key_m::exports;
|
||||
}
|
||||
|
Reference in New Issue
Block a user