diff --git a/cours/cours-01/dune b/cours/cours-01/dune new file mode 100644 index 0000000..5981202 --- /dev/null +++ b/cours/cours-01/dune @@ -0,0 +1,5 @@ +(executable + (name marthe) + (libraries unix) + (promote (until-clean)) + ) diff --git a/cours/cours-01/dune-project b/cours/cours-01/dune-project new file mode 100644 index 0000000..929c696 --- /dev/null +++ b/cours/cours-01/dune-project @@ -0,0 +1 @@ +(lang dune 2.0) diff --git a/cours/cours-01/marthe.ml b/cours/cours-01/marthe.ml new file mode 100644 index 0000000..3b2ad63 --- /dev/null +++ b/cours/cours-01/marthe.ml @@ -0,0 +1,842 @@ +(* + + Ce module implémente une boucle interactive pour le langage + "Marthe", un langage d'expressions arithmétiques avec un + opérateurs de sommation. + + Pour le compiler, utilisez par exemple la commande : + + ocamlopt -o marthe unix.cmxa marthe.ml + + Vous pouvez aussi l'évaluer pas à pas dans une boucle interactive + ocaml, liée au module standard Unix. Pour cela, dans le mode + Tuareg d'emacs, lancez la fonction "Run Caml Toplevel" du menu + Tuareg avec la commande : + + ocaml unix.cma + +*) + +(* + + Le langage Marthe est un langage minimaliste dont voici quelques + exemples de programmes corrects syntaxiquement: + + - "73" + - "6 * 7" + - "1 + (2 * 3)" + - "1 + 2 * 3" + - "sum (x, 1, 10, x * x)" + - "sum (x, 1, 10, sum (y, 1, 10, x + y))" + - "1 + + 2 * 3" + - "sum (x, 1, 10 * 10, y)" + + Nous allons écrire une boucle interactive incluant un interpréteur + de Marthe ainsi qu'un compilateur de Marthe vers une machine + abstraite minimaliste à pile. + +*) + +(* Cette fonction implémente une boucle interactive à + l'aide de trois fonctions: + - [read ()] demande une chaîne [s] à l'utilisateur ; + - [eval s] évalue le programme Marthe ; + - [print r] affiche la valeur résultat de l'évaluation de [s]. *) +let loop read eval print = + let rec aux () = + try + let p = read () in + let v = eval p in + print v; + aux () + with + | End_of_file -> + (* Si le flux entrant se termine, on arrête la boucle interactive après + avoir affiché une nouvelle ligne. *) + print_newline () + | exn -> + (* On utilise le module de la bibliothèque standard de Caml + pour afficher l'exception. L'échappement ANSI "\033[31m" + permet d'afficher en rouge l'erreur dans un terminal. + http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html + *) + Printf.printf "\027[31mError:%s\n\027[0m" (Printexc.to_string exn); + aux () + in + aux () + +(* + + Pour définir la syntaxe d'un langage, on utilise une *grammaire*. + + Une grammaire définit deux choses: + - un ensemble de mots (les programmes syntaxiquement corrects) ; + - la structure de ces mots. + + Ainsi, le premier problème à résoudre est de transformer une chaîne + de caractères (qui est une donnée toute plate, sans structure) en + un *arbre de syntaxe abstraite* (qui met en avant la structure des + programmes). Avoir une structure d'arbre permet d'*interpréter* les + programmes récursivement. Typiquement, on veut évaluer un expression + de la forme "e₁ + e₂" en évaluant e₁ en un entier n₁ puis e₂ en + un entier n₂ puis en faisant la somme de n₁ et n₂. + + Voici des exemples de programmes Marthe pour illustrent ces idées: + + * Exemple 1: + On veut traduire "1 + 2" en "EPlus (EInt 1, EInt 2)". + La sous-chaîne "1" a été reconnue comme "EInt 1". + La sous-chaîne "2" a été reconnue comme "EInt 2". + La sous-chaîne "1 + 2" a été reconnue en "EPlus (EInt 1, EInt 2)". + + * Exemple 2: + On veut traduire "1 + 2 * 3" en "EPlus (EInt 1, EMul (EInt 2, EInt 3))". + Le nombre d'espaces n'a pas de signification dans la grammaire du langage + Marthe. Il y a donc des caractères importants et des caractères à ignorer. + Nous allons voir que la phase d'analyse lexicale sert à se concentrer sur + les symboles importants. + + * Exemple 3: + On veut transformer "1 + 2 * 3" en "EPlus (EInt 1, EMul (EInt 2, EInt 3))". + Attention, ici, il serait incorrect de traduire "1 + 2 * 3" en + "EMul (EPlus (EInt 1, EInt 2), EInt 3)" parce que l'on donnerait + alors la priorité à l'addition devant la multiplication ! + + Ces exemples nous apprennent plusieurs choses: + + 1. Le type de la fonction "read" doit être quelque chose comme: + + string → expression + + où "expression" est un type de données arborescent. + + 2. L'analyse syntaxique peut être précédée d'une première phase qui + élimine les caractères non significatifs. + + 3. L'analyse syntaxique doit prendre en charge la reconnaissance des + priorités des opérateurs. + +*) + +(* [read] transforme une chaîne de caractères d'entrées en un arbre + de syntaxe abstraite en composant l'analyse lexicale et l'analyse + syntaxique. *) +let read lexer parse = + Printf.printf "marthe> %!"; + let s = input_line stdin in + let tokens = lexer s in + parse tokens + +(* Les lexèmes, aussi appelés "terminaux", sur lesquels + la grammaire du langage est définie. *) +type token = + | Int of int (* Ex: "42", "0", "231", ... *) + | Id of string (* Ex: "x", "abc", "foo" *) + | Sum (* "sum" *) + | Plus (* "+" *) + | Star (* "*" *) + | Lparen (* "(" *) + | Rparen (* ")" *) + | Comma (* "," *) + | EOF (* La fin de l'entrée. *) + +let string_of_token = function + | Int x -> "Int(" ^ string_of_int x ^ ")" + | Id x -> "Id(" ^ x ^ ")" + | Sum -> "Sum" + | Plus -> "Plus" + | Star -> "Star" + | Lparen -> "Lparen" + | Rparen -> "Rparen" + | Comma -> "Comma" + | EOF -> "EOF" + +exception LexingError of string + +(* L'analyse lexixale produit une liste de lexèmes à partir de la + chaîne de caractères d'entrée. Il s'agit essentiellement d'un + automate fini implémentée à la main. *) +let lexer : string -> token list = + fun s -> + let at_the_end i = i >= String.length s in + + (* Itère sur la chaîne en partant de l'indice [start], + et avance tant que le caractère [c] est tel que + [char_class c = true]. *) + let word char_class = + let rec aux start i = + let return stop = (String.sub s start (i - start), stop) in + if at_the_end i then + return (i + 1) + else if char_class s.[i] then + aux start (i + 1) + else + return i + in + fun start -> aux start start + in + + (* Les classes de caractères. *) + let is_digit c = c >= '0' && c <= '9' in + let is_letter c = c >= 'a' && c <= 'z' in + (* Les mots sur ces classes de caractères. *) + let number = word is_digit in + let identifier = word is_letter in + + (* La fonction récursive suivante itère sur la chaîne + à partir de [i] et tente de reconnaître un lexème. *) + let rec aux i = + (* Par défaut, pour continuer sur le caractère suivant, on augmente + l'indice et on fait un appel récursif. Dans certains cas, + l'indice [where] est fourni. *) + let continue ?(where=(i + 1)) () = aux where in + + (* Pour retourner un lexème reconnu, on le met en tête + de la liste des tokens produite par les appels récursifs. *) + let produce_and_continue ?where token = token :: (continue ?where ()) in + + if at_the_end i then + (* Le lexème EOF marque la fin de l'entrée. *) + [EOF] + else + (* Sinon, on peut décider quel lexème essayer de reconnaître + à l'aide du premier caractère croisé. *) + match s.[i] with + (* On saute les espaces. *) + | ' ' -> continue () + + (* Les symboles. *) + | '*' -> produce_and_continue Star + | '+' -> produce_and_continue Plus + | '(' -> produce_and_continue Lparen + | ')' -> produce_and_continue Rparen + | ',' -> produce_and_continue Comma + + (* Les nombres. *) + | c when is_digit c -> + let (n, eo_num) = number i in + (* [i] est l'indice du dernier caractère du nombre + reconnu. *) + produce_and_continue ~where:eo_num (Int (int_of_string n)) + + (* Les identificateurs. *) + | c when is_letter c -> + let (s, eo_id) = identifier i in + (* [i] est l'indice du dernier caractère de + l'identificateur reconnu. *) + produce_and_continue ~where:eo_id (if s = "sum" then Sum else Id s) + + (* Sinon, le caractère n'est pas accepté par le lexeur. *) + | _ -> + raise (LexingError "Invalid character") + in + aux 0 + +(* Tests de l'analyseur lexical. *) +let test_title s = + let max_test_title_len = 30 in + let s = String.escaped s in + if String.length s > max_test_title_len then + String.sub s 0 max_test_title_len ^ "..." + else + s + +let ok s = Printf.printf "\027[1;32m[OK] `%s'\027[0m\n" (test_title s) +let ko s = Printf.printf "\027[1;31m[KO] `%s'\027[0m\n" (test_title s) +let ( --> ) input output = (input, output) +let do_test positivity display test (input, expected) = + try + if positivity (test input = expected) then + ok (display input) + else + ko (display input) + with _ -> if positivity true then ko (display input) else ok (display input) + +let valid x = x +let invalid x = not x + +let test_lexer () = + Printf.printf "-*- Lexer -*-\n"; + (* Tests positifs. *) + List.iter (do_test valid (fun s -> s) lexer) [ + "1" --> [Int 1; EOF]; + "42" --> [Int 42; EOF]; + "231" --> [Int 231; EOF]; + "+" --> [Plus; EOF]; + "*" --> [Star; EOF]; + "(" --> [Lparen; EOF]; + ")" --> [Rparen; EOF]; + "," --> [Comma; EOF]; + "sum" --> [Sum; EOF]; + "a" --> [Id "a"; EOF]; + "sumx" --> [Id "sumx"; EOF]; + "( )" --> [Lparen; Rparen; EOF]; + "()" --> [Lparen; Rparen; EOF]; + "42," --> [Int 42; Comma; EOF]; + "" --> [EOF] + ]; + + (* Tests négatifs. *) + List.iter (do_test invalid (fun s -> s) lexer) [ + "#" --> []; + "!" --> []; + "\n" --> []; + ] + +(* Exercices de programmation: + + Étendre l'analyse lexicale, pour + 1. ignorer les tabulations ; + 2. rajouter la gestion des symboles '-' et '/' ; + 3. ignorer des commentaires écrits entre '(*' et '*)'. + +*) + +(* Les arbres de syntaxe abstraite du langage Marthe. + + Sont donnés ici en exemple des chaînes de caractères produisant + un arbre dont la racine est le constructeur de données de la + même ligne. *) +type e = + | EInt of int (* Ex: "42", "31" *) + | EVar of string (* Ex: "x", "y", "foo" *) + | EPlus of e * e (* Ex: "1 + 2", "2 * 3 + 4" *) + | EMult of e * e (* Ex: "1 * 2", "(1 + 2) * 3" *) + | ESum of string * e * e * e (* Ex: "sum (x, 1, 10, x * x)" *) + +exception ParseError of string * token + +(* On se donne la grammaire suivante pour les arbres de syntaxe + de Marthe: + + phrase ::= expression EOF + + expression ::= + term PLUS expression + | term + + term ::= + factor STAR term + | factor + + factor ::= + INT(x) + | VAR(x) + | SUM LPAREN VAR COMMA expression COMMA expression COMMA expression RPAREN + | LPAREN expression RPAREN + + La fonction [parse] transforme une liste de lexèmes en un arbre du type [e] + via l'analyse induite par la grammaire. +*) + +let parse : token list -> e = fun tokens -> + (* On utilise trois fonctions pour se construire une abstraction + au-dessus de la liste des lexèmes restant à traiter. À l'aide + des trois fonctions suivantes, on lit cette liste de gauche + à droite au fur et à mesure de l'analyse, qui accepte ou non + ces lexèmes comme étant à une position valide vis-à-vis de + la grammaire. *) + let (accept, current, next) = + + (* En utilisant une référence locale, on s'assure que seules + les trois fonctions suivantes peuvent modifier la variable + [token_stream]. *) + let token_stream = ref tokens in + + (* La fonction [next] supprime le lexème en tête de la liste + des lexèmes à traiter. *) + let next () = + match !token_stream with + | [] -> raise (ParseError ("No more tokens", EOF)) + | _ :: tokens -> + token_stream := tokens + in + + (* La fonction [current] renvoie le lexème courant. *) + let current () = + match !token_stream with + | [] -> assert false + | tok :: _ -> + tok + in + + (* [accept t] vérifie que le lexème courante est [t] et + passe alors au lexème suivant. *) + let accept token = + if (current () <> token) then + raise (ParseError ("Unexpected token", token)); + next () + in + (accept, current, next) + in + + (* L'analyseur syntaxique suit un algorithme récursif et + descendant. + + Il est défini par 4 fonctions mutuellement récursives + correspondant à chaque cas de la grammaire définie plus + haut. + *) + + (* Une phrase est une expression suivie obligatoirement + par la fin de l'entrée. *) + let rec phrase () = + let e = expression () in + accept EOF; + e + + (* Pour analyser une expression, ... *) + and expression () = + (* ... on commence par analyser un terme. *) + let e = term () in + match current () with + (* Si ce terme est suivi par un "Plus", on + est dans la seconde règle de la grammaire, + on doit donc accepter ce "Plus" et passer à + la suite pour reconnaître une expression. *) + | Plus -> + next (); + EPlus (e, expression ()) + + (* Dans les autres cas, nous étions dans + la première règle et nous avons reconnu + une expression [e]. Le travail est terminé. *) + | _ -> + e + + (* Pour analyser un terme, on suit le même schéma que pour + les expressions. *) + and term () = + let t = factor () in + match current () with + | Star -> + next (); + EMult (t, term ()) + + | _ -> t + + (* Pour décider dans quelle règle on se trouve, ... *) + and factor () = + (* on commence par observer le lexème courant. *) + match current () with + (* C'est une parenthèse ouvrante ? C'est la règle 4. *) + | Lparen -> + next (); + (* On doit reconnaître une expression ... *) + let e = expression () in + (* ... suivie d'une parenthèse fermante. *) + accept Rparen; + e + + (* C'est le mot-clé "sum" ? C'est la règle 3. *) + | Sum -> + next (); + (* On attend une parenthèse ouvrante. *) + accept Lparen; + (* Puis, un identificateur. *) + let id = + match current () with + | Id s -> next (); s + | token -> raise (ParseError ("Expecting an identifier", token)) + in + (* Une virgule. *) + accept Comma; + (* L'expression correspondante à l'initialisation de la variable + de sommation. *) + let start = expression () in + (* Une virgule. *) + accept Comma; + (* L'expression correspondante à la valeur finale de la variable + de sommation. *) + let stop = expression () in + (* Une virgule. *) + accept Comma; + (* L'expression correspondante au corps de la sommation. *) + let body = expression () in + (* Et enfin, une parenthèse fermante. *) + accept Rparen; + ESum (id, start, stop, body) + + (* C'est un identificateur ? C'est la règle 2. *) + | Id x -> + next (); + EVar x + + (* C'est un entier ? C'est la règle 1. *) + | Int x -> + next (); + EInt x + + (* Les autres cas sont des cas d'erreur. *) + | token -> + raise (ParseError ("Unexpected token", token)) + + in + phrase () + +let test_parser () = + Printf.printf "-*- Parser -*-\n"; + let display_tokens t = String.concat " " (List.map string_of_token t) in + (* Tests positifs. *) + List.iter (do_test valid display_tokens parse) [ + [Int 1; EOF] --> EInt 1; + [Int 1; Plus; Int 41; EOF] --> EPlus (EInt 1, EInt 41); + [Int 1; Star; Int 41; EOF] --> EMult (EInt 1, EInt 41); + (lexer "1 + 2 * 3") --> EPlus (EInt 1, EMult (EInt 2, EInt 3)); + (lexer "1 * 2 + 3") --> EPlus (EMult (EInt 1, EInt 2), EInt 3); + (lexer "sum (x, 1, 2, x * x)") + --> ESum ("x", EInt 1, EInt 2, EMult (EVar "x", EVar "x")) + ]; + + (* Tests négatifs. *) + + (* Une valeur bidon de type [e]. *) + let fail = EInt (-42) in + + List.iter (do_test invalid display_tokens parse) [ + [EOF] --> fail; + (lexer "1 + 2 *") --> fail; + (lexer "1 * (2)) + 3") --> fail; + (lexer "sum (x, 1, 2, x * x") --> fail + ] + +(* Exercices de programmation + + Étendre l'analyse syntaxique pour intégrer + la division et la soustraction. Comment reconnaissez-vous + 2 - 3 - 4 ? Comme "(2 - 3) - 4", ce qui est correct ou + plutôt comme "2 - (3 - 4)", ce qui est incorrect ? +*) + +(* Un interprète produit récursivement la valeur entière correspondante à + l'évaluation d'un arbre de syntaxe. *) +let interpret : e -> int = + (* Le paramètre [env] est une liste associative + contenant la valeur associée aux indices de + sommation. + + La fonction d'évaluation est définie par + cas sur la forme de l'arbre. *) + let rec aux env = function + (* Pour évaluer une expression de la forme "e1 + e2", + on évalue [e1] en un entier, on évalue [e2] en + un autre entier, puis on fait la somme des deux + entiers. *) + | EPlus (e1, e2) -> aux env e1 + aux env e2 + + (* Même raisonnement pour la multiplication. *) + | EMult (e1, e2) -> aux env e1 * aux env e2 + + (* Une expression qui est un entier s'évalue en cet entier. *) + | EInt x -> x + + (* Pour évaluer une expression de la forme + "sum (x, start, stop, body)". *) + | ESum (x, start, stop, body) -> + (* On évalue [start]. *) + let vstart = aux env start + (* On évalue [stop]. *) + and vstop = aux env stop + in + (* On itère sur toutes les valeurs [i] de + [start] à [stop] et on accumule les sommes + intermédiaires dans la variable [accu]. *) + let rec iter i accu = + if i > vstop then + accu + else + (* L'évaluation de [body] se fait dans un + environnement où l'indice [x] est associé + à la valeur [i]. *) + iter (i + 1) (accu + aux ((x, i) :: env) body) + in + iter vstart 0 + + (* Une expression qui est variable s'évalue en la valeur + associée à cette variable dans l'environnement. *) + | EVar x -> + List.assoc x env + in + aux [] + +(* En rejoignant toutes les composantes, on obtient une boucle interactive qui + utilise notre interprète pour évaluer le programme Marthe. *) +let interactive_loop () = + loop + (fun () -> read lexer parse) + interpret + (fun x -> Printf.printf ":- %d\n" x) + +let eval s = interpret (parse (lexer s)) + +(* Test de l'interprète. *) +let test_interpreter () = + Printf.printf "-*- Interpreter -*-\n"; + + (* Tests positifs. *) + List.iter (do_test valid (fun x -> x) eval) [ + "1" --> 1; + "1 + 1" --> 2; + "6 * 7" --> 42; + "1 + 2 * 3" --> 7; + "sum (i, 1, 10, i)" --> 55; + "sum (i, 1, 10, sum (j, 1, i, i * j))" + --> 1705 + ]; + + (* Tests négatifs. *) + let fail = 42 in + + List.iter (do_test invalid (fun x -> x) eval) [ + "i" --> fail; + "sum (i, 1, 10, j)" --> fail + ] + + +(* Exercice de programmation + + Étendre l'interprète pour traiter la division et la soustraction. +*) + +(* Nous allons maintenant définir une compilation des programmes + marthe vers la machine suivante : *) +type machine = { + (* Le pointeur de code courant. *) + mutable pc : int; + (* Le code est une liste d'instructions. *) + code : instruction array; + (* Des emplacements mémoires pour un nombre borné de variables. *) + variables : int array; + (* Des accumulateurs pour sommer en itérant sur ces variables. *) + accumulators : int array; + (* Une pile d'entiers pour stocker les valeurs intermédiaires. *) + mutable sp : int; + stack : int array; +} + +and instruction = + (* Pousse une valeur entière sur la pile. *) + | Push of int + (* Dépile la valeur au sommet de la pile. *) + | Pop + (* Dépile deux valeurs et empile leur somme. *) + | Add + (* Dépile deux valeurs et empile leur produit. *) + | Mul + (* Met l'accumulateur numéro x à zéro. *) + | ResetAccu of int + (* Pousse l'accumulateur numéro x au sommet de la pile. *) + | PushAccu of int + (* Augmente l'accumulateur numéro x de la valeur sur la pile. *) + | AddAccu of int + (* Pousse la valeur de la variable x sur pile. *) + | GetVar of int + (* Affecte la valeur au sommet de la pile à la variable numéro x. *) + | SetVar of int + (* Incrémente à la variable x. *) + | IncVar of int + (* L'instruction [JumpLe (x, a)] déplace le pointeur de code en a si la valeur + de la variable x est inférieure ou égale à celle présente au sommet de la + pile. *) + | JumpLe of int * int + (* Arrête la machine et renvoie l'entier au sommet de la pile. *) + | Halt + +(* Initialisation d'une machine abstraite. *) +let vm_init stack_size nb_variables code : machine = { + pc = 0; + sp = -1; + code = code; + variables = Array.make nb_variables 0; + accumulators = Array.make nb_variables 0; + stack = Array.make stack_size 0; +} + +(* Cette exception sera lancée pour interrompre le + calcul. *) +exception Exit of int + +(* Interprète le code d'une machine en un entier + (si le code est correct et ne dépasse pas les capacités + de la machine, en termes de nombre de variables et de + profondeur de pile). *) +let vm_interpret : machine -> int = + fun vm -> + (* Les opérations standards sur les piles. *) + let top () = vm.stack.(vm.sp) in + let pop () = let x = top () in vm.sp <- vm.sp - 1; x in + let push x = vm.sp <- vm.sp + 1; vm.stack.(vm.sp) <- x in + + let interpret_instruction = function + | Push x -> + push x + | Pop -> + vm.sp <- vm.sp - 1 + | Add -> + (* Notez l'optimisation: on travaille en place sur la pile. *) + vm.stack.(vm.sp - 1) <- vm.stack.(vm.sp - 1) + vm.stack.(vm.sp); + vm.sp <- vm.sp - 1 + | Mul -> + vm.stack.(vm.sp - 1) <- vm.stack.(vm.sp - 1) * vm.stack.(vm.sp); + vm.sp <- vm.sp - 1 + | ResetAccu x -> + vm.accumulators.(x) <- 0 + | PushAccu x -> + push vm.accumulators.(x) + | AddAccu x -> + vm.accumulators.(x) <- vm.accumulators.(x) + pop () + | IncVar x -> + vm.variables.(x) <- vm.variables.(x) + 1 + | SetVar x -> + vm.variables.(x) <- pop () + | GetVar x -> + push vm.variables.(x) + | JumpLe (x, a) -> + if vm.variables.(x) <= top () then vm.pc <- a - 1 + | Halt -> + raise (Exit (pop ())) + in + try + while true do + interpret_instruction (vm.code.(vm.pc)); + vm.pc <- vm.pc + 1 + done; + (* On peut sortir de la boucle précédente uniquement + si son corps lance une exception. On ne peut donc + pas atteindre le point de code suivant. Cette branche + est du code mort. *) + assert false + with Exit x -> x + +(* Voici une fonction de compilation des arbres de syntaxe + abstraite vers un code pour la machine précédente. *) +let compile : e -> instruction array = + fun e -> + (* [nb_idx] est le dernier indice utilisé pour nommer les variables de + sommation. + + [variable_idx] est une liste associative des noms de variable du code + source vers leurs indices associés. + + [pos] est la position courante dans le code machine produit. + + La fonction de compilation fait un parcours en profondeur de l'arbre de + syntaxe abstraite et pour chaque sous-arbre A produit un code machine C tel + que l'évaluation de C place le résultat de l'évaluation du sous-arbre A au + sommet de la pile de la machine, sans modifier celle-ci autrement. + + *) + let rec aux nb_idx variable_idx pos = function + | EInt x -> + (pos + 1, [ Push x ]) + + | EPlus (e1, e2) -> + let (pos, instrs_e1) = aux nb_idx variable_idx pos e1 in + let (pos, instrs_e2) = aux nb_idx variable_idx pos e2 in + (pos + 1, instrs_e1 @ instrs_e2 @ [ Add ]) + + | EMult (e1, e2) -> + let (pos, instrs_e1) = aux nb_idx variable_idx pos e1 in + let (pos, instrs_e2) = aux nb_idx variable_idx pos e2 in + (pos + 1, instrs_e1 @ instrs_e2 @ [ Mul ]) + + | ESum (x, start, stop, body) -> + let variable_idx' = (x, nb_idx + 1) :: variable_idx in + let nb_idx' = nb_idx + 1 in + let (pos, instrs_start) = aux nb_idx variable_idx pos start in + let (pos, set_x) = (pos + 1, [ SetVar nb_idx' ]) in + let (pos, instrs_stop) = aux nb_idx variable_idx pos stop in + let (pos, init_accu) = (pos + 1, [ ResetAccu nb_idx' ]) in + let pos_body = pos in + let (pos, instrs_body) = aux nb_idx' variable_idx' pos body in + (pos + 5, + instrs_start + @ set_x + @ instrs_stop + @ init_accu + @ instrs_body + @ [ AddAccu nb_idx' ] + @ [ IncVar nb_idx'; + JumpLe (nb_idx', pos_body); + Pop; + PushAccu nb_idx' ]) + + | EVar x -> + (pos + 1, [ GetVar (List.assoc x variable_idx) ]) + in + let (_, code) = aux 0 [] 0 e in + Array.of_list (code @ [ Halt ]) + +let lot_of_variables = 128 +let stack_size = 1024 + +let vm_eval s = + let vm = vm_init stack_size lot_of_variables (compile (parse (lexer s))) in + vm_interpret vm + +(* Tests du compilateur. *) + +let test_compiler () = + Printf.printf "-*- Compiler -*-\n"; + + (* Tests positifs. *) + let check s = s --> eval s in + + List.iter (do_test valid (fun x -> x) vm_eval) [ + check "1"; + check "1 + 2"; + check "1 + 2 * 3"; + check "sum (x, 1, 10, x + x)"; + check "1 + sum (x, 10 * 10, 10 * 10 * 10, x * sum (i, x, 2 * x, i + x))" + ] + +(* Exercice de programmation + + Étendre la machine et la fonction de compilation pour traiter la division et + la soustraction. +*) + +let benchmark_interactive_loop () = + let time f = + let start = Unix.gettimeofday () in + let y = f () in + let stop = Unix.gettimeofday () in + (stop -. start, y) + in + let eval e = + let (interpreted_time, x) = time (fun () -> interpret e) in + let vm = vm_init stack_size lot_of_variables (compile e) in + let (compiled_time, y) = time (fun () -> vm_interpret vm) in + (interpreted_time, compiled_time, x, y) + in + let print (interpreted_time, compiled_time, x, y) = + Printf.printf "(Interpreted: %f, Compiled: %f) :- %d\n" + interpreted_time compiled_time x; + if x <> y then Printf.printf "Warning: VM found %d\n" y + in + loop (fun () -> read lexer parse) eval print + +let test_suite () = + test_lexer (); + test_parser (); + test_interpreter (); + test_compiler () + +let batch () = + Printf.printf "%d\n" (interpret (read lexer parse)) + +let batch_mode = ref false + +let _ = + Arg.parse (Arg.align [ + "-bench", Arg.Unit benchmark_interactive_loop, + " Launch a toplevel that uses several evaluation strategies."; + "--", Arg.Set batch_mode, " Only interpret stdin."; + "-test", Arg.Unit test_suite, + " Launch the test suite of the program." + ]) ignore ("marthe [options]"); + if !batch_mode then + batch () + else + interactive_loop () diff --git a/cours/cours-introduction.pdf b/cours/cours-introduction.pdf new file mode 100644 index 0000000..05ca0f7 Binary files /dev/null and b/cours/cours-introduction.pdf differ diff --git a/cours/journal.org b/cours/journal.org new file mode 100644 index 0000000..0c0217d --- /dev/null +++ b/cours/journal.org @@ -0,0 +1,33 @@ +#+TITLE: Compilation 2023/2024 -- Journal du cours +#+AUTHOR: Adrien Guatto +#+EMAIL: guatto@irif.org +#+LANGUAGE: fr +#+OPTIONS: ^:nil p:nil +#+LATEX_CLASS: article +#+LATEX_CLASS_OPTIONS: [a4paper,11pt] +#+LATEX_HEADER: \usepackage{a4wide} +#+LATEX_HEADER: \usepackage{microtype} +#+LATEX_HEADER: \hypersetup{hidelinks} +#+LATEX_HEADER: \usepackage[french]{babel} +# (org-latex-export-to-pdf) + + Ce fichier est disponible au + [[https://www.irif.fr/~guatto/teaching/23-24/compilation/journal.html][format + HTML]]. +* Cours 01 <2023-09-20> +** Présentation + Voir les transparents dans + [[file:cours-introduction.pdf][cours-introduction.pdf]]. +** Introduction à la compilation : le micro-langage Marthe + Le reste de la séance est consacré à la lecture et discussion d'un + micro-langage de programmation, Marthe. Voir le fichier + [[file:cours-01/marthe.ml][marthe.ml]]. +** À préparer pour le prochain TP et le prochain cours +*** TODO Prochaine séance de travaux pratiques + - Recommandé : venir avec son ordinateur portable, lundi + - S'assurer que votre environnement de travail dispose d'un environnement de + développement OCaml fonctionnel (compilateur OCaml, OPAM, dune). +*** TODO S'inscrire sur la liste de diffusion du cours + https://listes.u-paris.fr/wws/info/m1.2023.compilation.info +*** TODO Prochain séance de cours + - Finir la gestion des commentaires dans ~marthe.ml~. diff --git a/cours/syllabus.pdf b/cours/syllabus.pdf new file mode 100644 index 0000000..d3f6698 Binary files /dev/null and b/cours/syllabus.pdf differ