From d830632aea126d6d4f9e8f5ab1a59130789bc8bc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 11:24:09 -0400 Subject: [PATCH] xo-object: initial implementation [wip] --- .gitignore | 8 ++ CMakeLists.txt | 34 ++++++ LICENSE | 29 +++++ README.md | 57 +++++++++ cmake/xo-bootstrap-macros.cmake | 35 ++++++ cmake/xo_objectConfig.cmake.in | 6 + example/CMakeLists.txt | 1 + example/ex1/CMakeLists.txt | 12 ++ example/ex1/ex1.cpp | 14 +++ include/xo/object/cons.hpp | 21 ++++ include/xo/object/object.hpp | 205 ++++++++++++++++++++++++++++++++ include/xo/object/symbol.hpp | 23 ++++ include/xo/object/variant.hpp | 128 ++++++++++++++++++++ include/xo/object/zstring.hpp | 26 ++++ src/object/CMakeLists.txt | 11 ++ src/object/object.cpp | 42 +++++++ 16 files changed, 652 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100755 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_objectConfig.cmake.in create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/ex1.cpp create mode 100644 include/xo/object/cons.hpp create mode 100644 include/xo/object/object.hpp create mode 100644 include/xo/object/symbol.hpp create mode 100644 include/xo/object/variant.hpp create mode 100644 include/xo/object/zstring.hpp create mode 100644 src/object/CMakeLists.txt create mode 100644 src/object/object.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f3b23fc --- /dev/null +++ b/.gitignore @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..31d0f3c --- /dev/null +++ b/CMakeLists.txt @@ -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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cae3cb5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024 Roland Conybeare , 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3dd272f --- /dev/null +++ b/README.md @@ -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 +``` diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100755 index 0000000..aba3116 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -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() diff --git a/cmake/xo_objectConfig.cmake.in b/cmake/xo_objectConfig.cmake.in new file mode 100644 index 0000000..22879be --- /dev/null +++ b/cmake/xo_objectConfig.cmake.in @@ -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@") diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..4151ec2 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ex1) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 0000000..9190d67 --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -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 diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp new file mode 100644 index 0000000..ab6ffa0 --- /dev/null +++ b/example/ex1/ex1.cpp @@ -0,0 +1,14 @@ +/* @file ex1.cpp */ + +#include "xo/object/object.hpp" +#include + +int +main() { + using std::cout; + using std::endl; + + cout << "hello, world!" << endl; +} + +/* end ex1.cpp */ diff --git a/include/xo/object/cons.hpp b/include/xo/object/cons.hpp new file mode 100644 index 0000000..ec5dee6 --- /dev/null +++ b/include/xo/object/cons.hpp @@ -0,0 +1,21 @@ +/** @file cons.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#Include "variant.hpp" +//#include + +namespace xo { + namespace var { + struct cons { + variant car_; + variant cdr_; + }; + } /*namespace var*/ +} /*namespace xo*/ + + +/** end cons.hpp **/ diff --git a/include/xo/object/object.hpp b/include/xo/object/object.hpp new file mode 100644 index 0000000..208e7d8 --- /dev/null +++ b/include/xo/object/object.hpp @@ -0,0 +1,205 @@ +/** @file object.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/reflect/Object.hpp" +#include + +namespace xo { + namespace obj { + enum class otag : std::uint16_t { + ot_invalid = static_cast(-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(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::quiet_NaN()) const noexcept { + if (tag() == otag::ot_f64) { + std::uint32_t bits = masked_value(); + + return * reinterpret_cast(&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::quiet_NaN()) const noexcept { + if (tag() == otag::ot_f64) { + return * reinterpret_cast(masked_value()); + } else { + return sentinel; + } + } + + zstring * as_zstring() const noexcept { + if (tag() == otag::ot_zstring) { + return reinterpret_cast(masked_value()); + } else { + return nullptr; + } + } + + Object * as_object() const noexcept { + if (tag() == otag::ot_rc_object) { + return reinterpret_cast(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(masked_value()); + } + + /** undefined behavior if tag != ot_f64 **/ + double cast_double() const noexcept { + return * reinterpret_cast(masked_value()); + } + + zstring * cast_zstring() const noexcept { + return reinterpret_cast(masked_value()); + } + + symbol * cast_symbol() const noexcept { + return reinterpret_cast(masked_value()); + } + + Object * cast_object() const noexcept { + return reinterpret_cast(masked_value()); + } + + /** only use bottom c_ptr_bits (48) from value **/ + void set_tag_value(otag tag, std::uint64_t value) { + value_ = (static_cast(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 **/ diff --git a/include/xo/object/symbol.hpp b/include/xo/object/symbol.hpp new file mode 100644 index 0000000..34ba61f --- /dev/null +++ b/include/xo/object/symbol.hpp @@ -0,0 +1,23 @@ +/** @file symbol.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "zstring.hpp" +//#include + +namespace xo { + namespace obj { + class symbol { + public: + symbol(const symbol & x) : name_{x.name_} {} + + zstring * name_; + }; + } /*namespace obj*/ +} /*namespace xo*/ + + +/** end symbol.hpp **/ diff --git a/include/xo/object/variant.hpp b/include/xo/object/variant.hpp new file mode 100644 index 0000000..672a00f --- /dev/null +++ b/include/xo/object/variant.hpp @@ -0,0 +1,128 @@ +/** @file object.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/reflect/Object.hpp" +#include + +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(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::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(masked_value()); + } else { + return sentinel; + } + } + + double as_float64(double sentinel = std::numeric_limits::quiet_NaN()) const noexcept { + if (tag() == vtag::vt_f64) { + return * reinterpret_cast(masked_value()); + } else { + return sentinel; + } + } + + Object * as_object() const noexcept { + if (tag() == vtag::vt_object) { + return reinterpret_cast(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 **/ diff --git a/include/xo/object/zstring.hpp b/include/xo/object/zstring.hpp new file mode 100644 index 0000000..fef9f9b --- /dev/null +++ b/include/xo/object/zstring.hpp @@ -0,0 +1,26 @@ +/** @file zstring.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "object.hpp" +//#include + +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 **/ diff --git a/src/object/CMakeLists.txt b/src/object/CMakeLists.txt new file mode 100644 index 0000000..b25f0b1 --- /dev/null +++ b/src/object/CMakeLists.txt @@ -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 diff --git a/src/object/object.cpp b/src/object/object.cpp new file mode 100644 index 0000000..80258b2 --- /dev/null +++ b/src/object/object.cpp @@ -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 */