Loment
EN

System · shipped in 0.1.4.6

LibL

Loment's standard library, in C's shape: the same function names, the same argument order and the same return values as libc — for people who already know C. One library with two faces, not two libraries.

Available since 0.1.4.6

A release build accepts use libl as of 0.1.4.6, and a release build can link the system's real libc.so.6 and run it end to end. The lock that held both back in 0.1.4.5 is open, and the release note says so.

"Available" is not "complete". Every name here is checked against the system's glibc, and the set that is missing is written out below rather than left for a reader to discover at the link step. The page also keeps the reasons, because a face of libc without them is a promise nobody can audit.

Why a second face

The standard library already existed, and it contained no libc name at all — not strlen, not printf, not malloc. That was not an omission; it was a shape. Its functions take an explicit length rather than searching for a terminator, address into a buffer with an offset, and never take a variable number of arguments. There are no NUL-terminated strings in the language and no variadic functions in it, so the library was written the way the language is.

That shape is right for people writing Loment, and it is a wall for people who know C. A second face answers a different question: what would this be called if you had never heard of Loment? strcmp, which returns the same thing C's does. qsort. isdigit. Nothing to learn.

The two faces point at the same bytes. isdigit calls the library's own character predicate; memcpy calls its own copy routine. So the failure you would expect from two faces — the same byte answering differently depending on which name you asked — is structurally impossible rather than merely avoided.

Two ways in

LibL is the library's name; std is still the package's name. Both entries reach the same package, and they differ in how much they pull in with them.

use std

The whole package. What everything written before LibL already does, and it keeps working. The cost is that a package-wide entry pulls every module into one unit, and this language's emitted-symbol space is flat — so all of those top-level names have to be unique together, and compile time grows faster than the module count.

use libl

The four libc faces and what they depend on — six modules. A small face: it compiles quickly and it has almost nothing to collide with. This is the entry to use when libc names are all you wanted.

What it covers

One libc header maps to one module, and that mapping is itself part of following libc.

<string.h>

strlen, strcpy, strncpy, strcat, strcmp, strncmp, strchr, strrchr, strstr, strspn, strcspn, strpbrk, strtok_r, memchr, memcmp, memcpy, memmove, memset.

<ctype.h>

isdigit, isalpha, isalnum, isspace, isupper, islower, isxdigit, ispunct, isprint, and the rest of the set, with toupper and tolower.

<stdlib.h>

The half that can be built without a heap: atoi, atol, strtol, strtoul, abs, labs, llabs, qsort, bsearch.

<stdio.h>

The formatting half: snprintf, sprintf, and the rest of the printf formats. The file half arrived in 0.1.4.6 as a separate host module — fopen, fclose, fread, fwrite, fseek, ftell, fflush, feof, fgets, fputs, puts, putchar, getchar — because those need system calls, and the core library makes none.

Plus one function libc does not have, and it is the interesting one — see below.

The control is libc itself

Every other part of the standard library is judged by comparing what Loment computes against what Python computes. That is the wrong control for this question, and provably so: on exactly the edges that define "behaves like libc" — whether strncpy zero-fills, whether strcmp returns a sign or a difference, whether strchr finds the terminator, whether memmove is safe when the ranges overlap — Python and libc are not the same answer. Comparing against Python would let two implementations be wrong together and pass.

So the criterion compiles a C program against the system's real glibc, runs the same set of calls on both sides, and compares standard output byte for byte, reporting the first line that differs. Two normalisations are deliberate, because otherwise the test would be measuring implementation details rather than semantics: the standard only promises the sign of strcmp, so both the sign and the difference are checked; and glibc's character predicates return a bitmask rather than 1, so both sides are reduced to a yes-or-no before comparison.

What the language forced

The rule for this work was "however libc implements it, LibL implements it that way". The language is not C, so four places had to differ and one had to be added. Each one is written down rather than hidden.

A string literal carries no terminator

So strlen on a literal reads past the end. In the first probe it returned 17 for the empty string, having run on until it happened to meet a zero byte. C does not need a function for this because "abc" in C already carries its NUL. This is not an omission — the two str types are not the same thing: this language's carries a length, C's does not. LibL adds cstr_of to convert one into the other, and every NUL-string function in the library needs it.

No variadic functions

So snprintf takes one more argument than libc's: snprintf(buf, cap, fmt, args, nargs), where args points at an array of 64-bit values. The conversion specifier still decides how each one is read, exactly as in libc. This is the only signature in the library that deviates, and it is not purely a loss: libc cannot count its arguments, so giving it too few reads whatever is on the stack; here, too few means the conversion stops and what was produced so far is finished properly.

No function pointers

So qsort's fourth argument is an enumeration saying what to compare by, not a comparison function. That is a real difference and it is not a downgrade in one direction: it moves "what does this sort compare by" out of a runtime address and into a constant in the binary, so it is answerable without reading the source. The price is stated with it: an ordering that is not one of the built-in kinds cannot be expressed — you write the loop yourself.

No module-level mutable global

So strtok becomes strtok_r, with the cursor held by the caller — which POSIX already offers, so this is not a downgrade. A rand that keeps a hidden seed cannot exist either; the shape this language uses takes the state from the caller, and it is deliberately not named rand. And there is no errno: strtol saturates to the boundary value and returns normally, so the caller compares against the boundary instead.

What it is not

Not a replacement for libc

LibL is a source library, not an archive a C object file can be linked against. C code cannot call into it; that needs a "Loment to object file" path, which does not exist. What it replaces is narrower and worth saying precisely: a Loment program having to hand-write strcmp.

Not the whole of libc

realloc and free are missing on purpose. realloc needs the old length, and the old length lives in a heap header private to the compiler; free's name is already taken by the builtin, so a library function by that name would not resolve at the call site. malloc and calloc are here as of 0.1.4.6 — spellings over the builtin heap with no length header of their own, which is what lets the builtin free accept what they return. stdin, stdout and stderr would be module-level mutable globals, and the language has none. Beyond exit and abort, nothing from the process or environment families. And %f/%e/%g are not "not done" but "cannot be done yet": the upstream formatter is software floating point whose parse direction needs a wide division the layer deliberately does not have.

Not finished, and not pretending to be

Writing it turned up defects in the surrounding system — among them a helper whose overlap check compared offsets where libc compares addresses, a formatter that emits IR the system assembler rejects, and a dependency check that blames the wrong module. Those are filed as issues rather than fixed in passing, and the library's own criterion is deliberately narrow: it claims these modules are legal units, and it does not claim their dependencies are.