2 points par GN⁺ 2024-11-03 | 1 commentaires | Partager sur WhatsApp
  • En travaillant sur 42 langages pour créer un nouveau surligneur syntaxique pour llamafile, il est apparu que même le simple lexing regorge d’exceptions propres à chaque langage et de grammaires anciennes
  • L’implémentation utilise le C++ et GNU gperf pour accélérer la recherche de mots-clés, et gère le traitement centré sur les chaînes, commentaires et mots-clés avec une machine à états finis basée sur des boucles for et des switch
  • Les trigraphes du C, les terminateurs de ligne JavaScript u2028 et u2029, les heredoc de Shell, Perl et Ruby, ainsi que l’interpolation de chaînes de Kotlin, Scala, TypeScript et Swift reviennent sans cesse comme des cas difficiles à traiter par le seul lexing
  • En nombre de lignes de code, FORTH tient en 125 lignes tandis que Ruby monte à 1042 lignes, et Ruby est particulièrement difficile à surligner sans parsing à cause des collisions entre l’opérateur <<, les heredoc et les backquotes
  • Le nouveau surligneur de llamafile a été montré sous Windows 10 avec Meta LLaMA 3.2 3B Instruct, et fonctionne aussi sur macOS, Linux, FreeBSD et NetBSD, ce qui le différencie d’ollama qui n’a pas de surlignage syntaxique

Ce qui a motivé la création du surligneur pour llamafile

  • Un mois a été consacré à l’étude de 42 langages de programmation pour créer un nouveau surligneur syntaxique pour llamafile
  • Les langages pris en charge sont : Ada, Assembly, BASIC, C, C#, C++, COBOL, CSS, D, FORTH, FORTRAN, Go, Haskell, HTML, Java, JavaScript, Julia, JSON, Kotlin, ld, LISP, Lua, m4, Make, Markdown, MATLAB, Pascal, Perl, PHP, Python, R, Ruby, Rust, Scala, Shell, SQL, Swift, Tcl, TeX, TXT, TypeScript et Zig
  • Cette liste couvre l’essentiel du TIOBE Index, mais Scratch en est exclu car il utilise des blocs plutôt que du texte

Méthode d’implémentation : gperf et machine à états finis

  • Le principal goulet d’étranglement d’un surligneur syntaxique de base apparaît lorsqu’il faut déterminer si un token est un mot-clé, car les comparaisons répétées de chaînes s’accumulent
  • Le C++ et GNU gperf sont utilisés pour générer une table de hachage parfaite
    • Un exemple d’entrée gperf définit comme mots-clés des constantes Java telles que true, false et null
    • Le fichier C généré par gperf peut produire une fonction de hachage ne prenant en compte qu’un seul caractère afin d’obtenir des recherches sans collision
  • Pour le surlignage du C, même avec environ 4 000 mots-clés définis, gperf permet d’atteindre 35 MB/s
  • Le reste du traitement peut en grande partie être géré par une machine à états finis
    • Il est possible de créer un surligneur de base uniquement avec des boucles for et des switch, sans flex, bison ni ragel
    • En se concentrant sur les chaînes, les commentaires et les mots-clés, le traitement reste généralement au niveau du lexing
    • Pour surligner des éléments comme les noms de fonctions C, un vrai parsing peut devenir nécessaire
  • highlight_ada.cpp sert d’exemple d’implémentation

Démo de llamafile et environnement d’utilisation

  • Le nouveau surligneur et l’interface de chatbot ont amélioré l’utilisabilité de llamafile, et le surlignage syntaxique constitue un différenciateur face à ollama
  • La démo a été exécutée sous Windows 10 avec le modèle Meta LLaMA 3.2 3B Instruct
  • Ce llamafile peut aussi s’exécuter sur macOS, Linux, FreeBSD et NetBSD
  • Avec l’amélioration de la qualité de modèles à poids ouverts comme gemma 27b it, l’intérêt d’utiliser Claude diminue

C : des exceptions de lexing à l’opposé de son image de langage simple

  • Contrairement à son image de langage simple, le C présente des éléments lexicaux très particuliers
  • Les trigraphes permettent de remplacer des caractères comme #, [, \\, ^, {, |, }, ~ par ??=, ??(, ??/, ??), ??', ??<, ??!, ??>, ??-
    • Ils ont été supprimés du standard C23, mais devraient continuer à être pris en charge par les compilateurs à cause des logiciels historiques
    • Un bon surligneur syntaxique doit donc aussi gérer cette grammaire
  • Les universal character du C permettent d’exprimer des identifiants Unicode dans du code source ASCII, comme dans int \\uFEB2 = 1;
    • GCC renvoie une erreur si le caractère n’appartient pas à certains plans Unicode autorisés par le comité de normalisation
  • Les commentaires C sur une ligne peuvent s’étendre sur plusieurs lignes grâce à un antislash en fin de ligne
    • Des langages comme Perl, Ruby ou Shell autorisent les échappements par antislash dans le code source, mais ne prennent pas en charge ce comportement de type C
    • Tcl et GNU Make semblent en revanche le prendre en charge
    • Emacs et Pygments le traitent parfois de façon incorrecte, tandis que Vim semble toujours gérer correctement l’antislash
  • Le C possède aussi une directive préprocesseur nulle
    • Les fichiers .c des premières sources v6 commencent souvent par une ligne contenant seulement #
    • Cela reste aujourd’hui du code valide, et peut servir à forcer le préprocesseur à supprimer certains commentaires même si cc -C -E demande leur conservation

Syntaxe des commentaires : Haskell et D

  • En C, on ne peut pas imbriquer de commentaires multilignes à l’intérieur d’un autre commentaire multiligne
  • Haskell prend en charge les commentaires imbriqués sous la forme {- ... {- ... -} ... -}
  • D reprend les commentaires // et /* ... */ du C, et ajoute séparément une syntaxe de commentaire récursif sous la forme /+ ... +/
  • La documentation de la syntaxe lexicale de D est formelle et détaillée, ce qui fournit bien les informations nécessaires à l’implémentation
    • Le document D lexical syntax traite de détails comme les chaînes hexadécimales et les chaînes heredoc
    • D dispose de nombreuses formes de chaînes : \"...\", chaînes avec backticks, r\"...\", q\"...\", x\"...\", etc.

Tcl et JavaScript : guillemets et terminateurs de ligne invisibles

  • En Tcl, un identifiant peut contenir des guillemets
    • puts a\"b affiche a\"b
    • On peut aussi mettre des guillemets dans les noms de variables, mais il faut alors utiliser la notation ${a\"b} plutôt que $a\"b pour les référencer
  • JavaScript possède une grammaire lexicale intégrée pour les expressions régulières
    • Dans /[/]/g, le / à l’intérieur des crochets d’un ensemble de caractères n’a pas besoin d’être échappé
    • Une méthode consistant à scanner simplement jusqu’au slash fermant peut se tromper sur du code minifié
  • ECMAScript définit u2028 LINE SEPARATOR et u2029 PARAGRAPH SEPARATOR comme des terminateurs de ligne
    • Ces caractères se comportent en pratique comme \\n
    • Comme ce sont des caractères de type Trojan Source, Emacs est configuré pour les afficher respectivement comme et
    • Beaucoup de logiciels ne reconnaissent pas ces caractères et les affichent parfois comme des points d’interrogation
    • En dehors de D, l’auteur dit ne pas connaître d’autre langage qui les traite ainsi
  • Cette propriété a permis de créer un polyglot C et JavaScript dans SectorLISP
    • lisp.js s’exécute dans le navigateur et peut aussi être compilé avec GCC pour tourner en local
    • llamafile surligne correctement ce type de code, ce que l’auteur dit ne pas avoir retrouvé dans d’autres surligneurs

Shell et les cas limites des heredoc

  • En Shell, un heredoc prend la forme cat <<EOF ... EOF pour écrire une chaîne sur plusieurs lignes
  • La syntaxe de heredoc quoté cat <<'END' désactive la substitution de variables
  • Si le marqueur de heredoc est une chaîne vide, le heredoc se termine à la ligne vide suivante
    • Le programme d’exemple affiche hello et world sur deux lignes
  • Dans les langages qui prennent en charge les heredoc, comme Shell, Ruby et Perl, on peut placer plusieurs heredoc sur une même ligne
  • En Shell, comme en Tcl, # ne commence pas toujours un commentaire
    • Dans ${x#hi-}, # sert à retirer un préfixe, et l’exemple affiche there

Interpolation de chaînes : les langages qui exigent une pile d’états

  • Même si une chaîne Kotlin commence par \", l’interpolation peut la faire basculer dans un autre état lorsqu’un { apparaît
  • TypeScript, Swift, Kotlin et Scala prennent fortement en charge l’interpolation de chaînes en permettant d’y insérer du vrai code
  • Pour surligner Kotlin, Scala et TypeScript, il faut compter les accolades et maintenir une pile d’états du parseur
    • TypeScript reste relativement simple et peut être géré en ajoutant quelques états à la machine à états finis
    • Kotlin et Scala prennent en charge l’interpolation aussi bien dans les chaînes à guillemets doubles que dans les chaînes à triples guillemets, ce qui a nécessité environ 13 états indépendants rien que pour le lexing des chaînes
    • Swift prend en charge la syntaxe d’interpolation \"\\(var)\" et les triples guillemets, mais son implémentation a tout de même demandé 10 états

Swift, C#, FORTH : différentes façons d’encadrer les chaînes

  • Swift prend en charge des syntaxes \"...\", \"\"\"...\"\"\" et /regex/ pouvant être entourées d’un nombre arbitraire de #
    • Le nombre de # doit être le même des deux côtés
    • Cette approche résout le problème de l’inclusion de guillemets ou de délimiteurs de regex dans la chaîne
  • C# ressemble aux chaînes multilignes à triples guillemets de Python, mais permet d’utiliser davantage de guillemets à l’ouverture et à la fermeture
    • Le nombre de guillemets à gauche détermine la condition de fermeture à droite
    • L’auteur estime qu’autoriser un nombre arbitraire de guillemets réduit les règles de validation par rapport aux triples guillemets classiques de Python et rend le décodage plus simple avec une machine à états finis
  • FORTH tokenize tout selon les séparations par espaces
    • La syntaxe de début de chaîne c\" constitue elle-même un token
    • c\" hello world\" a le même sens que \"hello world\" dans d’autres langages

FORTRAN et COBOL : règles de colonnes fixes

  • llamafile est présenté comme un cas d’usage pouvant aider à maintenir des systèmes bancaires en FORTRAN et COBOL même après le départ à la retraite des programmeurs
  • Il serait possible de demander du code COBOL et FORTRAN à une IA contrôlable en environnement air gap comme Gemma 27b
  • Les règles de colonnes fixes du FORTRAN sont les suivantes
    • Un *, c ou C en colonne 1 transforme la ligne en commentaire
    • Un caractère non blanc en colonne 6 permet de continuer une ligne au-delà de 80 caractères
    • Des chiffres en colonnes 1 à 5 forment une étiquette
  • Les règles du COBOL sont les suivantes
    • Un * en colonne 7 transforme la ligne en commentaire
    • Un - en colonne 7 permet de continuer une ligne au-delà de 80 caractères
    • Les colonnes 1 à 6 contiennent le numéro de ligne

Zig et Lua : deux autres solutions pour les chaînes multilignes

  • Zig possède une syntaxe de chaîne multiligne commençant par deux antislashs
    • Cela évite d’avoir à appeler textwrap.dedent() comme avec les chaînes Python à triples guillemets
    • L’auteur juge en contrepartie que les points-virgules sont peu élégants
    • Cette syntaxe de chaîne est proposée comme une idée que pourraient envisager des langages comme Go, Scala ou Python, qui n’exigent pas de point-virgule
  • Les chaînes multilignes de Lua reposent sur [[...]] et permettent un nombre arbitraire de = entre les crochets
    • Comme dans [==[ ... ]==], le nombre de = doit correspondre au début et à la fin
    • Le même mécanisme peut aussi servir pour les commentaires
    • Les formes --[[ ... ]] et --[==[ ... ]==] sont toutes deux possibles

Assembly : combinaison de dialectes et de préprocesseurs

  • L’Assembly fait partie des langages difficiles à surligner en raison de la multiplication des dialectes
  • llamafile vise à gérer de manière raisonnable plusieurs syntaxes, notamment AT&T et nasm
  • On peut obtenir un rendu correct de la plupart des codes assembly en traitant comme mot-clé le premier identifiant de la ligne qui ne suit pas un deux-points
  • La syntaxe des commentaires n’y est pas non plus simple
    • Le commentaire UNIX d’origine ne nécessitait qu’un simple /
    • GNU as le prend encore en charge, mais seulement en début de ligne
    • Clang ne prend pas en charge ce commentaire fixe, ce qui le rend peu praticable dans le code open source
  • L’assembleur UNIX d’origine n’utilisait pas de guillemet fermant pour les littéraux caractère
    • 'x représente la valeur 0x78 du caractère x
    • GNU as continue de le prendre en charge, contrairement à LLVM
    • Comme cette syntaxe existe dans du code ancien, un bon surligneur doit aussi la gérer
  • GNU assembler autorise les identifiants entre guillemets, ce qui permet de mettre presque n’importe quel caractère dans un symbole
  • L’Assembly est souvent utilisé avec le préprocesseur C ou avec m4
    • Les lignes commençant par dnl, m4_dnl ou C doivent aussi être traitées comme des commentaires

Ada et BASIC : quand une petite grammaire perturbe le lexing

  • Ada est relativement simple à lexer, mais son usage de l’apostrophe est particulier
    • Il peut avoir des littéraux caractère comme 'x', à la manière du C
    • Il utilise aussi l’apostrophe pour référencer des attributs, comme dans Foo'Size
    • Character'(')')'Image déclare un caractère puis le convertit en représentation textuelle via la fonction Image
  • L’exemple de Commodore BASIC casse plusieurs hypothèses courantes du surlignage syntaxique
    • Les chaînes peuvent omettre le guillemet fermant en fin de ligne
    • Les noms de variables portent des sigils comme $
    • Des mots-clés comme goto sont activement lexés même à l’intérieur des identifiants
  • Visual BASIC possède une syntaxe de littéral de date comme #1/1/2024#
  • Visual BASIC possède aussi des directives de préprocesseur comme #If DEBUG Then, #Else et #End If, ce qui complique le lexing

Perl : la complexité à la frontière entre Shell et langage de programmation

  • Perl se situe entre Shell et langage de programmation, et hérite des complexités des deux mondes
  • Il a fait des expressions régulières un élément de premier ordre du langage, influence reprise ensuite par d’autres langages comme Python
  • Sa syntaxe de substitution ressemble à celle de sed, comme dans s/hello/Perl/i
    • On peut utiliser n’importe quel signe de ponctuation comme délimiteur à la place de /
    • s!hello!Perl!i est pratique quand le slash fait partie de l’expression régulière
    • Avec des caractères appariés comme dans s{hello}{Perl}i, des règles supplémentaires entrent en jeu
  • Perl possède de nombreux préfixes magiques qui doivent être surlignés comme des chaînes
    • m, s, y, qr, qw, qq, qx s’emploient avec divers délimiteurs
  • Pour éviter de prendre y/x/y/ pour une division, il faut tenir compte du contexte
    • Comme les variables Perl portent des sigils tels que le scalaire $, le tableau @ ou le hash %, cela aide à les distinguer sans parser l’ensemble de la grammaire
  • Il est d’usage en Perl d’intégrer dans le code source de la documentation POD destinée aux man pages
    • Un =word en début de ligne ouvre une documentation POD, et =cut la referme

Ruby : la cible de lexing la plus difficile

  • Ruby apparaît comme l’union de tous les langages précédents, et sa syntaxe est jugée insuffisamment documentée de manière formelle
  • Le document syntax du manuel Ruby est considéré comme léger sur les détails
  • Ruby prend en charge la syntaxe des backquotes tout en autorisant aussi les backquotes comme noms de méthodes, ce qui rend difficile pour un surligneur de savoir s’il s’agit ou non d’une chaîne
  • Ruby possède à la fois l’opérateur << et les heredoc
    • Des formes comme options[:includes] <<arg; true existent dans du vrai code
    • Ce code peut ressembler à un heredoc, et Emacs se trompe aussi sur ce cas
  • Du code comme puts \"This is #{<<HERE.strip} evil\" est également valide, avec un heredoc à l’intérieur d’une interpolation de chaîne
  • Parmi les 42 langages, Ruby est décrit comme le plus grand choc : il pourrait être impossible de le lexer sans parsing, et même avec du parsing il resterait difficile d’être certain de l’interprétation

La complexité vue par le nombre de lignes de code d’implémentation

  • Si l’on se base sur le nombre de lignes de code des implémentations de surligneurs, FORTH est le plus simple et Ruby le plus complexe
  • L’implémentation la plus courte est highlight_forth.cpp avec 125 lignes
  • Parmi les implémentations relativement courtes figurent m4 avec 132 lignes, Ada avec 149, LISP avec 160, MATLAB avec 166, COBOL avec 186, BASIC avec 199 et FORTRAN avec 200
  • Les implémentations de taille intermédiaire comprennent JavaScript avec 337 lignes, TypeScript avec 371, Kotlin avec 387, Scala avec 387, Assembly avec 447, C avec 449, Swift avec 455 et D avec 521
  • Les plus longues sont Shell avec 570 lignes, Perl avec 583 et highlight_ruby.cpp avec 1042 lignes

1 commentaires

 
GN⁺ 2024-11-03
Avis sur Hacker News
  • Mon trigraph C préféré est une forme comme do_action() ??!??! handle_error()
    On dirait une syntaxe spéciale de gestion des erreurs, mais quand on comprend que ??!??! est transformé en ||, le OU logique, et qu’avec les règles d’évaluation en court-circuit handle_error() est exécuté quand do_action() renvoie une valeur non nulle, c’est assez satisfaisant.

  • Lecture amusante, mais elle m’a plutôt rendu plus sensible au point de vue de Lisp
    D’après ce que j’ai compris, la syntaxe n’est pas une partie très importante du langage ; comme elle tend à devenir un obstacle plus qu’une aide, elle devrait être aussi simple et uniforme que possible pour qu’on puisse se concentrer sur le reste.
    Cela dit, apprendre l’édition structurelle en Lisp a jusqu’ici été plus proche d’un obstacle que d’une aide, et j’imagine qu’un jour cela finira par payer.

    • Une syntaxe simple peut convenir à l’ordinateur, mais la syntaxe est surtout conçue pour être lue et écrite par des humains.
      La rendre aussi simple que Lisp ne fait que repousser les discussions de syntaxe vers des questions de sémantique ; on ne fait que changer de couche.
      Je pense qu’une syntaxe complexe est beaucoup plus facile à lire et à écrire qu’une syntaxe simple avec une sémantique complexe. Les erreurs de syntaxe donnent un retour rapide, alors que les erreurs de sens peuvent rester cachées jusqu’à l’exécution.
    • Lisp dispose de macros de lecture qui permettent de reprogrammer le lexer, et le passage de la structure visible à l’arbre syntaxique peut lui aussi être manipulé par des macros.
      Il existe par exemple quelque chose comme https://pyret.org/. En pratique, il est difficile de dire que Lisp est simple ou forcément uniforme.
    • Je suis un peu surpris que l’édition structurelle ait été un obstacle ; si je peux donner un conseil, il suffit d’utiliser parinfer et de connaître les raccourcis de trois commandes : slurp, barf et raise.
      Avec ces quatre éléments seulement, on profite déjà d’environ 95 % des avantages de paredit sans sa complexité, et le reste des astuces peut s’apprendre une fois qu’on est à l’aise.
  • Il y avait un passage disant : « Je ne sais pas qui veut faire de la coloration syntaxique du C à 35 Mo/s, mais c’est désormais possible ». C’est rapide, mais tcc compile du C en code binaire à 29 Mo/s, même sur de très vieux ordinateurs : https://bellard.org/tcc/#speed
    On pourrait sans doute faire plus vite, mais ce ne sera probablement pas nécessaire.

  • L’auteur a raté quelques points.
    TypeScript, Swift, Kotlin et Scala ne sont pas les seuls à aller jusqu’à autoriser du vrai code dans l’interpolation de chaînes ; C#, Python, JavaScript, Ruby, Shell et Make proposent aussi quelque chose de similaire.
    En Tcl, la distinction entre code et données est difficile, si bien que { } est en fait un délimiteur de chaîne particulier ; dans xyzzy {#hello world}, il est impossible de savoir avant l’évaluation si #hello world est un commentaire ou une chaîne.
    PostgreSQL dispose de pratiques chaînes entre dollars, si bien que, comme dans https://www.postgresql.org/docs/current/sql-syntax-lexical.h..., 'Dianne''s horse', $$Dianne's horse$$ et $SomeTag$Dianne's horse$SomeTag$ ont le même sens.

    • Perl le permet aussi.
      Une chaîne comme "I have $foo $bar's: @{[$bar x $foo]}" peut afficher I have 5 x's: xxxxx.
      La syntaxe @{[...]} exploite le fait que Perl permet l’interpolation de tableaux, et pas seulement de scalaires ; le [...] intérieur crée une référence de tableau, et le @{...} extérieur la déréférence. L’interpréteur Perl autorise du code arbitraire dans l’expression interne.
    • Je pense qu’il vaut mieux voir cela non pas comme « du code dans une chaîne », mais comme une syntaxe spéciale de concaténation de chaînes.
      On peut alors imbriquer comme dans "foo { toUpper("bar { x + y } bar") } foo", et si + est la concaténation de chaînes, cela devient pratiquement équivalent à "foo " + toUpper("bar " + (x + y) + " bar") + " foo".
      Je ne sais pas s’il existe réellement un langage qui fonctionne ainsi.
    • Ruby pousse cela à l’extrême.
      J’aime Ruby, mais il est difficile de défendre du Ruby valide comme puts "This is #{<, qui mélange interpolation de chaînes et heredoc.
      De plus, en Ruby, l’espace peut aussi servir de caractère de citation. Dans un contexte sans opérande à gauche, % commence une chaîne quotée et le caractère suivant indique le type de citation ; ainsi % hello devient une chaîne contenant hello. %(this is a string) ou %{this is a string} sont très bien, mais je n’ai jamais vu l’espace utilisé ainsi dans la nature, et même irb ne le gère pas correctement ; il pourrait donc très bien être supprimé.
    • L’interpolation de chaînes de Scala peut aussi servir de cible de pattern matching.
      Si l’on écrit val s"${a} + ${b}" = "1 + 2", alors a vaut 1 et b vaut 2.
    • Ce qui est élégant avec les chaînes interpolées de C#, c’est qu’elles sont évaluées paresseusement.
      Auparavant, dans une boucle chaude, un code comme log.trace($"Entering iteration {i} for customer {c.ID} [{c.ShortName}]"); appelait string.Concat à chaque fois avant même que le logger ne puisse sortir, si bien que beaucoup de loggers implémentaient leur propre interpolation.
      En C#, on peut déclarer une surcharge qui accepte DefaultInterpolatedStringHandler ou un modèle de handler personnalisé ; cette surcharge est prioritaire et permet de vérifier si le log est réellement nécessaire avant de différer la création de la chaîne.
  • Il existe une autre particularité grammaticale, non mentionnée ici, qui casse la plupart des colorateurs syntaxiques
    En Java, les échappements Unicode peuvent apparaître partout, pas seulement dans les chaînes
    Par exemple, class Foo\u007b} est une classe valide, et un \u000A dans un commentaire // peut être traité comme un véritable saut de ligne, si bien que l’exemple avec assert se comporte aussi autrement que prévu

    • Je considère aussi que ne pas savoir colorer correctement cela relève d’un problème de sécurité
      Comme on peut terminer un commentaire de bloc avec un échappement Unicode, pour cacher du code malveillant dans un commentaire d’un fichier source Java, il suffit de créer un prétexte plausible à la présence d’un bloc d’échappements Unicode dans le commentaire
      Un développeur qui ne connaît pas cette particularité risque fort de le voir comme du code commenté et de passer à côté
    • Je n’ai jamais vu ça en Java ; est-ce qu’il y a un vrai usage utile ?
  • Il y a un passage disant que « Ruby est un langage qui échappe à toute tentative de compréhension de sa syntaxe », mais du point de vue de TeX, qui possède un lexer arbitrairement reprogrammable, c’est plutôt mignon

    • Les macros de lecteur de Lisp permettent aussi de programmer le lexer
  • « Tous les programmeurs C savent qu’on ne peut pas mettre un commentaire multiligne dans un commentaire multiligne », mais pour un programmeur Standard ML, cela peut être une limitation surprenante
    Les commentaires imbriqués comme (* (* Nested (**) *) comment *) sont valides, et val _ = print "hello, world\n" qui suit s’exécute aussi normalement
    Quand on pense qu’à l’arrivée de C, il était considéré comme un langage expressif, il est intéressant que les commentaires imbriqués n’aient pas été inclus dans le langage

    • Il y a trois choses amusantes dans cette remarque
      ML n’avait pas de commentaires sur une seule ligne, ce qui est une limitation tout aussi surprenante, et c’est la première fois que j’entends qualifier C d’« expressif », même si en 1972, comparé à l’assembleur, c’était peut-être le cas
      Et je ne vois pas quel rapport la syntaxe des commentaires a avec l’expressivité d’un langage ; par définition, je dirais qu’il n’y en a aucun
    • Pour lexer des commentaires imbriqués, il faut une pile, ou au moins un compteur de niveau d’imbrication
      Traditionnellement, l’analyse lexicale était vue comme un domaine n’utilisant que des automates à états finis, comme les expressions régulières ; ce genre de fonctionnalité semblait donc hors du périmètre du lexing
    • En C aussi, avec #if 0, il existe bien une façon d’imbriquer des commentaires
      On peut encapsuler ainsi : #if 0 ... #if 0 ... #endif ... #endif
    • Pascal a lui aussi toujours pris en charge une syntaxe de commentaires imbriqués comme dans l’exemple
    • Cela vaut non seulement pour Standard ML, mais aussi pour ML en général
  • J’aimerais voir l’implémentation de la coloration syntaxique de joe sous une forme réutilisable
    Le format de https://joe-editor.sf.net/ est assez puissant pour colorer correctement les f-strings Python
    La documentation et les exemples correspondants se trouvent ici : https://github.com/cmur2/joe-syntax/blob/joe-4.4/misc/HowItW..., https://gist.github.com/irdc/6188f11b1e699d615ce2520f03f1d0d...

    • J’ai créé plusieurs lexers et parseurs en m’appuyant sur le parsing par DFA de joe
      La grammaire des états et des transitions était beaucoup plus facile à comprendre que celle des outils standard
      L’inconvénient est que l’ensemble de règles devient verbeux et qu’une structuration idéale est un peu difficile, mais comme cela rend plus simple l’inférence de toutes les règles de génération à partir du code, j’y vois plutôt un avantage
    • Fait intéressant, comme la syntaxe des f-strings Python a changé en 3.12, la coloration doit elle aussi varier selon la version
  • L’auteur ne semble pas avoir essayé de colorer la syntaxe TeX
    C’est peut-être mieux pour sa santé mentale, car avec TeX, une coloration syntaxique complète est généralement impossible sans interpréter le code
    Même le parsing ne suffit pas, car on peut redéfinir le rôle de chaque caractère, au point de faire des choses comme « désormais, K est { et C est } »
    En pratique, certains articles arXiv utilisent même cette fonctionnalité maudite

    • J’ai créé https://github.com/Mozilla-Ocho/llamafile/blob/main/llamafil..., et pour les fichiers .tex que j’ai trouvés sur mon disque dur, il produit une coloration plutôt correcte sans casser
      L’objectif est de couvrir 99,9 % des usages réels. Dans ce cas, il y a de bonnes chances de couvrir aussi la plupart de ce qu’un LLM pourrait produire
      Les syntaxes ésotériques ne posent généralement pas de gros problème tant qu’une chaîne ou un commentaire ne se prolonge pas jusqu’à la fin du fichier en recouvrant le reste du code source
    • J’ai eu du mal à croire, quand je l’ai appris, que \makeatletter ne signifie pas « transforme quelque chose en caractère at », mais qu’au parsing, il traite le caractère @ comme une lettre
    • Common Lisp peut aussi, de manière similaire, redéfinir les readtables. Cela dit, c’est probablement moins abusé sur arXiv
  • Je pense qu’écrire un bon moteur de coloration syntaxique comme celui de Vim n’est pas facile
    La coloration syntaxique exige un traitement contextuel, qui applique des règles différentes à des contenus imbriqués d’une certaine manière
    Le moteur de coloration syntaxique de Vim permet de déclarer deux types d’éléments, match et region : match est une règle lexicale simple, tandis que region possède des expressions qui correspondent respectivement au début, à la fin et au milieu
    On peut déclarer qu’un élément ne s’active que lorsqu’il se trouve dans une zone donnée, et déclarer aussi les relations d’inclusion entre éléments
    Sur cette structure sémantique de base viennent se greffer des fonctionnalités pour divers cas particuliers. Même Justine aurait du mal à le faire sur-le-champ en entretien ; cela ressemble plutôt à un exercice à faire en une nuit

    • Un exemple difficile à gérer est TXR Lisp intégré dans le langage TXR
      Voici le script genman qui prend le résultat de la conversion HTML d’une manpage et le retravaille en HTML pour le manuel TXR : https://www.kylheku.com/cgit/txr/tree/genman.txr
      Les parties qui apparaissent en blanc sont des templates littéraux, et le code Lisp se trouve dans des directives comme @(do ...). Les mots-clés TXR apparaissent en violet, ceux de TXR Lisp en vert, et un même mot peut donc changer selon le contexte
      Les quasi-chaînes peuvent elles aussi contenir une syntaxe imbriquée, avec à nouveau du code intégré ou d’autres quasi-chaînes à l’intérieur. Les fichiers de définition de syntaxe txr.vim et tl.vim sont tous deux générés depuis https://www.kylheku.com/cgit/txr/tree/genvim.txr
    • Naïvement, je pensais que pour faire correctement un moteur de coloration syntaxique, il suffisait de parser en AST, puis de parcourir les tokens en mettant à jour la couleur selon le type de nœud
      Si on ne procède pas ainsi, c’est probablement pour des raisons d’efficacité. Par exemple, au lieu de ne colorer que la partie visible à l’écran, il faudrait parser tout le fichier, ce qui pourrait coûter cher
    • Je ne parierais pas d’argent sur le fait que Justine n’arriverait pas à le faire en entretien ;)