xo-object: initial implementation [wip]

This commit is contained in:
Roland Conybeare 2024-06-26 11:24:09 -04:00
commit d830632aea
16 changed files with 652 additions and 0 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# emacs projectile config
.projectile
# clangd working space (see emacs+lsp)
.cache
# typical cmake build directory (source-tree-nephew)
.build*
# symlink to builddir/compile_commands.json; should be set manually in dev sandbox
compile_commands.json

34
CMakeLists.txt Normal file
View file

@ -0,0 +1,34 @@
# xo-object/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(xo_object VERSION 0.1)
include(GNUInstallDirs)
include(cmake/xo-bootstrap-macros.cmake)
xo_cxx_toplevel_options3()
# ----------------------------------------------------------------
# c++ settings
set(PROJECT_CXX_FLAGS "")
#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") # gcc-only!
add_definitions(${PROJECT_CXX_FLAGS})
# ----------------------------------------------------------------
# must complete definition of object lib before configuring examples
add_subdirectory(src/object)
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
# ----------------------------------------------------------------
add_subdirectory(example)
#add_subdirectory(utest)
# reminder: must come last: docs targets depend on all the other library/utest targets
#add_subdirectory(docs)
# end CMakeLists.txt

29
LICENSE Normal file
View file

@ -0,0 +1,29 @@
Copyright (c) 2024 Roland Conybeare <git3ub@nym.hush.com>, All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of
external contributions to this project including patches, pull requests, etc.

57
README.md Normal file
View file

@ -0,0 +1,57 @@
# xo-object library
A library for scaffolding an object hierarchy for dynamic typing.
Using this for interpreter integration with schematica
## Getting Started
### build + install `xo-cmake` dependency
- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake)
Installs a few cmake ingredients, along with a build assistant `xo-build` for XO projects such as this one.
### build + install other necessary XO dependencies
```
$ xo-build --clone --configure --build --install xo-indentlog
$ xo-build --clone --configure --build --install xo-refnct
$ xo-build --clone --configure --build --install xo-subsys
$ xo-build --clone --configure --build --install xo-reflect
```
Note: can use `xo-build -n` to dry-run here
### copy `xo-object` repository locally
```
$ xo-build --clone xo-object
```
or equivalently
```
$ git clone git@github.com:Rconybea/xo-object.git
```
### build + install xo-object
```
$ xo-build --configure --build --install xo-object
```
or equivalently:
```
$ PREFIX=/usr/local # or wherever you prefer
$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-object -B xo-object/.build
$ cmake --build xo-object/.build
$ cmake --install xo-object/.build
```
### build for unit test coverage
```
$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_INSTALL_PREFIX=$PREFIX xo-object/.build-ccov
$ cmake --build xo-object/.build-ccov
```
### LSP support
```
$ cd xo-object
$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree
```

35
cmake/xo-bootstrap-macros.cmake Executable file
View file

@ -0,0 +1,35 @@
# ----------------------------------------------------------------
# for example:
# $ PREFIX=/usr/local # for example
# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build
#
# will get
# CMAKE_MODULE_PATH
# from xo-cmake-config --cmake-module-path
#
# and expect .cmake macros in
# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake
# ----------------------------------------------------------------
find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED)
if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND")
message(FATAL "could not find xo-cmake-config executable")
endif()
message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}")
if (NOT XO_SUBMODULE_BUILD)
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix))
# default to typical install location for xo-project-macros
execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH)
message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
endif()
endif()
# needs to have been installed somewhere on CMAKE_MODULE_PATH,
# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX)
#
include(xo_macros/xo_cxx)
xo_cxx_bootstrap_message()

View file

@ -0,0 +1,6 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(xo_reflect)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
check_required_components("@PROJECT_NAME@")

1
example/CMakeLists.txt Normal file
View file

@ -0,0 +1 @@
add_subdirectory(ex1)

View file

@ -0,0 +1,12 @@
# xo-object/example/ex1/CMakeLists.txt
set(SELF_EXE xo_object_ex1)
set(SELF_SRCS ex1.cpp)
if (XO_ENABLE_EXAMPLES)
xo_add_executable(${SELF_EXE} ${SELF_SRCS})
xo_self_headeronly_dependency(${SELF_EXE} xo_reflect)
xo_dependency(${SELF_EXE} xo_flatstring)
endif()
# end CMakeLists.txt

14
example/ex1/ex1.cpp Normal file
View file

@ -0,0 +1,14 @@
/* @file ex1.cpp */
#include "xo/object/object.hpp"
#include <iostream>
int
main() {
using std::cout;
using std::endl;
cout << "hello, world!" << endl;
}
/* end ex1.cpp */

View file

@ -0,0 +1,21 @@
/** @file cons.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#Include "variant.hpp"
//#include <cstdint>
namespace xo {
namespace var {
struct cons {
variant car_;
variant cdr_;
};
} /*namespace var*/
} /*namespace xo*/
/** end cons.hpp **/

View file

@ -0,0 +1,205 @@
/** @file object.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "xo/reflect/Object.hpp"
#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 schematica
**/
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);
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_f64) {
std::uint32_t bits = masked_value();
return * reinterpret_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::uint16_t>(tag) | (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 **/

View file

@ -0,0 +1,23 @@
/** @file symbol.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "zstring.hpp"
//#include <cstdint>
namespace xo {
namespace obj {
class symbol {
public:
symbol(const symbol & x) : name_{x.name_} {}
zstring * name_;
};
} /*namespace obj*/
} /*namespace xo*/
/** end symbol.hpp **/

View file

@ -0,0 +1,128 @@
/** @file object.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "xo/reflect/Object.hpp"
#include <cstdint>
namespace xo {
namespace obj {
enum class otag : std::uint16_t {
vt_invalid = -1,
vt_sentinel = 0x00,
vt_boolean = 0x01,
vt_char = 0x02,
vt_i32 = 0x03,
vt_f32 = 0x04,
vt_i64 = 0x08,
vt_f64 = 0x09,
vt_list = 0x0a,
vt_object = 0x10,
};
class cons;
/** @class object
*
* @brief dynamically tyyped object; support for schematica
**/
class object {
public:
using Object = xo::reflect::Object;
/** 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_ptr_mask = (0UL - 1) >> c_tag_bits;
public:
object() = default;
otag tag() const noexcept { return reinterpret_cast<vtag>(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() == vtag::vt_i32) {
return masked_value();
} else {
return sentinel;
}
}
float as_float32(float sentinel = std::numeric_limits<float>::quiet_NaN()) const noexcept {
if (tag() == vtag::vt_f64) {
return masked_value();
} else {
return sentinel;
}
}
std::int64_t as_int64(int64_t sentinel = 0) const noexcept {
if (tag() == vtag::vt_i64) {
return * reinterpret_cast<std::int64_t *>(masked_value());
} else {
return sentinel;
}
}
double as_float64(double sentinel = std::numeric_limits<double>::quiet_NaN()) const noexcept {
if (tag() == vtag::vt_f64) {
return * reinterpret_cast<double *>(masked_value());
} else {
return sentinel;
}
}
Object * as_object() const noexcept {
if (tag() == vtag::vt_object) {
return reinterpret_cast<Object *>(masked_value());
} else {
return nullptr;
}
}
private:
/**
* Rely on being able to steal the 16 most-significant
* bits from a 64-bit pointer
*
* @code
* <- 16 -> <---------- 48 ---------->
* +--------+--------------------------+
* | tag | |
* +--------+--------------------------+
* @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 **/

View file

@ -0,0 +1,26 @@
/** @file zstring.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "object.hpp"
//#include <cstdint>
namespace xo {
namespace obj {
struct zstring {
/* need to count trailing \0 */
std::size_t alloc_size() const { return sizeof(zstring) + len_ + 1; }
/** number of characters in string, not counting trailing null **/
std::int32_t len_;
/** must use placement new to create **/
char data_[];
};
} /*namespace obj*/
} /*namespace xo*/
/** end zstring.hpp **/

11
src/object/CMakeLists.txt Normal file
View file

@ -0,0 +1,11 @@
# object/CMakeLists.txt
set(SELF_LIB xo_object)
set(SELF_SRCS
object.cpp)
xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS})
xo_dependency(${SELF_LIB} reflect)
#xo_boost_dependency(${SELF_LIB})
# end CMakeLists.txt

42
src/object/object.cpp Normal file
View file

@ -0,0 +1,42 @@
/* @file object.cpp */
#include "object.hpp"
#include "zstring.hpp"
namespace xo {
namespace obj {
object::object(const object & x) : value_{0} {
switch (x.tag()) {
case otag::ot_invalid:
case otag::ot_sentinel:
case otag::ot_boolean:
case otag::ot_char:
case otag::ot_i32:
case otag::ot_f32:
case otag::ot_i64:
case otag::ot_f64:
case otag::ot_zstring:
case otag::ot_symbol:
case otag::ot_cons:
/* in most cases where value_ embeds a pointer,
* it's a passive garbage-collected pointer,
* no special treatment required here.
*/
value_ = x.value_;
break;
case otag::ot_rc_object:
/* must bump refcount */
{
Object * rc_obj = cast_object();
intrusive_ptr_add_ref(rc_obj);
}
break;
}
}
} /*namespace obj*/
} /*namespace xo*/
/* end object.cpp */