3 points par GN⁺ 2024-10-25 | 1 commentaires | Partager sur WhatsApp
  • Pretty C est un nouveau langage de scripting compatible avec C, qui ajoute aux programmes C des commodités comme le typage dynamique, l’itération générique et le suivi des ressources
  • Il s’utilise comme une bibliothèque header-only en incluant un seul fichier pretty.h, et peut être employé avec des fichiers C et des bibliothèques C existants
  • Les fonctionnalités proposées incluent des alias comme string, any et nil, l’inférence de type var/let, la sortie générique print/println, la comparaison générique equal et des macros de ternaire plus lisibles
  • Pour réduire le code lié aux boucles et à la mémoire, il fournit une syntaxe basée sur des macros comme foreach, forthese, fortimes, forrange, new, vector, delete, with, lambda et try/catch
  • Certaines fonctionnalités ont des prérequis comme GCC, Clang, C23+ ou C11+, et les variables encapsulées par with étant de type void *, une conversion de type peut être nécessaire avant utilisation

Objectif et mode d’utilisation de Pretty C

  • Pretty C est un langage de scripting compatible avec C, qui ajoute aux programmes C le typage dynamique, l’itération générique, le suivi des ressources et plusieurs commodités syntaxiques
  • Il conserve la rétrocompatibilité avec le C existant et les bibliothèques C
  • Il s’inspire de Lua, Python, JavaScript et Lisp
  • Pour l’installer, il suffit de cloner le dépôt ou de copier uniquement le fichier pretty.h
    • Il peut être inclus dans n’importe quel fichier C avec #include "pretty.h"
    • Il peut aussi être utilisé en ajoutant le chemin de Pretty C au chemin d’inclusion

En-têtes inclus par défaut et macros simples

  • Pretty C inclut des en-têtes standard afin de rendre immédiatement disponibles des types courants et des expressions alternatives pour les opérateurs
    • stdbool.h : true, false, bool
    • stdint.h : types entiers à largeur fixe comme uint64_t
    • iso646.h : opérateurs alternatifs comme and à la place de &&, ou or à la place de ||
  • Il fournit aussi des macros simples fréquemment définies
    • max, min : valeur maximale et minimale de deux nombres
    • len : longueur d’un tableau
    • default : fournit une valeur de fallback
    • limit : limite une valeur à une plage
    • between : vérifie si un nombre se trouve dans une plage
    • divisible : vérifie si un nombre est divisible par un autre

Types et alias

  • Pretty C fournit de courts alias de types pour les types C
    • string correspond à char*
    • byte correspond à char
    • bytes correspond à char*
    • any correspond à void*
    • Il fournit aussi des raccourcis d’entiers : uchar, ushort, uint, ulong
  • Il inclut aussi des alias syntaxiques influencés par Lua et Lisp
    • eq, is : ==
    • bitnot, bitxor : alias pour les opérations bit à bit
    • success, fail, failure : alias pour le motif success == 0
    • below, above, upto, downto : opérateurs de comparaison
    • even, odd, positive, negative, zero, empty : prédicats sur les nombres et les données
    • nil : NULL
    • until : while négatif
    • elif : else if
    • ifnt, elifnt : expressions de type if(!...)
    • repeat : alias de do à la manière de Lua
    • done, finish, pass : alias de break et continue
    • always, forever, loop, indefinitely : expressions de boucle infinie
    • never, comment : expressions permettant au compilateur d’analyser du code sans l’exécuter

Inférence de type, sortie, comparaison et ternaire

  • L’inférence de type est disponible avec GCC, Clang ou C23+
    • Les exemples fournis incluent des formes comme var t = time(0);, let lt = localtime(&t);, local at = asctime(lt);
  • La sortie générique est disponible à partir de C11
    • print affiche la valeur transmise
    • println ajoute un saut de ligne après l’affichage
  • La comparaison générique d’égalité est aussi fournie comme fonctionnalité C11+
    • Comparaison de chaînes comme equal("NA", line)
    • Exemples de comparaison comme equal(0.3, 0.2 + 0.1)
  • L’opérateur ternaire peut être rendu plus lisible grâce à des macros textuelles
    • when se développe en chaîne vide pour améliorer la lisibilité
    • unless se développe en not et fonctionne comme la forme négative de when
    • then correspond à ?
    • other et otherwise correspondent à :
    • only s’utilise lorsqu’une clause otherwise n’est pas nécessaire
    • otherwhen s’utilise pour chaîner la condition suivante

Macros d’itération

  • Pretty C abstrait les motifs courants de boucles for sous forme de macros
  • foreach (var, type, length, ...)
    • Parcourt un tableau ou une zone mémoire
    • À chaque itération, var devient un pointeur vers l’élément correspondant du tableau
    • Comme il s’agit d’un pointeur, l’élément peut être modifié sur place si nécessaire
  • forthese (var, type, ...)
    • Parcourt les arguments variadiques transmis et associe chaque valeur à var de type type
  • fortimes (var, times)
    • Réduit un motif courant qui itère de 0 jusqu’à un nombre positif de répétitions
  • forrange (var, init, target)
    • Parcourt une plage numérique de init à target
    • init et target peuvent être des entiers signed ou unsigned quelconques
    • Si init est supérieur à target, le pas d’itération décroît
  • forrangeby (var, type, init, target, by)
    • Parcourt var, de type type, de init à target avec un pas de by

Allocation et utilitaires de bloc

  • Les macros d’allocation, inspirées de C++, simplifient les motifs courants d’allocation mémoire
    • new (type, ...) : crée une structure ou autre dans une forme similaire au new de C++
    • vector (length, type, ...) : simplifie l’allocation d’un tableau d’une longueur et d’un type donnés, avec un contenu initial
    • delete (...) : identique à free, mais utilisable si l’on préfère un nom à la C++
  • Les utilitaires de bloc fournissent des macros pour les liaisons locales, l’exécution différée et les opérations au niveau d’un bloc
    • lambda (ret, name, ...) : permet d’utiliser en C des fonctions imbriquées, des lambdas ou des closures avec GCC, Clang ou C++
    • with (var, close, ...) : utile pour les objets alloués dynamiquement ou les handles de fichiers, en fournissant à l’avance une fonction de libération des ressources close
      • Inconvénient : la variable liée var est de type void *, ce qui peut nécessiter une conversion vers le type souhaité avant utilisation
    • defer : a été supprimé, avec une indication d’utiliser le nouveau defer disponible
    • try et catch : permettent d’écrire la gestion d’erreurs en C sous une forme plus expressive
    • NOERR, NOERROR : fournis pour faciliter les switch-case d’erreurs

Mode de contribution

1 commentaires

 
GN⁺ 2024-10-25
Commentaires sur Hacker News
  • La tradition de créer des langages spécifiques à un domaine dans C est ancienne
    Stephen Bourne voulait écrire le shell en ALGOL, alors il a martelé le préprocesseur C avec acharnement jusqu’à le faire ressembler au langage qu’il préférait
    https://www.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh...

  • Je me demande si quelqu’un peut expliquer s’il s’agit d’une blague, ou si l’auteur est réellement confus
    Rien n’a de sens. Ce n’est pas du « scripting », il prétend introduire un « typage fort » mais ne fait absolument rien avec les types, et les alias d’opérateurs censés s’inspirer de Lua et Lisp n’existent en fait dans aucun de ces deux langages. Pourtant, ça ne ressemble pas clairement à une parodie, donc c’est vraiment déroutant

    • Ça a l’air sérieux. Il dit : « Jetez Lua, Python, JavaScript, Ruby et des dizaines d’autres langages. Pretty C est le langage de scripting ultime, rapide comme l’éclair et même fortement typé !! »
    • Je ne pense pas du tout que l’auteur soit confus. Après, être confus n’a rien de grave
  • Ça se présente comme un langage de scripting, mais il faut quand même compiler le programme. Huées
    En y ajoutant CINT(https://root.cern.ch/root/html534/guides/users-guide/CINT.ht...), on obtient une exécution immédiate et même un REPL

    • Si le but de ce dépôt est de faire souffrir, on pourrait aussi ajouter un shebang au fichier pour le rendre exécutable [0]
      J’avais lu autrefois un billet de blog qui détaillait le fonctionnement de ./foo et la manière dont les fichiers ELF sont exécutés. On pourrait sans doute enregistrer les programmes .c de la même façon pour les compiler puis les exécuter
      [0] https://gist.github.com/jdarpinian/1952a58b823222627cc1a8b83...
    • Autant utiliser tcc [0]. C’est bien plus léger que ce monstre, et c’est du C, pas du C++
      [0] https://bellard.org/tcc/tcc-doc.html
    • Le CERN utilise désormais cling (https://github.com/root-project/cling)
    • Qui a décidé qu’un langage de scripting ne pouvait pas être compilé ? Et Clang-REPL est une autre manière de le rendre compatible avec un usage REPL
  • « Fournit assez de sucre syntaxique pour provoquer une crise cardiaque diabétique chez les développeurs C. »
    J’adore vraiment cette formule

  • Je vois plusieurs choses à ajouter à CNoEvil[0] et ogw[1]. On dirait qu’il y en a toujours plus chaque fois que ce projet refait surface tous les quelques mois
    [0] https://git.sr.ht/~shakna/cnoevil3/
    [1] https://git.sr.ht/~shakna/ogw

    • « Prendre une multitude de mauvaises idées et les écraser ensemble en un monstre abominable. »
      Une phrase qu’on finit sincèrement par aimer au plus profond de soi
  • Ce projet refait la même erreur que Python 2. Chaînes de caractères et octets ne sont pas le même type, et ne doivent pas être traités comme tels

    • Je me demande quel type on devrait attribuer au texte du shell, c’est-à-dire aux valeurs dans argv ou à la sortie d’un sous-processus
      Ça peut contenir n’importe quelle donnée arbitraire, donc pas forcément une chaîne UTF-8 valide, mais des outils comme awk et grep sont utilisés partout
      On peut considérer que chaînes et octets relèvent d’un même type général, tout en trouvant utile de donner en interne un type distinct aux octets UTF-8 valides. Rust gère cela plutôt bien avec OsString et String
    • Comme ce projet s’appelle pretty.c et vise à rester rétrocompatible avec C et toutes ses bibliothèques, y a-t-il vraiment un choix plus logique que de conserver les chaînes multioctets de C ?
      https://en.cppreference.com/w/c/string/multibyte
    • Je ne suis pas d’accord. Ce principe repose entièrement sur les postulats suivants : les données textuelles sont correctement encodées, il n’existe qu’un seul encodage de chaînes (généralement UTF-8), les données textuelles doivent être validées au moment de leur création, il ne faut jamais autoriser à tronquer un point de code logique, il ne faut pas effectuer d’opérations de chaîne sur des octets « invalides », et la présence d’un encodage correct est à la fois le début et la fin de la validation
      Rien de tout cela n’est toujours vrai. Il est utile d’envelopper une séquence d’octets validée dans un type qui ne peut être créé qu’au terme d’une validation, mais dans ce cas Utf8String et EmailAddress relèvent en pratique de la même catégorie. Il n’y a aucune raison de traiter spécialement l’encodage dans le système de types
    • Cela dépend de la définition qu’on donne à une « chaîne de caractères »
      Si l’on entend par là un « texte lisible par un humain », alors oui, je suis d’accord pour dire qu’une chaîne n’est pas la même chose qu’un tableau d’octets arbitraires. Mais beaucoup de langages n’imposent pas cette définition
  • Cela me rappelle une base de code C++ que j’avais dû auditer il y a longtemps. Tout était écrit comme si c’était du Java : camel case partout, des getters/setters pour toutes les variables de classe, et des interfaces disséminées un peu partout

    • Tu n’as encore rien vu. Il suffit de regarder le code source du Bourne shell de la Seventh Edition d’Unix
      https://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd...
      Difficile de croire que ce n’est pas de l’ALGOL
    • Le camel case, les getters/setters et les interfaces ne sont souvent pas si éloignés des directives de style. C’est le cas par exemple du code Windows, même si ça ne va pas jusqu’à toutes les variables, bien sûr. Et une grande partie de la conception de Java a aussi été copiée depuis le C++
    • J’ai déjà vu des bases de code similaires écrites par des gens qui avaient passé trop longtemps sur Java. Il y avait même une classe String maison qui ne faisait qu’encapsuler std::string avec des méthodes à la Java
    • Heureusement qu’on n’utilisait pas MSVC, apparemment
      https://learn.microsoft.com/en-us/cpp/cpp/property-cpp?view=...
    • J’ai connu ça aussi, avec en plus des chaînes passées comme en Java via f(String *), si bien que le code C++ ressemblait à f(new String("Hello")
  • Si ça vous intéresse, vous aimerez peut-être aussi libcello.h
    https://www.libcello.org

    • Exact, c’est l’une des inspirations
  • Les noms de types sont bien trouvés, et le choix d’un macro de fonction intégrée comme min est parfait. J’aime bien aussi Len
    Les opérateurs booléens nommés sont peut-être un peu excessifs, mais ça vaut le coup d’essayer, et l’opérateur ternaire est de toute façon difficile à lire, donc il y a de la marge pour l’améliorer. Je ne suis pas totalement convaincu par toutes les définitions de boucles, mais certaines ont du sens. Le suivi des ressources est impressionnant, et la forme for... est un peu laide, donc on pourrait sans doute lui donner un autre nom
    Dans l’ensemble, c’est une tentative assez solide. Je donnerais autour de 8/10 au design. La manière dont c’est esquissé avec des macros C est vraiment élégante et donne l’impression d’un bon vrai code. Je ne sais pas si je l’utiliserais moi-même. C’est un nouveau langage, et j’aime déjà le C. Mais la façon dont c’est implémenté est très claire, donc ça peut aider les gens à apprendre le C et à réfléchir à la conception des langages

    • Il n’est pas nécessaire de tout utiliser. Dans mes projets, j’emploie surtout les booléens, len(), max/min et quelques macros d’opérateurs, simplement parce que je n’ai pas encore eu besoin des autres fonctionnalités appétissantes. Donc ce serait bien d’essayer ne serait-ce que quelques macros d’opérateurs
  • Pour ifnt comme équivalent de if(!...), unless me paraît plus lisible que ifnt

    • Autre sujet de débat sur les noms : appeler une boucle infinie for(;;) par always
      Dans d’autres langages, j’ai vu loop. Mais Qt l’appelle forever, et c’est vraiment joli. Très Qt, aussi
    • À l’inverse, ifnt est amusant à prononcer à voix haute
    • Oui. Mais j’ai gardé unless pour la condition ternaire. C’est plus utile là de toute façon
      Ah, attends, c’est John Tromp. Le créateur de BLC ! Je suis fan