C bindings

Note

The C bindings are experimental and still under development. The interface is incomplete, and function signatures and behavior may change in future releases.

The C binding exposes PUQ quantities through <snt/c/puq.h> and DIPL parsing through <snt/c/dip.h>. It uses opaque handles: C applications hold pointers to SNT objects and operate on them through functions, while the implementation remains in C++.

Getting started

Build and install SciNumTools with ENABLE_BINDING_C=ON (the default) and the PUQ and DIP modules enabled. See Installation for the source build instructions. In a consuming CMake project, link to snt-c:

cmake_minimum_required(VERSION 3.22)
project(snt_c_example LANGUAGES C CXX)

find_package(snt CONFIG REQUIRED)
add_executable(snt-c-example main.c)
target_link_libraries(snt-c-example PRIVATE snt-c)
set_target_properties(snt-c-example PROPERTIES LINKER_LANGUAGE CXX)

The C++ linker supplies the runtime needed by the underlying C++ libraries; the application source can remain C.

Quantities and units

Use snt_puq_quantity_eval to evaluate a PUEL expression, snt_puq_quantity_convert to create a quantity in the requested units, and snt_puq_quantity_format to write its textual representation into a buffer. This complete example converts metres to centimetres:

#include <stdio.h>
#include <snt/c/puq.h>

int main(void) {
    snt_puq_error error = {0, NULL};
    snt_puq_quantity* quantity = NULL;
    snt_puq_quantity* converted = NULL;
    char output[128];

    if (snt_puq_quantity_eval("2.5*m", &quantity, &error) != 0) {
        fprintf(stderr, "PUQ evaluation failed: %s\n", error.message);
        return 1;
    }
    if (snt_puq_quantity_convert(quantity, "cm", &converted, &error) != 0) {
        fprintf(stderr, "PUQ conversion failed: %s\n", error.message);
        snt_puq_quantity_free(quantity);
        return 1;
    }
    if (snt_puq_quantity_format(converted, output, sizeof(output), &error) != 0) {
        fprintf(stderr, "PUQ formatting failed: %s\n", error.message);
        snt_puq_quantity_free(converted);
        snt_puq_quantity_free(quantity);
        return 1;
    }

    puts(output);
    snt_puq_quantity_free(converted);
    snt_puq_quantity_free(quantity);
    return 0;
}

DIPL parameters

Create a parser with snt_dip_parser_create, add definitions with snt_dip_parser_add_string or snt_dip_parser_add_file, and call snt_dip_parser_parse. Then retrieve a value as text with snt_dip_parser_get. Pass a node path such as answer or project.name without a leading ?:

#include <stdio.h>
#include <snt/c/dip.h>

int main(void) {
    const char* source = "answer int = 42\nmessage str = \"hello\"\n";
    snt_dip_error error = {0, NULL};
    snt_dip* dip = NULL;
    char value[128];

    if (snt_dip_parser_create(&dip, &error) ||
        snt_dip_parser_add_string(dip, source, &error) ||
        snt_dip_parser_parse(dip, &error)) {
        fprintf(stderr, "DIPL error: %s\n", error.message);
        snt_dip_parser_free(dip);
        return 1;
    }
    if (snt_dip_parser_get(dip, "answer", value, sizeof(value), &error)) {
        fprintf(stderr, "DIPL lookup error: %s\n", error.message);
        snt_dip_parser_free(dip);
        return 1;
    }
    printf("answer = %s\n", value);
    snt_dip_parser_free(dip);
    return 0;
}

To load a DIPfile manifest, replace the individual input calls with snt_dip_parser_add_project(dip, "DIPfile", &error) before parsing. See DIPfile projects for the format.

Registering schemas

Use snt_dip_parser_add_schema_string or snt_dip_parser_add_schema_file with an explicit schema name and a body without a $schema wrapper:

snt_dip* parser = NULL;
snt_dip_error error = {0};
if (snt_dip_parser_create(&parser, &error) == 0) {
    if (snt_dip_parser_add_schema_string(parser, "settings", "value int = 42", &error) == 0 &&
        snt_dip_parser_add_string(parser, "physics : settings", &error) == 0) {
        int status = snt_dip_parser_parse(parser, &error);
        /* Handle status and inspect physics.value. */
    }
    snt_dip_parser_free(parser);
}

The file variant takes (parser, name, path, error). Both functions use the same return codes and error structure as the other parser functions.

Overriding values

snt_dip_parser_add_override_string(parser, body, error) accepts unwrapped path = value modifications before parsing. Use snt_dip_parser_add_override_file(parser, path, error) to read the body from a file. Registration is atomic and file provenance retains the source path.

After parsing or loading DIPH5, call snt_dip_parser_is_overridden(parser, path, &result, error) to inspect the override flag. result is 1 for an overridden value and 0 otherwise.

Environment persistence

Use snt_dip_environment_save and snt_dip_environment_load with a DIP handle, a const char* filename, and an snt_dip_error object. They return zero on success, using the same error convention as parsing. See Environment persistence for the DIPH5 format and current limitations.

Generating static parameters

After parsing or loading an environment, call snt_dip_environment_generate with an snt_dip_export_format and an output path. It follows the normal C binding error convention:

snt_dip_error error = {0};
if (snt_dip_environment_generate(
        dip, SNT_DIP_EXPORT_CPP, "parameters.hpp", &error) != 0) {
    fprintf(stderr, "%s\n", error.message);
}

The supported C ABI formats are SNT_DIP_EXPORT_CPP, SNT_DIP_EXPORT_C, SNT_DIP_EXPORT_FORTRAN, SNT_DIP_EXPORT_RUST, SNT_DIP_EXPORT_JULIA, SNT_DIP_EXPORT_JSON, and SNT_DIP_EXPORT_YAML. See Static parameter generation for the generated representations.

Generating reports

After parsing or loading an environment, call snt_dip_environment_generate_report. Use SNT_DIP_REPORT_TEX for TeX or SNT_DIP_REPORT_PDF for PDF:

snt_dip_error error = {0};
if (snt_dip_environment_generate_report(
        dip, SNT_DIP_REPORT_TEX, "report.tex", "DIPfile",
        "introduction.tex", NULL, &error) != 0) {
    fprintf(stderr, "%s\n", error.message);
}

Other format values are SNT_DIP_REPORT_MD, SNT_DIP_REPORT_RST, SNT_DIP_REPORT_HTML, SNT_DIP_REPORT_TYP, SNT_DIP_REPORT_TXT, and SNT_DIP_REPORT_JSON. They use Brief++ renderers without an external tool. JSON is a briefpp/1 document tree. Pass a null introduction path for these formats; a LaTeX introduction is accepted only for TeX and PDF.

The input label, introduction path, and compiler name may be null. A null compiler uses pdflatex for PDF output. The introduction file is a trusted LaTeX fragment without a preamble. PDF generation returns an error if the configured compiler is unavailable or fails. See Generating DIP reports for report contents and DIPH5 limits.

To set cover fields, use snt_dip_environment_generate_report_with_options:

snt_dip_report_options options = {0};
options.title = "Mock Heat Flow Study";
options.author = "Example Research Team";
options.date = "2026-09-28";
options.version = "1.0 demo";
snt_dip_environment_generate_report_with_options(
    dip, SNT_DIP_REPORT_PDF, "report.pdf", &options, &error);

The default title is DIP parameter report; an empty author appears as Not specified. Date and version default to the local generation date and SNT build version. See the CreateReport example for the cover and linked contents page.

Errors and ownership

Operations returning int return zero on success and nonzero on error. Pass an snt_puq_error or snt_dip_error to receive the error code and message. The message is owned by the library; copy it if it must survive a later error on the same thread.

Release every created quantity with snt_puq_quantity_free and every parser with snt_dip_parser_free. Conversion creates a separate quantity, so both the original and converted handles must be released. The free functions also accept null pointers.

Output buffers belong to the caller. Their capacity must include space for the terminating null character; insufficient capacity is reported as an error.