GizStack
View .mdOpen in ChatGPTOpen in Claude

C and C++

A Gizlang program calls C and C++ code in its own process. You write no glue code and no build file: clang reads the C or C++ file, and the giz command makes the module, compiles the code and links it.

You haveDo this
A C or C++ source fileImport the file as a module
A C library and its headerImport a header of two lines
One function of a libraryDeclare the function

giz run, giz check, giz test and giz build all work with C and C++ files. clang must be installed when you build from a source checkout; an installed toolchain has its own clang.

Import a C file

Put the C file beside the program and import it as you import a module.

stats.c
#include <stddef.h>
#include <stdint.h>

#define STATS_VERSION 2

typedef struct { int32_t low; int32_t high; } Range;

/* The lowest and the highest byte of DATA. */
Range span(const uint8_t *data, size_t length) {
    Range r = { 255, 0 };
    for (size_t i = 0; i < length; i++) {
        if (data[i] < r.low) r.low = data[i];
        if (data[i] > r.high) r.high = data[i];
    }
    return r;
}

/* The count of BYTE in DATA. FIRST gets the index of the first one, or -1. */
size_t count(const uint8_t *data, size_t length, uint8_t byte, int64_t *first) {
    size_t n = 0;
    *first = -1;
    for (size_t i = 0; i < length; i++) {
        if (data[i] == byte) {
            if (n == 0) *first = (int64_t)i;
            n++;
        }
    }
    return n;
}
stats.gizRun
import {STATS_VERSION, Range, span, count} from "./stats.c";

function report(data: bytes): void {
    const r: Range = span(data, u64(data.length));
    let first = 0;
    const n = count(data, u64(data.length), 97u, first);
    console.log(STATS_VERSION, r.low, r.high, n, first);
}

report(b"banana");
text
2 97 110 3 1
  • The functions, structs, enums and simple macros of the file are the exports of the module. So are those of each file that it includes with #include "...". A file that it includes with #include <...> is only read.
  • Range is a record with the layout of the C struct. A struct goes in and out by value.
  • int64_t *first is an out-parameter: pass a let local, and C writes it.
  • A change of the C file, or of a file that it includes in quotes, compiles it again. Otherwise the object comes from the cache.
  • A C error stops giz check with clang's message and the line in the C file.

Use a C library

Write a header of two lines in the program's folder. #pragma comment(lib, "NAME") links the library: with the flags of pkg-config when pkg-config knows NAME, else with -lNAME. #include "..." in quotes makes the functions of the library's header the exports of the module.

c
#pragma comment(lib, "zlib")
#include "zlib.h"

With that file saved as zip.h, the program calls zlib:

typescript
import {crc32, zlibVersion} from "./zip.h";

console.log(crc32(0u, b"hello", 5u), zlibVersion());

Use C++

A C++ file (.cc, .cpp, .cxx, .hh, .hpp, .hxx) is imported in the same way. It also links the C++ library.

counter.cpp
#include <stdexcept>
#include <string>

namespace demo {
std::string greet(const std::string &name) { return "hello, " + name; }

int divide(int a, int b) {
    if (b == 0) throw std::invalid_argument("divide by zero");
    return a / b;
}

class Counter {
public:
    explicit Counter(int start) : count(start) {}
    void add(int n) { count += n; }
    int value() const { return count; }
private:
    int count;
};
}
counter.gizRun
import * as Demo from "./counter.cpp";

const c = new Demo.Counter(5);
c.add(2);
console.log(Demo.greet("C++"), c.value());
c.close();
try {
    Demo.divide(1, 0);
} catch (e) {
    console.log(`caught ${e}`);
}
text
hello, C++ 7
caught divide by zero
  • std::string and std::string_view are string.
  • A class with members other than scalar and pointer fields is a record { handle: u64 }, the address of the object. new Name(...) runs its first constructor, and its public methods are methods of the record. A static method is the function Name_method. close() deletes the object; call it once.
  • A function that is not noexcept catches a C++ exception and throws its message as a string.
  • The names of a namespace are the exports, without the namespace.

Declare one C function

To call one function of a library, declare it. from "LIBRARY" links -lLIBRARY; without it, the function comes from the C library.

hypot.gizRun
declare function hypot(x: f64, y: f64): f64 effects {} from "m";

console.log(hypot(3.0, 4.0));
text
5

effects {} says that the function has no effects that Gizlang can see. Without a row, the effects are {io, fail}. Gizlang cannot see into C, so the row is your promise.

giz bind HEADER LIBRARY NAME... writes these declarations from a C header (the tools).

Types

The types of an imported file and of a declaration are the same:

GizlangC
i8 to i64, u8 to u64, f32, f64, boolThe integer, float or bool of that width. A value that does not fit the parameter fails (R003).
stringA const char * to a copy that ends with NUL. A string that contains NUL fails (R003). As the result, the returned text is copied. NULL is "". Invalid UTF-8 fails.
bytesA pointer to the first byte, or NULL when empty. Pass the length as a separate argument.
A record of the scalars aboveA pointer to a C struct with those fields in order. An imported file also passes it by value.
mut scalarA pointer to it: an out-parameter of C. It is read back into the local after the call.
mut bytes, mut recordC writes into the buffer or struct of the local.
u64Any other pointer, for example a handle that C returns (sqlite3 *).
(a: T, ...) => RA C function pointer. The argument names a top-level function whose types are the value types of the signature (i64 for i32). A maximum of 8 scalar parameters. R is a scalar or void.

In an imported file, these C types also map:

  • int64_t, size_t and the other names of <stdint.h> and <stddef.h> are their scalars.
  • void * is a u64. A pointer to bytes (char *, uint8_t *) is bytes. A pointer to another scalar is a mut argument.
  • The names of an enum, and a macro of an integer, a float or a string, are constants.

These are not exported, and a comment in the generated module says why: variadic functions, templates, operators, unions, structs with fields that are not scalars, and long double. To use one, write a small C or C++ function in your file that calls it with the types above.

Calls and callbacks

  • A mut argument is a let local. Module state cannot be passed as mut.
  • A callback runs on the calling task. After a fault inside a callback, later calls return 0 to C without running. The fault fails the caller of the C function when C returns.
  • A callback result that does not fit its C type fails (R003).
  • A callback receives pointers as u64. To read through one, declare a C function such as memcpy.
  • A C call that can fail (R003, a string with NUL, a failed callback) makes the function that calls it fail, as any other fault does.

Authority

A C function has all the authority of the process, and the checker cannot see into it.

  • Only the application can declare a C function: its entry module, or a package module whose manifest entry grants the library ("native": ["m"], or "c" for functions without from). Any other declaration is E_MODULE.
  • The manifest of a dependency can grant only what its parent granted to it (the same "native" on its entry in dependencies). If not, the package is E_PACKAGE.
  • The CLI grants an imported C or C++ file the object that it built, so its functions run. An "imports" limit decides which modules may import it (packages).
  • Symbols that start with gizlang_ are runtime services. Only the standard library declares them.

How the CLI reads, compiles and caches the files is in the system internals.