3 puntos por GN⁺ 2024-10-25 | 1 comentarios | Compartir por WhatsApp
  • Pretty C es un nuevo lenguaje de scripting compatible con C que añade a los programas en C funciones de conveniencia como tipado dinámico, iteración genérica y seguimiento de recursos
  • Se usa como una biblioteca header-only que solo requiere incluir pretty.h, y puede utilizarse junto con archivos C y bibliotecas C existentes
  • Entre las funciones que ofrece están alias como string, any, nil, inferencia de tipos con var/let, salida genérica con print/println, comparación genérica con equal y macros ternarias más legibles
  • Para reducir el trabajo repetitivo en iteración y memoria, proporciona sintaxis basada en macros como foreach, forthese, fortimes, forrange, new, vector, delete, with, lambda, try/catch
  • Algunas funciones dependen de condiciones como GCC, Clang, C23+, C11+, y las variables ligadas con with son void *, por lo que puede ser necesario convertirlas de tipo antes de usarlas

Objetivos y forma de uso de Pretty C

  • Pretty C es un lenguaje de scripting compatible con C que añade a los programas en C tipado dinámico, iteración genérica, seguimiento de recursos y varias comodidades sintácticas
  • Mantiene la compatibilidad hacia atrás con C existente y con bibliotecas C
  • Está inspirado en Lua, Python, JavaScript y Lisp
  • Para instalarlo, se puede clonar el repositorio o copiar únicamente el archivo pretty.h
    • Puede incluirse en cualquier archivo C con #include "pretty.h"
    • También puede usarse especificando la ruta de Pretty C en el include path

Headers incluidos por defecto y macros simples

  • Pretty C incluye headers estándar para que tipos de uso frecuente y expresiones alternativas de operadores estén disponibles de inmediato
    • stdbool.h: true, false, bool
    • stdint.h: tipos enteros de ancho fijo como uint64_t
    • iso646.h: operadores alternativos como and en lugar de && y or en lugar de ||
  • También proporciona macros simples de uso común
    • max, min: valor máximo y mínimo entre dos números
    • len: longitud de un arreglo
    • default: proporciona un valor fallback
    • limit: limita un valor a un rango
    • between: verifica si un número está dentro de un rango
    • divisible: verifica si se divide exactamente entre otro número

Tipos y alias

  • Pretty C ofrece alias de tipos cortos para tipos de C
    • string es char*
    • byte es char
    • bytes es char*
    • any es void*
    • También ofrece abreviaciones enteras como uchar, ushort, uint, ulong
  • También incluye alias sintácticos influenciados por Lua y Lisp
    • eq, is: ==
    • bitnot, bitxor: alias para operaciones de bits
    • success, fail, failure: alias para el patrón success == 0
    • below, above, upto, downto: operadores de comparación
    • even, odd, positive, negative, zero, empty: predicados para números y datos
    • nil: NULL
    • until: while negado
    • elif: else if
    • ifnt, elifnt: expresiones del tipo if(!...)
    • repeat: alias estilo Lua para do
    • done, finish, pass: alias para break, continue
    • always, forever, loop, indefinitely: expresiones para bucles infinitos
    • never, comment: expresiones que no ejecutan código pero permiten que el compilador lo analice

Inferencia de tipos, salida, comparación y ternarios

  • La inferencia de tipos puede usarse con GCC, Clang o C23+
    • Como ejemplo, ofrece formas como var t = time(0);, let lt = localtime(&t);, local at = asctime(lt);
  • La salida genérica está disponible en C11+
    • print imprime el valor recibido
    • println agrega un salto de línea después de imprimir
  • La comparación genérica de igualdad también se ofrece como función de C11+
    • Comparación de cadenas como equal("NA", line)
    • Incluye ejemplos como equal(0.3, 0.2 + 0.1)
  • El operador ternario puede hacerse más legible con macros basadas en texto
    • when se expande a una cadena vacía para mejorar la legibilidad
    • unless se expande a not y funciona como forma negativa de when
    • then es ?
    • other y otherwise son :
    • only se usa cuando no se necesita la cláusula otherwise
    • otherwhen se usa para encadenar la siguiente condición

Macros de iteración

  • Pretty C abstrae mediante macros patrones frecuentes de bucles for
  • foreach (var, type, length, ...)
    • Recorre un arreglo o una región de memoria
    • En cada iteración, var se convierte en un puntero al elemento correspondiente del arreglo
    • Como es un puntero, permite modificar el elemento in-place si hace falta
  • forthese (var, type, ...)
    • Recorre los argumentos variables recibidos y liga cada valor a var del type indicado
  • fortimes (var, times)
    • Reduce el patrón común de iterar desde 0 hasta una cantidad positiva de veces
  • forrange (var, init, target)
    • Recorre un rango numérico desde init hasta target
    • init y target pueden ser cualquier entero con signo o sin signo
    • Si init es mayor que target, el paso de iteración disminuye
  • forrangeby (var, type, init, target, by)
    • Recorre var del type especificado desde init hasta target con incrementos de by

Utilidades de asignación y de bloques

  • Las macros de asignación simplifican patrones comunes de reserva de memoria con una forma influenciada por C++
    • new (type, ...): crea structs y otros objetos con una forma parecida a new de C++
    • vector (length, type, ...): simplifica el patrón de asignar un arreglo del tamaño y tipo indicados con contenido inicial
    • delete (...): equivale a free, pero puede usarse si se prefiere un nombre al estilo C++
  • Las utilidades de bloque proporcionan macros para enlaces locales, ejecución diferida y trabajo por bloques
    • lambda (ret, name, ...): permite usar funciones anidadas, lambdas o cierres en C con GCC, Clang o C++
    • with (var, close, ...): proporciona por adelantado la función de liberación close, útil para objetos asignados dinámicamente o file handles
      • Como desventaja, la variable ligada var es void *, por lo que puede requerir conversión al tipo deseado antes de usarse
    • defer: fue eliminado, y se indica usar el nuevo defer disponible
    • try y catch: permiten escribir el manejo de errores en C con una forma más vistosa
    • NOERR, NOERROR: se proporcionan para facilitar switch-case de errores

Cómo contribuir

1 comentarios

 
GN⁺ 2024-10-25
Opiniones de Hacker News
  • La tradición de crear lenguajes específicos de dominio dentro de C viene de hace mucho.
    Stephen Bourne quería escribir el shell en ALGOL, así que martilló obstinadamente el preprocesador de C hasta hacerlo parecer el lenguaje que prefería.
    https://www.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh...

  • Me pregunto si alguien puede explicar si esto es una broma o si el autor está realmente confundido.
    Nada tiene sentido. No es “scripting”, dice que introduce “tipado fuerte” pero no hace nada con los tipos, y los alias de operadores supuestamente inspirados en Lua y Lisp en realidad no existen en ninguno de esos dos lenguajes. Pero tampoco parece una parodia evidente, así que de verdad desconcierta.

    • Parece que sí va en serio. Dice: “Desecha Lua, Python, JavaScript, Ruby y decenas de otros lenguajes. Pretty C es el lenguaje de scripting definitivo, ultrarrápido y además con tipado fuerte!!”
    • No creo en absoluto que el autor esté confundido. Claro que estar confundido, en sí, está bien.
  • Afirma ser un lenguaje de scripting, pero igual hay que compilar el programa. Dan ganas de abuchear.
    Si le agregas CINT(https://root.cern.ch/root/html534/guides/users-guide/CINT.ht...), puedes ejecutarlo de inmediato y tener un REPL.

    • Si el objetivo de este repositorio es causar dolor, podrían simplemente agregar un shebang al archivo para hacerlo ejecutable [0].
      Hace tiempo vi una entrada de blog que explicaba en detalle cómo funciona ./foo y cómo se ejecutan los archivos ELF. Parecería posible registrar programas .c de la misma manera para que se compilen y luego se ejecuten.
      [0] https://gist.github.com/jdarpinian/1952a58b823222627cc1a8b83...
    • Mejor sería usar tcc [0]. Es muchísimo más liviano que ese monstruo, y es C, no C++.
      [0] https://bellard.org/tcc/tcc-doc.html
    • CERN ahora usa cling (https://github.com/root-project/cling)
    • ¿Quién decidió que un lenguaje de scripting no puede compilarse? Además, Clang-REPL es otra forma de hacerlo más amigable para REPL.
  • “Ofrece suficiente azúcar sintáctico como para provocarle a un desarrollador de C un infarto diabético”.
    Esa frase me encanta.

  • Veo algunas cosas que habría que agregar a CNoEvil[0] y ogw[1]. Parece que cada vez que este proyecto vuelve a aparecer cada pocos meses, siempre hay más.
    [0] https://git.sr.ht/~shakna/cnoevil3/
    [1] https://git.sr.ht/~shakna/ogw

    • “Toma un montón de malas ideas y las aplasta hasta formar un monstruo abominable”.
      Es una frase que sinceramente termino amando desde lo más profundo.
  • Esto comete el mismo error que Python 2. Las cadenas y los bytes no son el mismo tipo, y no deberían tratarse como si lo fueran.

    • ¿Qué tipo se supone que debería tener el texto del shell, es decir, los valores dentro de argv o la salida de subprocesos?
      Ahí puede haber basura arbitraria, así que no es una cadena UTF-8 válida, pero herramientas como awk y grep se usan en todas partes.
      Creo que las cadenas y los bytes pueden verse como el mismo tipo general, aunque a veces es útil darles internamente otro tipo a los bytes UTF-8 válidos. Rust maneja esto bastante bien con OsString y String.
    • Dado que este proyecto es pretty.c y dice que busca ser retrocompatible con C y todas sus bibliotecas, ¿hay alguna opción que tenga más sentido que mantener las cadenas multibyte de C?
      https://en.cppreference.com/w/c/string/multibyte
    • No estoy de acuerdo. Ese principio se basa en todas estas premisas: los datos de cadena están correctamente codificados; solo hay una codificación de cadenas (normalmente UTF-8); los datos de cadena deben validarse al momento de crearse; nunca se permite cortar un code point lógico; no se deben hacer operaciones de cadena sobre bytes “inválidos”; la codificación correcta es el inicio y el fin de la validación.
      Ninguna de esas cosas es siempre cierta. Es útil envolver una secuencia de bytes validada en un tipo que solo pueda construirse mediante validación, pero una vez que haces eso, Utf8String y EmailAddress son básicamente de la misma categoría. No hay razón para darle un trato especial solo a la codificación dentro del sistema de tipos.
    • Depende de cómo definas “cadena”.
      Si significa “texto legible por humanos”, estoy de acuerdo en que una cadena no es lo mismo que un arreglo arbitrario de bytes. Pero muchos lenguajes no imponen esa definición.
  • Me recordó a una base de código en C++ que tuve que revisar hace tiempo. Todo estaba hecho como si estuviera escrito en Java: camelCase en todo, getters/setters para todas las variables de clase e interfaces por todas partes.

    • Es que todavía no viste lo bueno. Mira el código fuente del Bourne shell de Unix Seventh Edition:
      https://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd...
      Cuesta creer que no sea ALGOL.
    • camelCase, getters/setters e interfaces en muchos casos no difieren mucho de las guías de estilo. Por ejemplo, el código de Windows es así, aunque claro, no hasta en todas las variables. Muchas partes del diseño de Java también fueron copiadas de C++.
    • He visto una base de código parecida escrita por gente que usó Java durante demasiado tiempo. Incluso tenían su propia clase String, que no era más que un envoltorio de std::string con métodos al estilo Java.
    • Parece que tuvieron suerte de no usar MSVC.
      https://learn.microsoft.com/en-us/cpp/cpp/property-cpp?view=...
    • A mí también me tocó algo así, y encima pasaban las cadenas como en Java, con f(String *), así que el código C++ era del estilo f(new String("Hello").
  • Si esto te parece interesante, quizá también te guste libcello.h:
    https://www.libcello.org

    • Sí, fue una de las inspiraciones.
  • Me gustan los nombres de los tipos, y la elección de macros para funciones integradas como min es perfecta. También me gusta Len.
    Los operadores booleanos con nombre quizá sean un poco excesivos, pero vale la pena probarlos; el operador ternario ya de por sí es difícil de leer, así que tiene mucho margen de mejora. No me convencen del todo todas las definiciones de bucles, aunque algunas tienen sentido. El seguimiento de recursos es impresionante, y la forma for... se ve un poco fea, así que quizá se le podría poner otro nombre.
    En conjunto, es un intento bastante sólido. Le daría al diseño algo así como 8/10. La forma en que está esbozado con macros de C es realmente elegante y de hecho parece buen código. No sé si lo usaría yo mismo: es un lenguaje nuevo y ya me gusta C. Aun así, la implementación es muy clara, así que puede ayudar a la gente a aprender C y a pensar en el diseño de lenguajes.

    • No hace falta usarlo todo. Mis proyectos por lo general solo usan booleanos, len(), max/min y algunas macros de operadores. Es porque todavía no he necesitado muchas de las otras funciones sabrosas. Así que estaría bueno que probaras aunque sea algunas macros de operadores.
  • Si ifnt es para if(!...), unless me parece más legible que ifnt.

    • Otro tema para discutir nombres: llamar always al bucle infinito for(;;).
      En otros lenguajes he visto loop. Pero Qt lo llama forever, y eso es realmente bonito. También es muy Qt.
    • En cambio, ifnt suena divertido cuando lo dices en voz alta.
    • Sí. Pero reservé unless para las condiciones ternarias, porque ahí, al fin y al cabo, es más útil.
      Ah, espera, eres John Tromp. ¡El creador de BLC! Soy fan.