turbo-editors/turbo-golopublic Fork 0
v1.0.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-golo.git
git clone ssh://git@rickub.com/turbo-editors/turbo-golo.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

📦 Turbo Golo d710c1b · on v1.0.0 · k33g · 12h ago
colouring-and-completion.md · 118 lines · 16.1 KBmarkdown
Blame HistoryOpen raw

Coloration et complétion — explication

De quoi s'agit-il ?

Les deux fonctionnalités qui font de Turbo Golo un éditeur pour Golo plutôt qu'un éditeur de texte qui ouvre des fichiers .golo : la coloration syntaxique, et la complétion venue d'un serveur de langage. Elles fonctionnent très différemment, et la différence est instructive.

La coloration est à nous ; la complétion ne l'est pas

La coloration est faite ici, dans quelque six cents lignes de Go écrites à la main. La complétion est faite par golo lsp — l'interpréteur lui-même, en mode serveur de langage — et Turbo Golo ne fait que demander et dessiner.

Cette séparation n'est pas un accident d'effort. La coloration doit être instantanée et tolérante : elle s'exécute à chaque frappe, sur un texte invalide la plupart du temps pendant qu'on le tape, et un colorateur qui s'arrête pour réfléchir ou renonce devant une entrée cassée est pire que pas de colorateur. La complétion doit être juste, ce qui pour Golo signifie parser le fichier, suivre ses lignes import dans les modules embarqués dans le binaire, et savoir ce que prend chacun des 157 builtins — et rien de ce qui doit être instantané ne peut aussi être cela.

L'éditeur dessine donc des couleurs qu'il a calculées lui-même, et affiche des complétions que quelqu'un d'autre a calculées.

Pourquoi Golo est scanné à la main, avec un lexer à portée de main

GoloScript est écrit en Go, et son paquet lexer est un tokeniseur pour exactement ce langage. Turbo Go passe par go/scanner dans la même situation — la bibliothèque standard qui analyse son propre langage, si bien que l'éditeur et le compilateur s'accordent sur ce qu'est un token sans rien à maintenir en phase. Le geste évident était d'importer golo/lexer et de convertir ses positions avec le LineIndex de la bibliothèque.

On ne peut pas l'importer. Le go.mod de GoloScript dit module golo : un nom nu, sans hôte, et le système de modules de Go n'a aucun moyen d'aller chercher un module portant un tel chemin. L'importer exige une directive replace pointant vers un dépôt cloné à côté, et un replace committé casse tout clone qui n'a pas ce dépôt à côté — c'est pourquoi le script de release refuse d'en publier un. Vendre les deux paquets était l'autre voie, et cela reviendrait à une copie du lexer de quelqu'un d'autre qui cesse d'être le sien le jour où il change.

Le lexer est donc la spécification plutôt qu'une dépendance. lexer/lexer.go et token/token.go disent ce qu'est un token — quelles runes ouvrent un commentaire, comment un nombre se termine, quels mots sont réservés — et ce scanner dit la même chose dans le style LineScanner de la bibliothèque. Là où les deux pourraient diverger, la ligne du lexer est citée dans le code à côté de la décision.

Ce sera donc le scanner. Quelque six cents lignes, un fichier chacun pour le répartiteur, les littéraux et les mots — et aucune tentative de moteur général. Il n'y a ni langage de motifs, ni format de grammaire, ni table d'expressions régulières : c'est du Go ordinaire qu'un lecteur peut suivre, la règle que suivent les huit scanners de turbo-core.

Ce qui franchit une ligne, et pourquoi c'est porté plutôt que coupé

Quatre constructions peuvent courir d'une ligne à la suivante, et le lexer de l'interpréteur fait autorité sur chacune :

  • Un commentaire bloc court d'un ---- au suivant, où qu'il soit. Trois tirets sont deux signes moins et un troisième ; un cinquième tiret fait partie du texte.
  • Une chaîne court jusqu'à son guillemet fermant. Le lexer la lit avec for l.ch != '"' && l.ch != 0, qui s'arrête au guillemet ou à la fin du fichier et à rien entre les deux — un retour à la ligne dans une chaîne fait partie de la chaîne.
  • Une chaîne multiligne """ court jusqu'aux trois guillemets suivants, sans qu'aucun échappement soit considéré en chemin.
  • Un littéral de caractère '…' est lu par la même boucle qu'une chaîne, et se comporte donc de la même façon.

Tous les autres éditeurs de cette famille arrêtent un littéral à la fin de sa ligne quand le guillemet fermant manque, et Turbo MoonBit en fait une règle : la grammaire de MoonBit dit qu'un retour à la ligne avant le guillemet fermant est une erreur, il n'y a donc rien à porter. Le lexer de Golo dit le contraire, et le scanner suit le lexer : une chaîne non terminée peint le reste du fichier jusqu'à ce qu'un guillemet se présente, parce que c'est exactement ce que l'interpréteur lira comme chaîne. La couleur n'est pas un avertissement, c'est un énoncé sur ce que signifie le programme — et un écran de vert après un guillemet égaré est cet énoncé rendu visible.

Ce qui est porté est une valeur qui dit laquelle des quatre est ouverte. Aucune ne s'imbrique, une profondeur serait donc une affirmation que le langage ne fait pas.

Où le scanner s'appuie sur le langage, et où sur la convention

Les mots-clés, les constantes et les builtins sont des tables lues dans GoloScript, pas remémorées. Les 38 mots-clés sont la table de token/token.go moins les trois littéraux ; les 157 builtins sont ce que répond evaluator.BuiltinNames() moins les cinq compteurs de test qui commencent par un double souligné — les cinq mêmes que le serveur de langage tient hors de ses complétions. Un test tient cette table à un golo lsp en marche dans les deux sens, et c'est ainsi qu'elle reste une table lue dans l'outillage plutôt qu'une table que quelqu'un a tapée un jour.

Un nom qui commence par une majuscule est un type, et c'est ici une convention plutôt qu'une règle. Golo n'a pas de règle de casse dans son lexer : let Count = 1 est légal. Mais les structs, les unions et leurs variantes sont capitalisés par tout le monde — Point, Shape, Circle, Some, None — et rien d'autre ne l'est d'ordinaire, le scanner colore donc selon la convention, comme Turbo Python le fait avec la PEP 8. Ce que cela coûte : le constructeur d'une variante est coloré comme un type (Circle(1.0) et Result_Failure("no") ressemblent à des types appliqués à des arguments), et une variable capitalisée l'est aussi. Rien dans la syntaxe ne les sépare.

Some, None, Ok et Err sont ici des types, pas des constantes. Turbo Rust et Turbo MoonBit les colorent comme des constantes parce qu'un lecteur les rencontre partout et les lit comme intégrés. En Golo ils ne le sont pas : ce sont les variantes d'unions ordinaires déclarées dans gololang.Errors, disponibles seulement après import gololang.Errors, et les colorer comme appartenant au langage dirait au lecteur qu'il n'a pas besoin d'un import alors qu'il en a besoin.

Le nom après function est une fonction, et le chemin après module ou import est un seul nom. Partout ailleurs un nom est une fonction parce qu'une parenthèse le suit, et une déclaration — function main = |args| — est le seul endroit où ce n'est pas vrai ; sans cas particulier, chaque fonction qu'un fichier déclare serait colorée comme une variable ordinaire à l'endroit même où le lecteur la cherche. Un chemin de module — hello.World, gololang.Errors — est une seule portée et une seule couleur parce que c'est un seul nom, et la lecture dont il faut le sauver est celle où gololang.Errors ressemble à une variable à laquelle on fait quelque chose.

Un nom peut être presque n'importe quoi. Le isLetter du lexer admet toute lettre ou marque Unicode, un souligné et quatre blocs d'emoji, si bien que let 😀 = 1 et function 🚀launch = … sont du Golo légal. Le scanner utilise le même prédicat plutôt que celui, ASCII, de turbo-core, et ils sont donc colorés — comme été et 名前, que Turbo MoonBit laisse sans couleur pour son propre langage.

Ce que le lexer lit et que le parseur refuse

C'est la frontière qu'il vaut la peine d'énoncer clairement, parce que ce n'est pas une que le scanner peut voir. Le lexer et le parseur de GoloScript ont été écrits à des moments différents, et le lexer a de l'avance : il lit plusieurs tokens que le parseur, en v0.1.1, rejette ensuite.

Le lexer lit Le parseur dit
42L, un long could not parse "42L" as integer
3.14F, 2.0f, un flottant could not parse "3.14F" as float
'x', un caractère no prefix parse function for CHAR found
1..3, un intervalle expected next token to be ), got .. instead
orIfNull, oftype expected next token to be ), got orIfNull instead
local function … no prefix parse function for LOCAL found

Le scanner colore ce que le lexer lit, parce que le lexer est la spécification de ce qu'est un token et que l'avis du parseur sur ce qu'il faut en faire peut changer demain. 42L est donc un nombre et orIfNull un mot-clé, et un token coloré n'est pas une promesse que l'interpréteur l'accepte. Le serveur de langage vous le dit quand ce n'est pas le cas : ouvrez demos/syntax-tour/lexer-only.golo et chacune de ces lignes reçoit une marque dans la gouttière.

Ce que le scanner refuse de deviner

Là où une construction ne peut pas être reconnue à partir de ce qu'une ligne contient, elle est laissée telle quelle plutôt qu'approximée. Un colorateur qui se trompe est pire qu'un colorateur qui se tait :

Non reconnu Parce que
Les séparateurs de chiffres et les autres bases Le lexer n'a ni 1_000, ni 0xFF, ni 0b1010. 1_000 est le nombre 1 suivi du nom _000, et 0xFF est 0 suivi de xFF — c'est ce que voit l'interpréteur, et colorer l'un ou l'autre comme un seul nombre inventerait un littéral qu'il rejette
Un point initial comme nombre Le lexer exige un chiffre avant le point, .5 est donc un point puis 5
Un l minuscule comme suffixe long Le lexer n'accepte que L ; 42l est 42 et le nom l
Un mot-clé employé comme nom de méthode après un deux-points obj: match() garde match en mot-clé. Le scanner ne suit pas ce qu'un deux-points introduit, et le lexer refuserait le mot de toute façon
Les échappements dans """…""" Le lexer ajoute chaque rune jusqu'aux trois guillemets, """a\""" se termine donc au premier """ quoi que la barre oblique inverse ait voulu dire
Si un nom est lié dans cette portée Rien ici ne lit plus d'une ligne à la fois ; c'est la question du serveur de langage, et F1 y répond

Les huit autres langages viennent gratuitement

TOML, YAML, Markdown, JavaScript, HTML, XML, les Dockerfiles et le shell sont colorés par turbo-core, pas ici. Un projet Golo a un README.md, un compose.yaml pour le service auquel il parle, un Dockerfile pour être livré, et un éditeur qui ne colorerait que les fichiers .golo vous ferait le quitter pour le reste.

Qu'ils soient partagés plutôt que copiés est tout l'intérêt de la bibliothèque : ils ont été écrits une fois, pour Turbo Go, et Turbo Golo les a obtenus en important un paquet.

La complétion, et pourquoi elle peut échouer en silence

Turbo Golo ne sait rien de la sémantique de Golo et n'essaie pas. Il interroge golo lsp par le Language Server Protocol et dessine la réponse.

Trois choses valent d'être sues à ce propos, parce que toutes trois ressemblent à « la complétion est cassée » :

Le serveur est l'interpréteur. Il n'y a pas de binaire golo-lsp séparé à installer et aucun outillage dont il dépend : golo lsp réutilise le lexer, le parseur et l'AST de l'interpréteur. « Pas de complétion » sur une machine qui exécute des scripts Golo n'a donc qu'une seule cause — l'éditeur ne trouve pas golo — et la barre d'état le dit, avec l'adresse de la page des releases.

Seules les déclarations de premier niveau sont proposées. La complétion du serveur liste les mots-clés, les builtins, les fonctions et unions déclarées au premier niveau du fichier, et les symboles apportés par import depuis les modules embarqués dans le binaire. Une fonction déclarée dans le corps d'une autre n'est pas dans la liste, ni rien qui vienne d'un fichier .golo à vous sur le disque : les imports de modules utilisateur ne sont pas résolus. C'est la conception du serveur, et elle est écrite ici plutôt que contournée.

Les diagnostics portent sur le parsing, pas sur l'exécution. golo lsp publie les erreurs de syntaxe et deux lints — une confusion :/., et les commentaires // ou /* */ à la C là où Golo veut # et ----. Un programme qui parse puis échoue à l'exécution ne reçoit aucune marque, parce que le serveur ne l'exécute jamais. Et une erreur de syntaxe surligne une ligne entière : les messages du parseur portent un numéro de ligne et pas de colonne, la marque tombe donc sur la ligne.

La réponse de l'éditeur à la première est Run ▸ Language server status, qui dit ce qu'il a trouvé, où il l'a démarré et s'il est prêt — parce que « rien ne s'est passé » n'est pas quelque chose qu'un utilisateur peut traiter.

Neuf questions, une connexion — et les quatre auxquelles golo lsp ne répond pas

La complétion est la chose la plus bruyante que fait le serveur de langage et la moins révélatrice. La même connexion pose huit autres questions, et elles se répartissent en trois sortes selon ce qui revient.

Quelque chose à lire. hover — qu'est-ce que c'est ? — dessiné dans une boîte. Pour une fonction que vous avez déclarée, ce sont les commentaires # écrits juste au-dessus ; pour un builtin, sa signature et un exemple ; pour un mot-clé, une phrase.

Des endroits dans le code. definition, typeDefinition, implementation, references. Une requête chacune, une seule forme de réponse à elles quatre, c'est pourquoi elles sont une seule fonction en dessous. Un seul endroit est ouvert ; plusieurs sont proposés en liste, parce qu'une réponse unique est l'exception plutôt que la règle — et pendant longtemps les éditeurs de cette famille prenaient le premier et jetaient le reste.

Des noms. documentSymbol pour le plan d'un fichier, workspace/symbol pour une recherche dans tout le projet. Le protocole a trois formes pour un symbole et l'éditeur en veut une, l'aplatissement se fait donc là où les réponses arrivent plutôt que là où elles sont dessinées.

Et une chose que personne ne demande : publishDiagnostics arrive sans y être invité, chaque fois que le serveur a un avis, à l'ouverture et à chaque modification. C'est pourquoi la marque dans la gouttière apparaît sans qu'on ait appuyé sur rien.

Une des neuf revient vide avec golo lsp, et c'est la frontière du serveur, pas celle de l'éditeur. Il annonce completion, hover, definition, documentSymbol, references, implementation et workspaceSymbol — et pas typeDefinition, Code ▸ Type definition répond donc « rien trouvé ». Jusqu'à GoloScript v0.2.0 il n'annonçait que les quatre premiers, et Shift-F12 (références), Code ▸ Find implementations et Ctrl-T (un symbole n'importe où dans le projet) revenaient vides aussi ; le test qui épingle cette frontière a échoué le jour où le serveur s'est mis à leur répondre, et ce paragraphe a été revu — c'est à cela que sert le test. La lacune est écrite plutôt que cachée parce que l'alternative — griser une entrée de menu selon ce qu'un serveur a dit au démarrage — donne au menu une forme différente selon la machine, et un utilisateur qui a lu cette page en sait plus qu'un qui a trouvé une entrée grisée.

L'éditeur ne demande rien de tout cela avant que le serveur se dise prêt, et dit de quel cas il s'agit quand une question ne peut pas recevoir de réponse. « Rien trouvé » et « je n'ai pas fini de charger » sont la même réponse vide et des nouvelles très différentes ; les confondre est la façon la plus déroutante dont la complétion ait jamais échoué ici.

Liens avec le reste

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# Coloration et complétion — explication

## De quoi s'agit-il ?

Les deux fonctionnalités qui font de Turbo Golo un éditeur *pour Golo* plutôt qu'un éditeur de texte qui ouvre des fichiers `.golo` : la coloration syntaxique, et la complétion venue d'un serveur de langage. Elles fonctionnent très différemment, et la différence est instructive.

## La coloration est à nous ; la complétion ne l'est pas

La coloration est faite ici, dans quelque six cents lignes de Go écrites à la main. La complétion est faite par `golo lsp` — l'interpréteur lui-même, en mode serveur de langage — et Turbo Golo ne fait que demander et dessiner.

Cette séparation n'est pas un accident d'effort. La coloration doit être **instantanée et tolérante** : elle s'exécute à chaque frappe, sur un texte invalide la plupart du temps pendant qu'on le tape, et un colorateur qui s'arrête pour réfléchir ou renonce devant une entrée cassée est pire que pas de colorateur. La complétion doit être **juste**, ce qui pour Golo signifie parser le fichier, suivre ses lignes `import` dans les modules embarqués dans le binaire, et savoir ce que prend chacun des 157 builtins — et rien de ce qui doit être instantané ne peut aussi être cela.

L'éditeur dessine donc des couleurs qu'il a calculées lui-même, et affiche des complétions que quelqu'un d'autre a calculées.

## Pourquoi Golo est scanné à la main, avec un lexer à portée de main

GoloScript est écrit en Go, et son paquet `lexer` est un tokeniseur pour exactement ce langage. Turbo Go passe par `go/scanner` dans la même situation — la bibliothèque standard qui analyse son propre langage, si bien que l'éditeur et le compilateur s'accordent sur ce qu'est un token sans rien à maintenir en phase. Le geste évident était d'importer `golo/lexer` et de convertir ses positions avec le `LineIndex` de la bibliothèque.

On ne peut pas l'importer. Le `go.mod` de GoloScript dit `module golo` : un nom nu, sans hôte, et le système de modules de Go n'a aucun moyen d'aller chercher un module portant un tel chemin. L'importer exige une directive `replace` pointant vers un dépôt cloné à côté, et un `replace` committé casse tout clone qui n'a pas ce dépôt à côté — c'est pourquoi le script de release refuse d'en publier un. Vendre les deux paquets était l'autre voie, et cela reviendrait à une copie du lexer de quelqu'un d'autre qui cesse d'être le sien le jour où il change.

Le lexer est donc la **spécification** plutôt qu'une dépendance. `lexer/lexer.go` et `token/token.go` disent ce qu'est un token — quelles runes ouvrent un commentaire, comment un nombre se termine, quels mots sont réservés — et ce scanner dit la même chose dans le style `LineScanner` de la bibliothèque. Là où les deux pourraient diverger, la ligne du lexer est citée dans le code à côté de la décision.

Ce sera donc le scanner. Quelque six cents lignes, un fichier chacun pour le répartiteur, les littéraux et les mots — et aucune tentative de moteur général. Il n'y a ni langage de motifs, ni format de grammaire, ni table d'expressions régulières : c'est du Go ordinaire qu'un lecteur peut suivre, la règle que suivent les huit scanners de turbo-core.

## Ce qui franchit une ligne, et pourquoi c'est porté plutôt que coupé

Quatre constructions peuvent courir d'une ligne à la suivante, et le lexer de l'interpréteur fait autorité sur chacune :

- **Un commentaire bloc** court d'un `----` au suivant, où qu'il soit. Trois tirets sont deux signes moins et un troisième ; un cinquième tiret fait partie du texte.
- **Une chaîne** court jusqu'à son guillemet fermant. Le lexer la lit avec `for l.ch != '"' && l.ch != 0`, qui s'arrête au guillemet ou à la fin du fichier et à rien entre les deux — un retour à la ligne dans une chaîne fait partie de la chaîne.
- **Une chaîne multiligne `"""`** court jusqu'aux trois guillemets suivants, sans qu'aucun échappement soit considéré en chemin.
- **Un littéral de caractère `'…'`** est lu par la même boucle qu'une chaîne, et se comporte donc de la même façon.

Tous les autres éditeurs de cette famille arrêtent un littéral à la fin de sa ligne quand le guillemet fermant manque, et Turbo MoonBit en fait une règle : la grammaire de MoonBit dit qu'un retour à la ligne avant le guillemet fermant est une *erreur*, il n'y a donc rien à porter. Le lexer de Golo dit le contraire, et le scanner suit le lexer : **une chaîne non terminée peint le reste du fichier jusqu'à ce qu'un guillemet se présente**, parce que c'est exactement ce que l'interpréteur lira comme chaîne. La couleur n'est pas un avertissement, c'est un énoncé sur ce que signifie le programme — et un écran de vert après un guillemet égaré est cet énoncé rendu visible.

Ce qui est porté est une valeur qui dit *laquelle* des quatre est ouverte. Aucune ne s'imbrique, une profondeur serait donc une affirmation que le langage ne fait pas.

## Où le scanner s'appuie sur le langage, et où sur la convention

**Les mots-clés, les constantes et les builtins sont des tables lues dans GoloScript, pas remémorées.** Les 38 mots-clés sont la table de `token/token.go` moins les trois littéraux ; les 157 builtins sont ce que répond `evaluator.BuiltinNames()` moins les cinq compteurs de test qui commencent par un double souligné — les cinq mêmes que le serveur de langage tient hors de ses complétions. Un test tient cette table à un `golo lsp` en marche dans les deux sens, et c'est ainsi qu'elle reste une table lue dans l'outillage plutôt qu'une table que quelqu'un a tapée un jour.

**Un nom qui commence par une majuscule est un type, et c'est ici une convention plutôt qu'une règle.** Golo n'a pas de règle de casse dans son lexer : `let Count = 1` est légal. Mais les structs, les unions et leurs variantes sont capitalisés par tout le monde — `Point`, `Shape`, `Circle`, `Some`, `None` — et rien d'autre ne l'est d'ordinaire, le scanner colore donc selon la convention, comme Turbo Python le fait avec la PEP 8. Ce que cela coûte : le constructeur d'une variante est coloré comme un type (`Circle(1.0)` et `Result_Failure("no")` ressemblent à des types appliqués à des arguments), et une variable capitalisée l'est aussi. Rien dans la syntaxe ne les sépare.

**`Some`, `None`, `Ok` et `Err` sont ici des types, pas des constantes.** Turbo Rust et Turbo MoonBit les colorent comme des constantes parce qu'un lecteur les rencontre partout et les lit comme intégrés. En Golo ils ne le sont pas : ce sont les variantes d'unions ordinaires déclarées dans `gololang.Errors`, disponibles seulement après `import gololang.Errors`, et les colorer comme appartenant au langage dirait au lecteur qu'il n'a pas besoin d'un import alors qu'il en a besoin.

**Le nom après `function` est une fonction, et le chemin après `module` ou `import` est un seul nom.** Partout ailleurs un nom est une fonction parce qu'une parenthèse le suit, et une déclaration — `function main = |args|` — est le seul endroit où ce n'est pas vrai ; sans cas particulier, chaque fonction qu'un fichier déclare serait colorée comme une variable ordinaire à l'endroit même où le lecteur la cherche. Un chemin de module — `hello.World`, `gololang.Errors` — est une seule portée et une seule couleur parce que c'est un seul nom, et la lecture dont il faut le sauver est celle où `gololang.Errors` ressemble à une variable à laquelle on fait quelque chose.

**Un nom peut être presque n'importe quoi.** Le `isLetter` du lexer admet toute lettre ou marque Unicode, un souligné et quatre blocs d'emoji, si bien que `let 😀 = 1` et `function 🚀launch = …` sont du Golo légal. Le scanner utilise le même prédicat plutôt que celui, ASCII, de turbo-core, et ils sont donc colorés — comme `été` et `名前`, que Turbo MoonBit laisse sans couleur pour son propre langage.

## Ce que le lexer lit et que le parseur refuse

C'est la frontière qu'il vaut la peine d'énoncer clairement, parce que ce n'est pas une que le scanner peut voir. Le lexer et le parseur de GoloScript ont été écrits à des moments différents, et le lexer a de l'avance : il lit plusieurs tokens que le parseur, en v0.1.1, rejette ensuite.

| Le lexer lit | Le parseur dit |
| --- | --- |
| `42L`, un long | `could not parse "42L" as integer` |
| `3.14F`, `2.0f`, un flottant | `could not parse "3.14F" as float` |
| `'x'`, un caractère | `no prefix parse function for CHAR found` |
| `1..3`, un intervalle | `expected next token to be ), got .. instead` |
| `orIfNull`, `oftype` | `expected next token to be ), got orIfNull instead` |
| `local function …` | `no prefix parse function for LOCAL found` |

Le scanner colore ce que le lexer lit, parce que le lexer est la spécification de ce qu'*est* un token et que l'avis du parseur sur ce qu'il faut en faire peut changer demain. `42L` est donc un nombre et `orIfNull` un mot-clé, et **un token coloré n'est pas une promesse que l'interpréteur l'accepte**. Le serveur de langage vous le dit quand ce n'est pas le cas : ouvrez `demos/syntax-tour/lexer-only.golo` et chacune de ces lignes reçoit une marque dans la gouttière.

## Ce que le scanner refuse de deviner

Là où une construction ne peut pas être reconnue à partir de ce qu'une ligne contient, elle est laissée telle quelle plutôt qu'approximée. Un colorateur qui se trompe est pire qu'un colorateur qui se tait :

| Non reconnu | Parce que |
| --- | --- |
| Les séparateurs de chiffres et les autres bases | Le lexer n'a ni `1_000`, ni `0xFF`, ni `0b1010`. `1_000` est le nombre `1` suivi du nom `_000`, et `0xFF` est `0` suivi de `xFF` — c'est ce que voit l'interpréteur, et colorer l'un ou l'autre comme un seul nombre inventerait un littéral qu'il rejette |
| Un point initial comme nombre | Le lexer exige un chiffre avant le point, `.5` est donc un point puis `5` |
| Un `l` minuscule comme suffixe long | Le lexer n'accepte que `L` ; `42l` est `42` et le nom `l` |
| Un mot-clé employé comme nom de méthode après un deux-points | `obj: match()` garde `match` en mot-clé. Le scanner ne suit pas ce qu'un deux-points introduit, et le lexer refuserait le mot de toute façon |
| Les échappements dans `"""…"""` | Le lexer ajoute chaque rune jusqu'aux trois guillemets, `"""a\"""` se termine donc au premier `"""` quoi que la barre oblique inverse ait voulu dire |
| Si un nom est lié dans cette portée | Rien ici ne lit plus d'une ligne à la fois ; c'est la question du serveur de langage, et [F1 y répond](../how-to/ask-about-code.md) |

## Les huit autres langages viennent gratuitement

TOML, YAML, Markdown, JavaScript, HTML, XML, les Dockerfiles et le shell sont colorés par turbo-core, pas ici. Un projet Golo a un `README.md`, un `compose.yaml` pour le service auquel il parle, un Dockerfile pour être livré, et un éditeur qui ne colorerait que les fichiers `.golo` vous ferait le quitter pour le reste.

Qu'ils soient partagés plutôt que copiés est tout l'intérêt de la bibliothèque : ils ont été écrits une fois, pour Turbo Go, et Turbo Golo les a obtenus en important un paquet.

## La complétion, et pourquoi elle peut échouer en silence

Turbo Golo ne sait rien de la sémantique de Golo et n'essaie pas. Il interroge `golo lsp` par le Language Server Protocol et dessine la réponse.

Trois choses valent d'être sues à ce propos, parce que toutes trois ressemblent à « la complétion est cassée » :

**Le serveur est l'interpréteur.** Il n'y a pas de binaire `golo-lsp` séparé à installer et aucun outillage dont il dépend : `golo lsp` réutilise le lexer, le parseur et l'AST de l'interpréteur. « Pas de complétion » sur une machine qui exécute des scripts Golo n'a donc qu'une seule cause — l'éditeur ne trouve pas `golo` — et la barre d'état le dit, avec l'adresse de la page des releases.

**Seules les déclarations de premier niveau sont proposées.** La complétion du serveur liste les mots-clés, les builtins, les fonctions et unions déclarées au premier niveau du fichier, et les symboles apportés par `import` depuis les modules embarqués dans le binaire. Une fonction déclarée dans le corps d'une autre n'est pas dans la liste, ni rien qui vienne d'un fichier `.golo` à vous sur le disque : les imports de modules utilisateur ne sont pas résolus. C'est la conception du serveur, et elle est écrite ici plutôt que contournée.

**Les diagnostics portent sur le parsing, pas sur l'exécution.** `golo lsp` publie les erreurs de syntaxe et deux lints — une confusion `:`/`.`, et les commentaires `//` ou `/* */` à la C là où Golo veut `#` et `----`. Un programme qui parse puis échoue à l'exécution ne reçoit aucune marque, parce que le serveur ne l'exécute jamais. Et une erreur de syntaxe surligne une ligne entière : les messages du parseur portent un numéro de ligne et pas de colonne, la marque tombe donc sur la ligne.

La réponse de l'éditeur à la première est [Run ▸ Language server status](../reference/menus.md), qui dit ce qu'il a trouvé, où il l'a démarré et s'il est prêt — parce que « rien ne s'est passé » n'est pas quelque chose qu'un utilisateur peut traiter.

## Neuf questions, une connexion — et les quatre auxquelles `golo lsp` ne répond pas

La complétion est la chose la plus bruyante que fait le serveur de langage et la moins révélatrice. La même connexion pose huit autres questions, et elles se répartissent en trois sortes selon ce qui revient.

**Quelque chose à lire.** `hover` — qu'est-ce que c'est ? — dessiné dans une boîte. Pour une fonction que vous avez déclarée, ce sont les commentaires `#` écrits juste au-dessus ; pour un builtin, sa signature et un exemple ; pour un mot-clé, une phrase.

**Des endroits dans le code.** `definition`, `typeDefinition`, `implementation`, `references`. Une requête chacune, une seule forme de réponse à elles quatre, c'est pourquoi elles sont une seule fonction en dessous. Un seul endroit est ouvert ; plusieurs sont proposés en liste, parce qu'une réponse unique est l'exception plutôt que la règle — et pendant longtemps les éditeurs de cette famille prenaient le premier et jetaient le reste.

**Des noms.** `documentSymbol` pour le plan d'un fichier, `workspace/symbol` pour une recherche dans tout le projet. Le protocole a trois formes pour un symbole et l'éditeur en veut une, l'aplatissement se fait donc là où les réponses arrivent plutôt que là où elles sont dessinées.

Et une chose que personne ne demande : **`publishDiagnostics` arrive sans y être invité**, chaque fois que le serveur a un avis, à l'ouverture et à chaque modification. C'est pourquoi la marque dans la gouttière apparaît sans qu'on ait appuyé sur rien.

**Une des neuf revient vide avec `golo lsp`, et c'est la frontière du serveur, pas celle de l'éditeur.** Il annonce `completion`, `hover`, `definition`, `documentSymbol`, `references`, `implementation` et `workspaceSymbol` — et pas `typeDefinition`, **Code ▸ Type definition** répond donc « rien trouvé ». Jusqu'à GoloScript v0.2.0 il n'annonçait que les quatre premiers, et **Shift-F12** (références), **Code ▸ Find implementations** et **Ctrl-T** (un symbole n'importe où dans le projet) revenaient vides aussi ; le test qui épingle cette frontière a échoué le jour où le serveur s'est mis à leur répondre, et ce paragraphe a été revu — c'est à cela que sert le test. La lacune est écrite plutôt que cachée parce que l'alternative — griser une entrée de menu selon ce qu'un serveur a dit au démarrage — donne au menu une forme différente selon la machine, et un utilisateur qui a lu cette page en sait plus qu'un qui a trouvé une entrée grisée.

L'éditeur ne demande rien de tout cela avant que le serveur se dise prêt, et dit de quel cas il s'agit quand une question ne peut pas recevoir de réponse. « Rien trouvé » et « je n'ai pas fini de charger » sont la même réponse vide et des nouvelles très différentes ; les confondre est la façon la plus déroutante dont la complétion ait jamais échoué ici.

## Liens avec le reste

- Exactement ce qui est reconnu : [Langages colorés](../reference/languages.md)
- Faire fonctionner la complétion : [Activer la complétion Golo](../how-to/enable-completion.md)
- Où vit le scanner et pourquoi : [Architecture](architecture.md)