Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions libraries/sysiolib/core/sysio/basic_name.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#pragma once
/**
* @file sysio/basic_name.hpp
* @brief Generic MSB-first packed 64-bit identifier (contract side).
*
* basic_name<Traits> is the shared core behind sysio::name and
* sysio::slug_name. It mirrors the host-side fc::basic_name, but with CDT
* semantics: per-character validation via sysio::check (WASM has no
* exceptions) in a constexpr constructor, so `_n` / `_s` literals are checked
* at compile time.
*
* Traits is the policy that specialises the template; it must satisfy the
* basic_name_traits concept (declared below). alphabet[0] is the pad symbol;
* zero_terminates selects how to_string() treats a symbol-0 slot — a hard
* terminator (slug-style) or an ordinary interior character (name's '.').
*
* The symbol width is derived (the minimal bits to index the alphabet);
* symbols are packed most-significant-first, the final symbol narrowed if
* max_len * width would exceed 64.
*
* @see fc::basic_name (host-side mirror)
*/

#include "check.hpp"
#include "serialize.hpp"

#include <compare>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>

namespace sysio {

/// Compile-time contract for a basic_name Traits policy: an alphabet and a
/// length, a zero_terminates flag steering to_string(), and the three
/// sysio::check messages. Enforced in place of a prose list of requirements.
template <typename Traits>
concept basic_name_traits =
requires {
{ Traits::max_len } -> std::convertible_to<int>;
{ Traits::alphabet } -> std::convertible_to<std::string_view>;
{ Traits::zero_terminates } -> std::convertible_to<bool>;
{ Traits::bad_char_message } -> std::convertible_to<const char*>;
{ Traits::too_long_message } -> std::convertible_to<const char*>;
{ Traits::bad_final_symbol_message } -> std::convertible_to<const char*>;
}
&& Traits::max_len > 0
&& std::string_view{ Traits::alphabet }.size() > 0;

template <basic_name_traits Traits>
struct basic_name {
uint64_t value = 0;

constexpr basic_name() = default;
constexpr explicit basic_name( uint64_t v ) : value(v) {}

/// Per-character validated string constructor. sysio::check-throws on an
/// over-long string, an out-of-alphabet character, or a final symbol too
/// wide for its (possibly narrowed) slot. constexpr — so an invalid
/// `_n` / `_s` literal is a compile error.
constexpr explicit basic_name( std::string_view str ) : value(0) {
// sysio::check is not constexpr — invoke it only on the failure path so a
// valid `_n` / `_s` literal still constant-evaluates (a bad one reaches
// check and is therefore a compile error).
if ( str.size() > static_cast<std::size_t>(Traits::max_len) )
sysio::check( false, Traits::too_long_message );
const int n = static_cast<int>(str.size());
for ( int i = 0; i < Traits::max_len && i < n; ++i ) {
const uint64_t sym = symbol( str[i] );
if ( sym > width_mask(i) )
sysio::check( false, Traits::bad_final_symbol_message );
value |= sym << shift(i);
}
}

constexpr uint64_t to_uint64_t() const { return value; }
constexpr bool empty() const { return value == 0; }
constexpr bool good() const { return value != 0; }
constexpr explicit operator bool() const { return value != 0; }

std::string to_string() const {
std::string s;
for ( int i = 0; i < Traits::max_len; ++i ) {
const uint64_t sym = (value >> shift(i)) & width_mask(i);
// A zero-terminated alphabet (slug-style) ends at the first symbol-0
// slot; for name, symbol 0 ('.') is an ordinary interior character.
if ( Traits::zero_terminates && sym == 0 )
break;
s.push_back( character( sym ) );
}
if ( !Traits::zero_terminates ) {
const char pad = character(0);
while ( !s.empty() && s.back() == pad )
s.pop_back();
}
return s;
}

/// character -> symbol; sysio::check-throws on a character outside the
/// alphabet. (sysio::name re-exposes this as char_to_value.)
static constexpr uint64_t symbol( char c ) {
const std::string_view a = Traits::alphabet;
for ( std::size_t s = 0; s < a.size(); ++s )
if ( a[s] == c ) return static_cast<uint64_t>(s);
sysio::check( false, Traits::bad_char_message );
return 0; // unreachable
}
/// symbol -> character; out-of-range symbols decode as the pad (alphabet[0]).
static constexpr char character( uint64_t s ) {
const std::string_view a = Traits::alphabet;
return s < a.size() ? a[s] : a[0];
}

// Total order on the packed value; MSB-first packing makes it match the
// decoded string's lexicographic order. Defaulted <=> / == synthesize the
// four relational operators and !=.
friend constexpr std::strong_ordering operator<=>( basic_name a, basic_name b ) = default;
friend constexpr bool operator==( basic_name a, basic_name b ) = default;

SYSLIB_SERIALIZE( basic_name, (value) )

private:
// --- symbol width: minimal bits to index the alphabet ---
static constexpr int symbol_bits( std::size_t alphabet_size ) {
int b = 0;
while ( (std::size_t{1} << b) < alphabet_size ) ++b;
return b;
}
static constexpr int bits = symbol_bits( Traits::alphabet.size() );
static constexpr int total_bits = Traits::max_len * bits < 64
? Traits::max_len * bits : 64;
static_assert( (Traits::max_len - 1) * bits < 64,
"basic_name: symbol layout does not fit in 64 bits" );

// --- MSB-first bit layout; the final symbol absorbs any shortfall ---
static constexpr uint32_t shift( int i ) {
const int s = total_bits - bits * (i + 1);
return s > 0 ? static_cast<uint32_t>(s) : 0u;
}
static constexpr uint64_t width_mask( int i ) {
const int w = (i == Traits::max_len - 1) ? total_bits - bits * i : bits;
return (static_cast<uint64_t>(1) << w) - 1;
}
};

} // namespace sysio
162 changes: 36 additions & 126 deletions libraries/sysiolib/core/sysio/name.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
*/
#pragma once

#include "check.hpp"
#include "serialize.hpp"
#include "basic_name.hpp"
#include "reflect.hpp"

#include <string>
Expand All @@ -26,96 +25,54 @@ namespace sysio {
* @brief SYSIO Name Type
*/

/// Alphabet + length traits for the SYSIO account-name encoding: up to 13
/// base-32 symbols over ".12345a-z". Drives sysio::basic_name.
struct sysio_name_traits {
static constexpr int max_len = 13;
static constexpr std::string_view alphabet = ".12345abcdefghijklmnopqrstuvwxyz";
// Symbol 0 ('.') is an ordinary interior character, not a terminator.
static constexpr bool zero_terminates = false;
static constexpr const char* bad_char_message =
"character is not in allowed character set for names";
static constexpr const char* too_long_message =
"string is too long to be a valid name";
static constexpr const char* bad_final_symbol_message =
"thirteenth character in name cannot be a letter that comes after j";
};

/**
* Wraps a %uint64_t to ensure it is only passed to methods that expect a %name.
* Ensures value is only passed to methods that expect a %name and that no mathematical
* operations occur. Also enables specialization of print
*
* The packed encoding and value semantics (constructors, comparisons,
* to_string, serialization) live in sysio::basic_name; name adds the
* contract-side surface: raw, print, length, prefix()/suffix(),
* write_as_string.
*
* @ingroup name
*/
struct name {
public:
enum class raw : uint64_t {};
struct name : basic_name<sysio_name_traits> {
using base = basic_name<sysio_name_traits>;

/**
* Construct a new name
*
* @brief Construct a new name object defaulting to a value of 0
*
*/
constexpr name() : value(0) {}
/// Scoped enumerated alias of uint64_t, for the raw packed value.
enum class raw : uint64_t {};

/**
* Construct a new name given a unit64_t value
*
* @brief Construct a new name object initialising value with v
* @param v - The unit64_t value
*
*/
constexpr explicit name( uint64_t v )
:value(v)
{}
using base::base; // name(uint64_t), name(std::string_view)
constexpr name() = default;

/**
* Construct a new name given a scoped enumerated type of raw (uint64_t).
*
* @brief Construct a new name object initialising value with r
* @param r - The raw value which is a scoped enumerated type of unit64_t
*
*/
constexpr explicit name( name::raw r )
:value(static_cast<uint64_t>(r))
{}

/**
* Construct a new name given an string.
*
* @brief Construct a new name object initialising value with str
* @param str - The string value which validated then converted to unit64_t
*
*/
constexpr explicit name( std::string_view str )
:value(0)
{
if( str.size() > 13 ) {
sysio::check( false, "string is too long to be a valid name" );
}
if( str.empty() ) {
return;
}

auto n = std::min( (uint32_t)str.size(), (uint32_t)12u );
for( decltype(n) i = 0; i < n; ++i ) {
value <<= 5;
value |= char_to_value( str[i] );
}
value <<= ( 4 + 5*(12 - n) );
if( str.size() == 13 ) {
uint64_t v = char_to_value( str[12] );
if( v > 0x0Full ) {
sysio::check(false, "thirteenth character in name cannot be a letter that comes after j");
}
value |= v;
}
}
constexpr explicit name( name::raw r ) : base( static_cast<uint64_t>(r) ) {}

/**
* Converts a %name Base32 symbol into its corresponding value
* Converts a %name Base32 symbol into its corresponding value.
* Throws via sysio::check if the character is not in the allowed set.
*
* @param c - Character to be converted
* @return constexpr char - Converted value
* @return constexpr uint8_t - Converted value
*/
static constexpr uint8_t char_to_value( char c ) {
if( c == '.')
return 0;
else if( c >= '1' && c <= '5' )
return (c - '1') + 1;
else if( c >= 'a' && c <= 'z' )
return (c - 'a') + 6;
else
sysio::check( false, "character is not in allowed character set for names" );

return 0; // control flow will never reach here; just added to suppress warning
return static_cast<uint8_t>( base::symbol(c) );
}

/**
Expand Down Expand Up @@ -210,13 +167,6 @@ namespace sysio {
*/
constexpr operator raw()const { return raw(value); }

/**
* Explicit cast to bool of the uint64_t value of the name
*
* @return Returns true if the name is set to the default value of 0 else true.
*/
constexpr explicit operator bool()const { return value != 0; }

/**
* Writes the %name as a string to the provided char buffer
*
Expand Down Expand Up @@ -248,17 +198,6 @@ namespace sysio {
return begin;
}

/**
* Returns the name as a string.
*
* @brief Returns the name value as a string by calling write_as_string() and returning the buffer produced by write_as_string()
*/
std::string to_string()const {
char buffer[13];
auto end = write_as_string( buffer, buffer + sizeof(buffer) );
return {buffer, end};
}

/**
* Prints an names as base32 encoded string
*
Expand All @@ -268,40 +207,11 @@ namespace sysio {
internal_use_do_not_use::printn(value);
}

/// @cond INTERNAL

/**
* Equivalency operator. Returns true if a == b (are the same)
*
* @return boolean - true if both provided %name values are the same
*/
friend constexpr bool operator == ( const name& a, const name& b ) {
return a.value == b.value;
}

/**
* Inverted equivalency operator. Returns true if a != b (are different)
*
* @return boolean - true if both provided %name values are not the same
*/
friend constexpr bool operator != ( const name& a, const name& b ) {
return a.value != b.value;
}

/**
* Less than operator. Returns true if a < b.
*
* @return boolean - true if %name `a` is less than `b`
*/
friend constexpr bool operator < ( const name& a, const name& b ) {
return a.value < b.value;
}

/// @endcond

uint64_t value = 0;

CDT_REFLECT(value);
// name's own serialization: an exact-match operator<<(ds, const name&)
// must exist, else the generic bluegrass::meta field-iterator is chosen
// and rejects name as a non-aggregate. (basic_name has its own, used by
// slug_name, which is the alias type itself rather than a derived type.)
SYSLIB_SERIALIZE( name, (value) )
};

Expand Down
1 change: 1 addition & 0 deletions tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ add_cdt_unit_test(crypto_ext_tests)
add_cdt_unit_test(datastream_tests)
add_cdt_unit_test(fixed_bytes_tests)
add_cdt_unit_test(name_tests)
add_cdt_unit_test(basic_name_tests)
add_cdt_unit_test(rope_tests)
add_cdt_unit_test(serialize_tests)
add_cdt_unit_test(string_tests1)
Expand Down
Loading