Loment
ES

Sistema · publicado en 0.1.4.6

LibL

La biblioteca estándar de Loment, con la forma de C: los mismos nombres de función, el mismo orden de argumentos y los mismos valores de retorno que libc — para quien ya conoce C. Una biblioteca con dos caras, no dos bibliotecas.

Disponible desde 0.1.4.6

Desde 0.1.4.6, una compilación de versión acepta use libl, y una compilación de versión puede enlazar el libc.so.6 real del sistema y ejecutarlo de principio a fin. El cierre que retenía ambas cosas en 0.1.4.5 está abierto, y la nota de la versión lo dice.

«Disponible» no es «completo». Cada nombre de aquí se comprueba contra la glibc del sistema, y el conjunto que falta está escrito abajo, no dejado para que el lector lo descubra en el paso de enlace. La página conserva también las razones, porque una cara de libc sin ellas es una promesa que nadie puede auditar.

Por qué una segunda cara

La biblioteca estándar ya existía, y no contenía ningún nombre de libc — ni strlen, ni printf, ni malloc. No era una omisión; era una forma. Sus funciones toman una longitud explícita en lugar de buscar un terminador, direccionan dentro de un búfer con un desplazamiento, y nunca toman un número variable de argumentos. En el lenguaje no hay cadenas terminadas en NUL ni funciones variádicas, así que la biblioteca se escribió tal como es el lenguaje.

Esa forma es la correcta para quien escribe Loment, y es un muro para quien conoce C. Una segunda cara responde a otra pregunta: ¿cómo se llamaría esto si nunca se hubiera oído hablar de Loment? strcmp, que devuelve lo mismo que el de C. qsort. isdigit. Nada que aprender.

Las dos caras apuntan a los mismos bytes. isdigit llama al propio predicado de carácter de la biblioteca; memcpy llama a su propia rutina de copia. Así que el fallo que se esperaría de dos caras — el mismo byte respondiendo distinto según el nombre con que se pregunte — es estructuralmente imposible, no solo evitado.

Dos formas de entrar

LibL es el nombre de la biblioteca; std sigue siendo el nombre del paquete. Ambas entradas llegan al mismo paquete, y difieren en cuánto arrastran consigo.

use std

Todo el paquete. Lo que ya hace todo lo escrito antes de LibL, y sigue funcionando. El coste es que una entrada de alcance de paquete arrastra todos los módulos a una unidad, y el espacio de símbolos emitidos de este lenguaje es plano — así que todos esos nombres de nivel superior tienen que ser únicos entre sí, y el tiempo de compilación crece más rápido que el número de módulos.

use libl

Las cuatro caras libc y aquello de lo que dependen — seis módulos. Una cara pequeña: compila rápido y casi no tiene con qué colisionar. Es la entrada que hay que usar cuando lo único que se quería eran los nombres de libc.

Qué cubre

Una cabecera de libc se corresponde con un módulo, y ese mapeo es en sí mismo parte de seguir a 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, y el resto del conjunto, con toupper y tolower.

<stdlib.h>

La mitad que puede construirse sin un montículo: atoi, atol, strtol, strtoul, abs, labs, llabs, qsort, bsearch.

<stdio.h>

La mitad de formato: snprintf, sprintf y el resto de formatos de printf. La mitad de archivos llegó en 0.1.4.6 como un módulo host aparte — fopen, fclose, fread, fwrite, fseek, ftell, fflush, feof, fgets, fputs, puts, putchar, getchar — porque necesitan llamadas al sistema, y la biblioteca central no hace ninguna.

Más una función que libc no tiene, y es la interesante — ver más abajo.

El control es libc mismo

Todas las demás partes de la biblioteca estándar se juzgan comparando lo que Loment calcula con lo que calcula Python. Ese es el control equivocado para esta pregunta, y de forma demostrable: justo en los bordes que definen "se comporta como libc" — si strncpy rellena con ceros, si strcmp devuelve un signo o una diferencia, si strchr encuentra el terminador, si memmove es seguro cuando los rangos se solapan — Python y libc no dan la misma respuesta. Comparar contra Python permitiría que dos implementaciones estuvieran equivocadas a la vez y aun así pasaran.

Así que el criterio compila un programa en C contra la glibc real del sistema, ejecuta el mismo conjunto de llamadas en ambos lados, y compara la salida estándar byte a byte, reportando la primera línea que difiere. Dos normalizaciones son deliberadas, porque de lo contrario la prueba mediría detalles de implementación en vez de semántica: el estándar solo promete el signo de strcmp, así que se comprueban tanto el signo como la diferencia; y los predicados de carácter de glibc devuelven una máscara de bits en lugar de 1, así que ambos lados se reducen a un sí o un no antes de comparar.

Lo que el lenguaje obligó

La regla de este trabajo era "como quiera que libc lo implemente, LibL lo implementa así". El lenguaje no es C, así que cuatro lugares tuvieron que diferir y uno tuvo que añadirse. Cada uno está escrito en vez de oculto.

Un literal de cadena no lleva terminador

Así que strlen sobre un literal lee más allá del final. En la primera sonda devolvió 17 para la cadena vacía, tras avanzar hasta encontrarse con un byte cero. C no necesita una función para esto porque "abc" en C ya lleva su NUL. Esto no es una omisión — los dos tipos str no son la misma cosa: el de este lenguaje lleva una longitud, el de C no. LibL añade cstr_of para convertir uno en el otro, y toda función de cadena terminada en NUL de la biblioteca lo necesita.

Sin funciones variádicas

Así que snprintf toma un argumento más que el de libc: snprintf(buf, cap, fmt, args, nargs), donde args apunta a un array de valores de 64 bits. El especificador de conversión sigue decidiendo cómo se lee cada uno, exactamente como en libc. Esta es la única firma de la biblioteca que se desvía, y no es puramente una pérdida: libc no puede contar sus argumentos, así que darle demasiado pocos lee lo que haya en la pila; aquí, demasiado pocos significa que la conversión se detiene y lo producido hasta el momento se termina correctamente.

Sin punteros a función

Así que el cuarto argumento de qsort es una enumeración que dice por qué comparar, no una función de comparación. Es una diferencia real y no es una degradación en una dirección: saca "por qué compara este ordenamiento" de una dirección en tiempo de ejecución y lo pone en una constante en el binario, así que puede responderse sin leer el código fuente. El precio se enuncia con ella: un orden que no sea uno de los tipos incorporados no puede expresarse — el bucle se escribe uno mismo.

Sin global mutable a nivel de módulo

Así que strtok se convierte en strtok_r, con el cursor en manos de quien llama — que POSIX ya ofrece, así que esto no es una degradación. Un rand que guarde una semilla oculta tampoco puede existir; la forma que usa este lenguaje toma el estado de quien llama, y deliberadamente no se llama rand. Y no hay errno: strtol satura al valor límite y retorna con normalidad, así que quien llama compara contra el límite en su lugar.

Lo que no es

No es un reemplazo de libc

LibL es una biblioteca de código fuente, no un archivo contra el que se pueda enlazar un objeto de C. El código en C no puede llamarla; eso necesita una vía "de Loment a archivo objeto", que no existe. Lo que reemplaza es más estrecho, y merece decirse con precisión: que un programa de Loment tenga que escribir strcmp a mano.

No es toda libc

realloc y free faltan a propósito: realloc necesita la longitud antigua, y la longitud antigua vive en una cabecera de montón privada del compilador; el nombre free ya lo toma el integrado, así que una función de biblioteca con ese nombre no se resolvería en el punto de llamada. malloc y calloc están desde 0.1.4.6 — escrituras sobre el montón integrado sin cabecera de longitud propia, que es lo que permite al free integrado aceptar lo que devuelven. stdin, stdout y stderr serían globales mutables de nivel de módulo, y el lenguaje no los tiene. De la familia de proceso y entorno, aparte de exit y abort, no hay nada. Y %f/%e/%g no son «sin hacer» sino «todavía no se puede»: el formateador de arriba es coma flotante por software, y su dirección inversa necesita una división ancha que esa capa deliberadamente no tiene.

No está terminada, y no pretende estarlo

Escribirla sacó a la luz defectos en el sistema circundante — entre ellos un auxiliar cuya comprobación de solapamiento comparaba desplazamientos donde libc compara direcciones, un formateador que emite IR que el ensamblador del sistema rechaza, y una comprobación de dependencias que culpa al módulo equivocado. Se registran como incidencias en vez de corregirse de paso, y el criterio propio de la biblioteca es deliberadamente estrecho: afirma que estos módulos son unidades legales, y no afirma que lo sean sus dependencias.