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-LISP
package. TheHELLO-WORLD
package is defined to correspond to the C++ namespacehw
, andHWPkg
is 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_EXPOSE
preprocessor 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
sc
local variable to the Lisp package defined earlier. - Exposes the C++ function
helloWorld
to 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
def
are required: the name of the function and a pointer to it. - The lambda list for the function is provided as a policy, using the
_ll
user literal described below in LambdaList. - A documentation string is provided with the
_docstring
user 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
DerivableMatchCallback
class for the next piece,GCInfo
. - A
gctools::GCInfo
template 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
DerivableMatchCallback
cannot be moved or automatically collected. They need to be managed manually and carefully so that they do not leak memory. - The
DerivableMatchCallback
inherits from a special template class,clbind::Derivable<clang::ast_matchers::MatchFinder::MatchCallback>
. This makes it inherit from both the C++ classMatchCallback
and the ClaspInstance_O
class, which adds Common Lisp slots to the object. - The
AlienBase
type needs to be defined forderivable_class_
to function. - The
virtual void run(...) {...}
method is defined byclang::ast_matchers::MatchFinder::MatchCallback
and 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 therun
method, thendefault_run
should call it. If no C++ base class defines the run method, then an error should be signalled, and the programmer must provide arun
method in Common Lisp. - In this example, the
onStartOfTranslationUnit~/~default_onStartOfTranslationUnit
are another pair of functions that allow the user to overload an on-start-of-translation-unit method from Common Lisp. - A
describe
method is provided to print internal information about aDerivableMatchCallback
instance. - The
DerivableMatchCallback
class 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:defclass
as usual. It is a subclass of the exposed class. It has the special metaclass:metaclass core:derivable-cxx-class
. - The
core:defvirtual
macro is used to overload theasttooling:run
method. The overloaded method takes two arguments. The first argument is the instance,self
, and the second argument was passed from the C++run
virtual 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-WORLD
package. - Create a type translator that translates Common Lisp symbols into
ColorEnum
values. 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::red
with 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
ColorEnum
values 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) } }; };
DeclareType
is used by clbind, and must be defined in to thefrom_object
type.- We store the translated result into the
_v
field. 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
obj
doesn'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
_v
field is initialized using a Common Lisp value. - In the second form of the
from_object
translator the_v
field is left uninitialized. - It's really important to define a non-trivial destructor. Without one, the
_v
field 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 theaddMul
function 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
.