Tiny Lua-equipped C-preprocessor is a small integrated C/C++ preprocessor that acts as a compile-time scripting interface for code generation.

TLC works closely alongside the Lua scripting language and is written in Java.

TLC can also be extended to implement specific Java functionality as well.


There are various ways to script pre-processing. Initially TLC will include inline, side-by-side, and imported pre-processor scripts.


Inline scripts are Lua scripts included directly in the C++ source file. While arguably messy, these scripts are editable directly within the code.

Inline Lua is just performed using the Lua directive. If defining functions, for example, the closure will return nothing and will essentially eliminate the call from the output source (see the below note under Lua Directive).


Side-by-Side inclusion occurs when a .c or .cpp file has an adjacent .c.lua or .cpp.lua file, respectively. The Lua script is automatically imported into the state before the C++ source is preprocessed.


Lastly, scripts can be imported using the @import directive (see below). Import scripts can be found given inclusion directories passed along the command line.

Tricky Business

The import system is just a translation of built-in Lua keywords. Include directories passed along the command line are simply added to the module path (yes, ? is still valid), and @import simply require's the script.


There are two directive formats that are associated with TLC and activate two different portions of the project.

Function Directive

All function directives refer to a specific Java functionality.

Some examples:

@import "myScript" // Invokes the registered Java handler to import a script

@define var val // Invokes the registered Java handler to create a variable

The two directives above will invoke specific Java (not Lua) classes to handle the code.

Lua Directive

The Lua directive refers to specific Lua functionality and executes arbitrary Lua code. It works similarly to a macro, but instead is replaced by the return value of the closure.

There are two forms of the Lua directive; return form and closure form. The return form, $value, prepends return to the beginning of the statement. The closure form, ${statement}, simply executes Lua and doesn't necessarily have to return values. $value is functionally equivalent to ${return value}.

NOTE: If 0 values are returned from a closure, the directive is simply deleted from the output source. If more than one value is returned, the rest are discarded and only the first is used as a replacement.

NOTE: The return form allows for tricky use of meta-methods as it calls tostring on all values. Developers can use this to their advantage by writing meta-method replacements for tables to do some neat stuff without having to use ${return someCode()}.

See More Tricky Business for an example

For instance, if we have the Lua function:

function appendif(test, append, inp)
    if test == true then
        return inp .. append
        return inp

And then the equivalent C++ code

#include <iostream>

// Debug mode?
@set debug true

void output_d()
    std::cout << "Debug mode is ON";

void output()
    std::cout << "Debug mode is OFF";

int main()
    ${return appendif(debug, "_d", "output")}(); // Evaluates to output_d();
    return 0;

The above would effectively output Debug mode is ON:

It also recognizes Lua tables; the following would also be acceptable:

-- Lua
tbl = {}
function tbl.appendif(...) -- ...
// C++:
${return tbl.appendif(debug, "_d", "output")}();

More Tricky Business

The note above mentions that tostring is called on all replacement-type directives. This can lead to some interesting uses of meta-methods; the following is an example.


mt = {}
mt.__tostring = function()
    return "\"Woooo!\""

woostring = {}
setmetatable(woostring, mt)


#include <iostream>

int main()
    // Even though woostring() is a table, its
    //  __tostring() meta-method allows it to
    //  be used with TLC's replacement-type Lua directive.
    printf("The programmer says \"%s\"", $woostring);

    return 0;


Processing the files is defined very clearly and how you would (hope) to expect it to.

Order and Scope

Processing is handled in a linear fashion, and variables do not persist between files. Yes, TLC (as of this writing) does not acknowledge #include directives.

Evaluation of Invocation/Replacement Directives

All directives working with Lua (i.e. Replacement Directives and Invocation Directives) are evaluated as lua instead of parsed by Java. Invocation directives simply evaluate the code, whereas replacement directives evaluate and return the code.

For replacement directives, any code that cannot be successfully parsed with a return pre-pended onto the raw string will need to be placed into a function using one of the placement methods listed above.


The golden rule of TLC is that it should be run before the C preprocessor is ever invoked.

License / Disclaimer

This code is provided 'as-is' with no warranty whatsoever. Use it at your own risk.

TLC cannot be distributed or sold (including bundled with any software) neither in source or binary form. All other reproductions, communications, modifications, enhancements, hacks or uses of the code are permitted with credit back to the (this) original GitHub repository. If you do something cool with it, I ask that you show me/submit a pull request (please!). All limitations (or lackthereof) are only applicable to the fullest extent as the referenced supporting 3rd party libraries allow, respectively.

The views expressed in snarky (or anything synonymous) comments cannot be used against the original Author(s).