Uncategorized

Readable Regex: a Small Fluent API

Les expressions régulières sont extrêmement puissantes, et souvent utilisées dans pas mal de tâches informatiques, notamment de traitement automatique des langues. Cependant, la nature complexe des phénomènes à traiter entraîne souvent des expressions qui sont des soupes de symboles, difficiles à comprendre et à modifier.

Problématique

Dans le cadre d’un des mes projets de recherche, j’ai récemment eu à écrire une expressions régulière très longue, dont la première particularité est d’avoir des classes de caractères assez longues. Exit donc [a-zA-z]. Et puisqu’il fallait des groupes de caractères plus précis que l’alphabet entier, l’utilisation de la classe unicode \p{Bopomofo} n’était pas possible.

J’ai donc décidé d’écrire un petit peu de code pour régler le problème à un plus niveau : plutôt que d’écrire l’expression directement, un ensemble de fonctions simples allaient l’écrire à ma place. À la clef deux avantages : la possibilité d’utiliser des variables pour nommer les groupes de caractères, et celle de sauter des lignes et d’inclure des commentaires.

Analyse de l’existant

J’ai quand même fait ce que tout chercheur est censé faire en premier lieu : se renseigner sur l’existant. Plusieurs projets existent, mais tous articulé autour de l’idée d’écrire les regex dans un language plus proche de la langue naturelle, ce qui n’était pas mon but ici. En plus, je ne voulais pas prendre une dépendance à une bibliothèque logicielle externe alors que je pressentais qu’un fichier ou deux créés par mes soins pouvaient faire l’affaire.

Approche

L’approche prise est conceptuellement simple : la regex est écrite de droite à gauche par un appel successif de méthodes. Ces méthodes ajoutent du contenu à une liste de chaîne de caractères, qui seront finalement concaténées pour former le résultat final. Suivant l’approche dite Fluent, les méthodes auront zéro ou un argument. La transformation est minimale.

var letters = new string[] { "a", "b", "c" };

IRegexBuilder regex = new RegexBuilder()
    // on crée un group de capture nommé
    .StartGroup("first_letter") 
        .Disjunction(letters)
        .AtLeast(1)
    .CloseGroup();

Le code précédent est une illustration de l’utilisation de l’API. On y voit clairement l’intention de l’auteur: un groupe de capture nommé va capturer au moins une lettre. Le code peut être aisément modifié en ajoutant des lettres au tableau ‘letters’ sans modifier la logique de l’expression elle-même. C’est encore plus utile quand les lettres du groupe ‘letters’ sont réutilisées à plusieurs endroits: la modification affecte tous les emplacements et donc pas de risque d’oubli.

L’expression générée est: (?(?:a|b|c)+)

Le lecture attentif aura remarqué que la méthode Disjunction crée un groupe anonyme de la forme (?:x|…|z) ce qui permet de l’utiliser avec des chaînes de caractères en général et pas seulement des caractères.

Utilisation réelle

Ceci dit, l’example précédent est trivial. La vraie puissance de cette approche sera mieux communiquée par l’expression finale ainsi que la code qui l’a générée.

Expression en question est copiée ci-dessous, et son code disponible ici. Le code est contenu dans un petit exemple que vous pouvez télécharger, lancer et modifier à votre guise.

^˙?(?:(?(?:ㄚ|ㄛ|ㄜ|ㄝ))|(?(?:ㄧ|ㄨ|ㄩ))|(?(?:ㄞ|ㄟ|ㄠ|ㄢ|ㄣ|ㄤ|ㄥ|ㄦ))
|(?(?:ㄓ|ㄔ|ㄕ|ㄙ|ㄖ))|((?:ㄧ|ㄨ|ㄩ)(?:ㄞ|ㄟ|ㄠ|ㄢ|ㄣ|ㄤ|ㄥ|ㄦ))|(?(?:ㄅ|
ㄆ|ㄇ|ㄈ|ㄉ|ㄊ|ㄋ|ㄌ|ㄍ|ㄎ|ㄏ|ㄐ|ㄑ|ㄒ|ㄓ|ㄔ|ㄕ|ㄖ|ㄗ|ㄘ|ㄙ)(?:ㄚ|ㄛ|ㄜ|ㄝ))
|(?(?:ㄅ|ㄆ|ㄇ|ㄈ|ㄉ|ㄊ|ㄋ|ㄌ|ㄍㄎ|ㄏ|ㄐ|ㄑ|ㄒ|ㄓ|ㄔ|ㄕ|ㄖ|ㄗ|ㄘ|ㄙ)(?:ㄧ|ㄨ
|ㄩ))|(?(?:ㄅ|ㄆ|ㄇ|ㄈ|ㄉ|ㄊ|ㄋ|ㄌ|ㄍ|ㄎ|ㄏ|ㄐ|ㄑ|ㄒ|ㄓ|ㄔ|ㄕ|ㄖ|ㄗ|ㄘ|ㄙ)
(?:ㄞ|ㄟ|ㄠ|ㄢ|ㄣ|ㄤ|ㄥ|ㄦ))|(?(?:ㄅ|ㄆ|ㄇ|ㄈ|ㄉ|ㄊ|ㄋ|ㄌ|ㄍ|ㄎ|ㄏ|ㄐ|ㄑ|ㄒ|
ㄓ|ㄔ|ㄕ|ㄖ|ㄗ|ㄘ|ㄙ)(?:ㄧ|ㄨ|ㄩ)(?:ㄚ|ㄛ|ㄜ|ㄝ))|(?(?:ㄅ|ㄆ|ㄇ|ㄈ|ㄉ|ㄊ|ㄋ|
ㄌ|ㄍ|ㄎ|ㄏ|ㄐ|ㄑ|ㄒ|ㄓ|ㄔ|ㄕ|ㄖ|ㄗ|ㄘ|ㄙ)(?:ㄧ|ㄨ|ㄩ)(?:ㄞ|ㄟ|ㄠ|ㄢ|ㄣ|ㄤ|
ㄥ|ㄦ)))(?:ˉ|ˊ|ˇ|ˋ)?( ˙?(?:(?(?:ㄚ|ㄛ|ㄜ|ㄝ))|(?(?:ㄧ|ㄨ|ㄩ))|(?(?:ㄞ|
ㄟ|ㄠ|ㄢ|ㄣ|ㄤ|ㄥ|ㄦ))|(?(?:ㄓ|ㄔ|ㄕ|ㄙ|ㄖ))|((?:ㄧ|ㄨ|ㄩ)(?:ㄞ|ㄟ|ㄠ|ㄢ|ㄣ
|ㄤ|ㄥ|ㄦ))|(?(?:ㄅ|ㄆ|ㄇ|ㄈ|ㄉ|ㄊ|ㄋ|ㄌ|ㄍ|ㄎ|ㄏ|ㄐ|ㄑ|ㄒ|ㄓ|ㄔ|ㄕ|ㄖ|ㄗ|ㄘ
|ㄙ)(?:ㄚ|ㄛ|ㄜ|ㄝ))|(?(?:ㄅ|ㄆ|ㄇ|ㄈ|ㄉ|ㄊ|ㄋ|ㄌ|ㄍ|ㄎ|ㄏ|ㄐ|ㄑ|ㄒ|ㄓ|ㄔ|ㄕ
|ㄖ|ㄗ|ㄘ|ㄙ)(?:ㄧ|ㄨ|ㄩ))|(?(?:ㄅ|ㄆ|ㄇ|ㄈ|ㄉ|ㄊ|ㄋ|ㄌ|ㄍ|ㄎ|ㄏ|ㄐ|ㄑ|ㄒ|ㄓ
|ㄔ|ㄕ|ㄖ|ㄗ|ㄘ|ㄙ)(?:ㄞ|ㄟ|ㄠ|ㄢ|ㄣ|ㄤ|ㄥ|ㄦ))|(?(?:ㄅ|ㄆ|ㄇ|ㄈ|ㄉ|ㄊ|ㄋ|ㄌ
|ㄍ|ㄎ|ㄏ|ㄐ|ㄑ|ㄒ|ㄓ|ㄔ|ㄕ|ㄖ|ㄗ|ㄘ|ㄙ)(?:ㄧ|ㄨ|ㄩ)(?:ㄚ|ㄛ|ㄜ|ㄝ))|(?(?:ㄅ
|ㄆ|ㄇ|ㄈ|ㄉ|ㄊ|ㄋ|ㄌ|ㄍ|ㄎ|ㄏ|ㄐ|ㄑ|ㄒ|ㄓ|ㄔ|ㄕ|ㄖ|ㄗ|ㄘ|ㄙ)(?:ㄧ|ㄨ|ㄩ)
(?:ㄞ|ㄟ|ㄠ|ㄢ|ㄣ|ㄤ|ㄥ|ㄦ)))(?:ˉ|ˊ|ˇ|ˋ)?)*$

Happy hacking 🙂

Répondre

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google

Vous commentez à l'aide de votre compte Google. Déconnexion /  Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s