Merge branch 'jalon4'
This commit is contained in:
commit
38ab723583
1 changed files with 349 additions and 168 deletions
|
@ -8,9 +8,7 @@ module S = Source.AST
|
|||
module Target = Fopix
|
||||
module T = Target.AST
|
||||
|
||||
(**
|
||||
|
||||
The translation from Hobix to Fopix turns anonymous
|
||||
(** The translation from Hobix to Fopix turns anonymous
|
||||
lambda-abstractions into toplevel functions and applications into
|
||||
function calls. In other words, it translates a high-level language
|
||||
(like OCaml) into a first order language (like C).
|
||||
|
@ -63,97 +61,108 @@ module T = Target.AST
|
|||
|
||||
(Remark: Did you notice that this form of "auto-application" is
|
||||
very similar to the way "this" is defined in object-oriented
|
||||
programming languages?)
|
||||
programming languages?) *)
|
||||
|
||||
*)
|
||||
(** Helpers functions. *)
|
||||
|
||||
(**
|
||||
Helpers functions.
|
||||
*)
|
||||
|
||||
let error pos msg =
|
||||
Error.error "compilation" pos msg
|
||||
let error pos msg = Error.error "compilation" pos msg
|
||||
|
||||
let make_fresh_variable =
|
||||
let r = ref 0 in
|
||||
fun () -> incr r; T.Id (Printf.sprintf "_%d" !r)
|
||||
|
||||
fun () ->
|
||||
incr r;
|
||||
T.Id (Printf.sprintf "_%d" !r)
|
||||
;;
|
||||
|
||||
let make_fresh_function_identifier =
|
||||
let r = ref 0 in
|
||||
fun () -> incr r; T.FunId (Printf.sprintf "_%d" !r)
|
||||
fun () ->
|
||||
incr r;
|
||||
T.FunId (Printf.sprintf "_%d" !r)
|
||||
;;
|
||||
|
||||
let define e f =
|
||||
let x = make_fresh_variable () in
|
||||
T.Define (x, e, f x)
|
||||
;;
|
||||
|
||||
let rec defines ds e =
|
||||
match ds with
|
||||
| [] ->
|
||||
e
|
||||
| (x, d) :: ds ->
|
||||
T.Define (x, d, defines ds e)
|
||||
| [] -> e
|
||||
| (x, d) :: ds -> T.Define (x, d, defines ds e)
|
||||
;;
|
||||
|
||||
let seq a b =
|
||||
define a (fun _ -> b)
|
||||
let seq a b = define a (fun _ -> b)
|
||||
|
||||
let rec seqs = function
|
||||
| [] -> assert false
|
||||
| [x] -> x
|
||||
| [ x ] -> x
|
||||
| x :: xs -> seq x (seqs xs)
|
||||
;;
|
||||
|
||||
let allocate_block e =
|
||||
T.(FunCall (FunId "allocate_block", [e]))
|
||||
let allocate_block e = T.(FunCall (FunId "allocate_block", [ e ]))
|
||||
let write_block e i v = T.(FunCall (FunId "write_block", [ e; i; v ]))
|
||||
let read_block e i = T.(FunCall (FunId "read_block", [ e; i ]))
|
||||
let lint i = T.(Literal (LInt (Int64.of_int i)))
|
||||
|
||||
let write_block e i v =
|
||||
T.(FunCall (FunId "write_block", [e; i; v]))
|
||||
|
||||
let read_block e i =
|
||||
T.(FunCall (FunId "read_block", [e; i]))
|
||||
|
||||
let lint i =
|
||||
T.(Literal (LInt (Int64.of_int i)))
|
||||
(* Liste de fonction prédéfini par le code, que l'on retrouve dans les tests *)
|
||||
let fonction_predef =
|
||||
[ "print_string"; "equal_string"; "equal_char"; "observe_int"; "print_int" ]
|
||||
;;
|
||||
|
||||
(* On regarde si la fonction est prédéfini :
|
||||
- soit par des noms
|
||||
- soit par des opérations binaires *)
|
||||
let is_a_predefined_function (S.Id op) =
|
||||
FopixInterpreter.is_binary_primitive op || List.mem op fonction_predef
|
||||
;;
|
||||
|
||||
(** [free_variables e] returns the list of free variables that
|
||||
occur in [e].*)
|
||||
let free_variables =
|
||||
let module M =
|
||||
Set.Make (struct type t = S.identifier let compare = compare end)
|
||||
Set.Make (struct
|
||||
type t = S.identifier
|
||||
|
||||
let compare = compare
|
||||
end)
|
||||
in
|
||||
let rec unions f = function
|
||||
| [] -> M.empty
|
||||
| [s] -> f s
|
||||
| [ s ] -> f s
|
||||
| s :: xs -> M.union (f s) (unions f xs)
|
||||
in
|
||||
(* Une variable libre est une variable qui peut être substitué *)
|
||||
let rec fvs = function
|
||||
| S.Literal _ ->
|
||||
M.empty
|
||||
| S.Variable x ->
|
||||
M.singleton x
|
||||
| S.While (cond, e) ->
|
||||
failwith "Students! This is your job!"
|
||||
| S.Literal _ -> M.empty
|
||||
(* Si la fonction est prédéfini, alors ce n'est pas une variable libre. *)
|
||||
| S.Variable x -> if is_a_predefined_function x then M.empty else M.singleton x
|
||||
| S.While (cond, e) -> unions fvs [ cond; e ]
|
||||
| S.Define (vd, a) ->
|
||||
failwith "Students! This is your job!"
|
||||
| S.ReadBlock (a, b) ->
|
||||
unions fvs [a; b]
|
||||
| S.Apply (a, b) ->
|
||||
unions fvs (a :: b)
|
||||
| S.WriteBlock (a, b, c) | S.IfThenElse (a, b, c) ->
|
||||
unions fvs [a; b; c]
|
||||
| S.AllocateBlock a ->
|
||||
fvs a
|
||||
| S.Fun (xs, e) ->
|
||||
failwith "Students! This is your job!"
|
||||
let liste_def_valeur =
|
||||
match vd with
|
||||
| S.SimpleValue (id, expr) -> [ id, expr ]
|
||||
| S.RecFunctions list -> list
|
||||
in
|
||||
let id, expr = List.split liste_def_valeur in
|
||||
M.diff (unions fvs (a :: expr)) (M.of_list id)
|
||||
| S.ReadBlock (a, b) -> unions fvs [ a; b ]
|
||||
| S.Apply (a, b) -> unions fvs (a :: b)
|
||||
| S.WriteBlock (a, b, c) | S.IfThenElse (a, b, c) -> unions fvs [ a; b; c ]
|
||||
| S.AllocateBlock a -> fvs a
|
||||
| S.Fun (xs, e) -> M.diff (fvs e) (M.of_list xs)
|
||||
| S.Switch (a, b, c) ->
|
||||
let c = match c with None -> [] | Some c -> [c] in
|
||||
unions fvs (a :: ExtStd.Array.present_to_list b @ c)
|
||||
let c =
|
||||
match c with
|
||||
| None -> []
|
||||
| Some c -> [ c ]
|
||||
in
|
||||
unions fvs ((a :: ExtStd.Array.present_to_list b) @ c)
|
||||
in
|
||||
fun e -> M.elements (fvs e)
|
||||
;;
|
||||
|
||||
(**
|
||||
|
||||
A closure compilation environment relates an identifier to the way
|
||||
(** A closure compilation environment relates an identifier to the way
|
||||
it is accessed in the compiled version of the function's
|
||||
body.
|
||||
|
||||
|
@ -170,58 +179,101 @@ let free_variables =
|
|||
Indeed, "x" is a local variable that can be accessed directly in
|
||||
the compiled version of this function's body whereas "y" is a free
|
||||
variable whose value must be retrieved from the closure's
|
||||
environment.
|
||||
environment. *)
|
||||
type environment =
|
||||
{ vars : (HobixAST.identifier, FopixAST.expression) Dict.t
|
||||
; externals : (HobixAST.identifier, int) Dict.t
|
||||
}
|
||||
|
||||
*)
|
||||
type environment = {
|
||||
vars : (HobixAST.identifier, FopixAST.expression) Dict.t;
|
||||
externals : (HobixAST.identifier, int) Dict.t;
|
||||
}
|
||||
|
||||
let initial_environment () =
|
||||
{ vars = Dict.empty; externals = Dict.empty }
|
||||
|
||||
let bind_external id n env =
|
||||
{ env with externals = Dict.insert id n env.externals }
|
||||
|
||||
let is_external id env =
|
||||
Dict.lookup id env.externals <> None
|
||||
|
||||
let reset_vars env =
|
||||
{ env with vars = Dict.empty }
|
||||
let initial_environment () = { vars = Dict.empty; externals = Dict.empty }
|
||||
let bind_external id n env = { env with externals = Dict.insert id n env.externals }
|
||||
let is_external id env = Dict.lookup id env.externals <> None
|
||||
let reset_vars env = { env with vars = Dict.empty }
|
||||
let bind_var env id expr = { env with vars = Dict.insert id expr env.vars }
|
||||
|
||||
(** Precondition: [is_external id env = true]. *)
|
||||
let arity_of_external id env =
|
||||
match Dict.lookup id env.externals with
|
||||
| Some n -> n
|
||||
| None -> assert false (* By is_external. *)
|
||||
|
||||
;;
|
||||
|
||||
(** [translate p env] turns an Hobix program [p] into a Fopix program
|
||||
using [env] to retrieve contextual information. *)
|
||||
let translate (p : S.t) env =
|
||||
let rec program env defs =
|
||||
let env, defs = ExtStd.List.foldmap definition env defs in
|
||||
(List.flatten defs, env)
|
||||
List.flatten defs, env
|
||||
and definition env = function
|
||||
| S.DeclareExtern (id, n) ->
|
||||
let env = bind_external id n env in
|
||||
(env, [T.ExternalFunction (function_identifier id, n)])
|
||||
| S.DefineValue vd ->
|
||||
(env, value_definition env vd)
|
||||
env, [ T.ExternalFunction (function_identifier id, n) ]
|
||||
| S.DefineValue vd -> env, value_definition env vd
|
||||
and value_definition env = function
|
||||
| S.SimpleValue (x, e) ->
|
||||
let fs, e = expression (reset_vars env) e in
|
||||
fs @ [T.DefineValue (identifier x, e)]
|
||||
fs @ [ T.DefineValue (identifier x, e) ]
|
||||
| S.RecFunctions fdefs ->
|
||||
let fs, defs = define_recursive_functions fdefs in
|
||||
let fs, defs = define_recursive_functions env fdefs in
|
||||
fs @ List.map (fun (x, e) -> T.DefineValue (x, e)) defs
|
||||
|
||||
and define_recursive_functions rdefs =
|
||||
failwith "Students! This is your job!"
|
||||
(* Ce qu'il faut faire dans le cadre des fonctions mutuellement récursives :
|
||||
- On récupère la liste des fonctions
|
||||
- On créé des clotures pour chaque récursion
|
||||
- On traduit chaque fonction mutuellement récursive en appel
|
||||
de fonction anonyme
|
||||
- On finit par combiner chaque liste de définition,
|
||||
celle des la fonction de base, puis celle des des fonctions traduites. *)
|
||||
and define_recursive_functions env rdefs =
|
||||
(* On récupère d'abord les informations qui nous intéresse :
|
||||
nom, variables libres, arguments, et le corps de la fonction récursive *)
|
||||
let rdefs =
|
||||
let rname = List.map fst rdefs in
|
||||
let free_vars f =
|
||||
match f with
|
||||
| S.Fun (id, expr) ->
|
||||
let new_id = id @ rname in
|
||||
let lfree_vars = free_variables (S.Fun (new_id, expr)) in
|
||||
lfree_vars, id, expr
|
||||
| _ -> failwith "Error recFunctions : This is not a function"
|
||||
in
|
||||
List.map (fun (name, expr) -> name, free_vars expr) rdefs
|
||||
in
|
||||
(* On regarde si la fonction recursive est seule, si c'est le cas, on traite
|
||||
le cas simple d'une fonction anonyme seul. *)
|
||||
match rdefs with
|
||||
| [ (name, (free_vars, arguments, expr)) ] ->
|
||||
let defs, expre = fonction_anonyme env ~name free_vars arguments expr in
|
||||
defs, [ identifier name, expre ]
|
||||
| _ ->
|
||||
(* Sinon, on créé des clotures pour chaque fonction de
|
||||
nos fonctions récursives *)
|
||||
let rdefs = creation_cloture_rec rdefs in
|
||||
let fs, rdefs = List.split rdefs in
|
||||
(* Puis on les traduit toute les fonctions *)
|
||||
let trad_rdef = trad_rec_definition rdefs in
|
||||
let fs', exprs = List.split trad_rdef in
|
||||
fs @ List.concat fs', exprs
|
||||
(* Fonction qui créé des clotures pour chaque récursion *)
|
||||
and creation_cloture_rec rdefs =
|
||||
let nb = List.length rdefs - 1 in
|
||||
List.map
|
||||
(fun (name, (free_vars, x, e)) ->
|
||||
let new_name = make_fresh_variable () in
|
||||
( T.DefineValue (new_name, new_cloture nb free_vars)
|
||||
, ((name, new_name), (free_vars, x, e)) ))
|
||||
rdefs
|
||||
(* Fonction qui traduit chaque fonction en appel de fonction anonyme
|
||||
mais également le nom en identifiant de fonction pour fopix. *)
|
||||
and trad_rec_definition rdefs =
|
||||
let rname = List.map fst rdefs in
|
||||
List.map
|
||||
(fun ((name, new_name), (fv, x, e)) ->
|
||||
let names = List.filter (fun (xi, _) -> xi != name) rname in
|
||||
let defs, expr = fonction_anonyme ~name ~block:(new_name, names) env fv x e in
|
||||
defs, (identifier name, expr))
|
||||
rdefs
|
||||
and expression env = function
|
||||
| S.Literal l ->
|
||||
[], T.Literal (literal l)
|
||||
| S.Literal l -> [], T.Literal (literal l)
|
||||
| S.While (cond, e) ->
|
||||
let cfs, cond = expression env cond in
|
||||
let efs, e = expression env e in
|
||||
|
@ -232,66 +284,195 @@ let translate (p : S.t) env =
|
|||
| None -> T.Variable (identifier x)
|
||||
| Some e -> e
|
||||
in
|
||||
([], xc)
|
||||
[], xc
|
||||
| S.Define (vdef, a) ->
|
||||
failwith "Students! This is your job!"
|
||||
| S.Apply (a, bs) ->
|
||||
failwith "Students! This is your job!"
|
||||
let afs, a = expression env a in
|
||||
(match vdef with
|
||||
| S.SimpleValue (id, b) ->
|
||||
let bfs, b = expression env b in
|
||||
afs @ bfs, T.Define (identifier id, a, b)
|
||||
| S.RecFunctions rdefs ->
|
||||
let fs, defs = define_recursive_functions env rdefs in
|
||||
afs @ fs, defines defs a)
|
||||
| S.Apply (a, bs) -> apply env a bs
|
||||
| S.IfThenElse (a, b, c) ->
|
||||
let afs, a = expression env a in
|
||||
let bfs, b = expression env b in
|
||||
let cfs, c = expression env c in
|
||||
afs @ bfs @ cfs, T.IfThenElse (a, b, c)
|
||||
|
||||
| S.Fun (x, e) ->
|
||||
failwith "Students! This is your job!"
|
||||
| S.Fun (x, e) as f -> fonction_anonyme env (free_variables f) x e
|
||||
| S.AllocateBlock a ->
|
||||
let afs, a = expression env a in
|
||||
(afs, allocate_block a)
|
||||
afs, allocate_block a
|
||||
| S.WriteBlock (a, b, c) ->
|
||||
let afs, a = expression env a in
|
||||
let bfs, b = expression env b in
|
||||
let cfs, c = expression env c in
|
||||
afs @ bfs @ cfs,
|
||||
T.FunCall (T.FunId "write_block", [a; b; c])
|
||||
afs @ bfs @ cfs, T.FunCall (T.FunId "write_block", [ a; b; c ])
|
||||
| S.ReadBlock (a, b) ->
|
||||
let afs, a = expression env a in
|
||||
let bfs, b = expression env b in
|
||||
afs @ bfs,
|
||||
T.FunCall (T.FunId "read_block", [a; b])
|
||||
afs @ bfs, T.FunCall (T.FunId "read_block", [ a; b ])
|
||||
| S.Switch (a, bs, default) ->
|
||||
let afs, a = expression env a in
|
||||
let bsfs, bs =
|
||||
ExtStd.List.foldmap (fun bs t ->
|
||||
ExtStd.List.foldmap
|
||||
(fun bs t ->
|
||||
match ExtStd.Option.map (expression env) t with
|
||||
| None -> (bs, None)
|
||||
| Some (bs', t') -> (bs @ bs', Some t')
|
||||
) [] (Array.to_list bs)
|
||||
| None -> bs, None
|
||||
| Some (bs', t') -> bs @ bs', Some t')
|
||||
[]
|
||||
(Array.to_list bs)
|
||||
in
|
||||
let dfs, default = match default with
|
||||
let dfs, default =
|
||||
match default with
|
||||
| None -> [], None
|
||||
| Some e -> let bs, e = expression env e in bs, Some e
|
||||
| Some e ->
|
||||
let bs, e = expression env e in
|
||||
bs, Some e
|
||||
in
|
||||
afs @ bsfs @ dfs,
|
||||
T.Switch (a, Array.of_list bs, default)
|
||||
|
||||
|
||||
afs @ bsfs @ dfs, T.Switch (a, Array.of_list bs, default)
|
||||
and expressions env = function
|
||||
| [] ->
|
||||
[], []
|
||||
| [] -> [], []
|
||||
| e :: es ->
|
||||
let efs, es = expressions env es in
|
||||
let fs, e = expression env e in
|
||||
fs @ efs, e :: es
|
||||
|
||||
and literal = function
|
||||
| S.LInt x -> T.LInt x
|
||||
| S.LString s -> T.LString s
|
||||
| S.LChar c -> T.LChar c
|
||||
|
||||
and identifier (S.Id x) = T.Id x
|
||||
|
||||
and function_identifier (S.Id x) = T.FunId x
|
||||
|
||||
and old_env = make_fresh_variable ()
|
||||
and curr_env = make_fresh_variable ()
|
||||
(* Ici, on rajoute notre fonction anonyme dans la liste des
|
||||
définitions de fonctions *)
|
||||
and add_liste_funcdef env fid x expr =
|
||||
let dfs, expr = expression env expr in
|
||||
let dfs, expr =
|
||||
let aux x (xs, acc) =
|
||||
match x with
|
||||
| T.DefineValue (id, exp) -> xs, T.Define (id, exp, acc)
|
||||
| x -> x :: xs, acc
|
||||
in
|
||||
List.fold_right aux dfs ([], expr)
|
||||
in
|
||||
dfs @ [ T.DefineFunction (fid, old_env :: x, expr) ]
|
||||
(* Traitement des fonctions anonymes *)
|
||||
and fonction_anonyme ?name ?block env f x e =
|
||||
(* On commence par générer de nouveaux identifiants pour nos fonctions *)
|
||||
let fname = make_fresh_function_identifier () in
|
||||
(* On traduit l'id de chaque argument *)
|
||||
let arguments_x = List.map identifier x in
|
||||
(* On créé la cloture pour notre fonction anonyme *)
|
||||
let cloture, env = creation_cloture env name block fname f in
|
||||
(* On met à jour la liste des définitions de fonctions *)
|
||||
let dfs = add_liste_funcdef env fname arguments_x e in
|
||||
dfs, cloture
|
||||
and creation_cloture env name block fname free_vars =
|
||||
let env =
|
||||
match name with
|
||||
| None -> env
|
||||
| Some name -> bind_var env name (T.Variable old_env)
|
||||
in
|
||||
match block with
|
||||
| None ->
|
||||
(* Cas où on a une simple fonction anonyme *)
|
||||
let new_clot = new_cloture 0 free_vars in
|
||||
let blocks, env = add_to_cloture env fname (T.Variable curr_env) free_vars [] in
|
||||
T.Define (curr_env, new_clot, blocks), env
|
||||
| Some (block, rdefs) ->
|
||||
(* Cas pour les fonctions mutuellements récursive *)
|
||||
add_to_cloture env fname (T.Variable block) free_vars rdefs
|
||||
(* Fonction qui initialise une cloture de taille espace
|
||||
+ la taille de la liste de variable (le nombre de variable libre) *)
|
||||
and new_cloture espace list_variable =
|
||||
allocate_block (lint (espace + List.length list_variable + 1))
|
||||
(* Fonction qui rajoute à la cloture l'ensemble de variable libres *)
|
||||
and add_to_cloture env fname env_var free_vars rdefs =
|
||||
(* On commence d'abord par écrire dans le premier bloc le nom de
|
||||
la fonction fopix *)
|
||||
let first_block = write_block env_var (lint 0) (T.Literal (T.LFun fname)) in
|
||||
(* Ensuite, on s'occupe d'écrire dans chaque bloc de notre cloture
|
||||
les variables libres *)
|
||||
let env, vars_free, k =
|
||||
List.fold_left
|
||||
(fun (env, list, k) id ->
|
||||
(* Pour chaque élément de la liste ...
|
||||
On commence par lié la variable dans le nouvelle environnement
|
||||
qui se trouve dans l'ancien environnement *)
|
||||
let curr_env = bind_var env id (read_block (T.Variable old_env) (lint k)) in
|
||||
(* On cherche la valeur de la variable dans
|
||||
le dictionnaire du programme *)
|
||||
let new_valeur =
|
||||
match Dict.lookup id env.vars with
|
||||
| None -> T.Variable (identifier id)
|
||||
| Some v -> v
|
||||
in
|
||||
(* Enfin, on écrit dans notre cloture la valeur de la variable libre
|
||||
à la position k *)
|
||||
let new_instr = write_block env_var (lint k) new_valeur in
|
||||
(* On rappelle notre fonction avec le nouvelle environnement,
|
||||
en rajoutant notre instruction à la liste d'instruction,
|
||||
et en incrémentant l'index *)
|
||||
curr_env, new_instr :: list, k + 1)
|
||||
(* On commence notre fonction avec l'env de base,
|
||||
la liste d'instruction vide et l'indice à 1
|
||||
(0 étant l'initialisation du début) *)
|
||||
(env, [], 1)
|
||||
free_vars
|
||||
in
|
||||
(* Dans le cas d'une récursion, on rajoute chaque fonction dans l'env et
|
||||
à la liste d'instruction*)
|
||||
let env, vars_fun, _ =
|
||||
List.fold_left
|
||||
(fun (env, list, k) (id, id_var) ->
|
||||
let curr_env = bind_var env id (read_block (T.Variable old_env) (lint k)) in
|
||||
let new_instr = write_block env_var (lint k) (T.Variable id_var) in
|
||||
curr_env, new_instr :: list, k + 1)
|
||||
(* On commence avec k car on a mis k variables libres juste avant *)
|
||||
(env, [], k)
|
||||
rdefs
|
||||
in
|
||||
(* On créé une séquence d'instructions contenant
|
||||
le premier bloc et la suite *)
|
||||
let instrs = List.rev ((env_var :: first_block :: vars_free) @ vars_fun) in
|
||||
seqs instrs, env
|
||||
(* Revoir les explications c'est pas clair *)
|
||||
and apply env f arguments =
|
||||
(* D'abord, on traduit chaque arguments *)
|
||||
let defs_args, trad_arguments = expressions env arguments in
|
||||
(* On créé un FunCall en fonction de f *)
|
||||
match f with
|
||||
| S.Variable x when Dict.lookup x env.externals <> None || is_a_predefined_function x
|
||||
->
|
||||
(* Si f est une externe fonction, on créé directement un FunCall *)
|
||||
[], T.FunCall (function_identifier x, trad_arguments)
|
||||
| _ ->
|
||||
(* Sinon, ça veut dire que nous l'avons défini au préalable *)
|
||||
(* On traduit la fonction *)
|
||||
let defs_func, func = expression env f in
|
||||
(* On récupère le nom de fonction et son expression *)
|
||||
let fname, func_expr =
|
||||
match func with
|
||||
| T.Variable x -> x, fun x -> x
|
||||
| _ ->
|
||||
let fname = make_fresh_variable () in
|
||||
fname, fun x -> T.Define (fname, func, x)
|
||||
in
|
||||
(* On récupère le pointeur qui pointe vers la première case
|
||||
de la fonction *)
|
||||
let get_pointer = read_block (T.Variable fname) (lint 0) in
|
||||
(* On récupère l'appelle de la fonction *)
|
||||
let defs_call =
|
||||
func_expr (T.UnknownFunCall (get_pointer, T.Variable fname :: trad_arguments))
|
||||
in
|
||||
(* Enfin, on concatène toute les parties de la fonction
|
||||
(la traduction de tout les arguments) *)
|
||||
let defs = defs_func @ defs_args in
|
||||
(* Et on renvoie la def de la fonction ainsi que l'appel *)
|
||||
defs, defs_call
|
||||
in
|
||||
program env p
|
||||
;;
|
||||
|
|
Reference in a new issue