Libraries · Tips & Tricks · Uncategorized

libclang for Delphi

You may know Clang as a compiler for the C family of languages. But is a library too; one that you can also use from Delphi to create tools to work with C(++) and Objective-C code. Tools such as header translators or C-to-Pascal converters.

About libclang

Clang is a C based language front-end for the LLVM compiler architecture. It compiles C, C++, Objective-C/C++ and other derivatives such as OpenCL C into LLVM bitcode, and ultimately into optimized platform-dependent machine code.

Parts of the Clang feature set are also exposed as a flat C library called libclang. In the early days of Clang, the functionality of this library was pretty limited and somewhat experimental. But libclang has evolved into a feature-rich library that can be used to create a variety of tools to work with C-family code. Although libclang will probably always expose just a small subset of Clangs features, the features it does expose are quite useful, such as:

  • Parse C-family code into an Abstract Syntax Tree (AST).
  • Load an AST from a file (such as a pre-compiled header).
  • Traverse the AST. Each AST node provides information such as source location, type of declaration, name, data type, visibility, linkage, templates and much more.
  • Retrieve parsing diagnostics (warnings and errors).
  • Provide suggestions for Code Completion based on a “cursor position”.
  • Extract comments and convert these to HTML or XML. Doxygen-style documentation tags are extracted as well.
  • And more…

For C-family developers, libclang can be used to write stand-alone coding tools (such as documentation generators) or tools that run in an IDE (such as refactoring tools or code completion tools). Libclang can be configured to continue parsing in case of syntax errors, which is important for IDE tools that work on code-in-progress.

libclang for Delphi Developers

For Delphi developers, libclang also provides interesting functionality, especially when it comes to exposing the AST. Examples that are of use for Delphi developers include:

  • Use libclang to build a (better) C-to-Pascal header converter. If Clang is able to parse a header file, then you can use libclang to automate the process of converting the header file to Pascal. I may provide such a tool in the future…
  • Use libclang to convert C code to Delphi code. In this case, we are not talking about just translating a header, but converting function bodies as well. If you are adventurous, you can even try to convert C++ code to Delphi (although, depending on the C++ features used, this can get very tricky).
  • Use libclang to create a flat C library for a set of C++ classes, and translate this flat C interface to Pascal for consumption in Delphi. This is something I am currently working on to interface with a particular C++ library.

There are probably some other creative uses as well…

libclang for Delphi

Libclang itself is a flat C library, so it is possible to use it from Delphi code. For this purpose, I translated the libclang header files to Delphi.

Unfortunately, I wasn’t able to use libclang itself (from Delphi) to translate the libclang header files. A classic bootstrapping situation…

However, to make it easier to deal with memory management and some C idiosyncrasies, I also created a thin class library on top of the C API that makes it easier to use libclang from Delphi code.

You can find these on my personal GitHub page in the Neslib.Clang respository.

This version (currently) only works on Windows. The repository includes version 6.0.0 of the libclang DLL for 32-bit Windows (which is over 50 MB!). If you prefer to use the 64-bit version instead, then you will have to download Clang for Windows (64-bit) from the LLVM Downloads page. Be sure to download version 6.0.0 since the API may not be compatible with older (or newer) versions. I will try to keep the repository up-to-date with official new Clang releases.

It should be possible to use libclang for Delphi on macOS and Linux as well. But this will probably require some code changes and additional testing. Pull requests are welcome if you want to help out!

High Level Interface

It is probably easiest to use the higher-level class library built on top of the libclang API. This class library wraps the C API almost completely, so there should rarely be a need to resort to the C API. However, the class library exposes the underlying C types (through Handle properties), so you can always interface with the C API as well should you want to.

The following example shows how you can parse a C(++) file and traverse the AST using this class library (this example is also included in the repository):

procedure Run;
var
  Index: IIndex;
  TU: ITranslationUnit;
  Cursor: TCursor;
begin
  Index := TIndex.Create(False, False);
  TU := Index.ParseTranslationUnit('sample.cpp', []);
  if (TU = nil) then
    raise Exception.Create('Unable to parse C++ file');

  Cursor := TU.Cursor;
  Cursor.VisitChildren(
    function(const ACursor, AParent: TCursor): TChildVisitResult
    begin
      Write(ACursor.Kind.Spelling);

      if (ACursor.Spelling = '') then
        WriteLn
      else
        WriteLn(' (', ACursor.Spelling, ')');

      Result := TChildVisitResult.Recurse;
    end);
end;

The starting point is usually the IIndex interface (and corresponding TIndex class). It contains a collection of translation units (source files) and provides methods for parsing them. You can pass Clang command line parameters to customize parsing.

libclang uses the concept of a “cursor” to represent an element in an AST, such as a statement, expression, type etc.  You can use the root cursor of the translation unit to recursively traverse the AST using the VisitChildren method. In the example above, we pass an anonymous method to VisitChildren. This method gets called for each node in the AST. We print some information about these nodes in this example. If you prefer, you can also pass an instance method to VisitChildren (instead of an anonymous method).

Consider the following piece of C(++) code:

int Add(int a, int b)
{
  return a + b;
};

Running the sample application on this code results in the following output:

FunctionDecl (Add)
ParmDecl (a)
ParmDecl (b)
CompoundStmt
ReturnStmt
BinaryOperator
UnexposedExpr (a)
DeclRefExpr (a)
UnexposedExpr (b)
DeclRefExpr (b)
UnexposedDecl

It shows the function declarations and its parameters, as well is the function body which consists of a single compound statement that combines a return statement and a binary operator (“+”) expression.

Documentation for the high level interface is included in the repository in the help file Neslib.Clang.chm. You can also browse it on-line.

Low Level Interface

You can also use the C API directly if you prefer. The same example would look like this:

function ChildVisitor(ACursor, AParent: TCXCursor;
  AClientData: TCXClientData): TCXChildVisitResult; cdecl;
var
  Kind: TCXCursorKind;
  Str: TCXString;
  S: AnsiString;
begin
  Kind := clang_getCursorKind(ACursor);
  Str := clang_getCursorKindSpelling(Kind);

  S := AnsiString(clang_getCString(Str));
  Write(S);
  clang_disposeString(Str);

  Str := clang_getCursorSpelling(ACursor);
  S := AnsiString(clang_getCString(Str));
  if (S = '') then
    WriteLn
  else
    WriteLn(' (', S, ')');
  clang_disposeString(Str);

  Result := CXChildVisit_Recurse;
end;

procedure Run;
var
  Index: TCXIndex;
  TU: TCXTranslationUnit;
  Cursor: TCXCursor;
begin
  Index := clang_createIndex(0, 0);
  try
    TU := clang_parseTranslationUnit(Index, 'sample.cpp', nil, 0, nil, 0, 0);
    if (TU = nil) then
      raise Exception.Create('Unable to parse C++ file');

    try
      Cursor := clang_getTranslationUnitCursor(TU);
      clang_visitChildren(Cursor, ChildVisitor, nil);
    finally
      clang_disposeTranslationUnit(TU);
    end;
  finally
    clang_disposeIndex(Index);
  end;
end;

As you can see, this is a bit more involved, but true to the original API. Note that you have to take care of converting C-style strings and disposing them, as well as disposing the translation unit and index when you are finished.

You can find API documentation for the low level interface on Clang’s website.

In Closing

You can do some pretty powerful stuff with libclang. Let us know if you find a use case that is of interest for the Delphi community!

One thought on “libclang for Delphi

Leave a comment