open Format
open Ast
open Codevm
(* Déclaration de l'exception à lever quand une variable (locale ou globale)
est mal utilisée *)
exception VarUndef of string
(* Fonction de pretty printing utilisée pour écrire dans le fichier de sortie
le code machine généré à la compilation. *)
let emit_code fmt = function
PUSHI n -> fprintf fmt "\tpushi %d\n" n
| STOREL n -> fprintf fmt "\tstorel %d\n" n
| PUSHL n -> fprintf fmt "\tpushl %d\n" n
| STOREG n -> fprintf fmt "\tstoreg %d\n" n
| PUSHG n -> fprintf fmt "\tpushg %d\n" n
| PUSHN n -> fprintf fmt "\tpushn %d\n" n
| START -> fprintf fmt "\tstart\n"
| ADD -> fprintf fmt "\tadd\n"
| SUB -> fprintf fmt "\tsub\n"
| MUL -> fprintf fmt "\tmul\n"
| DIV -> fprintf fmt "\tdiv\n"
| WRITEI -> fprintf fmt "\twritei\n"
| STOP -> fprintf fmt "\tstop\n"
(* gp_max (resp. fp_max) contient le nombre maximal de variables
globales (resp. locales) utilisées par le programme. Ces deux
références sont mises à jour pendant la compilation et utilisées
pour générer le code d'initialisation de la machine virtuelle
(cf. fonction preambule). *)
let gp = ref 0
let fp_max = ref 0
(* Cette fonction génère le code machine pour initialiser les
registres de la machine virtuelle avant chaque programme
(réservation de l'espace nécessaire pour les variables globales et
locales). *)
let preambule gp fp = [(PUSHN gp) ; START ; (PUSHN fp)]
(* La table des symboles du compilateur est implémentée à l'aide
d'une table de hachage. Chaque entrée lie une variable globale à son
indice dans la pile par rapport au registre gp. *)
let ( genv : (string,int) Hashtbl.t ) = Hashtbl.create 17
(* On utilise une table d'association dont les clés (les variables locales)
sont des chaînes de caractères *)
module StrMap = Map.Make(String)
(* Compilation d'une expression *)
let compile_expr =
(* Fonction récursive locale à compile_expr utilisée pour générer le code
machine de l'arbre de syntaxe abstraite associé à une valeur de type
Ast.expr *)
let rec comprec env fp = function
Cst i -> [PUSHI i]
| Var x ->
begin
try [PUSHL(StrMap.find x env)]
with Not_found ->
try [PUSHG(Hashtbl.find genv x)]
with Not_found -> raise(VarUndef x)
end
| Op(o,e1,e2)->
let op =
match o with
Sum -> [ADD]
| Diff -> [SUB]
| Prod -> [MUL]
| Quot -> [DIV]
in
let code_e1 = comprec env fp e1 in
let code_e2 = comprec env fp e2 in
code_e1@code_e2@op
| Letin(x,e1,e2) ->
begin
let code_e1 = comprec env fp e1 in
if !fp_max=fp then incr fp_max;
let code_e2 = comprec (StrMap.add x fp env) (fp+1) e2 in
code_e1@(STOREL(fp)::code_e2)
end
in
comprec StrMap.empty 0
(* Compilation d'une instruction *)
let compile_instr = function
Set(x,e) ->
let code = (compile_expr e)@[STOREG(!gp)] in
begin
Hashtbl.add genv x !gp;
incr gp;
code
end
| Print(e) -> (compile_expr e)@[WRITEI]
(* Enregistre dans le fichier ofile le code machine correspondant à la
compilation du programme p *)
let compile_prg p ofile =
let code = List.map compile_instr p in
(* On ajoute le préambule d'initialisation au code du programme p *)
let code_final = (preambule !gp !fp_max)::code in
let f = open_out ofile in
(* On utilise les fonctions de la librairie Format pour écrire le code dans
le fichier de sortie *)
let fmt = formatter_of_out_channel f in
List.iter (List.iter (emit_code fmt)) code_final;
(* Il ne faut pas oublier de générer le code pour arrêter
correctement la machine virtuelle à la fin d'un programme. *)
emit_code fmt STOP;
(* On "flush" le buffer afin de s'assurer que tout y a été écrit
avant de le fermer *)
fprintf fmt "@?";
close_out f