xo-umbrella2/xo-object/include/xo/object/object.hpp
Roland Conybeare da7ab63552
All checks were successful
cmake-docker / cmake-build (push) Successful in 3m52s
CI / smoke-test (push) Successful in 5m46s
xo-object: + immediate-value factories so object.hpp compiles
2026-07-22 21:12:31 -04:00

245 lines
8.9 KiB
C++

/** @file object.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "xo/reflect/Object.hpp"
#include <bit>
#include <cstdint>
namespace xo {
namespace obj {
enum class otag : std::uint16_t {
ot_invalid = static_cast<uint16_t>(-1),
ot_sentinel = 0x00,
ot_boolean = 0x01,
ot_char = 0x02,
ot_i32 = 0x03,
ot_f32 = 0x04,
/* 0x05, 0x06, 0x07 reserved */
ot_i64 = 0x08,
ot_f64 = 0x09,
ot_zstring = 0x0a,
ot_symbol = 0x0b,
ot_cons = 0x0c,
/* 0x0d, 0x0e reserved */
ot_rc_object = 0x10, /* refcounted pointer to xo::reflect::Object */
};
class cons;
class zstring;
class symbol;
/** @class object
*
* @brief dynamically tyyped object; support for schematika
**/
class object {
public:
using Object = xo::reflect::Object;
/** tags involving pointers use values 0x08 .. 0x10 **/
static constexpr uint16_t c_tag_pointer_mask = 0xfff8;
/** number of pointer bits stolen for type tag **/
static constexpr int c_tag_bits = 16;
/** number of pointer bits remaining after hi tag bits stolen **/
static constexpr int c_ptr_bits = 64 - c_tag_bits;
static constexpr std::uint64_t c_tag_mask = (0UL - 1) << c_ptr_bits;
static constexpr std::uint64_t c_ptr_mask = (0UL - 1) >> c_tag_bits;
public:
object() = default;
object(const object & x);
/** @name Immediate-value factories
*
* @brief Construct an @ref object whose payload is embedded directly
* in the tagged word.
*
* Pointer-tagged values (@c ot_i64, @c ot_f64, @c ot_zstring,
* @c ot_symbol, @c ot_cons, @c ot_rc_object) embed an address instead,
* so they need allocator support; those factories are deferred until
* the storage/ownership model is settled.
*/
///@{
/** @brief Make the sentinel object (not accessible from schematica). **/
static object make_sentinel() noexcept {
return object(otag::ot_sentinel, 0);
}
/** @brief Make a boolean object; truth value in the least-significant bit. **/
static object make_boolean(bool x) noexcept {
return object(otag::ot_boolean, x ? 1 : 0);
}
/** @brief Make a character object; ascii in the least-significant 8 bits. **/
static object make_char(char x) noexcept {
return object(otag::ot_char, static_cast<unsigned char>(x));
}
/** @brief Make a 32-bit signed integer object.
*
* @note Casts via @c uint32_t first, so negative @p x is not
* sign-extended into the tag bits before @ref c_ptr_mask is applied.
**/
static object make_int32(std::int32_t x) noexcept {
return object(otag::ot_i32, static_cast<std::uint32_t>(x));
}
/** @brief Make a 32-bit floating-point object. **/
static object make_float32(float x) noexcept {
return object(otag::ot_f32, std::bit_cast<std::uint32_t>(x));
}
///@}
otag tag() const noexcept { return (static_cast<otag>(value_ >> c_ptr_bits)); }
std::uint64_t masked_value() const noexcept { return value_ & c_ptr_mask; }
std::int32_t as_int32(std::int32_t sentinel = 0) const noexcept {
if (tag() == otag::ot_i32) {
return masked_value();
} else {
return sentinel;
}
}
float as_float32(float sentinel = std::numeric_limits<float>::quiet_NaN()) const noexcept {
if (tag() == otag::ot_f32) {
std::uint32_t bits = masked_value();
return std::bit_cast<float>(bits);
} else {
return sentinel;
}
}
std::int64_t as_int64(int64_t sentinel = 0) const noexcept {
if (tag() == otag::ot_i64) {
return cast_int64();
} else {
return sentinel;
}
}
double as_float64(double sentinel = std::numeric_limits<double>::quiet_NaN()) const noexcept {
if (tag() == otag::ot_f64) {
return * reinterpret_cast<double *>(masked_value());
} else {
return sentinel;
}
}
zstring * as_zstring() const noexcept {
if (tag() == otag::ot_zstring) {
return reinterpret_cast<zstring *>(masked_value());
} else {
return nullptr;
}
}
Object * as_object() const noexcept {
if (tag() == otag::ot_rc_object) {
return reinterpret_cast<Object *>(masked_value());
} else {
return nullptr;
}
}
private:
/** ctor. only use least-significant c_ptr_bits (48) bits from value **/
explicit object(otag tag, std::uint64_t value) {
this->set_tag_value(tag, value);
}
/** undefined behavior if tag != ot_i64 **/
std::int64_t cast_int64() const noexcept {
return * reinterpret_cast<std::int64_t *>(masked_value());
}
/** undefined behavior if tag != ot_f64 **/
double cast_double() const noexcept {
return * reinterpret_cast<double *>(masked_value());
}
zstring * cast_zstring() const noexcept {
return reinterpret_cast<zstring *>(masked_value());
}
symbol * cast_symbol() const noexcept {
return reinterpret_cast<symbol *>(masked_value());
}
Object * cast_object() const noexcept {
return reinterpret_cast<Object *>(masked_value());
}
/** only use bottom c_ptr_bits (48) from value **/
void set_tag_value(otag tag, std::uint64_t value) {
value_ = ((static_cast<std::uint64_t>(tag) << c_ptr_bits)
| (value & c_ptr_mask));
}
private:
/**
* Rely on being able to steal the 16 most-significant
* bits from a 64-bit pointer
*
* @code
*
* - ot_i64, ot_f64, ot_zstring, ot_symbol, ot_cons, ot_rc_object:
*
* <- 16 -> <---------- 48 ---------->
* +--------+--------------------------+
* | tag | ptr |
* +--------+--------------------------+
*
* - ot_f32:
*
* <- 16 -> <- 16 -> <------ 32 ----->
* +--------+--------+-----------------+
* | ot_f32 | unused | 32-bit float |
* +--------+--------+-----------------+
*
* - ot_boolean
*
* <- 16 -> <-------- 47 ----------> 1
* +--------+------------------------+-+
* | ot_f32 | unused |b|
* +--------+------------------------+-+
*
* etc...
*
* @endcode
*
* tag values given by @ref otag
*
* 0x00 -> sentinel. sentinel value. not accessible from schematica
* 0x01 -> boolean. truth value in least-significant bit
* 0x02 -> char. ascii character in least-significant 8 bits
* 0x03 -> int. integer in least-significant 32 bits
* 0x04 -> float. 32-bit float in least-significant 32 bitsg
*
* 0x05..0x07 reserved
*
* 0x08 -> long. ptr refers to 64-bit integer
* 0x09 -> double. ptr refers to 64-bit floating-point value
* 0x0a -> zstring. ptr refers to length-prefixed null-terminated cstring.
* 0x0b -> symbol. ptr refers to interned (unique'd) symbol
* 0x0c -> cons. ptr refers to 128-bit cons-cell (or is nullptr, representing nil)
*
* 0x0d..0x0f reserved
*
* 0x10 -> Object. ptr to refcounted xo::obj::Object
* see xo-reflect/ Object.hpp
**/
std::uint64_t value_ = 0;
};
} /*namespace obj*/
} /*namespace xo*/
/** end object.hpp **/