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.