Apprendre Fix — 5. La syntaxe
Cinquième étape. Tes données ont souvent déjà une notation — un DSL métier, un format d'échange. Fix te laisse la déclarer, et la déclaration marche dans les deux sens : le même programme lit le texte vers l'AST et écrit l'AST vers le texte. Compte 20 minutes.
1. L'AST d'abord, la syntaxe ensuite
Le principe tient en une phrase : la structure d'un langage vit dans son type (les constructeurs, leur récursivité) ; la syntaxe ne fait que donner une apparence à chaque constructeur. On déclare donc le type, puis on l'habille — et comme un langage, c'est un module, tout vit ensemble :
module(er, []).
type atype = string | int. % FERMÉ : le vocabulaire de types
type attr_t = attr(aname, atype). % aname OUVERT (nom) · atype fermé
type attrs = nil | cons(attr_t, attrs).
type ent_t = ent(ename, attrs). % ename OUVERT : tout identifiant
ent(N, L) := "entity" N "fields" L "end". % la syntaxe : une apparence par constructeur
attr(N, T) := "attr" N T.
syntax(er, cons, sequence("[", ",", "]", cons, nil)).
end_module.
Une clause ent(N, L) := "entity" N "fields" L "end" se lit : « le constructeur ent(N, L) s'écrit entity, puis le nom, puis fields, puis la liste, puis end ». Les guillemets sont des mots-clés ; les variables sont les trous, remplis récursivement selon leur type. Remarque ce qui manque : aucune règle de grammaire pour « une liste d'attributs » — la récursion vient du type (attrs = nil | cons(...)), pas de la syntaxe.
2. Lire : du texte vers l'AST
lire est le pont texte → terme, en requête. Donne-lui le langage, le type racine, le texte — il te rend le terme :
?- lire(er, ent_t, "entity person fields [ attr name string ] end", M).
M = ent(person, cons(attr(name, string), nil))
Le texte est devenu un AST — un terme Fix ordinaire, que tu peux interroger, transformer, tester comme tout le reste. person (un nom d'entité) et name (un nom d'attribut) sont acceptés parce que leurs types sont ouverts ; mais string en position de type est vérifié (atype est fermé) — un non-type y serait refusé.
3. Écrire : et l'aller-retour
Il n'y a qu'une déclaration par constructeur, et elle sert aux deux sens. ecrire rend un terme dans un langage :
?- ecrire(er, ent_t, ent(person, cons(attr(name, string), nil)), S).
S = entity person fields [ attr name string ] end
Pas de parseur à écrire, pas d'imprimeur à maintenir en miroir : c'est la même clause, parcourue dans un sens ou dans l'autre. Ce qui est lu puis écrit retombe sur ses pieds (aller-retour exact). Et le même AST peut porter plusieurs notations — une par module/langage — ce qui rend la transformation de modèles courte (l'étape suivante). Le détail : La syntaxe de surface.
Ce que tu sais faire
Déclarer un langage = déclarer un type (l'AST) + l'habiller (la syntaxe), dans un module. lire fait texte → terme, ecrire fait terme → texte, avec une seule clause par constructeur (aller-retour exact).
→ 6. Dériver : le sommet — transformer un modèle en un autre, texte à texte.