Planche 1
Planche 2
Plan
- Analyse lexicale
- Analyse syntaxique
- Grammaires formelles
- Graphes
- Parcours en profondeur ou largeur d'abord
- Connexité
- Exercices
Planche 3
Analyse lexicale
1ère passe pour supprimer les blancs et les commentaires. Il ne reste
plus qu'un flot d opérateurs, les identificateurs, les nombres, les
chaînes de caractères, etc (lexèmes).
let rec cur_char = ref ' '
and next_char() = cur_char := input_char(stdin);;
let is_letter c = ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');;
let is_digit c = ('0' <= c && c <= '9') ;;
let is_blank c = c = ' ' || c = '\t' || c = '\r' || c = '\n' ;;
let skip_blanks () = while is_blank (!cur_char) do next_char() done;;
let next_ident() =
let buf = String.make 100 ' ' and i = ref 0 in
while is_letter (!cur_char) || is_digit (!cur_char) do
buf.[!i] <- !cur_char; incr i;
next_char()
done;
sub_string buf 0 !i;;
let next_number() =
let res = ref 0 in
while is_digit (!cur_char) do
res := !res*10 + Char.int (!cur_char) - Char.int ('0');
next_char()
done;
!res;;
let erreur s = failwith ("Erreur: " ^ s) ;;
Il existe des outils pour générer automatiquement les analyseurs
lexicaux: lex de M. Lesk ou camllex. La théorie se fait avec les
automates finis (ou expressions régulières et langages réguliers).
Planche 4
Analyse lexicale -- programme
On ne garde plus qu'une suite de lexèmes, encore appelés tokens. On
écrit une fonction next_lexem qui saute les espaces et retourne le
prochain lexem dans la variable globale cur_item.
type lexem =
L_Ident of string (* identificateur *)
| L_Nb of int (* nombre *)
| L_Plus (* + *) | L_Moins (* - *)
| L_Mult (* * *) | L_Div (* / *)
| L_Opar (* ( *) | L_Fpar (* ) *)
| L_Assign1 (* := *) | L_Assign2 (* <- *)
| L_EOF (* end of file *) ;;
let cur_lexem = ref L_EOF and next_lexem () =
cur_lexem := try
skip_blanks ();
match !cur_char with
'a'..'z' | 'A'..'Z' -> L_Ident(next_ident ())
|'0'..'9' -> L_Nb(next_number ())
|'+' -> begin next_char(); L_Plus end
|'-' -> begin next_char(); L_Moins end
|'*' -> begin next_char(); L_Mult end
|'/' -> begin next_char(); L_Div end
|':' -> begin next_char();
if !cur_char <> '=' then L_Div
else begin next_char(); L_Assign1 end
end
|'(' -> begin next_char(); L_Opar end
|')' -> begin next_char(); L_Fpar end
| _ -> erreur "forbidden char"
with
End_of_file -> L_EOF;;
Planche 5
Analyse syntaxique des expressions arithmétiques
A partir d'une chaîne de caractères tapée sur l'input, comme (x +
1) * (3 * x + 2) , on veut générer l'arbre de syntaxe abstraite suivant
(cf PC8 pour le type des termes).
let rec terme () =
let a = produit () in
match !cur_lexem with
L_Plus -> begin next_lexem(); Add (a, terme ()) end
| L_Moins -> begin next_lexem(); Sub (a, terme ()) end
| _ -> a
and produit () =
let a = facteur () in
match !cur_lexem with
L_Mult -> begin next_lexem(); Mul (a, produit ()) end
| L_Div -> begin next_lexem(); Div (a, produit ()) end
| _ -> a
and facteur () =
match !cur_lexem with
L_Opar -> begin next_lexem();
let a = terme () in
if !cur_lexem = L_Fpar then begin next_lexem(); a end
else erreur "missing right parenthesis" end
| L_Ident(s) -> begin next_lexem(); Var (s) end
| L_Nb (x) -> begin next_lexem(); Const (x) end
| _ -> erreur "forbidden character";;
Il existe des outils pour générer automatiquement les analyseurs
syntaxique: yacc de S. Johnson ou camlyacc. La théorie se
fait avec les automates à pile (ou langages context-free, aussi
appelés algébriques).
Planche 6
Grammaires et langages formels
L'ensemble des mots sur un alphabet S est noté S *. Si
a Î S * et b Î S, alors ab Î
S est le mot obtenu par concaténation de a et de b.
Une grammaire (context-free) G=(S, VN, S, P) est définie par
un alphabet S, un ensemble de variables (non-terminales) VN,
un axiome S Î VN et un ensemble fini de règles de production P
de la forme E ® a où E Î VN et a Î (S È
VN)*.
Une dérivation a ®* b est une chaîne a = a0
® a1 ® ··· an = b où n ³ 0 et pour
0 £ i < n, on a ai = uEv et ai+1 = u
g v pour un E ® g Î P et deux mots u, v de
(S È VN)*.
Le langage généré par G est l'ensemble LG={w | wÎS *,
S ®*w }.
A chaque mot reconnu, on peut associer un arbre syntaxique (différent
de l'arbre de syntaxe abstraite).
Planche 7
Exemples de grammaire
S={(, ) }, VN={S },
S ® SS S ® (S) S ® ()
S={ x, +, -, *, /, (, ) },
VN={T, P, F },
|
T ® P+T |
T ® P-T |
T ® P |
|
P ® F*P |
P ® F/P |
P ® F |
|
F ® x |
F ® (T) |
|
Dessiner l'arbre syntaxique pour (x+1)*(3*x+2)
La syntaxe de Java, (O)Caml, Pascal, C, C++ est décrite en BNF (Backus Naur Form), forme industrielle de la notion de grammaire
formelle.
Planche 8
Graphes
Un graphe G=(V,E) a un ensemble de sommets V et d'arcs EÌ
V × V. Un arc v=(n1,n2) a pour origine org(v) = n1 et
pour extrémité ext(v) = n2. Un exemple est le plan des rues de
Paris ou le plan du métro.
G=(N,V) est un graphe non orienté ssi (n1,n2) Î V implique
(n2,n1) Î V. Par exemple, les couloirs de l'X.
Un chemin est une suite d'arcs v1, v2, ...vn, telle que
org(vi+1) = ext(vi) pour 1 £ i < n, où n
³ 0. Un circuit (ou cycle) est un chemin où ext(vn) =
org(v1). Un arbre est donc un graphe orienté sans cycle, il
existe des graphes orientés sans cycles plus généraux (dag).
Un exemple de dag est celui des relations de dépendances inter modules
pour la création d'un projet informatique. (Makefile)
Planche 9
Représentations d'un graphe
En gros, deux méthodes:
- Matrice de connexion M, où mi,j=1 ssi (ni,nj) est un
arc du graphe. Matrice symétrique pour un graphe non orienté.
let nMax = 100;;
let graphe = Array.make_matrix nMax nMax false;;
let mk_arc i j = graphe.(i).(j) <- true;;
- Tableau de listes de successeurs:
let graphe = Array.make nMax [];;
let mk_arc i j = graphe.(i) <- j :: graphe.(i);;
- On peut stocker la liste précédente dans un tableau, si les
noeuds ont tous à peu près le même nombre de successeurs, et on met
une sentinelle W après le dernier successeur. Pour remplir
l'entrée i, on fera:
let Omega = -1 and n = ref 0;;
let graphe = Array.make nMax vMax Omega;;
let mk_arc i j = begin graphe.(i).(!n) <- j; incr n end;;
Planche 10
Parcours en profondeur d'abord, Depth-First Search
R. Tarjan (1972) a montré que c'était une méthode
très utile sur les graphes. Son temps est en O(V+E), ie linéaire
dans le nombre de noeuds et d'arcs.
let num = Array.make (Array.length graphe) (-1) and id = ref (-1);;
let rec dfs k =
incr id;
num.(k) <- !id;
List.iter (function x -> if num.(x) = -1 then dfs (x))
graphe.(k);;
let visit () =
for i=0 to Array.length graphe - 1 do
if num.(i) = -1 then dfs i;
done;;
Aussi appelé arborescences de Trémaux dans le polycopié.
Planche 11
Arbres de recouvrement
L'arbre des appels récursifs à dfs est un arbre de recouvrement
(spanning tree). Les arcs d'un arc sont de 3 sortes:
- d'un noeud de l'arbre à un de ses fils dans l'arbre
- d'un noeud de l'arbre à un de ses ancètres dans l'arbre
- d'un noeud de l'arbre à un de ses descendants non direct dans l'arbre
- d'un noeud à un de ses cousins dans l'arbre
[Dans dfs d'un graphe non orienté, il n'y a que les arcs du type 1-2.]
Planche 12
Parcours en largeur d'abord, Breadth-First Search
let num = Array.make (Array.length graphe) (-1) and id = ref (-1);;
let bfs q =
while not (FIFO.vide(q)) do
let k = FIFO.supprimer(q) in
incr id;
num.(k) <- !id;
List.iter (function x -> if num.(x) = -1 then
begin FIFO.ajouter x q; num.(x) <- -2 end)
graphe.(k)
done;;
let visit() =
let q = FIFO.make() in
for i=0 to Array.length graphe - 1 do
if num.(i) = -1 then
begin FIFO.ajouter i q; num.(i) <- -2; bfs q end;
done;;
Itératif et plus compliqué que le parcours en profondeur d'abord.
Planche 13
Quelques problèmes standards
- Sortie de labyrinthe (dans un graphe non-orienté)
- Test de non-cyclicité dans un graphe orienté
- Tri topologique dans un graphe orienté (Makefiles)
- Planarité (Tarjan)
- Connexité dans un graphe non orienté
- Plus court chemin
Planche 14
Sortie de labyrinthe
On cherche un chemin de d à s.
let rec chemin d s =
num.(d) <- true;
let p = ref [] in
List.iter (function x ->
if not num.(x) && !p = [] then
if x = s then p := [s] else p := chemin x s)
graphe.(d);
if !p = [] then [] else d :: !p;;
Comment faire la sortie par le chemin le plus court?
Comment trouver et imprimer tous les chemins?
Planche 15
Bi-connectivité
Dans un graphe non orienté, existe-t-il 2 chemins différents entre
toute paire de noeuds? Un point d'articulation est un point qui
sépare le graphe en parties non connexes si on l'enlève. Un graphe
sans point d'articulation est un graphe bi-connexe. La fonction
suivante (en dfs) imprime les points d'articulation.
let min = ref nMax;;
let rec visit k =
incr id;
num.(k) <- !id; min := !id;
List.iter (function x ->
if num.(x) = -1 then begin
let m = visit x in
if m < !min then min := m;
if m >= num.(k) then printf "%d " k
end else
if num.(x) < !min then min := num.(x))
graphe.(k);
!min;;
Planche 16
Exercices en TD
- Faire une calculette avec entrée textuelle
- Calculer les composantes connexes dans un graphe non orienté
This document was translated from LATEX by HEVEA.