Exposing C++ Libraries with clbind
Table of Contents
1. General information
clbind is based on luabind with some ideas borrowed from pybind11 and boost::python.
In order to expose C++ code to Lisp, you will need to write a bridge file, which is a C++ source file. This file uses the Clasp extension API to describe to Clasp exactly what of your system should be exposed, and how.
2. Defining bindings
Clasp is able to load C++ libraries and expose functions, classes, methods and enums to Clasp Common Lisp. Clasp needs a function that it can call at startup to expose these library members. This function will be present in your bridge file, along with several other markers used to inform Clasp of the nature of your library.
The CL_EXPOSE preprocessor macro is used to mark the startup function.
Within the startup function, packages can be declared. Functions, classes, methods and enums
can be added to packages, and they will then automatically be available within Clasp.
Here is an example of a simple bridge file. The code for this and further examples is available on GitHub.
#include <clasp/clasp.h> // (1)
void helloWorld() {
printf("Hello World\nThis is C++ code being invoked from Clasp Common Lisp\n");
}
PACKAGE_USE("COMMON-LISP");
PACKAGE_NICKNAME("HW");
NAMESPACE_PACKAGE_ASSOCIATION(hw,HWPkg,"HELLO-WORLD"); // (2)
namespace hw { // (3)
CL_EXPOSE // (4)
void hello_world_startup () { // (5)
using namespace clbind; // (6)
package_ sc(HWPkg); // (7)
sc.def("hello-world", &helloWorld, // (8)
"The classic! Print Hello world"_docstring);
}
- This header makes the exposure API available.
- These three lines define a Lisp package named
HELLO-WORLD, with nicknameHW, which uses the standardCOMMON-LISPpackage. TheHELLO-WORLDpackage is defined to correspond to the C++ namespacehw, andHWPkgis defined to refer to this package from within C++, for subsequent definitions. - To avoid symbol clashes in C++, we recommend putting your code in a unique C++ namespace.
- The
CL_EXPOSEpreprocessor macro is used to identify the startup function. This function will be run when Clasp starts up to define the package. - The name of the function is arbitrary.
- For convenience we use the clbind namespace, but you can use explicit prefixes if you like.
- Binds the
sclocal variable to the Lisp package defined earlier. - Exposes the C++ function
helloWorldto Lisp ashello-world:hello-world, and supplies a docstring.
After building Clasp with this extension it can be accessed from within Lisp.
COMMON-LISP-USER> (hw:hello-world) Hello World This is C++ code being invoked from Clasp Common Lisp COMMON-LISP-USER>
3. Exposing Functions
Exposing functions is done via the function def:
template <typename F, class... Policies>
scope def(char const* name,
F f,
Policies... policies )
In this function
- name
- is the name that the function will have in clasp. It is automatically lispified, i.e. a name with camel-casing is converted to a downcase name with dashes in between. Using this automatic lispification is optional; see the Lispification section for details.
- f
- a pointer to the C++ function.
- policies
- Gives additional information (see Policies).
For example, suppose we have a C++ function
double addThreeNumbers(double x, double y, double z) {
return x + y + z;
}
and we want to expose this to Lisp, but with only one required parameter, the others being optional and defaulting to zero. We could do that like so:
PACKAGE_NICKNAME("HW");
NAMESPACE_PACKAGE_ASSOCIATION(hw,HWPkg,"HELLO-WORLD");
namespace hw {
CL_EXPOSE
void hello_world_startup() {
using namespace clbind;
package_ sc(HWPkg);
sc.def("hello-world",&helloWorld,
"The classic! Print \"Hello World\""_docstring);
sc.def("addThreeNumbers",&addThreeNumbers, // (1)
"(x cl:&optional (y 0.0) (z 0.0))"_ll, // (2)
"Add three numbers and return the result"_docstring); // (3)
- Only the first two arguments to
defare required: the name of the function and a pointer to it. - The lambda list for the function is provided as a policy, using the
_lluser literal described below in LambdaList. - A documentation string is provided with the
_docstringuser literal.
From Clasp, this function can be called with
(hw:add-three-numbers 1), (hw:add-three-numbers 1 2), or (hw:add-three-numbers 1 2 3).
clbind performs automatic conversions, similar to C++, in order to translate Lisp numbers
of different types into C++ numbers.
The corresponding C++ calls would be addThreeNumbers(1,0,0), addThreeNumbers(1,2,0),
and addThreeNumbers(1,2,3), respectively.
The lambda list and documentation string can be seen using the Common Lisp function describe:
COMMON-LISP-USER> (describe 'hw:add-three-numbers) HELLO-WORLD:ADD-THREE-NUMBERS - external symbol in HELLO-WORLD package ----------------------------------------------------------------------------- HELLO-WORLD:ADD-THREE-NUMBERS [Function] Documentation: "Add three numbers and return the result" Arguments: (HELLO-WORLD::X &OPTIONAL (HELLO-WORLD::Y 2.0) (HELLO-WORLD::Z 3.0)) Source: #P"=external=" -----------------------------------------------------------------------------
3.1. Overloaded Functions
To expose overloaded functions, you have to cast the function pointer to the correct signature. Suppose the function from the previous example was overloaded. It would then need to be exposed as follows:
def("addThreeNumbers-double", (double(*)(double, double, double)) &addThreeNumbers),
It is important that every function have a unique Lisp name - similar to name mangling in C++. The convention we adopt in Clasp is to append type names to the original name.
4. Exposing Classes
Exposing a class is done via the class class_.
For example, say we have the class DoubleVector below:
class DoubleVector {
private:
vector<double> values;
public:
DoubleVector(int sz) {this->values.resize(sz);};
DoubleVector(const vector<double>& arg) {
this->fill(arg);
}
//...
};
PACKAGE_USE("COMMON-LISP");
PACKAGE_NICKNAME("DV");
NAMESPACE_PACKAGE_ASSOCIATION(hw,HWPkg,"DOUBLE-VECTOR");
namespace dv {
CL_EXPOSE
void double_vector_startup() {
using namespace clbind;
package_ s("DV");
class_<DoubleVector>(s,"double-vector" )
//...
This code creates a binding to the C++ class DoubleVector, with the name
dv:double-vector in Lisp.
This does not automatically creates a binding to the default constructor - use def_constructor for that.
4.1. Inheritance
clbind can handle member functions of derived classes correctly, provided that they are all exposed. To expose the inheritance structure of C++ classes, expose the base class, and then use the following definition format for the derived class:
class_<CppDerivedClassName, CppBaseClassName>("lisp-class-name")
If multiple inheritance brings in several base classes, use the following format:
class_<CppDerivedClassName, bases<CppBaseClassName1, CppBaseClassName2>>("lisp-class-name")
If a base class is a pure virtual class, i.e. it has only pure virtual functions or its
constructor is private, make sure to use the no_default_constructor option when exposing
the base class, or you will get a compilation error.
4.2. Constructors
Add constructors to exposed classes with the function def_constructor:
template<typename... Types, typename... Policies>
class_& def_constructor(const string& name,
constructor<Types...> sig,
Policies... policies)
In this function
- name
- is the name of the constructor that will be visible in clasp. Again, it will be lispified.
- sig
- is the parameter signature of the C++ constructor.
Use a comma-separated parameter-list list in the form
constructor<parameter-list>()of all the types used as parameters in the constructor you want to expose. - policies
- 7.
4.3. Member Functions
Exposing member functions is similar to exposing free functions.
Call the class_ member function def:
template<class F, class... Policies>
class_& def(char const* name,
F fn,
Policies... policies )
Thus exposing a member function is not different from exposing free functions, and the same arguments apply.
The exception to this rule is the lambda-list (arguments),
which always requires self as its first parameter,
which becomes the this argument within the method.
namespace dv {
CL_EXPOSE
void double_vector_startup() {
using namespace clbind;
package_ s("DV");
class_<DoubleVector>(s,"double-vector" )
. def_constructor("make-double-vector-with-size",constructor<int>())
. def_constructor("make-double-vector-with-values",constructor<const vector<double>&>())
. def("fill",&DoubleVector::fill)
. def("add",&DoubleVector::add)
. def("dot",&DoubleVector::dot)
. def("at",&DoubleVector::at)
. def("dump",&DoubleVector::dump);
}
};
4.4. Static Member Function
As Common Lisp does not have the notion of static member functions, exposing them is similar to exposing free functions.
4.5. Public Member Variables
Exposing public member variables works similar to exposing member functions.
4.6. Derivable classes
Some C++ libraries provide base classes that the library user is meant to subclass to add additional application specific functionality. For this situation Clasp, allows one to create classes in Common Lisp that derive from these C++ classes, and implement methods that may be called from both C++ and Common Lisp code.
An example of this is within Clasp itself - where Clasp exposes Clang's ASTMatchers library. Clasp exposes a facility of the Clang ASTMatcher library that evaluates callbacks on Clang's abstract syntax trees.
To make a class derivable, in place of class_, use derivable_class_, and provide two class template arguments.
The first template argument is a class that needs to be provided to clbind and is shown below (in this case DerivableMatchCallback).
The second template argument is the original library class that is to be subclassed (in this case clang::ast_matchers::MatchFinder::MatchCallback).
derivable_class_<DerivableMatchCallback, clang::ast_matchers::MatchFinder::MatchCallback> cl_bc(m,"MatchCallback",create_default_constructor);
cl_bc.def("run", &DerivableMatchCallback::default_run)
.def("onStartOfTranslationUnit", &DerivableMatchCallback::default_onStartOfTranslationUnit)
.def("onEndOfTranslationUnit", &DerivableMatchCallback::default_onEndOfTranslationUnit);
The DerivableMatchCallback must be defined before the derivable_class_ declaration above.
namespace asttooling {
class DerivableMatchCallback; // (1)
};
template <> // (2)
struct gctools::GCInfo<asttooling::DerivableMatchCallback> {
static bool constexpr NeedsInitialization = false;
static bool constexpr NeedsFinalization = false;
static GCInfo_policy constexpr Policy = unmanaged; // (3)
};
namespace asttooling {
class DerivableMatchCallback
: public clbind::Derivable<clang::ast_matchers::MatchFinder::MatchCallback> { // (4)
typedef clang::ast_matchers::MatchFinder::MatchCallback AlienBase; // (5)
public:
virtual void run(const clang::ast_matchers::MatchFinder::MatchResult &Result) { // (6)
const clang::ast_matchers::MatchFinderMatchResult conv(Result);
core::T_sp val = translate::to_object<const clang::ast_matchers::MatchFinderMatchResult &>::convert(conv);
core::eval::funcall(asttooling::_sym_run, this->asSmartPtr(), val);
}
void default_run(const clang::ast_matchers::MatchFinderMatchResult &Result) { // (7)
SIMPLE_ERROR(BF("Subclass must implement"));
};
virtual void onStartOfTranslationUnit() { // (8)
printf("%s:%d entered onStartOfTranslationUnit funcalling\n", __FILE__, __LINE__);
core::eval::funcall(_sym_onStartOfTranslationUnit, this->asSmartPtr());
}
void default_onStartOfTranslationUnit() {
printf("%s:%d entered default_onStartOfTranslationUnit\n", __FILE__, __LINE__);
this->AlienBase::onStartOfTranslationUnit();
}
void describe() { // (9)
printf("%s:%d Entered DerivableMatchCallback::describe()\n", __FILE__, __LINE__);
printf("this=%p typeid(this)@%p typeid(this).name=%s\n", this, &typeid(this), typeid(this).name());
printf("dynamic_cast<void*>(this) = %p\n", dynamic_cast<void *>(this));
printf("dynamic_cast<core::T_O*>(this) = %p\n", dynamic_cast<core::T_O *>(this));
printf("typeid(dynamic_cast<core::T_O>*>(this))@%p typeid.name=%s\n", &typeid(dynamic_cast<core::T_O *>(this)), typeid(dynamic_cast<core::T_O *>(this)).name());
printf("dynamic_cast<Derivable<clang::ast_matchers::MatchFinder::MatchCallback>*>(this) = %p\n", dynamic_cast<Derivable<clang::ast_matchers::MatchFinder::MatchCallback> *>(this));
printf("dynamic_cast<DerivableMatchCallback*>(this) = %p\n", dynamic_cast<DerivableMatchCallback *>(this));
printf("alien pointer = %p\n", this->pointerToAlienWithin());
printf("_Class: %s\n", _rep_(this->_Class).c_str());
for (size_t i(0); i < this->numberOfSlots(); ++i) {
printf("_Slots[%lu]: %s\n", i, _rep_(this->instanceRef(i)).c_str());
}
}
virtual ~DerivableMatchCallback() { // (10)
// Non trivial dtor
}
};
};
- A forward declaration of the
DerivableMatchCallbackclass for the next piece,GCInfo. - A
gctools::GCInfotemplate struct is used to tell the Clasp memory manager how to deal with this class. The NeedsInitialization field tell the memory manager that the DerivableMatchCallback::initialize() function must be called after the object is allocated. The NeedsFinalization field tells the memory manager that the destructor for this class needs to be registered with a finalizer. NeedsFinalization is used for resources like streams and anything that needs cleanup when it is collected. The Policy tell the memory manager how the memory for this object is managed.- Policy = normal means the object is managed by the memory manager: it can be collected and it can be moved.
- Policy = collectable_immobile means the object can be collected by the memory manager but it cannot be moved.
- Policy = atomic means the object contains no internal pointers (such as strings or integer vectors) and so it can be placed in special memory that doesn't need to be scanned during garbage collection.
- Policy = unmanaged means the object will not be automatically collected and it cannot be moved. This is used in special cases like static vectors.
- Instances of
DerivableMatchCallbackcannot be moved or automatically collected. They need to be managed manually and carefully so that they do not leak memory. - The
DerivableMatchCallbackinherits from a special template class,clbind::Derivable<clang::ast_matchers::MatchFinder::MatchCallback>. This makes it inherit from both the C++ classMatchCallbackand the ClaspInstance_Oclass, which adds Common Lisp slots to the object. - The
AlienBasetype needs to be defined forderivable_class_to function. - The
virtual void run(...) {...}method is defined byclang::ast_matchers::MatchFinder::MatchCallbackand we need to overload it. The body of this method translates the argument(s) into Common Lisp types and then invokes a Common Lisp function,core::eval::funcall(asttooling::_sym_run, this->asSmartPtr(), val), that the programmer will define in Common Lisp. - The
void default_run(...)method is a non-virtual method that is exposed to Common Lisp. If a C++ base class defines therunmethod, thendefault_runshould call it. If no C++ base class defines the run method, then an error should be signalled, and the programmer must provide arunmethod in Common Lisp. - In this example, the
onStartOfTranslationUnit~/~default_onStartOfTranslationUnitare another pair of functions that allow the user to overload an on-start-of-translation-unit method from Common Lisp. - A
describemethod is provided to print internal information about aDerivableMatchCallbackinstance. - The
DerivableMatchCallbackclass should have a destructor.
In the above example, the run~/~default_run pair of methods demonstrate what you need to
do to overload the run C++ method from Common Lisp.
In Common Lisp, to create a derived class one would use
(defclass count-match-callback (ast-tooling:match-callback) () ;; (1)
(:metaclass core:derivable-cxx-class))
(core:defvirtual ast-tooling:run ((self count-match-callback) match) ;; (2)
(let* ((nodes (ast-tooling:nodes match))
(id-to-node-map (ast-tooling:idto-node-map nodes))
(node (gethash :whole id-to-node-map)))
(advance-match-counter)))
- The derived class is defined using
cl:defclassas usual. It is a subclass of the exposed class. It has the special metaclass:metaclass core:derivable-cxx-class. - The
core:defvirtualmacro is used to overload theasttooling:runmethod. The overloaded method takes two arguments. The first argument is the instance,self, and the second argument was passed from the C++runvirtual method.
5. Exposing Enums
enum ColorEnum { red, green, blue };
void printColor(ColorEnum color) {
switch (color) {
case red:
printf("red\n");
break;
case green:
printf("green\n");
break;
case blue:
printf("blue\n");
break;
}
}
// Then - to expose it...
PACKAGE_NICKNAME("HW");
NAMESPACE_PACKAGE_ASSOCIATION(hw,HWPkg,"HELLO-WORLD");
SYMBOL_EXPORT_SC_(HWPkg,STARcolorTranslatorSTAR); // (1)
CLBIND_TRANSLATE_SYMBOL_TO_ENUM(ColorEnum, hw::_sym_STARcolorTranslatorSTAR ); // (2)
namespace hw {
CL_EXPOSE
void hello_world_startup() {
printf("Entered %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__ );
using namespace clbind;
package_ s(HWPkg);
enum_<ColorEnum>(s,hw::_sym_STARcolorTranslatorSTAR) // (3)
.value("red",red) // (4)
.value("green",green)
.value("blue",blue);
s.def("printColor",&printColor); // (5)
}
- Export the symbol
*COLOR-TRANSLATOR*from theHELLO-WORLDpackage. - Create a type translator that translates Common Lisp symbols into
ColorEnumvalues. If given a symbol without a corresponding enum value, an error is signaled. - Define the enum binding
ColorEnum, and bind it to the symbolHW:*COLOR-TRANSLATOR*. - Define one enum value associating
ColorEnum::redwith the Lisp symbol'HW:RED. The symbols for the enum values will be in the same package as theHW:*COLOR-TRANSLATOR*. - Expose a function that accepts
ColorEnumvalues as its argument.
Then the enum can be used from within Clasp as follows:
COMMON-LISP-USER> (hw:print-color 'hw:red) red COMMON-LISP-USER> (hw:print-color 'hw:green) green COMMON-LISP-USER> (hw:print-color 'hw:blue) blue COMMON-LISP-USER> hw:*color-translator* #<SYMBOL-TO-ENUM-CONVERTER> COMMON-LISP-USER>
6. Translators
Translators are used to automatically convert C++ objects to Common Lisp objects and vice versa. This is a convenience functionality, allowing for easier interoperation. It is especially useful for small C++ classes and structs that are passed to and from functions and are meant to be created on the fly. Instead of exposing them and then creating and filling them from Common Lisp, it is often easier to write a translator, e.g. from a list, which makes it possible to pass a list as a parameter in place of the object. This list is then automatically converted to the respective C++ object by the translator.
6.1. Translation from C++ objects to Common Lisp objects
Translating from C++ to Common Lisp objects is done by specializing the templated struct
to_object, in the namespace translate, to the type of the C++ object.
The static function member function convert of that struct takes an object of that type
as a parameter, and returns the Common Lisp object. The translator must appear in the C++
source code before any functions/methods are exposed that need to use it. Translators are
incorporated into the template code that clbind generates for each exposed function/method.
Here is an example defining a conversion from std::pair<int,int> into a cl:cons. Once
this definition is in place, def can be used to expose C++ functions that take a
std::pair<int,int> as a parameter.
namespace translate
{
template <>
struct to_object<std::pair<int,int>>
{
static core::T_sp convert(std::pair<int,int> arg)
{
core::Cons_sp cons = core::Cons_O::create(core::Integer_O::create(arg.first),
core::Integer_O::create(arg.second));
return cons;
}
};
};
6.2. Translation from Common Lisp objects to C++ objects
Translating from Common Lisp to C++ objects is done by specializing the templated struct
from_object, in the namespace translate, to the C++ object type.
A constructor must be provided that takes the Common Lisp object as a parameter,
and writes the resulting C++ object into an object called _v.
from_object::DeclareType must be defined to be the C++ type in question.
Here is the converse of the above example, converting a Lisp cons into a std::pair<int,int>.
namespace translate
{
template <>
struct from_object<std::pair<int,int>>
{
typedef std::pair<int,int> DeclareType; // (1)
DeclareType _v;
from_object(core::T_sp obj)
{
if (obj.consp()) {
this->_v = std::make_pair(core::clasp_to_int(CONS_CAR(obj)), // (2)
core::clasp_to_int(CONS_CDR(obj)));
}
TYPE_ERROR(obj,cl::_sym_Cons_O); // (3)
}
};
};
DeclareTypeis used by clbind, and must be defined in to thefrom_objecttype.- We store the translated result into the
_vfield. This is so that if the argument is used as a return value from a Lisp function, the value can be recovered from here. - If the type of
objdoesn't match what this translator handles, then signal a type error that tells the user what types are accepted.
6.2.1. Advanced from_object translators
struct from_object takes a second template argument that can have the
value std::true_type or std::false_type. The default is std::true_type, and
it means that the _v instance variable will be initialized by the from_object
constructor using the Common Lisp value in the T_sp constructor argument.
std::false_type is subtle - it is used to express the pureOutValue<N> policy.
std::false_type means that the from_object translator does not initialize its _v
field. Instead, the field can be passed by reference to a function and written in to.
The wrapper will take the result out and return it as multiple return values.
template <>
struct from_object<int&,std::true_type> {
typedef int DeclareType;
int _v;
from_object(gctools::smart_ptr<core::T_O> vv) : _v(core::clasp_to_int(vv)) {}; // (1)
~from_object() { /* Non-trivial */ };
};
template <>
struct from_object<int&,std::false_type> {
typedef int DeclareType;
int _v;
from_object(gctools::smart_ptr<core::T_O> vv) {
(void)vv;
// Note - the _v field is NOT initialized! // (2)
};
~from_object() { // (3)
// non-trivial dtor to keep _v around
};
};
- In the first form of from_object the
_vfield is initialized using a Common Lisp value. - In the second form of the
from_objecttranslator the_vfield is left uninitialized. - It's really important to define a non-trivial destructor. Without one, the
_vfield gets overwritten by the C++ compiler.
7. Policies
Policies tell clbind how to handle return values and C++ arguments, and provide Clasp with miscellaneous extra information about the function.
7.1. pureOutValue<N>
Let's say you have a C++ function that uses reference parameters to output values, like this:
void addMul(int x, int y, int z, int& sum, int& product ) {
sum = x + y + z;
product = x * y * z;
}
Common Lisp doesn't have a concept of "pass-by-reference", but it does have multiple return
values. The pureOutValue<N> policy tells clbind that a C++ parameter passed by reference
should be translated into Lisp multiple return values.
using namespace clbind;
package_ pkg("HELLO-WORLD",{"HW"},{});
pkg.scope.def( "addMul", &addMul, pureOutValue<3>(), pureOutValue<4>() ); // 1
- The
pureOutValue<3>()andpureOutValue<4>()arguments tell clbind that the third and fourth arguments to theaddMulfunction references that are written into but not read from. The argument counting starts at 0.pureOutValue<N>()further says that these values can be passed in uninitialized. When the function returns the values in sum and product, they should be returned as the first and second multiple-return values.
COMMON-LISP-USER> (hw:add-mul 2 3 4) 9 24
The function returns the two values 9 and 24.
In that example, the function had void return type, so the references provided all
return values into Lisp. For other return types, clbind uses the returned value as the
primary return value into Lisp, and out references provide subsequent return values.
int returnThreeValues(int& second, int& third)
{
second = 2;
third = 3;
return 1;
}
//...
s.def("returnThreeValues",&returnThreeValues,
clbind::pureOutValue<0>(),
clbind::pureOutValue<1>());
COMMON-LISP-USER> (multiple-value-list (hw:return-three-values)) (1 2 3)
7.2. outValue<N>
In some cases, a reference parameter may be used both to pass values into a C++ function and out of it. For example, let's say you have a C++ function like this:
void addMulRunning(int x, int y, int z, int& sum, int& product ) {
sum = x + y + z + sum;
product = x * y * z * product;
}
In this case the outValue<N> policy tells clbind that values will be passed in to these
arguments and multiple return values will be returned using these arguments.
using namespace clbind;
package_ pkg("TEACH",{},{});
pkg.scope.def( "addMulRunning", &addMulRunning, outValue<3>(), outValue<4>() );
The outValue<3>() and outValue<4>() arguments tell clbind that the third and fourth
arguments to the addMulRunning function are references used as both arguments and return
values. The argument counting starts at 0.
COMMON-LISP-USER> (multiple-value-list (hw:add-mul-running 2 3 4 5 6)) (14 144)
As with pureOutValue, for non-void functions, the C++ return value will serve as the
primary Lisp return value, and the reference parameters will provide subsequent return values.
7.3. adopt<n>
adopt<n> is used to instruct clbind that a pointer to an object that is returned by a
function is to be managed by Clasp's memory manager. The template argument for adopt can
be "result", as in adopt<result>, to indicate the function return value pointer is to
be adopted. The template argument can also be an integer 0…N, as in adopt<0>, to
indicate that the first argument is a pointer that should be adopted by the memory manager.
adopt<i> when i is an integer must be combined with pureOutValue<i>.
7.4. LambdaList
A lambda list for the Lisp version of the function to be exposed can be provided as a policy.
The most convenient way to do this is to use the _ll user literal. The lambda list is
provided as a string, which clbind will parse as a Common Lisp lambda list. For example,
"(x y z)"_ll means the Lisp lambda list (x y z).
7.5. DocString
A documentation string, accessible by cl:documentation, can be provided as a policy.
Just pass "documentation string here"_docstring as an argument to def.
8. Lispification
Lispification is a process used to convert C++ identifiers into Common Lisp hyphenated names.
8.1. Camel case
Camel case strings are converted to hyphenated names by inserting hyphens into the final name whenever there is a transition between a lower case character and an upper case character.
A few examples:
- aCamelCaseName -> a-camel-case-name
- ANameWithANumber42 -> aname-with-anumber42
8.2. Underscores become hyphens
Examples:
- a_name_with_underscores -> a-name-with-underscores
- a_nameWithUnderscores -> a-name-with-underscores
Underscores and camel case can be mixed.
9. Building
Integrating an external library, or your own C++ code, into Clasp involves integrating it into Clasp's build system. The build system is described in more detail on its page, but briefly, here is how extensions can be integrated.
All you need to do is clone your extension into the clasp/extensions directory, and create
a cscript.lisp file in each directory of your extension.
For instance - suppose you have the following directory structure.
clasp
└── extensions
└── my-demo
└── src
└── my-demo.cc
The my-demo.cc file might look like:
#include <stdio.h>
#include <clasp/clasp.h>
void my_func()
{
printf("This is not the greatest function in the world. It's just a tribute!\n");
}
PACKAGE_USE("COMMON-LISP");
PACKAGE_NICKNAME("MD");
NAMESPACE_PACKAGE_ASSOCIATION(hw,HWPkg,"MY-DEMO");
namespace md {
CL_EXPOSE
void my_demo_startup() {
printf("Entered %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__ );
using namespace clbind;
package_ sc(HWPkg);
sc.def("my-func", &my_func);
}
In the extensions/my-demo directory you need to add a cscript.lisp file that looks like so.
(k:sources :iclasp #~"my-demo.cc") (k:systems :my-demo)
This informs Koga how to build your demo. Add a filename to `k:sources` for each source file. More information is available in the Koga documentation.
To actually build, run Koga and inform it of the extension. This can be done by running it
with an --extensions= parameter, or by mentioning the extension in your config.sexp.
For this example, you could use ./koga --extensions=my-demo for example. Once Koga has
configured things, simply build Clasp normally with ninja -C build.