Corrigé Centrale 2026 Informatique MPI — Partie B (Q6 à Q15) : Analyse lexicale et syntaxique d'une formule déontique (OCaml)
Sujet officiel du concours Centrale, session 2026, épreuve Informatique MPI. Corrigé rédigé par Ibrahim Alame.
Sujet officiel (PDF, site du concours)
Énoncé
On construit l'arbre de syntaxe abstraite d'une formule écrite avec « non », « et », « doit » et des variables. Ainsi « ( Grand_mere_a_prepare_des_crepes et doit je_mange_les_crepes ) » donne l'arbre :
I. Analyse lexicale. Lexèmes : les opérateurs, les deux parenthèses, et les variables (lettres non accentuées et caractère _, ce dernier jamais en tête). : espace, t, n ; : les lettres ; : les deux parenthèses.
type lexeme =
Lex_var of string
| Lex_et
| Lex_non
| Lex_doit
| Lex_PO (* parenthèse ouvrante *)
| Lex_PF (* parenthèse fermante *)
let alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
let espaces = " \t\n"
Q6. appartient : string -> char -> bool, sans String.contains.
Q7. sous_chaine : string -> int -> int -> string (début, longueur), sans String.sub, avec exception si l'extraction est impossible ; sous_chaine "abracadabra" 3 4 = "acad".
Automate de reconnaissance des lexèmes (état initial ) :
Q8. detecte_lexeme chaine debut renvoie (debut_lexeme, fin_lexeme) : chaine[debut..fin_lexeme[ est le plus long préfixe reconnu à partir de debut, debut_lexeme le premier indice debut hors de ; exception Erreur_Lexicale si aucun préfixe n'est reconnu. Exemple : (4, 8) pour "non doit manger_crepe" et debut ou .
let produit_lexeme chaine debut fin =
match sous_chaine chaine debut (fin-debut) with
| "et" -> Lex_et
| "non" -> Lex_non
| "doit" -> Lex_doit
| "(" -> Lex_PO
| ")" -> Lex_PF
| x -> Lex_var x
Q9. analyse_lexicale : string -> lexeme list (exception Erreur_Lexicale en cas d'échec).
II. Grammaires (terminaux et, non, doit, parenthèses, pour une variable). , d'axiome : et . , d'axiome : , , .
Q10. « x doit ( y et z ) » et « x et doit ( non y et z ) » dérivent-ils de ? de ?
Q11. et engendrent-elles le même langage ?
Q12. est-elle ambiguë ? ?
III. Analyse syntaxique pour : chaque fonction renvoie (f, reste), f l'arbre d'un préfixe prefixe et prefixe @ reste = lexemes.
type formule =
Var of string
| Et of formule * formule
| Non of formule
| Doit of formule
exception Erreur_Syntaxique
let rec derive_E lexemes = match lexemes with
| Lex_var x :: q -> (Var x, q); (* E -> n *)
| Lex_PO :: q -> (* E -> ( B ) *)
begin
match derive_B q with
| f , Lex_PF :: q2 -> (f, q2)
| _ -> raise Erreur_Syntaxique
end
| _ -> derive_C lexemes (* E -> C *)
and derive_B lexemes = match derive_E lexemes with
| f1, Lex_et::q -> (* B -> E et E *)
begin
(* bloc de code 1 *)
end
| _ -> raise Erreur_Syntaxique
and derive_C lexemes = match lexemes with
(* bloc de code 2 *)
Exemple : derive_E [Lex_non; Lex_var "manger_crepe"; Lex_et; Lex_doit; Lex_var "remercier_mamie"; Lex_PF] vaut (Non (Var "manger_crepe"), [Lex_et; Lex_doit; Lex_var "remercier_mamie"; Lex_PF]).
Q13. Écrire le bloc de code 1.
Q14. Écrire le bloc de code 2.
Q15. analyse_syntaxique : lexeme list -> formule, exception Erreur_Syntaxique si la liste n'est pas une formule.
Corrigé
Tout le code de ce corrigé a été exécuté (OCaml 5.4) sur les exemples du sujet et sur des cas limites : chaîne vide, espaces seuls, caractère interdit, _ en tête, parenthèse non fermée, lexèmes restants.
Q6. On parcourt les indices de gauche à droite, en s'arrêtant au premier caractère égal à c.
let appartient chaine c =
let n = String.length chaine in
let rec cherche i = i < n && (chaine.[i] = c || cherche (i + 1)) in
cherche 0
Correction. Invariant : à l'appel cherche i, c n'est à aucun indice . Si , la réponse est donc false ; sinon c est présent si et seulement s'il est en ou à un indice . Terminaison : décroît strictement. L'opérateur || est paresseux et son second membre est en position terminale : la récursion est terminale, en espace constant. Coût : au plus comparaisons, .
Q7. La précondition est , et ; une assertion la vérifie, puis String.init construit la chaîne caractère par caractère.
let sous_chaine chaine debut longueur =
assert (debut >= 0 && longueur >= 0
&& debut + longueur <= String.length chaine);
String.init longueur (fun k -> chaine.[debut + k])
Le caractère d'indice du résultat est chaine.[debut + k] pour longueur : c'est la définition de la sous-chaîne, et l'assertion garantit que tous ces indices sont valides. Coût . Exécuté : sous_chaine "abracadabra" 3 4 vaut "acad" (indices à : a, c, a, d) ; sous_chaine "abc" 1 3 lève Assert_failure () ; sous_chaine "abc" 3 0 vaut "". Construire le résultat par concaténations successives \^ coûterait , les chaînes étant immuables. Seul String.sub est interdit ; si l'on veut aussi se passer de String.init, on remplit un tampon mutable : let b = Bytes.create longueur in for k = 0 to longueur - 1 do Bytes.set b k chaine.[debut + k] done; Bytes.to_string b (même précondition, même coût, exécuté avec les mêmes résultats).
Q8. On code l'automate par sa fonction de transition (-1 pour l'absence de transition) et ses états acceptants , , , puis on le simule depuis l'indice debut en retenant la dernière position où l'état courant est acceptant : c'est la règle du plus long préfixe.
exception Erreur_Lexicale
let transition q c = match q with
| 0 -> if appartient espaces c then 0
else if appartient alphabet c then 1
else if c = '(' || c = ')' then 3
else -1
| 1 -> if appartient alphabet c then 1 else if c = '_' then 2 else -1
| 2 -> if appartient alphabet c || c = '_' then 2 else -1
| _ -> -1 (* l'état 3 n'a pas de transition *)
let acceptant q = q = 1 || q = 2 || q = 3
let rec premier_non_espace chaine i =
if i < String.length chaine && appartient espaces chaine.[i]
then premier_non_espace chaine (i + 1)
else i
let detecte_lexeme chaine debut =
let n = String.length chaine in
(* q : état après lecture de chaine[debut..i-1] ;
fin : plus grand j <= i avec chaine[debut..j-1] reconnu, -1 s'il n'y en a pas *)
let rec lire q i fin =
let fin = if acceptant q then i else fin in
if i >= n then fin
else
let q' = transition q chaine.[i] in
if q' = -1 then fin else lire q' (i + 1) fin
in
let fin = lire 0 debut (-1) in
if fin = -1 then raise Erreur_Lexicale
else (premier_non_espace chaine debut, fin)
Correction. L'automate est déterministe (au plus une transition par état et par caractère) ; invariant de lire : q est l'état atteint après lecture de chaine[debut..i-1] et fin est le plus grand tel que chaine[debut..j-1] soit reconnu ( sinon). La mise à jour de fin en tête de fonction maintient l'invariant. Lorsque la lecture s'arrête (fin de chaîne ou transition absente), aucun préfixe plus long ne peut être reconnu — l'automate est bloqué, et aucun état n'est atteignable ensuite — donc fin est la fin du plus long préfixe reconnu. Si fin vaut , aucun préfixe n'est reconnu (l'état n'étant pas acceptant, même le préfixe vide ne l'est pas) : on lève l'exception. Les espaces initiaux sont consommés par la boucle sur l'état , et premier_non_espace calcule le début du lexème proprement dit. Terminaison : décroît. Coût : appels de transition, où est la longueur lue, chacun en à cause de appartient : linéaire.
100 % gratuit
La suite du corrigé est sur Adloun
Le corrigé rédigé en entier, question par question, et le tuteur pour poser des questions sur ce sujet.
Voir le corrigé complet, gratuitementInscription gratuite en 30 secondes (e-mail, nom d'utilisateur, mot de passe, classe ou profil), sans carte bancaire : arrivée directe sur le corrigé.