<aside> ⁉️ memgraph repo stop using LCP at https://github.com/memgraph/memgraph/pull/814 primarily because it wasn’t possible/easy to make SBCL/Quicklisp work on all required operating systems

</aside>

In our development process we are using Common Lisp to generate some parts of the C++ codebase. The idea behind this is supplementing C++ with better meta-programming capabilities to automate tasks and prevent bugs due to code duplication. Primary candidate for using more powerful meta-programming is generating serialization code. Such code is almost always the same: go through all struct or class members and invoke the serialization function on them. Writing such code manually is error prone when adding members, because you may easily forget to correctly update the serialization code. Thus, the Lisp C++ Preprocessor was born. It is hooked in our build process as a step before compilation. The remainder of the document describes how to use LCP and its features.

Contents

Running LCP

You can generate C++ from an LCP file by running the following command.

./tools/lcp <path-to-file.lcp>

The LCP will produce a path-to-file.hpp file and potentially a path-to-file.lcp.cpp file. The .cpp file is generated if some parts of the code need to be in the implementation file. This is usually the case when generating serialization code. Note that the .cpp file has the extension appended to .lcp, so that you are free to define your own path-to-file.cpp which includes the generated path-to-file.hpp.

One serialization format uses Cap’n Proto library, but to use it, you need to provide an ID. The ID is generated by invoking capnp id. When you want to generate Cap’n Proto serialization, you need to pass the generated ID to LCP.

./tools/lcp <path-to-file.lcp> $(capnp id)

Generating Cap’n Proto serialization will produce an additional file, path-to-file.capnp, which contains the serialization schema.

You may wonder why the LCP doesn’t invoke capnp id itself. Unfortunately, such behaviour would be wrong when running LCP on the same file multiple times. Each run would produce a different ID and the serialization code would be incompatible between versions.

CMake

The LCP is run in CMake using the add_lcp function defined in CMakeLists.txt. You can take a look at the function documentation there for information on how to add your new LCP files to the build system.

Writing LCP

A LCP file should have the .lcp extension, but the code written is completely valid Common Lisp code. This means that you have a complete language at your disposal before even the C++ is compiled. You can view this as similar to the C++ templates and macros, but they do not have access to a complete language.

Besides Common Lisp, you are allowed to write C++ code verbatim. This means that C++ and Lisp code coexist in the file. How to do that, as well as other features are described below.

Inlining C++ in Common Lisp

To insert C++ code, you need to use a #>cpp ... cpp<# block. This is most often used at the top of the file to write Doxygen documentation and put some includes. For example:

#>cpp
/// @file My Doxygen style documentation about this file

#pragma once

#include <vector>
cpp<#