Rappel : évaluation d'expressions sans variables Ralf Treinen Université Paris Diderot UFR Informatique Institut de Recherche en Informatique Fondamentale treinen@irif.fr c Ralf Treinen 2015-2017 29 mars 2017 L'ensemble E des arbres de syntaxe abstraite représentant des expressions arithmétiques sans variables est déni comme suit : tout Int(n), où n N, est un élément de E si e 1, e 2 E, alors Sum(e 1, e 2 ) E si e 1, e 2 E, alors Product(e 1, e 2 ) E Règles sémantiques pour l'évaluation : Int(n) n e 1 v 1 e 2 v 2 Sum(e 1, e 2 ) v 1 + int v 2 e 1 v 1 e 2 v 2 Product(e 1, e 2 ) v 1 int v 2 Expressions avec variables L'ensemble E des arbres de syntaxe abstraite représentant des expressions arithmétiques est déni comme suit : tout Int(n), où n N, est un élément de E tout Ident(s), où s Σ, est un élément de E si e 1, e 2 E, alors Sum(e 1, e 2 ) E si e 1, e 2 E, alors Product(e 1, e 2 ) E Σ est l'alphabet utilisé pour former des noms de variables (par exemple, tous les caractères UNICODE). Éventuellement, l'ensemble des noms de variables possibles dans la syntaxe abstraite est plus riche que les noms de variables permis par la syntaxe concrète. Évaluation d'expressions avec variables Maintenant, l'évaluation d'une expression dépend aussi d'un contexte. La dénition du contexte dépend de l'application. Pour l'évaluation : Il s'agit d'une fonction partielle Σ N aussi appelée environnement de valeurs. Le jugement d'évaluation : Γ e v exprime : Dans l'environnement de valeurs Γ, la valeur de l'expression e est v. Exemple d'un jugement vrai : {x = 3, y = 5 Sum(Product(Ident(x), Int(2)), Ident(y)) 11
Règles pour l'évaluation des expressions avec variables Remarques Axiomes : Règles récursives : Γ Int(n) n Γ Ident(s) n si Γ(s) = n Γ e 1 v 1 Γ e 2 v 2 Γ Sum(e 1, e 2 ) v 1 + int v 2 Γ e 1 v 1 Γ e 2 v 2 Γ Product(e 1, e 2 ) v 1 int v 2 Quand s domaine(γ), alors le schéma d'axiome ne s'applique pas à Ident(s). Dans ce cas on ne peut donc pas calculer la valeur d'une expression qui contient Ident(s). Dans les règles récursives, comme Γ e 1 v 1 Γ e 2 v 2 Γ Sum(e 1, e 2 ) v 1 + int v 2 le contexte Γ utilisé dans les hypothèses est le même que dans la conclusion. Le calcul correspondant aux deux hypothèses peut donc se faire en parallèle. Implémentation : ValueEnvironment.java Implémentation : Expression.java I Implémentation des environnements de valeur : table de hachage. i m p o r t j a v a. u t i l. HashMap ; c l a s s V a l u e E n v i r o n m e n t e x t e n d s HashMap<S t r i n g, I n t e g e r > { a b s t r a c t c l a s s E x p r e s s i o n { a b s t r a c t i n t e v a l ( V a l u e E n v i r o n m e n t env ) ; c l a s s I n t e x t e n d s E x p r e s s i o n { p r i v a t e i n t v a l u e ; p u b l i c I n t ( i n t i ) { v a l u e=i ; p u b l i c i n t e v a l ( V a l u e E n v i r o n m e n t env ) { r e t u r n v a l u e ;
Implémentation : Expression.java II Implémentation : Expression.java III c l a s s I d e n t e x t e n d s E x p r e s s i o n { p r i v a t e S t r i n g name ; p u b l i c I d e n t ( S t r i n g s ) { name=s ; p u b l i c i n t e v a l ( V a l u e E n v i r o n m e n t env ) { r e t u r n env. g e t ( name ) ; c l a s s Sum e x t e n d s E x p r e s s i o n { p r i v a t e E x p r e s s i o n l e f t ; p r i v a t e E x p r e s s i o n r i g h t ; p u b l i c Sum( E x p r e s s i o n e1, E x p r e s s i o n e2 ) { l e f t =e1 ; r i g h t=e2 ; p u b l i c i n t e v a l ( V a l u e E n v i r o n m e n t env ) { r e t u r n l e f t. e v a l ( env ) + r i g h t. e v a l ( env ) ; c l a s s P r o d u c t e x t e n d s E x p r e s s i o n { p r i v a t e E x p r e s s i o n l e f t ; p r i v a t e E x p r e s s i o n r i g h t ; p u b l i c P r o d u c t ( E x p r e s s i o n e1, E x p r e s s i o n e2 ) { l e f t =e1 ; r i g h t=e2 ; p u b l i c i n t e v a l ( V a l u e E n v i r o n m e n t env ) { Implémentation : Expression.java IV Aectation et Achage Objectif : exécution de programmes. Première étape : séquences d'aectations à des variables, et d'instructions d'achage. r e t u r n l e f t. e v a l ( env ) r i g h t. e v a l ( env ) ; Instruction "print" pour acher, symbole " :=" pour l'aectation. Instructions terminées par le symbole " ;" Fragment de grammaire (le reste est comme avant) : S IL EOF IL ɛ I ; IL I ident := E print E
Règles sémantiques Instructions Syntaxe abstraite : L'ensemble I des arbres de syntaxe abstraite de sorte Instruction est déni comme suit : L'exécution d'une instruction peut modier l'environnement. Jugement Γ 1 i Γ 2 : l'exécution de l'instruction (ou de la liste d'instructions) i dans l'environnement Γ 1 résulte dans l'environnement Γ 2. Modélisation des achages : omise. tout Assign(s, e), où s Σ et e E, est un élément de I ; tout Print(e), où e E, est un élément de I. Règles sémantiques : Γ 1 e v Γ 1 Affect(s, e) Γ 1 [s v] Γ 1 Print(e) Γ 1 Remarques Listes d'instructions Les hypothèses des règles peuvent être des jugements qui portent sur des expressions de syntaxe abstraite de sorte diérente que la conclusion (exemple : règle pour Affect). Γ[s v] est l'environnement qui associe à s la valeur v, et qui associe Γ(x) à tout x s. La deuxième règle exprime que l'exécution de Print ne modie pas l'environnement. Syntaxe abstraite : L'ensemble IL des arbres de syntaxe abstraite de sorte InstructionList est déni comme suit : Nil est un élément de IL ; Si il IL et i I, alors Seq(i, il) est un élément de IL. Règles sémantiques : Γ 1 Nil Γ 1 Γ 1 i Γ 2 Γ 2 il Γ 3 Γ 1 Seq(i, il) Γ 3
Remarques Implémentation : Instruction.java I La première règle (qui est un axiome) exprime que l'exécution de Nil ne modie pas l'environnement. La deuxième règle impose maintenant un ordre sur l'exécution des deux composantes i et il : Donné initialement : Γ 1 et Seq(i, il). Il faut d'abord exécuter la première hypothèse qui donne, à partir de Γ 1 et de i, l'environnement Γ 2. Puis on peut exécuter la deuxième hypothèse qui donne, à partir de Γ 2 et de il, l'environnement Γ 3. L'implémentation utilise des tables de hachage modiables, il faut bien respecter l'ordre! a b s t r a c t c l a s s I n s t r u c t i o n { a b s t r a c t v o i d e x e c ( V a l u e E n v i r o n m e n t env ) ; c l a s s Assignment e x t e n d s I n s t r u c t i o n { p r i v a t e E x p r e s s i o n e x p r e s s i o n ; p r i v a t e S t r i n g v a r i a b l e ; p u b l i c Assignment ( S t r i n g v, E x p r e s s i o n e ) { e x p r e s s i o n=e ; v a r i a b l e=v ; p u b l i c v o i d e x e c ( V a l u e E n v i r o n m e n t env ) { env. put ( v a r i a b l e, e x p r e s s i o n. e v a l ( env ) ) ; Implémentation : Instruction.java II Implémentation : InstructionList.java I c l a s s P r i n t e x t e n d s I n s t r u c t i o n { p r i v a t e E x p r e s s i o n e x p r e s s i o n ; p u b l i c P r i n t ( E x p r e s s i o n e ) { e x p r e s s i o n=e ; p u b l i c v o i d e x e c ( V a l u e E n v i r o n m e n t env ) { System. out. p r i n t l n ( e x p r e s s i o n. e v a l ( env ) ) ; a b s t r a c t c l a s s I n s t r u c t i o n L i s t { a b s t r a c t v o i d e x e c ( V a l u e E n v i r o n m e n t env ) ; c l a s s N i l e x t e n d s I n s t r u c t i o n L i s t { p u b l i c N i l ( ) { p u b l i c v o i d e x e c ( V a l u e E n v i r o n m e n t env ) { c l a s s Seq e x t e n d s I n s t r u c t i o n L i s t { p r i v a t e I n s t r u c t i o n head ; p r i v a t e I n s t r u c t i o n L i s t r e s t ;
Implémentation : InstructionList.java II Expressions et Types p u b l i c Seq ( I n s t r u c t i o n i, I n s t r u c t i o n L i s t i l ) { head=i ; r e s t= i l ; p u b l i c v o i d e x e c ( V a l u e E n v i r o n m e n t env ) { head. e x e c ( env ) ; r e s t. e x e c ( env ) ; Pour l'exécution des programmes avec, par exemple des instructions conditionnelles et des boucles, nous avons besoin des expressions booléennes. On ne peut pas faire la distinction entre expressions entières et expressions booléennes par la grammaire à cause des variables : le type d'une expression contenant des variables dépend maintenant de la déclaration de ces variables. statique et typage dynamique statique et typage dynamique statique : vérication des types après construction de la syntaxe abstraite, et avant l'exécution. Avantage : Sécurité. Une fois le typage vérié par le compilateur on sait que des erreurs de type ne peuvent plus se produire (sauf dans certains langages de programmations qui permettent de contourner le typage). Avantage : le typage peut annoter la syntaxe abstraite par des types des expressions, ce qui simplie l'exécution. Désambiguïson des opérateurs surchargés. Exemples : Java, OCaml,... dynamique : vérication des types pendant l'exécution du programme, quand on applique un opérateur à des valeurs. Avantage : plus de exibilité (mais : exibilité aussi possible dans le cas statique, par ex. polymorphie). Avantage : programmes moins verbeux, car il n'est pas nécessaire de déclarer les variables avec leur type (mais : le même avantage peut être obtenu par une inférence de type). Exemples : Python, bash, Perl.
Retour sur quelques langages de programmation Bash Quelques langages de programmation que vous avez vu, ou allez voir, pendant vos études, regardés à la lumière du cours ADS4 : Découpage en unités lexicales à la (JF)LEX? Construction d'un arbre de syntaxe abstraite? Quel système de typage? Analyse syntaxique à la volée (intercalée avec l'exécution). Un mot peut être un mot clé ou pas selon l'endroit où il parait. Possibilité de faire interpréter une chaîne de caractères comme une commande de la shell (commande eval). dynamique : bash ne construit pas d'arbre de syntaxe abstraite avant l'exécution, il n'y a donc aucun moyen pour analyser statiquement le typage. Bash : analyse syntaxique à la volée Bash : si un mot est un mot clé dépend du contexte #! / b i n / bash # l e s deux p r e m i è r e s l i g n e s s o n t ex é c u t é e s a v a n t # que l ' e r r e u r de s y n t a x e e s t d e t e c t é e. echo " h e l l o " echo " b o n j o u r " i f f i #! / b i n / bash f o r do i n f o r do i n echo done ; do echo $do ; done
Bash : eval Python #! / b i n / bash # é v a l u a t i o n d ' une cha î ne c o n s t r u i t e p e n d a n t # l ' ex é c u t i o n du programme. i =10 mot=" i f ( ( i > 0 ) ) ; t h e n echo ' i f ' ; ( ( i )); f i " e v a l $mot Analyse syntaxique complète avant exécution. dynamique. mot=$ ( sed e ' s / i f / w h i l e /g ' e ' s / then /do / ' \ e ' s / f i / done / ' <<< $mot ) e v a l "$mot" Python : analyse syntaxique complète Python : typage dynamique #! / u s r / b i n / p y t h o n # e r r e u r de s y n t a x e d e t e c t e e a v a n t e x e c u t i o n p r i n t " h e l l o " p r i n t " b o n j o u r " i f f i #! / u s r / b i n / p y t h o n # l a meme v a r i a b l e p e u t p r e n d r e d e s v a l e u r s de # t y p e d i f f e r e n t. x=42 p r i n t x x=true p r i n t x
Python : typage dynamique Java #! / u s r / b i n / p y t h o n # e r r e u r de t y p a g e s e u l e m e n t d e t e c t e e quand # l a b r a n c h e e l s e e s t e x e c u t e e import s y s r e a d=s y s. s t d i n. r e a d l i n e ( ) i f r e a d!= ' coocoo \n ' : p r i n t 17+21 e l s e : p r i n t ( ' abc ' & ' d e f ' ) Construction de la syntaxe abstraite par le compilateur. statique. L'information de typage doit être écrite explicitement dans le programme. Vérication statique (donc, par le compilateur) des exceptions OCaml OCaml : inférence de types statique Inférence de type : le compilateur infère les types des identicateurs, il n'est pas nécessaire de les écrire dans le programme. On peut écrire dans le programme les types des identicateurs. Avantage : programmes concis, et au même moment sûreté de typage. ( l e t y p e de l a f o n c t i o n f e s t i n f é r é ) l e t somme x y = x + y ; ; ( e r r e u r de t y p a g e ) somme " h e l l o " " b o n j o u r " ; ; Surcharge des opérateurs très limitée. Voir le cours Programmation Fonctionnelle du L3