How to Create Custom Built-in Functions in Nelson Using the C++ Gateway API

You create custom built-in functions in Nelson by implementing a C++ function that follows the CPP_BUILTIN or CPP_BUILTIN_WITH_EVALUATOR prototype, registering it in a gateway table defined by the nlsGateway struct, and compiling the module as a shared library that Nelson discovers at runtime.

Nelson is an open-source numerical computing environment that enables extending its core interpreter through native C++ modules. By leveraging the C++ Gateway API provided in the nelson-lang/nelson repository, developers can implement high-performance custom built-in functions that integrate seamlessly with the interpreter's overload resolution and evaluation mechanisms.

Understanding the Gateway Architecture

Nelson extends its functionality through modules—shared libraries (.so, .dll, or .dylib) that export a gateway function. When Nelson loads a module, it calls AddGateway, which registers a table of function pointers with the interpreter.

The nlsGateway Struct

The foundation of the C++ Gateway API is the nlsGateway struct defined in modules/interpreter/src/include/NelsonGateway.hpp. This struct maps a Nelson function name to its C++ implementation:

using nlsGateway = struct nlsGatewayStructType
{
    std::string functionName;                 // Name used from the Nelson prompt
    ptrBuiltin   fptr;                        // Pointer to the C++ entry point
    int          nLhs;                       // Minimum number of outputs
    int          nRhs;                       // Minimum number of inputs
    BUILTIN_PROTOTYPE builtinPrototype;      // CPP_BUILTIN, CPP_BUILTIN_WITH_EVALUATOR, etc.
    FunctionOverloadAutoMode builtinOverloadAutoMode;
};

Each field serves a specific purpose. The functionName field specifies the command users type at the Nelson prompt. The fptr field holds the function pointer, which must match the signature specified in builtinPrototype. The nLhs and nRhs fields enforce minimum input and output argument counts at the gateway level.

Built-in Prototypes

The BUILTIN_PROTOTYPE enum, also defined in modules/interpreter/src/include/NelsonGateway.hpp, defines three calling conventions:

enum BUILTIN_PROTOTYPE {
    CPP_BUILTIN = 0,               // ArrayOfVector func(int nLhs, const ArrayOfVector& args);
    CPP_BUILTIN_WITH_EVALUATOR,    // ArrayOfVector func(Evaluator* eval, int nLhs, const ArrayOfVector& args);
    C_MEX_BUILTIN                  // Legacy MATLAB-compatible signatures
};

CPP_BUILTIN is the standard choice for most functions. It receives the argument vector and the requested number of outputs. CPP_BUILTIN_WITH_EVALUATOR provides additional access to the current Evaluator* instance, enabling programmatic calls to other built-ins and advanced interpreter interaction.

The NLSGATEWAYFUNC Macro

Modules export their function table through the NLSGATEWAYFUNC macro found in modules/interpreter/src/include/NelsonGateway.hpp. This macro generates the AddGateway entry point that Nelson calls during module loading:

#define NLSGATEWAYFUNC(gateway) \
    NLSGATEWAYFUNCEXTENDED(gateway, NULL)

#define NLSGATEWAYFUNCEXTENDED(gateway, ptrInitializeFunction) \
    EXTERN_AS_C EXPORTSYMBOL int AddGateway(void* eval, const wchar_t* moduleFilename) \
    { \
        return NelsonAddGatewayWithEvaluator(eval, moduleFilename, (void*)gateway, \
            sizeof(gateway) / sizeof(nlsGateway), gatewayName.c_str(), (void*)ptrInitializeFunction); \
    }

In practice, each module contains a Gateway.cpp file that defines the gateway table and invokes this macro, as seen in modules/fftw/builtin/cpp/Gateway.cpp:

const std::wstring gatewayName = L"fftw";

static const nlsGateway gateway[] = {
    { "fft",  (ptrBuiltin)Nelson::FftwGateway::fftBuiltin,  1, 3, CPP_BUILTIN, AUTO_MODE_NONE },
    { "ifft", (ptrBuiltin)Nelson::FftwGateway::ifftBuiltin, 1, 3, CPP_BUILTIN, AUTO_MODE_NONE },
};

NLSGATEWAYFUNC(gateway)

Step 1: Write the Built-in Implementation

Create a C++ source file in your module's builtin/cpp directory. The implementation must conform to one of the supported prototypes. Here is a minimal example implementing mySquareBuiltin using the CPP_BUILTIN signature:

// mySquareBuiltin.cpp
#include "mySquareBuiltin.hpp"
#include "Error.hpp"
#include "InputOutputArgumentsCheckers.hpp"

using namespace Nelson;

ArrayOfVector mySquareBuiltin(int nLhs, const ArrayOfVector& argIn)
{
    // Enforce exactly one input and one output
    nargoutcheck(nLhs, 0, 1);
    nargincheck(argIn, 1, 1);

    ArrayOf X = argIn[0];
    if (!X.isNumeric()) {
        Error(ERROR_WRONG_ARGUMENT_1_TYPE);
    }

    // Perform element-wise square using Nelson's ArrayOf operators
    ArrayOf Y = X .* X;
    return {Y};
}

The nargoutcheck and nargincheck functions from InputOutputArgumentsCheckers.hpp validate argument counts against the function's requirements. For a production-quality example, examine modules/fftw/builtin/cpp/fftBuiltin.cpp, which demonstrates input validation, dimension handling, and error reporting within the Nelson framework.

Step 2: Register the Function in a Gateway

After implementing the function, you must register it in a gateway table. You have two approaches: manual editing for single functions, or automated generation for multiple functions.

Manual Gateway Registration

Edit or create Gateway.cpp in your module's builtin/cpp directory. Include the header for your new function and add an entry to the gateway array:

#include "NelsonGateway.hpp"
#include "mySquareBuiltin.hpp"

const std::wstring gatewayName = L"demo";

static const nlsGateway gateway[] = {
    { "mySquare", (ptrBuiltin)mySquareBuiltin, 1, 1, CPP_BUILTIN, AUTO_MODE_NONE },
};

NLSGATEWAYFUNC(gateway)

The array entry specifies: the command name "mySquare", the function pointer cast to ptrBuiltin, minimum output count 1, minimum input count 1, the prototype CPP_BUILTIN, and the overload mode.

Automated Generation with dlgenerategateway.m

For modules containing many built-ins, use the helper script modules/dynamic_link/functions/dlgenerategateway.m. This MATLAB-compatible function generates a complete Gateway.cpp from a descriptor table:

builtin_table = {
    {"mySquare", 1, 1, "CPP_BUILTIN"},
    {"myCube",   1, 1, "CPP_BUILTIN"}
};

dlgenerategateway('modules/demo/builtin/cpp', 'demo', builtin_table);

The script automatically writes the Gateway.cpp file with correct extern declarations, the nlsGateway table, and all necessary macros including NLSGATEWAYFUNC, NLSGATEWAYINFO, and NLSGATEWAYNAME.

Step 3: Build and Load the Module

Nelson modules build as standard CMake targets. Add your source files to the module's CMakeLists.txt:

add_library(demo SHARED
    builtin/cpp/mySquareBuiltin.cpp
    builtin/cpp/Gateway.cpp
)

target_link_libraries(demo PRIVATE nelson_core)

Compile the module using standard CMake commands:

mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)

Place the resulting shared library (demo.dll on Windows, libdemo.so on Linux) in a directory listed in NELSON_MODULE_PATH, or load it explicitly using Nelson's addpath function. Once loaded, the function is available at the prompt:

>> mySquare(4)
ans = 16

Summary

  • Implement your function using the CPP_BUILTIN or CPP_BUILTIN_WITH_EVALUATOR signature, validating inputs with nargincheck and nargoutcheck.
  • Register the function in a gateway table defined in Gateway.cpp, either manually or using the dlgenerategateway.m generator script.
  • Compile the module as a shared library using CMake, linking against Nelson core libraries.
  • Load the module by placing it in the Nelson modules directory or using addpath, making the function available immediately.

Frequently Asked Questions

What is the difference between CPP_BUILTIN and CPP_BUILTIN_WITH_EVALUATOR?

CPP_BUILTIN uses the signature ArrayOfVector func(int nLhs, const ArrayOfVector& args) and is suitable for standalone computations. CPP_BUILTIN_WITH_EVALUATOR adds an Evaluator* parameter, allowing the function to access the interpreter state, call other built-ins programmatically, or evaluate expressions within the current context. Choose the latter when your function needs deep integration with Nelson's execution engine.

How does Nelson enforce argument count constraints?

The nlsGateway struct contains nLhs and nRhs fields that specify minimum argument counts. Additionally, implementations typically call nargincheck(argIn, min, max) and nargoutcheck(nLhs, min, max) from InputOutputArgumentsCheckers.hpp to enforce exact counts at runtime. The gateway validates the minimums before the function executes, while the internal checks provide detailed error messages.

Can I generate the Gateway.cpp file automatically?

Yes. The dlgenerategateway.m script located in modules/dynamic_link/functions/dlgenerategateway.m generates a complete, compilable Gateway.cpp from a table of function specifications. This approach reduces boilerplate when creating modules with dozens of built-in functions and ensures consistent macro usage across the nelson-lang/nelson codebase.

Where should I place the compiled shared library for Nelson to find it?

Nelson searches for modules in directories listed in the NELSON_MODULE_PATH environment variable or in the standard modules subdirectory of the Nelson installation. You can also load modules explicitly using the addpath function with the directory containing your .so, .dll, or .dylib file. The shared library must export the AddGateway symbol generated by the NLSGATEWAYFUNC macro.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →