L'idée de départ est la suivante : si un automate fini reconnaît le langage correspondant à l'expression régulière r, alors à toute lettre d'un mot reconnu on peut faire correspondre une occurrence d'une lettre apparaissant dans r. Pour distinguer les différentes occurrences d'une même lettre dans r, on va les indexer par des entiers. Considérons par exemple l'expression régulière (a|b)* a (a|b), qui définit les mots sur l'alphabet {a,b} dont l'avant-dernière lettre est un a. Si on indice les caractères, on obtient
(a1|b1)* a2 (a3|b2)
a1a1b1a1a2b2
L'idée est alors de construire un automate dont les états sont des ensembles de lettres indexées, correspondant aux occurrences qui peuvent être lues à un instant. Ainsi l'état initial contient les premières lettres possibles des mots reconnus. Dans notre exemple, il s'agit de a1,b1,a2. Pour construire les transitions, il suffit de calculer, pour chaque occurrence d'une lettre, l'ensemble des occurrences qui peuvent la suivre. Dans notre exemple, si on vient de lire a1, alors les caractères possibles suivants sont a1,b1,a2 ; si on vient de lire a2, alors ce sont a3,b2.
type ichar = char * int type regexp = | Epsilon | Character of ichar | Union of regexp * regexp | Concat of regexp * regexp | Star of regexpÉcrire une fonction
val null : regexp -> boolqui détermine si epsilon (le mot vide) appartient au langage reconnu par une expression régulière.
module Cset = Set.Make(struct type t = ichar let compare = compare end)
Écrire une fonction
val first : regexp -> Cset.tqui calcule l'ensemble des premières lettres des mots reconnus par une expression régulière. (Il faut se servir de null.)
De même, écrire une fonction
val last : regexp -> Cset.tqui calcule l'ensemble des dernières lettres des mots reconnus.
val follow : ichar -> regexp -> Cset.tqui calcule l'ensemble des lettres qui peuvent suivre une lettre donnée dans l'ensemble des mots reconnus.
On notera que la lettre d appartient à l'ensemble follow c r si et seulement si
Écrire une fonction
val next_state : regexp -> Cset.t -> char -> Cset.tqui calcule l'état résultant d'une transition.
Pour représenter l'automate fini, on se donne le type Caml suivant :
module Cmap = Map.Make(Char) module Smap = Map.Make(Cset) type state = Cset.t type autom = { start : state; trans : state Cmap.t Smap.t }On peut choisir de représenter ainsi le caractère # :
let eof = ('#', -1)Écrire une fonction
val make_dfa : regexp -> automqui construit l'automate correspondant à une expression régulière. L'idée est de construire les états par nécessité, en partant de l'état initial. On pourra par exemple adopter l'approche suivante :
let make_dfa r = let r = Concat (r, Character eof) in (* transitions en cours de construction *) let trans = ref Smap.empty in let rec transitions q = (* la fonction transitions construit toutes les transitions de l'état q, si c'est la première fois que q est visité *) ... in let q0 = first r in transitions q0; { start = q0; trans = !trans }
Note : il est bien entendu possible de construire au final un automate dont les états ne sont pas des ensembles mais, par exemple, des entiers, pour plus d'efficacité dans l'exécution de l'automate. Cela pourrait d'ailleurs être fait pendant la construction, ou a posteriori. Mais ce n'est pas ce qui nous intéresse ici.
val recognize : autom -> string -> boolqui détermine si un mot est reconnu par un automate.
Pour tester, on reprend l'exemple ci-dessus :
(* (a|b)*a(a|b) *) let r = Concat (Star (Union (Character ('a', 1), Character ('b', 1))), Concat (Character ('a', 2), Union (Character ('a', 3), Character ('b', 2)))) let a = make_dfa rVoici quelques tests positifs (la réponse doit être true) :
let _ = recognize a "aa" let _ = recognize a "ab" let _ = recognize a "abababaab" let _ = recognize a "babababab" let _ = recognize a (String.make 1000 'b' ^ "ab")et quelques tests négatifs (la réponse doit être false) :
let _ = recognize a "" let _ = recognize a "a" let _ = recognize a "b" let _ = recognize a "ba" let _ = recognize a "aba" let _ = recognize a "abababaaba"
Voici un autre test avec une expression régulière caractérisant un nombre pair de b :
let r = Star (Union (Star (Character ('a', 1)), Concat (Character ('b', 1), Concat (Star (Character ('a',2)), Character ('b', 2))))) let a = make_dfa rQuelques tests positifs :
let _ = recognize a "" let _ = recognize a "bb" let _ = recognize a "aaa" let _ = recognize a "aaabbaaababaaa" let _ = recognize a "bbbbbbbbbbbbbb" let _ = recognize a "bbbbabbbbabbbabbb"et quelques tests négatifs :
let _ = recognize a "b" let _ = recognize a "ba" let _ = recognize a "ab" let _ = recognize a "aaabbaaaaabaaa" let _ = recognize a "bbbbbbbbbbbbb" let _ = recognize a "bbbbabbbbabbbabbbb"