Le but de cet exercice est d'apprendre à manipuler les automates. Les automates sont principalement utilisés pour l'analyse lexicale, et servent à représenter/implementer les expression régulières. Nous allons écrire deux classes au cours de ce TD : State et Automaton.
TreeSet<State>
.
Il s'agit d'une classe générique et on
précise
entre chevrons la classe des objets stockés dans les sommets
de
l'arbre. La classe TreeSet
réalise un ensemble d'objets par des arbres AVL.import java.util.*;
au fichier .java. Nous auront besoin des méthodes suivantesfor (State s: a)
permet
d'itérer sur tous les états s de l'ensemble a.s.compareTo(t)
doit retourner un
entier <0, l'entier 0 ou un entier >0 suivant que s<t,
s=t
ou s>t
. On va tout
simplement utiliser l'ordre sur les identifiant et donc retourner id-t.id
.TreeSet
sache que State
est munie d'une méthode compareTo,
il faut déclarer que State implémente
l'interface Comparable<State>.
java.util.*
pour TreeSet)
int id;
// identificateur unique
de l'étatState()
// le constructeur pose
id. Savez-vous
comment attribuer succéssivement 0,1,2,.. à
chaque
nouvelle création ?public int compareTo(State t)
//comparer
cet objet avec tState transition[];
// la table de
transitionvoid addTransition(char c, State next)
// ajoute une transition pour la lettre cTreeSet<State> epsilon;
// l'ensemble des états cibles des transitions epsilonvoid addTransition(State next)
// ajoute une epsilon transitionstatic void
epsilon_closure(TreeSet<State> set)
//
ajoute à l'ensemble set
tous les
états atteignables par
des epsilon-transitions.set
. Puis
l'itération extérieure prendra fin quand cet
ensemble sera enfin vide. public static void main(String []args) {
State []tab = {new State(), new State(), new State(), new State()};
tab[2].addTransition('x', tab[3]);
tab[2].addTransition(tab[0]);
tab[1].addTransition(tab[2]);
tab[0].addTransition(tab[1]);
for (State s: tab) {
for (State n: s.epsilon)
System.out.println(""+s.id+"->"+n.id);
for (char c=0; c<256; c++)
if (s.transition[c]!=null)
System.out.println(""+s.id+"-"+c+"->"+s.transition[c].id);
}
TreeSet<State> set = new TreeSet<State>();
set.add(tab[1]);
State.epsilon_closure(set);
System.out.print("cloture(1) =");
for (State s: set)
System.out.print(" "+s.id);
System.out.println();
}
Vous devriez avoir l'affichage suivant.0->1
1->2
2->0
2-x->3
cloture(1) = 0 1 2
dump
a
besoin qu'on importe
java.io.*.
)
TreeSet<State> states;
// l'ensemble des états de l'automateState initial;
//
l'état initialTreeSet<State> accept;
// l'ensemble des états acceptantsAutomaton()
//
crée un automate qui ne reconnait rien du toutAutomaton(TreeSet<State> _states,
State _initial, TreeSet<State> _accept)
static Automaton new_WORD(String word)
// cr'ee un automate qui reconnait un mot donné et rien
d'autre
tmp??.dot
où ?? est l'entier
qui est passé en paramètre à dump. /** affiche l'automate comme un graphe en notation DOT
... enfin retourne une chaine qui correspond a l'affichage plutot
*/
public void dump(String word, int i, Set<State> marked) {
try {
PrintWriter out = new PrintWriter(String.format("tmp%02d.dot",i));
String label="";
if (word!=null) {
label=" label=\"";
for (int j=0; j<=word.length(); j++) {
if (j==i)
label+='>';
else
label+=' ';
if (j<word.length())
label+=word.charAt(j);
}
label += "\";\n";
}
// imprimer tout ce qui est sp'ecifique au graphe
out.println("digraph {\n"+
" rankdir=LR;\n"+
label+
" init [shape=point];\n"+
" init->"+initial.id+";\n" );
// imprimer les 'etats
for (State s: states) {
out.print(" "+s.id+" [");
if (accept.contains(s))
out.print("shape=doublecircle");
else
out.print("shape=circle");
if (marked!=null && marked.contains(s))
out.print(",style=filled");
out.println("];");
}
out.println();
// imprimer les epsilon transitions
for (State s: states)
for (State n: s.epsilon)
out.println(" "+s.id+"->"+n.id+";");
out.println();
// imprimer les autres transitions
for (State s: states) {
char c1 = 0;
State n1 = null;
for (char c2=0; c2<=256; c2++) {
State n2 = (c2<256) ? s.transition[c2] : null;
if (n1!=n2) {
if (n1!=null) {
if (c1==0 && c2==256)
label = "\"?\"";
else if (c1==c2-1)
label = "\""+c1+"\"";
else
label = "\""+c1+"-"+(c2-1)+"\"";
out.println(" "+s.id+"->"+n1.id+"[label="+label+"];");
}
c1 = c2;
n1 = n2;
}
}
}
out.println("}");
out.close();
}
catch (FileNotFoundException e) {
throw new Error("fichier tmp??.dot n'a pas pu ^etre cr'ee");
}
}
Testez avec ce code qui produit un fichier tmp00.dot.
static public void main(String args[]) {Puis dans une commande shell visualisez le résultat avec la commande
Automaton isa = new_WORD("isa");
isa.dump(null, 0, null);
}
dot -Tgif -o tmp00.gif tmp00.dot && xv tmp00.gifVous devriez voir le graphe suivant, à renommage des sommets près.
new_WORD
.static Automaton new_EMPTY()
//
cr'ee un
automate qui reconnait le mot videstatic Automaton new_CHAR(char c)
// cr'ee un automate qui reconnait un caractère
donnéstatic Automaton new_WILD()
// cr'ee un automate qui reconnait un caractère quelconquestatic Automaton new_OR(Automaton a1, Automaton
a2)
// cr'ee un automate qui reconnait l'union des
languages rec. par a1 et a2static Automaton new_SEQ(Automaton a1,
Automaton a2)
// cr'ee un automate qui reconnait la
concaténation des languagesstatic Automaton new_STAR(Automaton a)
// cr'ee un automate qui reconnait la répétition
du langage reconnu par a
static public void main(String args[]) {
Automaton a,b,c;
a = new_OR(new_WORD("cafe"),new_WORD("toi"));
b = new_SEQ(new_WORD("the", new_WORD("sur"));
c = new_STAR(new_WORD("quoi"));
a.dump(null, 0, null);
b.dump(null, 1, null);
c.dump(null, 2, null);
}
boolean match(String word)
qui teste si un mot donné est reconnu par l'automate. Pour
cela vous avez besoin d'une variable locale TreeSet<State>
current
,
qui contient l'ensemble des états courants, qui initialement
ne contient que la cloture par epsilon transitions de l'état
initial.
Puis sucessivement
pour chaque caractère c
de la
chaîne word
vous mettez à jour current
: Vous
calculez un ensemble next
,
composé de tous les états qu'on peut atteindre
avec une transition avec
c
sur un état s
de
current
.
Faites attention à ne pas mettre dans next
l'état
poubelle null
. Après chaque
itération vous remplacerez current
par la cloture
transitive par epsilon transitions de next
.
Si à la
fin current
contient un état
acceptant, le mot word
est
accepté.static final String[] strings = {Vous devriez avoir le résultat suivant :
"", "a", "ab", "ac",
"abc", "acb", "adc",
"abcd", "abcde",
"abcdef", "abcdefe", "abcdefef"
};
public static void main(String [] args) {
Automaton a = new_SEQ(new_CHAR('a'),
new_OR( new_SEQ(new_WILD(), new_CHAR('c')),
new_SEQ(new_WORD("bcd"), new_STAR(new_WORD("ef")))));
for(String s: strings) {
System.out.println("matches " + s + " : " + a.match(s));
}
}
java Automaton
matches : false
matches a : false
matches ab : false
matches ac : false
matches abc : true
matches acb : false
matches adc : true
matches abcd : true
matches abcde : false
matches abcdef : true
matches abcdefe : false
matches abcdefef : true
dump(word,
i, current)
,
où i
et l'indice du
caractère traité dans word. Appelez
une dernière fois après la boucle dump(word,
word.length(),
current)
. De cette manière votre programme va
générer différents
fichiers qui représentent le parcours de l'automate.
Le script shell make_image
vous permet de
transformer les fichiers
produits par le programme en une image GIF animée. On
l'exécute avec bash
make_image
. Vous devriez
avoir le résultat suivant. (C'est cool, non ?)