Langage Élémentaire Algorithmique Spécifications du langage Lionel Clément Version provisoire Novembre 2012 Le but de ce projet est de livrer un compilateur pour un langage élémentaire algorithmique (Léa). Ce langage est destiné à enseigner la programmation dans les cursus de sciences humaines. Pour en donner une idée, voici un petit exemple dans le cadre des éléments de programmation (suite de Fibonacci). 1 /* *********************************************** 2 * Un petit exemple de code Léa 3 *********************************************** */ 4 m: map of <int, int>; 5 6 function fib(n: int): int{ 7 if ((n == 1) (n == 2)) 8 return 1; 9 else { 10 if (m[n] == none) 11 m[n] := fib(n-1) + fib(n-2); 12 return m[n]; 13 } 14 } 15 16 // Définition de la fonction principale 17 procedure main(){ 18 for i in [1.. 20] 19 writeln (fib(i).tostring()); 20 } Le langage est procédural et orienté objet. Il utilise un typage statique explicite. C est-à-dire que toutes les variables sont déclarées avec un type connu durant l exécution du code. L allocation mémoire est entièrement dynamique, mais n est pas laissée à l initiative de l utilisateur. Les types sont nombreux et permettent de manipuler directement des objets complexes : ensembles, suites, tuples, dictionnaires. Structure générale d un programme Léa 1. Déclaration des constantes, des types et des variables globales 2. Déclaration des signatures des fonctions et procédures 3. Implémentation des fonctions et procédures dont la fonction main Commentaires Les commentaires sont comme en Java et en C++. sur une ligne ou en fin de ligne // 1
sur plusieurs lignes /* */ commentaires de documentation /** **/ Identificateurs Un identificateur est une séquence de lettres dont le signe _ et de chiffres encodés en UTF-8. Il doit commencer par une lettre. On différencie les capitales des bas-de-casse et les accents sont acceptés comme en C#. Déclarations Une constante ou un type n ont qu une seule déclaration possible pour l ensemble du programme. Une variable ne peut être déclarée qu une seule fois par bloc. Les procédures et fonctions peuvent avoir des déclarations multiples avec des signatures différentes en cas de surcharge. Un programme doit contenir une déclaration de la fonction function main(int, list of string): int;. La déclaration procedure main(); est également acceptée (le programme retournera 0 et les arguments seront inaccessibles). 1. Déclaration de constantes : identificateur = constante; où constant est un littéral élémentaire ou complexe 2. Déclaration de types : identificateur = expression de type; 3. Déclaration de classes : class identificateur { champs } 4. Déclaration de variables : identificateur: type; identificateur: type := valeur; 5. Déclaration de procédures : procedure identificateur (liste d arguments); procedure identificateur (liste d arguments) bloc 6. Déclaration de fonctions : function identificateur (liste d arguments): type; function identificateur (liste d arguments): type bloc Les arguments de type élémentaire sont passés par valeur, les autres par référence. Pour passer un argument élémentaire par référence, ont le fait précédé de &. Types Les types suivants sont disponibles (T, T 1, T 2,..., T k sont des types quelconques) : Types élémentaires : char, int, float, bool Chaînes de caractères : string 2
Énumérés : enum {slot 1,..., slot k } Suites : list of T Tuples : < T 1,..., T k > Ensembles : set of T Applications : map of < T 1, T 2 > Fichier : file Classes : class nom [: liste des super-classes ] { définition de classe } La définition de classe contient Les variables d instances Les variables de classes static Les constructeurs Les destructeurs Les méthodes Les opérateurs d instances La liste des super-classes permet un héritage des variables, fonctions et opérateurs membres. Encapsulation Les variables membres ne sont accessibles que depuis la classe ou ses sous-classes. Les opérateurs et méthodes sont accessibles partout. Instructions et structures de contrôle Affectation Pour dissuader l apprenant de confondre l égalité avec l affectation, nous utilisons le signe := AffectableExpr := expr; On pourra associer l affectation d initialisation et la déclaration de variable : variable: type := expr; Les affectations spéciales +=, -=, *=, /=,!=, &&=, = ont les significations habituelles, où variable op= expr; vaut pour variable := variable op expr; Appel de procédure Si foo est une procédure préalablement déclarée, foo(...) est accessible comme instruction. Remarque. Contrairement à de nombreux langages, une expression n est pas une instruction ni l inverse. Il n est donc pas possible d écrire fact(7) comme instruction où fact est une fonction ni une affectation comme expression if (x := foo(i))... P rocedurename (expr 1, expr 2,..., expr k ) ; Entrées sorties Les procédures write(expr), writeln(expr) permettent un accès aux sorties standard. Le type de l argument expr est un type élémentaire. Ajoutons read() comme expression pour un accès à l entrée standard (une chaîne de caractères terminée par return) qui renvoie un string. 3
Conditionnelles et choix multiples Si (I, I 1, I 2,... I k sont des instructions), les instructions conditionnelles suivantes sont admises : if (expr) I if (expr) I 1 else I 2 expr doit être du type bool à l exclusion de tout autre type, contrairement à de nombreux langages case expr of enum slot1 : I 1 enum slot2 : I 2... enum slotk : I k expr doit être de type enum, enum sloti doit être un énuméré de expr. Boucles Si I est une instruction, les boucles suivantes sont admises : 1. boucle while et repeat while (expr) I repeat I while (expr) ; Comme pour les conditionnelles, expr doit être du type bool. 2. boucle for for var in expr I Expr est une expression de type Liste Ensemble Application Chaîne de caractères Énuméré La variable var est déclarée localement et son type se rapporte au type de expr (char si expr est un string, l un des énumérés pour enum, élément pour la liste ou l ensemble, tuple pour l application). 1 { 2 k: map of <int, string>; 3 s: string; 4 t: enum (ROUGE, BLEU, VERT); 5 for i in [1.. 100] writeln (i); 6 for i in [1, 2, 3] writeln (i); 7 for i in {1, 2, 3} writeln (i); 8 for i in k { 9 write(j.first()); 10 writeln (" => " + j.second()); 11 } 12 for i in s 13 writeln(i); 14 for i in t 15 writeln(i); 16 } 4
Blocs Un bloc est un ensemble d instructions et de déclarations de variables. On ne mettra pas de déclaration de constantes, de types ni de fonctions dans les blocs. Expressions Les expressions manipulent les variables, les constantes élémentaires et complexes, les opérateurs, les fonctions et les fonctions membres d instances, de types ou de classes. On peut trouver des expressions qui ont des effets comme x++ ou foo(y) où y est un argument passé en paramètre. Nous rappelons qu une expression ne peut être assimilée à une instruction, donc nous écrirons x := x + 1; ou x+ = 1; au lieu de x + +; Les opérateurs et fonctions membres seront repris du langage Java comme traditionnellement. Nous dressons dans le tableau suivant les expressions du langage Léa 5
Expression Type Valeur... string chaîne de caractères où sont échappés les caractères spéciaux \t,\n,\r,\\,\"... char caractère où est échappé le caractère spécial \ nombre entier int nombre entier décimal signé de Integer.MIN VALUE à Integer.MAX VALUE nombre f lottant float nombre flottant décimal signé de Float.MIN VALUE à Float.MAX VALUE true, false bool none Objet non alloué E + E type des opérandes addition, concaténation, union E + + type de l opérande incrémentation, la valeur retournée est E + 1 + + E type de l opérande incrémentation, la valeur retournée est E E E type des opérandes soustraction, différence E type de l opérande décrémentation, la valeur retournée est E 1 E type de l opérande décrémentation, la valeur retournée est E E E type des opérandes multiplication, intersection* E/E type des opérandes division E%E type des opérandes modulo E type de l opérande moins unaire, complément E < E bool inférieur E > E bool supérieur E E bool inférieur ou égal E E bool supérieur ou égal E == E bool égal E!= E bool différent E && E bool et E E bool ou!e bool non < E 1, E 2,... E k > tuple < type(e 1 ), type(e 2 ),..., type(e k ) > construction d un tuple E 1 E 2 paire < type(e 1 ), type(e 2 ) > construction d une paire [E 1, E 2,... E k ] list of type(e i ) construction d une liste [E 1 E k ] list of type(e) construction d une liste [E 1,..., E k ] où i < j E i < E j L opérateur < est donné pour les types élémentaires, il doit être défini pour les classes {E 1, E 2,... E k } set of type(e i ) construction d un ensemble. Les opérateurs < et = sont donnés pour les types élémentaires, ils doivent être définis pour les classes {(key 1, val 1 ),... map of (type(key i ), type(val i )) construction d une relation. (key k, val k )} ou Les opérateurs < et = sont donnés {key 1 => val 1,... pour les types de key i élémentaires, key k => val k } ils doivent être définis pour les classes 6
foo(e 1, E 2,... E k ) y où type(foo) = x y application de foo où x s unifie avec type(e 1 ) type(e 2 )... type(e k ) ou objet instance de la classe foo si foo est une classe déclarée et foo(a 1, a 2,..., a k ) un constructeur pour cette classe t[e] x où type(t) = liste of x E ieme élément de la liste t (E doit être de type int) t[e] y où type(t) = map of ( x, y) deuxième élément du E ieme élément de la relation t (E doit être de type ( x, y)) E.F type de F F est une variable membre de l objet ou de la classe E E.foo(E 1, E 2,... E k ) y où type(foo) = x y appel d une fonction membre de l objet ou de la classe E ou du type de E Exemple de surcharge 1 function foo(i: int): int{ 2 code 1 3 } 4 5 function foo(i: int): bool{ 6 code 2 7 } 8 9 bool a := foo(6); // appel du code code 2 Exemple de déclaration d une fonction polymorphe 1 function findmin(t: list of x): x { 2 min: x := T[0]; 3 for i in T 4 if (min > i) 5 min := T[i]; 6 return min; 7 } Exemple de manipulation de classes 1 /* *********************************************** 2 * Un exemple complet de code Léa 3 *********************************************** */ 4 include "io.lea" 5 6 // Définition des constantes 7 MAX = 100; 8 9 // Définition des types 10 class Fiche { 11 static id: int; // variable de classe 12 nom: string; // variable d instance 13 age: int; 7
14 15 // définition d un constructeur 16 Fiche(){ 17 nom := ""; 18 age := 0; 19 } 20 21 // définition d un constructeur 22 Fiche(nom: string){ 23 this.nom := nom; 24 age := 0; 25 } 26 27 // définition d un destructeur 28 Fiche(){ 29 } 30 31 // définition de l opérateur > pour deux objets de la classe fiche 32 // remarque: la signature fiche X fiche -> bool ne peut pas être modifiée 33 // les mots clefs first et second valent respectivement 34 // pour la première et la seconde opérande 35 operator >{ 36 return (first.age > second.age); 37 } 38 39 // fonction par défaut public et membre d instance 40 function getnom(): string{ 41 return nom; 42 } 43 44 procedure setnom(nom: string){ 45 this.nom := nom; 46 } 47 48 function getage(): int{ 49 return age; 50 } 51 52 procedure setage(age: int){ 53 this.age := age; 54 } 55 } 56 57 // Définition d une fonction globale polymorphe 58 function sort(t: list of x) { 59 repeat { 60 ech: bool := false; 61 for i in [0.. MAX-2] { 62 if (T[i] > T[i+1]){ 63 // Variable locale 64 aux: x := T[i]; 65 T[i] := T[i+1]; 66 T[i+1] := aux; 67 ech := true; 68 } 69 } 70 } while (ech); 71 } 72 73 // Définition de la fonction principale 74 procedure main(){ 75 // Définition des variables 76 table: list of Fiche("essai"); 77 78 // Instructions 79 for i in [0.. MAX-1] 80 table[i].setage(random(100)); 8
81 82 sort(table); 83 } Allocation Tout objet de type élémentaire est alloué statiquement. Un objet de type non élémentaire est alloué dynamiquement au moment de sa déclaration, de son affectation ou de l application d un constructeur. L utilisateur n a donc jamais besoin d allouer ou de désallouer la mémoire explicitement. Un objet non encore alloué vaut none. 1 procedure main(){ 2 l: list of char; 3 l[0] := a ; 4 l[2] := c ; 5 writeln(l[1]); 6 // none 7 8 m: map of <int, char>; 9 m[1] := a ; 10 writeln(m[1]); 11 // a 12 writeln(m[0]); 13 // none 14 } 1 class arbre { 2 left: arbre; 3 right: arbre; 4 etiq: x; 5 arbre(left: arbre, right: arbre, etiq: x){ 6 this.left := left; 7 this.right := right; 8 this.etiq := etiq; 9 } 10 arbre(etiq: x){ 11 this.etiq := etiq; 12 } 13 } 14 15 function parcours(root: arbre): string; 16 17 procedure main() { 18 a: arbre := arbre(arbre(1), arbre(2), 0); 19 writeln (parcours(a)); 20 } 21 22 function parcours(root: arbre): string { 23 s: string := ""; 24 if (root.left!= none) 25 s += parcours(root.left) 26 if (root.right!= none) 27 s += parcours(root.right) 28 s += root.etiq.tostring(); 29 return s; 30 } 9