diff --git a/doc/README.org b/doc/README.org new file mode 100644 index 0000000..441e80f --- /dev/null +++ b/doc/README.org @@ -0,0 +1,354 @@ +* Introduction + + This library provides an interface to C++ from Common Lisp. + + As [[https://cffi.common-lisp.dev/][CFFI]] is normally used for C code, there are no builtin functions + to deal with C++ extensions from Common Lisp like class definitions, + their instantiation, accessing and defining member functions, + etc. [[https://github.com/Islam0mar/cl-cxx][cl-cxx]] is an attempt to step in and facilitate easy interfacing + from Common Lisp to C​++ libs. It uses CFFI under the hood, but + generates its definitions automagically from C​++ code written by + the user. + + The code was inspired by Julia's [[https://github.com/JuliaInterop/CxxWrap.jl][CxxWrap]] and provides a similar + functionality as python's [[https://github.com/pybind/pybind11][pybind11]] for the Common Lisp community. + +* Quickstart + + - Install [[https://github.com/Islam0mar/CLCXX][CLCXX]] into =/usr/local/lib= + + - copy the =example= directory to =/tmp/example= + + - Build the library with + +#+BEGIN_SRC bash +$ cd /tmp/examples/my-lib && mkdir build && cd build && cmake .. && make +#+END_SRC + + - with [[https://www.quicklisp.org/beta/][quicklisp]] installed do + +#+BEGIN_SRC bash + $ ln -s /tmp/example ~/quicklisp/local-projects +#+END_SRC + + - Start Common Lisp and issue + +#+BEGIN_SRC lisp + Cl-USER​> (ql:quickload "my-cl-lib") +#+END_SRC + + - Explore the example code in =/tmp/example/scratch.lisp= + +* Architecture + +** Provided parts + + The provided architecture consists of two parts: + + - [[https://github.com/Islam0mar/CLCXX][CLCXX]] + + A C​++ library which gets compiled once and installed systemwide, + visible by the C++ toolchain. + + - [[https://github.com/Islam0mar/cl-cxx][cl-cxx]] + + The Common Lisp part, which translates the definitions of the + custom C​++ library (see below) to CFFI code suitable to be used + directly from Common Lisp. + + In addition existing C++ Libraries and their header files can be + included in case they are targeted. + +** Custom parts + + There are also two user-defined parts: + + - A custom C​++ library + + This contains the C​++ code to be exposed to Lisp, custom + definitions or glue code to existing libraries, the API bindings, + package name definitions for the Lisp side, the Lisp names of + functions, methods, member functions, etc... + + - The custom Common Lisp code + + This contains the code to load the foreign libraries and + initialize the cl-cxx system and package definitions from the C++ + lib to be accessible by Lisp. In addition this code can also + contain macros and such to make the API "lispier", as it is common + for CFFI related packages. + + The following diagram shows the relation of the different parts: + + [[./block-diagram.svg]] + + + Note that with this design, the definition of functions, structs, + etc. is *not* done by the user on the Lisp side, as it is the case + when working with standard CFFI directly, but rather on the C​++ + side, with the translations, name mangling, etc. done automatically + by CLCXX and cl-cxx. The two Common Lisp functions taking care of + translating the compiled C​++ definitions into cffi are =(cxx:init)= + and =(cxx:add-package...)= + +* Examples + + The =example= folder contains a full example. It can be used as a + skeleton for a new project. The necessary steps to make the example + work are explained using linux commands. Please translate them into + the corresponding commands for your OS. + +** Setup + - Copy the folder with all contents to a location of your choice: + +#+BEGIN_SRC bash +$ cp -R examples /tmp/ +$ cd /tmp/examples +#+END_SRC + +The necessary files for the custom C++ library are located in the +subfolder =custom-lib=. If additional libs are needed, put their +header files into the =custom-lib/include= folder or optionally into +=custom-lib/modules= and add their reference to the file +=/tmp/example/custom-lib/CMakeLists.txt= as in a normal C++ project. + +** The C​++ source file + + The C​++ source file is located in =custom-lib/src/my-lib.cpp= Here + are its contents: + +#+BEGIN_SRC c +#include +#include "clcxx/clcxx.hpp" + +// standard C function definitions + +std::string greet() { return "Hello, World"; } +int Int(int x) { return x + 100; } +float Float(float y) { return y + 100.34; } +auto gr(std::complex x) { return x; } +std::string hi(char* s) { return std::string("hi, " + std::string(s)); } +void ref_int(int& x) { x += 30; } + +// standard C++ class definition with members and method + +class xx { + public: + xx(int xx, int yy) : y(yy), x(xx) {} + std::string greet() { return "Hello, World"; } + int y; + int x; +}; +void ref_class(xx& x) { x.y = 1000000; } + +// definitions of the API exposure to Common Lisp + +CLCXX_PACKAGE TEST(clcxx::Package& pack) { + pack.defun("hi", F_PTR(&hi)); + pack.defun("test-int", F_PTR(&Int)); + pack.defun("greet", F_PTR(&greet)); + pack.defun("test-float", F_PTR(&Float)); + pack.defun("test-complex", F_PTR(&gr)); + pack.defun("ref-int", F_PTR(&ref_int)); + pack.defun("ref-class", F_PTR(&ref_class)); + pack.defclass("cl-xx") + .member("y", &xx::y) + .defmethod("foo", F_PTR(&xx::greet)) + .defmethod("foo.x", F_PTR([](xx x){return x.x;})) + .constructor(); +} +#+END_SRC + + +*** Definitions of the API exposure to Common Lisp + + The API exposure to Common Lisp happens inside a =CLCXX_PACKAGE= + block of the C++ file. After compilation of the lib and its + loading from Common Lisp, all the definitions get pulled into a + custom, newly created Common Lisp package with the command + =add-package=. The command takes two arguments: The name defined + in the C++ file ("TEST" in the example above) and the name of the + Common Lisp package to use ("CL-TEST" in the Common Lisp example + below). + + The =CLCXX_PACKAGE= block defines the bindings of the C++ + functions, classes, methods, members and constructors to Common + lisp functions. Everything *not* defined here will not be visible + by Common Lisp. + +#+BEGIN_SRC c +CLCXX_PACKAGE TEST(clcxx::Package& pack) { +// ... +} +#+END_SRC + +Defines the package named =TEST=, using the package pointer =pack=. + +#+BEGIN_SRC c + pack.defun("greet", F_PTR(&greet)); +#+END_SRC + +Defines the Common Lisp function =#'cl-test:greet= to call the C++ +function =greet=. + +#+BEGIN_SRC c + pack.defclass("cl-xx") +#+END_SRC + +Defines a class framework of the C​++ class =xx= for Common Lisp (named +=cl-xx= in Common Lisp). This creates bindings for the Common Lisp +destructor function =#'cl-test:destruct-cl-xx= + +#+BEGIN_SRC c + pack.member("y", &xx::y) +#+END_SRC + +Define the member =#'cl-test:y= of the xx class. This creates +bindinges for the Common Lisp getter and setter functions +=#'cl-test:y.get= and =#'cl-test:y.set=. + +#+BEGIN_SRC c + pack.defmethod("foo", F_PTR(&xx::greet)) +#+END_SRC + +Defines the Common Lisp function =#'cl-test:foo= as the greet method +of class xx. The Common Lisp functions binding C​++ class methods will +always take the instance of the class (meaning a CFFI foreign pointer +to it) as first argument and the arguments of the C method as +additional arguments (if any). + +#+BEGIN_SRC c + pack.defmethod("foo.x", F_PTR([](xx x){return x.x;})) +#+END_SRC + +An alternative way to define a getter function of the member x of +class xx using a C​++ lambda expression. + +#+BEGIN_SRC c + pack.constructor() +#+END_SRC + +Define a constructor function for the class =xx=. The constructor +function is automatically named =cl-test:create-cl-xx2=. The number at +the end of the name specifies the number of arguments of the +constructor function and is omitted when the constructor function +takes no arguments. + +Alternatively you can specify a Common Lisp name of the constructor +function explicitely: + +#+BEGIN_SRC c + pack.constructor("create-my-xx") +#+END_SRC + +This will create the Common Lisp Constructor function +=#'cl-test:create-my-xx=. + +** Building the Library + +#+BEGIN_SRC bash +$ cd /tmp/examples/my-lib +$ mkdir build +$ cd build +$ cmake .. +$ make +#+END_SRC + +This should compile a shared library named =myLib.so= (maybe with +another extension according to your OS) and put it into the folder +=/tmp/example/my-lib/lib/= + +** Using the Library from Common Lisp + + In order to use the Library from Common Lisp refer to the file + =/tmp/example/load-lib.lisp= + + The standard =CFFI= way of loading a lib is also used for + =cl-cxx=. Before loading the custom library, the CLCXX library has + to be loaded. The lisp code assumes the library is located in + =/usr/local/lib=. Note that plain CLCXX from github will install it + in =~/.local/lib=. To install in =/usr/local/lib= instead, use + =cmake -DCMAKE_INSTALL_PREFIX=/usr/local ..= instead of =cmake ..= + in the build step of CLCXX. + + First both libs are defined: + +#+BEGIN_SRC lisp + ;;; change this to the load path of libClCxx + (pushnew (pathname "/usr/local/lib/") + cffi:*foreign-library-directories* + :test #'equal) + + (cffi:define-foreign-library clcxx + (t (:default "libClCxx"))) + + (pushnew (asdf:system-relative-pathname :my-cl-lib "custom-lib/lib/") + cffi:*foreign-library-directories* + :test #'equal) + + (cffi:define-foreign-library my-lib + (t (:default "libMyLib"))) +#+END_SRC + + Then the libs are loaded into CFFI: + +#+BEGIN_SRC lisp + (cffi:use-foreign-library clcxx) + (cffi:use-foreign-library my-lib) +#+END_SRC + + After this, CLCXX needs to be initialized: + +#+BEGIN_SRC lisp + (cxx:init) +#+END_SRC + + Finally all the bindings from C​++ to Common Lisp are defined and + added to the newly created package "CL-TEST". This step is + comparable to the loading of a file containing =(defcfun ...)= and + such in CFFI: + +#+BEGIN_SRC lisp + (cxx:add-package "CL-TEST" "TEST") +#+END_SRC + + After this step, all definitions of the C​++ file should be + available in Common Lisp. + +** Exploring the example code + + The example code for the Common Lisp part is also located in the + =/tmp/example/= directory. To load it into lisp, make the example + directory visible to asdf or quicklisp. + + In quicklisp this can be done like this: + +#+BEGIN_SRC bash + $ ln -s /tmp/example ~/quicklisp/local-projects +#+END_SRC + + Then start up Common Lisp and load the project with: + +#+BEGIN_SRC lisp + Cl-USER​> (ql:quickload "my-cl-lib") + To load "my-cl-lib": + Load 1 ASDF system: + my-cl-lib + ; Loading "my-cl-lib" + ............... + ("my-cl-lib") + CL-USER​> +#+END_SRC + + Open the file =/tmp/example/scratch.lisp= to see and explore + commented examples of using the lib. + +* Future Direction + + none yet... + +* Help Wanted + + please send bug reports, suggestions, comments and code to custom + libs using the system, etc. for expanding the user base. + diff --git a/doc/block-diagram.svg b/doc/block-diagram.svg new file mode 100644 index 0000000..62bbd5f --- /dev/null +++ b/doc/block-diagram.svg @@ -0,0 +1,413 @@ + + + + + + + + + + + + + user defined + provided + + + + + targeted C++ lib + + libClCxx + + C++ + Common Lisp + + + include <targeted C++ lib.h>include <ClCxx.h>Class Definitions, etc... of targeted C++ lib,their Common Lisp names,the "CUSTOM_PACKAGE" name,... + custom C++ lib + + + + (asdf:load :cffi)(asdf:load :cxx)(use-package :cffi)(use-package :cxx)(cffi:define-foreign-library clcxx (t (:default "libClCxx")))(cffi:define-foreign-library my-custom-lib (t (:default "customC++Lib")))(cffi:use-foreign-library clcxx)(cffi:use-foreign-library my-custom-lib)(cxx:init)(cxx:add-package "CUSTOM-PACKAGE" "CUSTOM_PACKAGE") + custom cl code + + + cl-cxx + + cffi + + + diff --git a/example/custom-lib/CMakeLists.txt b/example/custom-lib/CMakeLists.txt new file mode 100644 index 0000000..70c0319 --- /dev/null +++ b/example/custom-lib/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.0) + +# set the name of the library to compile +project(MyLib) + +# set the directory to save the compiled lib +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib) + +# optionally include the code for additional libs +# include(modules/link/AbletonLinkConfig.cmake) + +# add the include directory for clcxx +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +# set the cpp file to conpile +add_library(MyLib SHARED ${CMAKE_CURRENT_SOURCE_DIR}/src/my-lib.cpp) + +# optionally add libraries needed for linking +# target_link_libraries(AbletonLink PRIVATE Ableton::Link) + + diff --git a/example/custom-lib/include/clcxx/clcxx.hpp b/example/custom-lib/include/clcxx/clcxx.hpp new file mode 100755 index 0000000..e59b7e4 --- /dev/null +++ b/example/custom-lib/include/clcxx/clcxx.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "package.hpp" +#include "type_conversion.hpp" diff --git a/example/custom-lib/include/clcxx/clcxx_config.hpp b/example/custom-lib/include/clcxx/clcxx_config.hpp new file mode 100755 index 0000000..e516869 --- /dev/null +++ b/example/custom-lib/include/clcxx/clcxx_config.hpp @@ -0,0 +1,25 @@ +#pragma once + +#ifdef _WIN32 +#ifdef CLCXX_EXPORTS +#define CLCXX_API __declspec(dllexport) +#else +#define CLCXX_API __declspec(dllimport) +#endif +#define CLCXX_ONLY_EXPORTS __declspec(dllexport) +#else +#define CLCXX_API __attribute__((visibility("default"))) +#define CLCXX_ONLY_EXPORTS CLCXX_API +#endif + +#define CLCXX_VERSION_MAJOR 1 +#define CLCXX_VERSION_MINOR 0 +#define CLCXX_VERSION_PATCH 0 + +// From +// https://stackoverflow.com/questions/5459868/concatenate-int-to-string-using-c-preprocessor +#define __CLCXX_STR_HELPER(x) #x +#define __CLCXX_STR(x) __CLCXX_STR_HELPER(x) +#define CLCXX_VERSION_STRING \ + __CLCXX_STR(CLCXX_VERSION_MAJOR) \ + "." __CLCXX_STR(CLCXX_VERSION_MINOR) "." __CLCXX_STR(CLCXX_VERSION_PATCH) diff --git a/example/custom-lib/include/clcxx/hash_type.hpp b/example/custom-lib/include/clcxx/hash_type.hpp new file mode 100644 index 0000000..1ebd778 --- /dev/null +++ b/example/custom-lib/include/clcxx/hash_type.hpp @@ -0,0 +1,149 @@ +#pragma once + +#include +#include + +namespace clcxx { + +using SizeT = std::uint_fast32_t; + +// C++ type name from: +// https://stackoverflow.com/questions/81870/is-it-possible-to-prSize-a-variables-type-in-standard-c/56766138#56766138 +template +constexpr auto TypeName() { + std::string_view name, prefix, suffix; +#ifdef __clang__ + name = __PRETTY_FUNCTION__; + prefix = "auto utils::TypeName() [T = "; + suffix = "]"; +#elif defined(__GNUC__) + name = __PRETTY_FUNCTION__; + prefix = "constexpr auto utils::TypeName() [with T = "; + suffix = "]"; +#elif defined(_MSC_VER) + name = __FUNCSIG__; + prefix = "auto __cdecl utils::TypeName<"; + suffix = ">(void)"; +#endif + name.remove_prefix(prefix.size()); + name.remove_suffix(suffix.size()); + return name; +} + +// compile time string hash for types from: +// https://roartindon.blogspot.com/2014/10/compile-time-murmur-hash-in-c.html +namespace detail { +template +struct Sequence {}; +template +struct CreateSequence : CreateSequence {}; +template +struct CreateSequence<0, S...> { + typedef Sequence Type; +}; + +constexpr SizeT UpdateHash1(SizeT k) { return k * 0xcc9e2d51; } +constexpr SizeT UpdateHash2(SizeT k) { return (k << 15) | (k >> (32 - 15)); } +constexpr SizeT UpdateHash3(SizeT k) { return k * 0x1b873593; } +constexpr SizeT UpdateHash4(SizeT hash, SizeT block) { return hash ^ block; } +constexpr SizeT UpdateHash5(SizeT hash) { + return ((hash << 13) | (hash >> (32 - 13))) * 5 + 0xe6546b64; +} + +constexpr SizeT UpdateHash(SizeT hash, SizeT block) { + return UpdateHash5( + UpdateHash4(hash, UpdateHash3(UpdateHash2(UpdateHash1(block))))); +} + +constexpr SizeT UpdateLastHash(SizeT hash, SizeT block) { + return UpdateHash4(hash, UpdateHash3(UpdateHash2(UpdateHash1(block)))); +} + +template +constexpr SizeT CalculateHashRounds(SizeT seed, C... c); + +template <> +constexpr SizeT CalculateHashRounds(SizeT seed) { + return seed; +} + +template <> +constexpr SizeT CalculateHashRounds(SizeT seed, char c0) { + return UpdateLastHash(seed, std::uint8_t(c0)); +} + +template <> +constexpr SizeT CalculateHashRounds(SizeT seed, char c0, char c1) { + return UpdateLastHash(seed, std::uint8_t(c0) | std::uint8_t(c1) << 8); +} + +template <> +constexpr SizeT CalculateHashRounds(SizeT seed, char c0, char c1, char c2) { + return UpdateLastHash( + seed, std::uint8_t(c0) | std::uint8_t(c1) << 8 | std::uint8_t(c2) << 16); +} + +template +constexpr SizeT CalculateHashRounds(SizeT seed, char c0, char c1, char c2, + char c3, C... c) { + return CalculateHashRounds( + UpdateHash(seed, std::uint8_t(c0) | std::uint8_t(c1) << 8 | + std::uint8_t(c2) << 16 | std::uint8_t(c3) << 24), + c...); +} + +constexpr SizeT CalculateFinalHash1(SizeT h, SizeT length) { + return h ^ length; +} + +constexpr SizeT CalculateFinalHash2(SizeT h) { return h ^ (h >> 16); } + +constexpr SizeT CalculateFinalHash3(SizeT h) { return h * 0x85ebca6b; } + +constexpr SizeT CalculateFinalHash4(SizeT h) { return h ^ (h >> 13); } + +constexpr SizeT CalculateFinalHash5(SizeT h) { return h * 0xc2b2ae35; } + +constexpr SizeT CalculateFinalHash6(SizeT h) { return h ^ (h >> 16); } + +constexpr SizeT CalculateFinalHash(SizeT h, SizeT length) { + return CalculateFinalHash6( + CalculateFinalHash5(CalculateFinalHash4(CalculateFinalHash3( + CalculateFinalHash2(CalculateFinalHash1(h, length)))))); +} + +// This is used to convert from calling const char (&s)[N] +// To CalculateHashRounds(seed, s[0], s[1], s[2], s[3], ... ) +template +constexpr SizeT Unpack(unsigned seed, const char (&s)[N], Sequence) { + return CalculateHashRounds(seed, s[S]...); +} +// This is used to convert from calling std::string_view +// To CalculateHashRounds(seed, s[0], s[1], s[2], s[3], ... ) +template +constexpr SizeT Unpack(unsigned seed, const std::string_view &s, + Sequence) { + return CalculateHashRounds(seed, s[S]...); +} + +template +constexpr SizeT murmur3_32(const char (&s)[N], SizeT seed = 0) { + return CalculateFinalHash( + Unpack(seed, s, typename CreateSequence::Type()), N - 1); +} + +template +constexpr SizeT murmur3_32(const std::string_view &s, SizeT seed = 0) { + return CalculateFinalHash( + Unpack(seed, s, typename CreateSequence::Type()), N); +} + +} // namespace detail + +template +constexpr auto Hash32TypeName() { + constexpr auto s = TypeName(); + return detail::murmur3_32(std::move(s)); +} + +} // namespace clcxx diff --git a/example/custom-lib/include/clcxx/memory.hpp b/example/custom-lib/include/clcxx/memory.hpp new file mode 100644 index 0000000..cbc4aa2 --- /dev/null +++ b/example/custom-lib/include/clcxx/memory.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +namespace clcxx { + +// allocator +constexpr auto BUF_SIZE = 1 * 1024 * 1024; + +class VerboseResource : public std::pmr::memory_resource { + public: + explicit VerboseResource(std::pmr::memory_resource *upstream_resource) + : upstream_resource_(upstream_resource), num_of_bytes_allocated(0) {} + + size_t get_num_of_bytes_allocated() { return num_of_bytes_allocated; } + + private: + void *do_allocate(size_t bytes, size_t alignment) override { + num_of_bytes_allocated += bytes; + return upstream_resource_->allocate(bytes, alignment); + } + + void do_deallocate(void *p, size_t bytes, size_t alignment) override { + num_of_bytes_allocated -= bytes; + upstream_resource_->deallocate(p, bytes, alignment); + } + + [[nodiscard]] bool do_is_equal( + const memory_resource &other) const noexcept override { + return this == &other; + } + + std::pmr::memory_resource *upstream_resource_; + size_t num_of_bytes_allocated; +}; + +[[nodiscard]] VerboseResource &MemPool(); + +} // namespace clcxx diff --git a/example/custom-lib/include/clcxx/package.hpp b/example/custom-lib/include/clcxx/package.hpp new file mode 100755 index 0000000..39bf52c --- /dev/null +++ b/example/custom-lib/include/clcxx/package.hpp @@ -0,0 +1,638 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "type_conversion.hpp" + +/// helpper for Import function +#define F_PTR(...) \ + reinterpret_cast(clcxx::Import([&]() { return __VA_ARGS__; })), \ + __VA_ARGS__ + +namespace clcxx { + +extern "C" typedef struct { + char *name; + char *super_classes; + char *slot_names; + char *slot_types; + void (*constructor)(); // null := pod class + void (*destructor)(); // null := pod class +} ClassInfo; + +extern "C" typedef struct { + char *name; + bool method_p; + char *class_obj; + void (*func_ptr)(); + char *arg_types; + char *return_type; +} FunctionInfo; + +extern "C" typedef struct { + char *name; + char *value; +} ConstantInfo; + +extern "C" typedef union { + FunctionInfo Func; + ClassInfo Class; + ConstantInfo Const; +} MetaData; + +class CLCXX_API Package; + +inline void LispError(const char *error); + +namespace detail { +template +struct is_functional : public std::false_type {}; +template +struct is_functional> : public std::true_type {}; + +template +inline constexpr bool is_functional_v = is_functional::value; + +template +ToLisp_t DoApply(ToLisp_t... args) { + try { + if constexpr (std::is_invocable_v...>) { + if constexpr (std::is_same_v, void>) { + std::invoke(invocable_pointer, ToCpp(std::move(args))...); + return; + } else { + return ToLisp( + std::invoke(invocable_pointer, ToCpp(std::move(args))...)); + } + } else { + if constexpr (std::is_same_v, void>) { + std::invoke(*invocable_pointer, ToCpp(std::move(args))...); + return; + } else { + return ToLisp( + std::invoke(*invocable_pointer, ToCpp(std::move(args))...)); + } + } + } catch (const std::exception &err) { + LispError(err.what()); + } + return ToLisp_t(); +} + +template +constexpr auto ResolveInvocable(std::function *) { + return &DoApply, + std::remove_const_t...>; +} +template +constexpr auto ResolveInvocable(R (*)(Args...)) { + return &DoApply, + std::remove_const_t...>; +} + +template +constexpr auto ResolveInvocable(R (CT::*)(Args...)) { + return &DoApply, std::remove_const_t, + std::remove_const_t...>; +} +template +constexpr auto ResolveInvocable(R (CT::*)(Args...) const) { + return &DoApply, std::remove_const_t, + std::remove_const_t...>; +} +template +constexpr auto ResolveInvocableLambda(R (LambdaT::*)(Args...) const) { + return &DoApply, + std::remove_const_t...>; +} + +/// mutable lambda +template +constexpr auto ResolveInvocableLambda(R (LambdaT::*)(Args...)) { + return &DoApply, + std::remove_const_t...>; +} +template +constexpr auto ResolveInvocable( + std::enable_if_t< + std::is_class_v> && + !detail::is_functional_v< + std::remove_pointer_t>, + decltype(lambda_ptr)> + p) { + return ResolveInvocableLambda, lambda_ptr>( + &std::remove_pointer_t::operator()); +} + +template +inline constexpr auto DecayThenResolve() { + return ResolveInvocable>(x)>( + std::forward>(x)); +} +} // namespace detail + +/** + * @brief import a function, method, lambda, or std_function + * + * @details takes a lambda input such as this: + * [&](){return ;} + * and convert it to the address of thunk_function which + * takes arguments to cxx function. + * when method is imported, you have to pass object as + * the first argument. + * example 1: + * given: int foo(float f){return (int)f;} + * import: Import([&]() { return foo; }) + * that would give the address of the thunk function. + * example 2: + * given: int foo(float f){return (int)f;} + * import: Import([&]() { return foo; })(5.123) + * that would execute foo(5.123) and return 5. + * could be invoked with Import([&]() { return &foo; }) + * use cases: + * Import([&]() { return &MyClass::foo; })(foo_object, args...) + * Import([&]() { return &MyClass::operator(); })(foo_object) + * Import([&]() { return []() { return "Hello, World\n"; }; })() + * + * @param lambda class + * + * @return function pointer to the thunk fuction + */ +template +inline auto Import(T lambda) { + if constexpr (std::is_class_v) { + static auto w = lambda(); + constexpr auto res = detail::DecayThenResolve<&w>(); + return res; + } else { + constexpr auto res = detail::DecayThenResolve(); + return res; + } +} + +namespace detail { + +char *str_dup(const char *src); +char *str_append(char *old_str, const char *src); + +template +void remove_c_strings(T obj); + +// Base class to specialize for constructor +template +CppT *CppConstructor(Args... args) { + auto obj_ptr = static_cast( + MemPool().allocate(sizeof(CppT), std::alignment_of_v)); + ::new (obj_ptr) CppT(args...); + return obj_ptr; +} + +using FuncPtr = void (*)(); +template +struct CreateClass { + inline FuncPtr operator()() { + return reinterpret_cast( + clcxx::Import([&]() { return &CppConstructor; })); + } +}; + +template +struct CreateClass { + inline FuncPtr operator()() { return nullptr; } +}; + +template +void free_obj_ptr(void *ptr) { + auto obj_ptr = static_cast(ptr); + obj_ptr->~T(); + MemPool().deallocate(ptr, sizeof(T), std::alignment_of_v); +} + +/// handle POD class +template +std::string arg_type_pod_fix() { + using T = std::remove_cv_t; + auto return_type = LispType(); // case 1 + if constexpr (internal::is_pod_struct_v) { + if (::clcxx::pod_class_name() == "") { + throw std::runtime_error("Pod class " + std::string(TypeName()) + + " isn't registered and it is passed by value"); + } + } + return return_type; +} + +/// Make a string with the types in the variadic template parameter pack +template +std::string arg_types_string() { + std::vector vec = {arg_type_pod_fix()...}; + std::string s; + for (auto arg_types : vec) { + s.append(arg_types); + s.append("+"); + } + return s; +} + +/// Make a string with the super classes in the variadic template parameter +/// pack +template +std::string super_classes_string() { + std::vector vec = {general_class_name()...}; + std::string s; + for (auto super_types : vec) { + s.append(super_types); + s.append("+"); + } + return s; +} +} // namespace detail + +/// Registry containing different packages +class CLCXX_API PackageRegistry { + public: + /// Create a package and register it + Package &create_package(std::string lpack); + + auto get_package_iter(std::string pack) const { + const auto iter = p_packages.find(pack); + if (iter == p_packages.end()) { + throw std::runtime_error("Pack with name " + pack + + " was not found in registry"); + } + return iter; + } + + bool has_package(std::string lpack) const { + return p_packages.find(lpack) != p_packages.end(); + } + + void remove_package(std::string lpack); + + using Iter = std::map>::iterator; + [[nodiscard]] Iter remove_package(Iter iter); + + bool has_current_package() { return p_current_package != nullptr; } + Package ¤t_package(); + void reset_current_package() { p_current_package = nullptr; } + + ~PackageRegistry() { + for (auto it = begin(p_packages); it != end(p_packages);) { + it = remove_package(it); + } + } + + PackageRegistry() + : p_error_handler_callback(nullptr), + p_meta_data_handler_callback(nullptr) {} + + void set_error_handler(void (*callback)(char *)) { + p_error_handler_callback = callback; + } + + void handle_error(char *err_msg) { p_error_handler_callback(err_msg); } + + void set_meta_data_handler(void (*callback)(MetaData *, uint8_t)) { + p_meta_data_handler_callback = callback; + } + + void send_data(MetaData *M, uint8_t n) { p_meta_data_handler_callback(M, n); } + + private: + std::map> p_packages; + Package *p_current_package = nullptr; + void (*p_error_handler_callback)(char *); + void (*p_meta_data_handler_callback)(MetaData *, uint8_t); +}; + +CLCXX_API PackageRegistry ®istry(); + +inline void LispError(const char *error) { + clcxx::registry().handle_error(const_cast(error)); +} + +template +class ClassWrapper; +template +class PodClassWrapper; + +/// Store all exposed C++ functions associated with a package +class CLCXX_API Package { + public: + explicit Package(std::string cl_pack) : p_cl_pack(cl_pack) {} + + /// Define a new function base + template + void defun(const std::string &name, void (*func_ptr)(), T &&functor, + bool is_method = false, const char *class_name = "") { + defun(name, std::forward(functor), is_method, class_name, func_ptr); + } + + /// Add a class type + template + ClassWrapper defclass(const std::string &name, s_classes...) { + static_assert(!internal::is_pod_struct_v, + "Use defcstruct for pod class types."); + auto iter = general_class_name.find(Hash32TypeName()); + if (iter != general_class_name.end()) { + throw std::runtime_error("Class with name " + name + + " was already defined in the package, or maybe " + "murmur3 string collision!"); + } + general_class_name[Hash32TypeName()] = name; + + ClassInfo c_info; + c_info.constructor = detail::CreateClass()(); + c_info.destructor = reinterpret_cast(detail::free_obj_ptr); + c_info.slot_types = nullptr; + c_info.slot_names = nullptr; + c_info.name = detail::str_dup(name.c_str()); + c_info.super_classes = + detail::str_dup(detail::super_classes_string().c_str()); + // Store data + p_classes_meta_data.push_back(c_info); + return ClassWrapper(*this); + } + + template + PodClassWrapper defcstruct(const std::string &name) { + static_assert(internal::is_pod_struct_v, + "defcstruct can be used for pod class types only, you should " + "defclass."); + auto iter = pod_class_name.find(Hash32TypeName()); + if (iter != pod_class_name.end()) { + throw std::runtime_error("struct with name " + name + + " was already defined in the package, or maybe " + "murmur3 string collision!" + + iter->second); + } + pod_class_name[Hash32TypeName()] = name; + + ClassInfo c_info; + c_info.constructor = nullptr; + c_info.destructor = nullptr; + c_info.slot_types = nullptr; + c_info.slot_names = nullptr; + c_info.name = detail::str_dup(name.c_str()); + c_info.super_classes = nullptr; + // Store data + p_classes_meta_data.push_back(c_info); + return PodClassWrapper(*this); + } + + /// Set a global constant value at the package level + template + void defconstant(const std::string &name, T &&value) { + ConstantInfo const_info; + const_info.name = detail::str_dup(name.c_str()); + const_info.value = detail::str_dup(std::to_string(value).c_str()); + p_constants.push_back(const_info); + } + + std::string name() const { return p_cl_pack; } + + const std::unordered_map &general_classes() const { + return general_class_name; + } + + const std::unordered_map &pod_classes() const { + return pod_class_name; + } + + std::vector &classes_meta_data() { return p_classes_meta_data; } + std::vector &functions_meta_data() { + return p_functions_meta_data; + } + std::vector &constants_meta_data() { return p_constants; } + + private: + /// Define a new function + template + void defun(const std::string &name, std::function, bool is_method, + const char *class_name, void (*func_ptr)()) { + FunctionInfo f_info; + f_info.name = detail::str_dup(name.c_str()); + f_info.method_p = is_method; + f_info.class_obj = detail::str_dup(class_name); + f_info.func_ptr = func_ptr; + f_info.arg_types = + detail::str_dup(detail::arg_types_string().c_str()); + f_info.return_type = detail::str_dup(detail::arg_type_pod_fix().c_str()); + // store data + p_functions_meta_data.push_back(f_info); + } + + /// Define a new function. Overload for pointers + template + void defun(const std::string &name, R (*f)(Args...), bool is_method, + const char *class_name, void (*func_ptr)()) { + defun(name, std::function(f), is_method, class_name, func_ptr); + } + + /// Define a new function. Overload for lambda + template + void defun(const std::string &name, LambdaT &&lambda, bool is_method, + const char *class_name, void (*func_ptr)() // , + // std::enable_if_t, + // bool> = true + ) { + static_assert(!std::is_member_function_pointer_v, + "Use defmethod for member functions"); + add_lambda(name, std::forward(lambda), &LambdaT::operator(), + is_method, class_name, func_ptr); + } + + template + void add_lambda(const std::string &name, LambdaT &&lambda, + R (LambdaT::*)(ArgsT...) const, bool is_method, + const char *class_name, void (*func_ptr)()) { + defun(name, std::function(std::forward(lambda)), + is_method, class_name, func_ptr); + } + + template + void add_lambda(const std::string &name, LambdaT &&lambda, + R (LambdaT::*)(ArgsT...), bool is_method, + const char *class_name, void (*func_ptr)()) { + defun(name, std::function(std::forward(lambda)), + is_method, class_name, func_ptr); + } + + std::string p_cl_pack; + std::vector p_classes_meta_data; + std::vector p_functions_meta_data; + std::vector p_constants; + std::unordered_map general_class_name; + std::unordered_map pod_class_name; + template + friend class PodClassWrapper; + template + friend class ClassWrapper; +}; + +// Helper class to wrap type methods +template +class PodClassWrapper { + public: + typedef T type; + + explicit PodClassWrapper(Package &pack) : p_package(pack) {} + + // Add public member >> readwrite + template + PodClassWrapper &member(const std::string &name, MemberT CT::*pm) { + check_member_and_append_its_slots(name, pm); + return *this; + } + + // Access to the module + Package &package() { return p_package; } + + private: + template + constexpr void check_member_and_append_its_slots(const std::string &name, + MemberT CT::*) { + static_assert(std::is_base_of::value, + "member() requires a class member (or base class member)"); + auto &curr_class = p_package.p_classes_meta_data.back(); + + curr_class.slot_types = detail::str_append( + curr_class.slot_types, std::string(LispType() + "+").c_str()); + curr_class.slot_names = detail::str_append(curr_class.slot_names, + std::string(name + "+").c_str()); + } + + Package &p_package; +}; + +template +class ClassWrapper { + public: + typedef T type; + + explicit ClassWrapper(Package &pack) : p_package(pack) {} + + /// Add a constructor with the given argument types + template + ClassWrapper &constructor(std::string name = "") { + auto &curr_class = p_package.p_classes_meta_data.back(); + + if (name == "") + // Use name as a flag + name = std::string("create-" + std::string(curr_class.name) + + std::to_string(sizeof...(Args))); + + p_package.defun(name, F_PTR(detail::CppConstructor), false, + curr_class.name); + return *this; + } + + /// Define a member function + template + ClassWrapper &defmethod(const std::string &name, void (*func_ptr)(), + FuncT &&functor) { + defmethod(name, std::forward(functor), func_ptr); + return *this; + } + + // Add public member >> readwrite + template + ClassWrapper &member(const std::string &name, MemberT CT::*pm) { + check_member_and_append_its_slots(name, pm); + defmethod(std::string(name + ".get"), + F_PTR([pm](const T &c) -> const MemberT { return c.*pm; })); + defmethod(std::string(name + ".set"), + F_PTR([pm](T &c, const MemberT val) { c.*pm = val; })); + return *this; + } + + // Access to the module + Package &package() { return p_package; } + + private: + template + constexpr void check_member_and_append_its_slots(const std::string &name, + MemberT CT::*) { + static_assert(std::is_base_of::value, + "member() requires a class member (or base class member)"); + auto &curr_class = p_package.p_classes_meta_data.back(); + + curr_class.slot_types = detail::str_append( + curr_class.slot_types, std::string(LispType() + "+").c_str()); + curr_class.slot_names = detail::str_append(curr_class.slot_names, + std::string(name + "+").c_str()); + } + + /// Define a member function + template + void defmethod(const std::string &name, R (CT::*f)(ArgsT...), + void (*func_ptr)()) { + auto curr_class = p_package.p_classes_meta_data.back(); + p_package.defun( + name, [f](T &obj, ArgsT... args) -> R { return (obj.*f)(args...); }, + true, curr_class.name, func_ptr); + } + + /// Define a member function, const version + template + void defmethod(const std::string &name, R (CT::*f)(ArgsT...) const, + void (*func_ptr)()) { + auto curr_class = p_package.p_classes_meta_data.back(); + p_package.defun( + name, + [f](const T &obj, ArgsT... args) -> R { return (obj.*f)(args...); }, + true, curr_class.name, func_ptr); + } + + /// Define a "member" function using a lambda + template + void defmethod( + const std::string &name, LambdaT &&lambda, void (*func_ptr)(), + typename std::enable_if::value, + bool>::type = true) { + auto curr_class = p_package.p_classes_meta_data.back(); + p_package.defun(name, std::forward(lambda), true, curr_class.name, + func_ptr); + } + + Package &p_package; +}; + +template +inline std::string general_class_name() { + auto classes = registry().current_package().general_classes(); + auto iter = classes.find(Hash32TypeName()); + return iter == classes.end() ? "" : iter->second; +} + +template +inline std::string pod_class_name() { + auto classes = registry().current_package().pod_classes(); + auto iter = classes.find(Hash32TypeName()); + return iter == classes.end() ? "" : iter->second; +} + +} // namespace clcxx +extern "C" { +CLCXX_API bool clcxx_init(void (*error_handler)(char *), + void (*reg_data_callback)(clcxx::MetaData *, + uint8_t)); +CLCXX_API bool remove_package(const char *pack_name); +CLCXX_API bool register_package(const char *cl_pack, + void (*regfunc)(clcxx::Package &)); +CLCXX_API size_t used_bytes_size(); +CLCXX_API size_t max_stack_bytes_size(); +CLCXX_API bool delete_string(char *string); +} + +#define CLCXX_PACKAGE extern "C" CLCXX_ONLY_EXPORTS void diff --git a/example/custom-lib/include/clcxx/type_conversion.hpp b/example/custom-lib/include/clcxx/type_conversion.hpp new file mode 100755 index 0000000..e6abef1 --- /dev/null +++ b/example/custom-lib/include/clcxx/type_conversion.hpp @@ -0,0 +1,617 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "clcxx_config.hpp" +#include "hash_type.hpp" +#include "memory.hpp" + +namespace clcxx { +// supported types are Primitive CFFI Types with +// void + complex + struct + class +// POD structs are passed through libffi +// structs,class are passed as pointers with *new* +// (e.g. (:pointer :char)) +// (:array :int 10) +// for nonPOD struct, class, pointer +// references are converted to pointers + +extern "C" typedef union { + float Float; + double Double; + // long double LongDouble; +} ComplexType; + +extern "C" typedef struct { + ComplexType real; + ComplexType imag; +} LispComplex; + +template +inline std::string general_class_name(); +template +inline std::string pod_class_name(); + +namespace internal { +template +struct is_complex { + static constexpr bool value = std::is_same_v> || + std::is_same_v>; +}; + +template +inline constexpr bool is_complex_v = is_complex::value; + +template +struct is_pod_struct { + static constexpr bool value = std::is_trivial_v && + std::is_standard_layout_v && + std::is_class_v && !is_complex_v; +}; + +template +inline constexpr bool is_pod_struct_v = is_pod_struct::value; + +template +struct is_std_string { + static constexpr bool value = + std::is_same_v, std::string>; +}; + +template +inline constexpr bool is_std_string_v = is_std_string::value; + +template +struct is_general_class { + static constexpr bool value = !(is_std_string_v || is_complex_v || + is_pod_struct_v)&&std::is_class_v; +}; + +template +inline constexpr bool is_general_class_v = is_general_class::value; + +namespace detail { +template +struct unused_type {}; + +template +struct DefineIfDifferent { + typedef T1 type; +}; + +template +struct DefineIfDifferent { + typedef unused_type type; +}; + +template +using define_if_different = typename DefineIfDifferent::type; + +} // namespace detail + +/// Convenience function to get the lisp data type associated with T +template +struct static_type_mapping { + typedef typename std::conditional_t, T, void *> type; + static std::string lisp_type() { + static_assert(std::is_class_v, "Unkown type"); + using ClassT = std::remove_cv_t; + if constexpr (is_pod_struct_v) + return std::string("(:struct " + pod_class_name() + ")"); + else + return std::string("(:class " + general_class_name() + ")"); + } +}; + +template <> +struct static_type_mapping { + typedef char type; + static std::string lisp_type() { return ":char"; } +}; +template <> +struct static_type_mapping< + detail::define_if_different> { + typedef unsigned char type; + static std::string lisp_type() { return ":uchar"; } +}; +template <> +struct static_type_mapping> { + typedef short type; + static std::string lisp_type() { return ":short"; } +}; +template <> +struct static_type_mapping< + detail::define_if_different> { + typedef unsigned short type; + static std::string lisp_type() { return ":ushort"; } +}; +template <> +struct static_type_mapping> { + typedef int type; + static std::string lisp_type() { return ":int"; } +}; +template <> +struct static_type_mapping< + detail::define_if_different> { + typedef unsigned int type; + static std::string lisp_type() { return ":uint"; } +}; +template <> +struct static_type_mapping> { + typedef long type; + static std::string lisp_type() { return ":long"; } +}; +template <> +struct static_type_mapping< + detail::define_if_different> { + typedef unsigned long type; + static std::string lisp_type() { return ":ulong"; } +}; +template <> +struct static_type_mapping> { + typedef long long type; + static std::string lisp_type() { return ":llong"; } +}; +template <> +struct static_type_mapping< + detail::define_if_different> { + typedef unsigned long long type; + static std::string lisp_type() { return ":ullong"; } +}; +template <> +struct static_type_mapping { + typedef int8_t type; + static std::string lisp_type() { return ":int8"; } +}; +template <> +struct static_type_mapping { + typedef uint8_t type; + static std::string lisp_type() { return ":uint8"; } +}; +template <> +struct static_type_mapping { + typedef int16_t type; + static std::string lisp_type() { return ":int16"; } +}; +template <> +struct static_type_mapping { + typedef uint16_t type; + static std::string lisp_type() { return ":uint16"; } +}; +template <> +struct static_type_mapping { + typedef int32_t type; + static std::string lisp_type() { return ":int32"; } +}; +template <> +struct static_type_mapping { + typedef uint32_t type; + static std::string lisp_type() { return ":uint32"; } +}; +template <> +struct static_type_mapping { + typedef int64_t type; + static std::string lisp_type() { return ":int64"; } +}; +template <> +struct static_type_mapping { + typedef uint64_t type; + static std::string lisp_type() { return ":uint64"; } +}; +template <> +struct static_type_mapping { + typedef float type; + static std::string lisp_type() { return ":float"; } +}; +template <> +struct static_type_mapping { + typedef double type; + static std::string lisp_type() { return ":double"; } +}; +// template <> struct static_type_mapping { +// typedef long double type; +// static std::string lisp_type() { return ":long-double"; } +// }; + +// References +template +struct static_type_mapping { + // reference to any type => passed and returned as pointers + typedef void *type; + static std::string lisp_type() { + return std::string("(:reference " + static_type_mapping::lisp_type() + + ")"); + } +}; + +template +struct static_type_mapping { + // reference to any type => passed and returned as pointers + typedef const void *type; + static std::string lisp_type() { + return std::string("(:const-reference " + + static_type_mapping::lisp_type() + ")"); + } +}; + +// resolve const types +template +struct static_type_mapping { + using l_type = typename static_type_mapping::type; + typedef l_type const type; + static std::string lisp_type() { return static_type_mapping::lisp_type(); } +}; + +template +struct static_type_mapping { + typedef void *type; + static std::string lisp_type() { + return std::string("(:pointer " + static_type_mapping::lisp_type() + + ")"); + } +}; + +template +struct static_type_mapping { + typedef T type[N]; + static std::string lisp_type() { + return std::string("(:array " + static_type_mapping::lisp_type() + " " + + N + ")"); + } +}; +template +struct static_type_mapping { + typedef T type[N]; + static std::string lisp_type() { + return std::string("(:array " + static_type_mapping::lisp_type() + " " + + N + ")"); + } +}; + +template <> +struct static_type_mapping { + typedef bool type; + static std::string lisp_type() { return ":bool"; } +}; +template <> +struct static_type_mapping { + typedef const char *type; + static std::string lisp_type() { return ":string+ptr"; } +}; +template <> +struct static_type_mapping { + typedef const char *type; + static std::string lisp_type() { return ":string+ptr"; } +}; +template <> +struct static_type_mapping { + typedef void type; + static std::string lisp_type() { return ":void"; } +}; + +template <> +struct static_type_mapping> { + typedef LispComplex type; + static std::string lisp_type() { + return std::string(std::string("(:complex ") + + static_type_mapping::lisp_type() + ")"); + } +}; + +template <> +struct static_type_mapping> { + typedef LispComplex type; + static std::string lisp_type() { + return std::string(std::string("(:complex ") + + static_type_mapping::lisp_type() + ")"); + } +}; + +// ------------------------------------------------------------------// +// Box an automatically converted value +/// Wrap a C++ pointer in void pointer lisp cffi +template +inline LispT box(CppT cpp_val) { + static_assert(std::is_pointer_v, "Box unkown type"); + return static_cast(cpp_val); +} + +template <> +inline const char *box(const char *str) { + const auto n = std::strlen(str); + auto new_str = static_cast( + MemPool().allocate((n + 1) * sizeof(char), std::alignment_of_v)); + std::strcpy(new_str, str); + new_str[n] = '\0'; + return new_str; +} + +template <> +inline LispComplex box(std::complex x) { + LispComplex c; + c.real.Float = std::real(x); + c.imag.Float = std::imag(x); + return c; +} + +template <> +inline LispComplex box(std::complex x) { + LispComplex c; + c.real.Double = std::real(x); + c.imag.Double = std::imag(x); + return c; +} + +// template <> inline LispComplex box(std::complex x) { +// LispComplex c; +// c.real.LongDouble = std::real(x); +// c.imag.LongDouble = std::imag(x); +// return c; +// } + +// unbox -----------------------------------------------------------------// +/// pointers +template +CppT unbox(LispT lisp_val) { + static_assert(std::is_pointer_v, "Box unkown type"); + return static_cast(lisp_val); +} + +template <> +inline const char *unbox(const char *v) { + return v; +} + +template <> +inline std::complex unbox(LispComplex v) { + return std::complex(v.real.Float, v.imag.Float); +} + +template <> +inline std::complex unbox(LispComplex v) { + return std::complex(v.real.Double, v.imag.Double); +} + +// template <> inline std::complex unbox(LispComplex v) { +// return std::complex(v.real.LongDouble, v.imag.LongDouble); +// } + +///////////////////////////////////// + +// Base template for converting to CPP +template +struct ConvertToCpp { + using LispT = void; + template + inline CppT *operator()(T &&) { + static_assert(sizeof(CppT) == 0, + "No appropriate specialization for ConvertToCpp"); + return nullptr; + } +}; + +// Fundamental/array/pod types conversion +template +struct ConvertToCpp || + std::is_array_v || is_pod_struct_v>> { + using LispT = typename static_type_mapping::type; + inline CppT operator()(LispT lisp_val) const { + static_assert(std::is_same_v, "Fundamental type mismatch"); + return lisp_val; + } +}; + +namespace detail { +template +struct RefToCpp { + // reference to pointer + CppT operator()(LispT lisp_val) const { + auto obj_ptr = + static_cast *>(lisp_val); + return *obj_ptr; + } +}; +} // namespace detail + +// reference conversion +template +struct ConvertToCpp>> { + using LispT = typename static_type_mapping::type; + CppT operator()(LispT lisp_val) const { + static_assert( + std::is_same_v || std::is_same_v, + "type mismatch"); + return detail::RefToCpp()(lisp_val); + } +}; + +// pointers conversion +template +struct ConvertToCpp>> { + using LispT = typename static_type_mapping::type; + CppT operator()(LispT lisp_val) const { return unbox(lisp_val); } +}; + +// complex numbers types +template +struct ConvertToCpp>> { + using LispT = typename static_type_mapping::type; + CppT operator()(LispT lisp_val) const { return unbox(lisp_val); } +}; + +// strings +template +struct ConvertToCpp>> { + using LispT = typename static_type_mapping::type; + std::string operator()(const char *str) const { + static_assert(std::is_same_v, "type mismatch"); + return std::string(ConvertToCpp()(str)); + } +}; + +// class +template +struct ConvertToCpp>> { + using LispT = typename static_type_mapping::type; + // return reference to avoid extra copy + CppT &operator()(LispT class_ptr) const { + static_assert(std::is_same_v, void *>, + "type mismatch"); + auto cpp_class_ptr = static_cast(class_ptr); + return *cpp_class_ptr; + } +}; + +// Base template for converting To lisp +template +struct ConvertToLisp { + using type = typename static_type_mapping::type; + template + LispT *operator()(CppT &&) { + static_assert(sizeof(CppT) == 0, + "No appropriate specialization for ConvertToLisp"); + return nullptr; + } +}; + +template +struct ConvertToLisp>> { + using type = typename static_type_mapping::type; + template + void operator()() { + static_assert(sizeof(CppT) == 0, + "No appropriate specialization for ConvertToLisp"); + } +}; + +// Fundamental type conversion +template +struct ConvertToLisp && + (std::is_fundamental_v || + std::is_array_v || + is_pod_struct_v)>> { + using type = typename static_type_mapping::type; + CppT operator()(CppT cpp_val) const { + static_assert(std::is_same_v, "type mismatch"); + return cpp_val; + } +}; + +namespace detail { +template +struct RefToLisp { + LispT operator()(CppT cpp_val) const { + return static_cast(std::addressof(cpp_val)); + } +}; +} // namespace detail + +// Reference conversion +template +struct ConvertToLisp>> { + // reference to fundamental type + using type = typename static_type_mapping::type; + using LispT = typename static_type_mapping::type; + LispT operator()(CppT cpp_val) const { + static_assert( + std::is_same_v || std::is_same_v, + "type mismatch"); + return detail::RefToLisp()(cpp_val); + } +}; + +// pointers conversion +template +struct ConvertToLisp>> { + using type = typename static_type_mapping::type; + using LispT = typename static_type_mapping::type; + LispT operator()(CppT cpp_val) const { + static_assert(std::is_pointer_v, "type mismatch"); + return box(cpp_val); + } +}; + +// complex numbers types +template +struct ConvertToLisp>> { + using type = typename static_type_mapping::type; + using LispT = typename static_type_mapping::type; + LispT operator()(CppT cpp_val) const { + static_assert(std::is_same_v, "type mismatch"); + return box(cpp_val); + } +}; + +// Srings +template +struct ConvertToLisp>> { + using type = typename static_type_mapping::type; + const char *operator()(const std::string &str) const { + static_assert(std::is_same_v, "type mismatch"); + return ConvertToLisp()(str.c_str()); + } +}; + +// class exclude std::string +template +struct ConvertToLisp>> { + using type = typename static_type_mapping::type; + using LispT = typename static_type_mapping::type; + LispT operator()(CppT cpp_class) const { + static_assert(std::is_same_v, void *>, + "type mismatch"); + + auto obj_ptr = static_cast( + MemPool().allocate(sizeof(CppT), std::alignment_of_v)); + ::new (obj_ptr) CppT(std::move(cpp_class)); + return static_cast(obj_ptr); + } +}; + +template +struct CppTypeAdapter { + using type = T; +}; +template +struct CppTypeAdapter< + T, typename std::enable_if_t>> { + using type = T &; +}; + +} // namespace internal + +template +inline std::string LispType() { + return internal::static_type_mapping::lisp_type(); +} + +///// +template +using ToLisp_t = typename internal::ConvertToLisp::type; + +template +using ToCpp_t = typename internal::CppTypeAdapter::type; + +/// Conversion to lisp +template +inline ToLisp_t ToLisp(CppT cpp_val) { + return internal::ConvertToLisp()(std::forward(cpp_val)); +} + +/// Conversion to C++ +template +inline ToCpp_t ToCpp(LispT &&lisp_val) { + return internal::ConvertToCpp()(std::forward(lisp_val)); +} + +} // namespace clcxx diff --git a/example/custom-lib/src/my-lib.cpp b/example/custom-lib/src/my-lib.cpp new file mode 100644 index 0000000..887abaf --- /dev/null +++ b/example/custom-lib/src/my-lib.cpp @@ -0,0 +1,40 @@ +#include +#include "clcxx/clcxx.hpp" + +// standard C function definitions + +std::string greet() { return "Hello, World"; } +int Int(int x) { return x + 100; } +float Float(float y) { return y + 100.34; } +auto gr(std::complex x) { return x; } +std::string hi(char* s) { return std::string("hi, " + std::string(s)); } +void ref_int(int& x) { x += 30; } + +// standard C++ class definition with members and method + +class xx { + public: + xx(int xx, int yy) : y(yy), x(xx) {} + std::string greet() { return "Hello, World"; } + int y; + int x; +}; +void ref_class(xx& x) { x.y = 1000000; } + +// definitions of the API exposure to Common Lisp + +CLCXX_PACKAGE TEST(clcxx::Package& pack) { + pack.defun("hi", F_PTR(&hi)); + pack.defun("test-int", F_PTR(&Int)); + pack.defun("greet", F_PTR(&greet)); + pack.defun("test-float", F_PTR(&Float)); + pack.defun("test-complex", F_PTR(&gr)); + pack.defun("ref-int", F_PTR(&ref_int)); + pack.defun("ref-class", F_PTR(&ref_class)); + pack.defclass("cl-xx") + .member("y", &xx::y) + .defmethod("foo", F_PTR(&xx::greet)) + .defmethod("foo.x", F_PTR([](xx x){return x.x;})) + .constructor() + ; +} diff --git a/example/export-syms.lisp b/example/export-syms.lisp new file mode 100644 index 0000000..abf4a29 --- /dev/null +++ b/example/export-syms.lisp @@ -0,0 +1,41 @@ +;;; +;;; export-syms.lisp +;;; +;;; ********************************************************************** +;;; Copyright (c) 2023 Orm Finnendahl +;;; +;;; Revision history: See git repository. +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the Gnu Public License, version 2 or +;;; later. See https://www.gnu.org/licenses/gpl-2.0.html for the text +;;; of this agreement. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; ********************************************************************** + +(in-package :my-cl-lib) + +;;; optional exports from my-cl-lib to make the symbols of cl-test +;;; available. For this to work you have to uncomment (use-package +;;; "cl-test") in load-lisp.lib + +(export + '(y.set + create-xx + cxx-ptr + destruct-xx + foo + foo.x + greet + hi + ref-class + ref-int + test-complex + test-float + test-int + y.get)) diff --git a/example/load-lib.lisp b/example/load-lib.lisp new file mode 100644 index 0000000..61126cb --- /dev/null +++ b/example/load-lib.lisp @@ -0,0 +1,29 @@ +;;;; load-lib.lisp + +(in-package :my-cl-lib) + +;;; change this to the load path of libClCxx +(pushnew (pathname "/usr/local/lib/") + cffi:*foreign-library-directories* + :test #'equal) + +(cffi:define-foreign-library clcxx + (t (:default "libClCxx"))) + +(pushnew (asdf:system-relative-pathname :my-cl-lib "custom-lib/lib/") + cffi:*foreign-library-directories* + :test #'equal) + +(cffi:define-foreign-library my-lib + (t (:default "libMyLib"))) + +(cffi:use-foreign-library clcxx) +(cffi:use-foreign-library my-lib) + +(cxx:init) + +(cxx:add-package "CL-TEST" "TEST") + +;;; optionally import the symbols of cl-test into my-cl-lib +;;; (use-package :cl-test) + diff --git a/example/my-cl-lib.asd b/example/my-cl-lib.asd new file mode 100644 index 0000000..567d68b --- /dev/null +++ b/example/my-cl-lib.asd @@ -0,0 +1,13 @@ +;;;; cl-link.asd + +(asdf:defsystem #:my-cl-lib + :description "example for cl-cxx" + :author "Orm Finnendahl " + :license "gpl 2.0 or later" + :version "0.0.1" + :serial t + :depends-on (:cffi :cxx) + :components ((:file "package") + (:file "load-lib") +;;; (:file "export-syms") + )) diff --git a/example/package.lisp b/example/package.lisp new file mode 100755 index 0000000..586b2db --- /dev/null +++ b/example/package.lisp @@ -0,0 +1,7 @@ +;;;; package.lisp +;; +;;;; Copyright (c) 2023 Orm Finnendahl + +(defpackage #:my-cl-lib + (:use #:cl) + ) diff --git a/example/scratch.lisp b/example/scratch.lisp new file mode 100644 index 0000000..39c0761 --- /dev/null +++ b/example/scratch.lisp @@ -0,0 +1,36 @@ +;;; +;;; scratch.lisp +;;; + +(in-package :my-cl-lib) + +(cl-test:greet) +;;; -> "Hello, World" + +(defparameter *testclass* (cl-test:create-cl-xx2 10 20)) +;;; -> *TESTCLASS* + +(cl-test:foo *testclass*) +;;; -> "Hello, World" + +(cl-test:foo.x *testclass*) +;;; -> 10 + +(cl-test:y.get *testclass*) +;;; -> 20 + +(cl-test:y.set *testclass* 22) +;;; ; No value + + + +;;; import the symbols to avoid the leading cl-test: in the function +;;; calls + +(use-package :cl-test) + +(greet) +;;; -> "Hello, World" + + + diff --git a/package.lisp b/package.lisp old mode 100755 new mode 100644