(***********************************************************************) (* *) (* Objective Caml *) (* *) (* Xavier Leroy, projet Cristal, INRIA Rocquencourt *) (* *) (* Copyright 1996 Institut National de Recherche en Informatique et *) (* en Automatique. All rights reserved. This file is distributed *) (* under the terms of the Q Public License version 1.0. *) (* *) (***********************************************************************) (* $Id$ *) (* Compilation of pattern matching *) open Misc open Asttypes open Primitive open Types open Typedtree open Lambda open Parmatch (* See Peyton-Jones, ``The Implementation of functional programming languages'', chapter 5. *) (* Bon, au commencement du monde c'etait vrai. *) type matrix = pattern list list type ctx = {left:pattern list ; right:pattern list} let pretty_ctx ctx = List.iter (fun {left=left ; right=right} -> prerr_string "LEFT:" ; pretty_line left ; prerr_string " RIGHT:" ; pretty_line right ; prerr_endline "") ctx let le_ctx c1 c2 = le_pats c1.left c2.left && le_pats c1.right c2.right let lshift {left=left ; right=right} = match right with | x::xs -> {left=x::left ; right=xs} | _ -> assert false let lforget {left=left ; right=right} = match right with | x::xs -> {left=omega::left ; right=xs} | _ -> assert false let rec small_enough n = function | [] -> true | _::rem -> if n <= 0 then false else small_enough (n-1) rem let ctx_lshift ctx = if small_enough 31 ctx then List.map lshift ctx else (* Context pruning *) begin get_mins le_ctx (List.map lforget ctx) end let rshift {left=left ; right=right} = match left with | p::ps -> {left=ps ; right=p::right} | _ -> assert false let ctx_rshift ctx = List.map rshift ctx let rec nchars n ps = if n <= 0 then [],ps else match ps with | p::rem -> let chars, cdrs = nchars (n-1) rem in p::chars,cdrs | _ -> assert false let rshift_num n {left=left ; right=right} = let shifted,left = nchars n left in {left=left ; right = shifted@right} let ctx_rshift_num n ctx = List.map (rshift_num n) ctx let combine {left=left ; right=right} = match left with | p::ps -> {left=ps ; right=set_args p right} | _ -> assert false let ctx_combine ctx = List.map combine ctx let ncols = function | [] -> 0 | ps::_ -> List.length ps exception NoMatch exception OrPat exception Unused let filter_matrix matcher pss = let rec filter_rec = function | (p::ps)::rem -> begin match p.pat_desc with | Tpat_alias (p,_) -> filter_rec ((p::ps)::rem) | Tpat_var _ -> filter_rec ((omega::ps)::rem) | _ -> begin let rem = filter_rec rem in try matcher p ps::rem with | NoMatch -> rem | OrPat -> match p.pat_desc with | Tpat_or (p1,p2,_) -> filter_rec [(p1::ps) ;(p2::ps)]@rem | _ -> assert false end end | [] -> [] | _ -> pretty_matrix pss ; fatal_error "Matching.filter_matrix" in filter_rec pss let ctx_matcher p = let p = normalize_pat p in match p.pat_desc with | Tpat_construct (cstr,omegas) -> (fun q rem -> match q.pat_desc with | Tpat_construct (cstr',args) when cstr.cstr_tag=cstr'.cstr_tag -> p,args @ rem | Tpat_any -> p,omegas @ rem | _ -> raise NoMatch) | Tpat_constant cst -> (fun q rem -> match q.pat_desc with | Tpat_constant cst' when cst=cst' -> p,rem | Tpat_any -> p,rem | _ -> raise NoMatch) | Tpat_variant (lab,Some omega,_) -> (fun q rem -> match q.pat_desc with | Tpat_variant (lab',Some arg,_) when lab=lab' -> p,arg::rem | Tpat_any -> p,omega::rem | _ -> raise NoMatch) | Tpat_variant (lab,None,_) -> (fun q rem -> match q.pat_desc with | Tpat_variant (lab',None,_) when lab=lab' -> p,rem | Tpat_any -> p,rem | _ -> raise NoMatch) | Tpat_array omegas -> let len = List.length omegas in (fun q rem -> match q.pat_desc with | Tpat_array args when List.length args=len -> p,args @ rem | Tpat_any -> p, omegas @ rem | _ -> raise NoMatch) | Tpat_tuple omegas -> (fun q rem -> match q.pat_desc with | Tpat_tuple args -> p,args @ rem | _ -> p, omegas @ rem) | Tpat_record l -> (* Records are normalized *) (fun q rem -> match q.pat_desc with | Tpat_record l' -> let l' = all_record_args l' in p, List.fold_right (fun (_,p) r -> p::r) l' rem | _ -> p,List.fold_right (fun (_,p) r -> p::r) l rem) | _ -> fatal_error "Matching.ctx_matcher" let filter_ctx q ctx = let matcher = ctx_matcher q in let rec filter_rec = function | ({right=p::ps} as l)::rem -> begin match p.pat_desc with | Tpat_or (p1,p2,_) -> filter_rec ({l with right=p1::ps}::{l with right=p2::ps}::rem) | Tpat_alias (p,_) -> filter_rec ({l with right=p::ps}::rem) | Tpat_var _ -> filter_rec ({l with right=omega::ps}::rem) | _ -> begin let rem = filter_rec rem in try let to_left, right = matcher p ps in {left=to_left::l.left ; right=right}::rem with | NoMatch -> rem end end | [] -> [] | _ -> fatal_error "Matching.filter_ctx" in filter_rec ctx let select_columns pss ctx = let n = ncols pss in List.fold_right (fun ps r -> List.fold_right (fun {left=left ; right=right} r -> let transfert, right = nchars n right in try {left = lubs transfert ps @ left ; right=right}::r with | Empty -> r) ctx r) pss [] let ctx_lub p ctx = List.fold_right (fun {left=left ; right=right} r -> match right with | q::rem -> begin try {left=left ; right = lub p q::rem}::r with | Empty -> r end | _ -> fatal_error "Matching.ctx_lub") ctx [] let ctx_match ctx pss = List.exists (fun {right=qs} -> List.exists (fun ps -> compats qs ps) pss) ctx type jumps = (int * ctx ) list let pretty_jumps env = match env with | [] -> () | _ -> List.iter (fun (i,ctx) -> Printf.fprintf stderr "jump for %d\n" i ; pretty_ctx ctx) env let rec jumps_extract i = function | [] -> [],[] | (j,pss) as x::rem as all -> if i=j then pss,rem else if j < i then [],all else let r,rem = jumps_extract i rem in r,(x::rem) let rec jumps_remove i = function | [] -> [] | (j,_)::rem when i=j -> rem | x::rem -> x::jumps_remove i rem let jumps_empty = [] and jumps_is_empty = function | [] -> true | _ -> false let jumps_singleton i = function | [] -> [] | ctx -> [i,ctx] let jumps_add i pss jumps = match pss with | [] -> jumps | _ -> let rec add = function | [] -> [i,pss] | (j,qss) as x::rem as all -> if j > i then x::add rem else if j < i then (i,pss)::all else (i,(get_mins le_ctx (pss@qss)))::rem in add jumps let rec jumps_union env1 env2 = match env1,env2 with | [],_ -> env2 | _,[] -> env1 | ((i1,pss1) as x1::rem1), ((i2,pss2) as x2::rem2) -> if i1=i2 then (i1,get_mins le_ctx (pss1@pss2))::jumps_union rem1 rem2 else if i1 > i2 then x1::jumps_union rem1 env2 else x2::jumps_union env1 rem2 let rec merge = function | env1::env2::rem -> jumps_union env1 env2::merge rem | envs -> envs let rec jumps_unions envs = match envs with | [] -> [] | [env] -> env | _ -> jumps_unions (merge envs) let rec jumps_map f env = List.map (fun (i,pss) -> i,f pss) env type pattern_matching = { mutable cases : (pattern list * lambda) list; args : (lambda * let_kind) list ; default : int * (matrix * int) list} type pattern_matching_ext = {to_match : pattern_matching ; to_catch : (matrix * int * Ident.t list * pattern_matching) list} let pretty_cases cases = List.iter (fun ((ps),l) -> List.iter (fun p -> Parmatch.top_pretty Format.str_formatter p ; prerr_string " " ; prerr_string (Format.flush_str_formatter ())) ps ; prerr_string " -> " ; Printlambda.lambda Format.str_formatter l ; prerr_string (Format.flush_str_formatter ()) ; prerr_endline "") cases let pretty_pm pm = pretty_cases pm.cases let pretty_def def = List.iter (fun (pss,i) -> Printf.fprintf stderr "Matrix for %d\n" i ; pretty_matrix pss) def let pretty_ext m = prerr_endline "++++++++ To Match ++++++++" ; pretty_pm m.to_match ; begin match m.to_catch with | [] -> prerr_endline "++++++++ No Catch ++++++++" | to_catch -> prerr_endline "++++++++ To Catch ++++++++" ; List.iter (fun (p,i,_,pm) -> Printf.fprintf stderr "Handler %d: " i ; prerr_endline "" ; pretty_pm pm) to_catch end ; prerr_endline "+++++ Defaults +++++" ; pretty_def (snd m.to_match.default) ; prerr_endline "+++++++++++++++++++++" (* To group lines of patterns with identical keys *) let add_line patl_action pm = pm.cases <- patl_action :: pm.cases; pm type cell = {pm : pattern_matching ; ctx : ctx list ; pat : pattern} let add make_matching_fun division key patl_action args = try let cell = List.assoc key division in cell.pm.cases <- patl_action :: cell.pm.cases; division with Not_found -> let cell = make_matching_fun args in cell.pm.cases <- [patl_action] ; (key, cell) :: division (* To find reasonable names for let-bound and lambda-bound idents *) let rec name_pattern default = function (pat :: patl, action) :: rem -> begin match pat.pat_desc with Tpat_var id -> id | Tpat_alias(p, id) -> id | _ -> name_pattern default rem end | _ -> Ident.create default exception Not_simple let rec raw_rec env = function | Llet(Alias,x,ex, body) -> raw_rec ((x,raw_rec env ex)::env) body | Lvar id as l -> begin try List.assoc id env with | Not_found -> l end | Lprim (Pfield i,args) -> Lprim (Pfield i, List.map (raw_rec env) args) | Lconst _ as l -> l | Lstaticraise (i,args) -> Lstaticraise (i, List.map (raw_rec env) args) | _ -> raise Not_simple let raw_action l = try raw_rec [] l with Not_simple -> l let same_actions = function | [] -> None | [_,act] -> Some act | (_,act0) :: rem -> try let raw_act0 = raw_rec [] act0 in let rec s_rec = function | [] -> Some act0 | (_,act)::rem -> if raw_act0 = raw_rec [] act then s_rec rem else None in s_rec rem with | Not_simple -> None let equal_action act1 act2 = try let raw1 = raw_rec [] act1 and raw2 = raw_rec [] act2 in raw1 = raw2 with | Not_simple -> false let up_ok_action act1 act2 = try let raw1 = raw_rec [] act1 and raw2 = raw_rec [] act2 in match raw1, raw2 with | Lstaticraise (i1,_), Lstaticraise (i2,_) -> i1=i2 | _,_ -> raw1 = raw2 with | Not_simple -> false let up_ok (ps,act_p) l = List.for_all (fun (qs,act_q) -> up_ok_action act_p act_q || not (Parmatch.compats ps qs)) l exception Same let rec what_is_or = function | {pat_desc = Tpat_or (p,_,_)} -> what_is_or p | {pat_desc = (Tpat_alias (p,_))} -> what_is_or p | {pat_desc=(Tpat_var _|Tpat_any)} -> fatal_error "Matching.what_is_or" | p -> p (* Simplify fonction normalize the first column of the match - records are expanded so that they posses all fields - or-patterns equivalent to variables are replaced by those variables *) exception Var of pattern let simplify_or p = let rec simpl_rec p = match p with | {pat_desc = Tpat_any|Tpat_var _} -> raise (Var p) | {pat_desc = Tpat_alias (q,id)} -> begin try {p with pat_desc = Tpat_alias (simpl_rec q,id)} with | Var q -> raise (Var {p with pat_desc = Tpat_alias (q,id)}) end | {pat_desc = Tpat_or (p1,p2,_)} -> {p with pat_desc = Tpat_or (simpl_rec p1, simpl_rec p2, None)} | {pat_desc = Tpat_record lbls} -> let all_lbls = all_record_args lbls in {p with pat_desc=Tpat_record all_lbls} | _ -> p in try simpl_rec p with | Var p -> p let simplify_matching m = match m.args with | [] -> omega,m | (arg, _) :: _ -> let ex_pat = ref None in let record_ex_pat p = match !ex_pat with | None -> ex_pat := Some p | _ -> () in let rec simplify = function | (pat :: patl, action as patl_action) :: rem -> begin match pat.pat_desc with | Tpat_var id -> (omega :: patl, bind Alias id arg action) :: simplify rem | Tpat_any -> patl_action :: simplify rem | Tpat_alias(p, id) -> simplify ((p :: patl, bind Alias id arg action) :: rem) | Tpat_record [] -> (omega :: patl, action):: simplify rem | Tpat_record lbls -> let all_lbls = all_record_args lbls in let full_pat = {pat with pat_desc=Tpat_record all_lbls} in record_ex_pat full_pat ; (full_pat::patl,action):: simplify rem | Tpat_or _ -> let pat_simple = simplify_or pat in begin match pat_simple.pat_desc with | Tpat_or _ -> let ex_pat = what_is_or pat_simple in record_ex_pat ex_pat ; (pat_simple :: patl, action) :: simplify rem | _ -> simplify ((pat_simple::patl,action) :: rem) end | _ -> record_ex_pat pat ; patl_action :: simplify rem end | [] -> [] | _ -> assert false in let cases = simplify m.cases in match !ex_pat with | None -> omega, {m with cases=cases} | Some p -> p,{m with cases = cases} let rec what_is_cases cases = match cases with | ({pat_desc=Tpat_any} :: _, _) :: rem -> what_is_cases rem | (p::_,_)::_ -> p | _ -> omega (* Optimize breaks *) let as_matrix cases = get_mins le_pats (List.map (fun (ps,_) -> ps) cases) let cons_default matrix raise_num (_,default) = match matrix with | [] -> raise_num,default | _ -> raise_num,((matrix,raise_num)::default) let default_compat p (exit,def) = exit, List.fold_right (fun (pss,i) r -> let qss = List.fold_right (fun qs r -> match qs with | q::rem when Parmatch.compat p q -> rem::r | _ -> r) pss [] in match qss with | [] -> r | _ -> (qss,i)::r) def [] let rec extract_vars r p = match p.pat_desc with | Tpat_var id -> IdentSet.add id r | Tpat_alias (p, id) -> extract_vars (IdentSet.add id r) p | Tpat_tuple pats -> List.fold_left extract_vars r pats | Tpat_record lpats -> List.fold_left (fun r (_,p) -> extract_vars r p) r lpats | Tpat_construct (_,pats) -> List.fold_left extract_vars r pats | Tpat_array pats -> List.fold_left extract_vars r pats | Tpat_variant (_,Some p, _) -> extract_vars r p | Tpat_or (p,_,_) -> extract_vars r p | Tpat_constant _|Tpat_any|Tpat_variant (_,None,_) -> r exception Cannot_flatten let mk_alpha_env arg aliases ids = List.map (fun id -> id, if List.mem id aliases then match arg with | Some v -> v | _ -> raise Cannot_flatten else Ident.create (Ident.name id)) ids let rec explode_or_pat arg patl mk_action rem vars aliases = function | {pat_desc = Tpat_or (p1,p2,_)} -> explode_or_pat arg patl mk_action (explode_or_pat arg patl mk_action rem vars aliases p2) vars aliases p1 | {pat_desc = Tpat_alias (p,id)} -> explode_or_pat arg patl mk_action rem vars (id::aliases) p | p -> let env = mk_alpha_env arg aliases vars in (alpha_pat env p::patl,mk_action (List.map snd env))::rem let equiv_pat p q = le_pat p q && le_pat q p let rec get_equiv p l = match l with | (q::_,_) as cl::rem -> if equiv_pat p q then let others,rem = get_equiv p rem in cl::others,rem else [],l | _ -> [],l let pm_free_variables {cases=cases} = List.fold_right (fun (_,act) r -> IdentSet.union (free_variables act) r) cases IdentSet.empty let compile_or argo cl clor al def = match clor with | [] -> {to_match = {cases=cl ; args=al ; default=def} ; to_catch = []} | _ -> let rec do_cases = function | ({pat_desc=Tpat_or _} as orp::patl, action)::rem -> let others,rem = get_equiv orp rem in let orpm = {cases = (patl, action):: List.map (function | (_::ps,action) -> ps,action | _ -> assert false) others ; args = List.tl al ; default = default_compat orp def} in begin match patl,action with | [],Lstaticraise (_,[]) -> let new_ord,new_to_catch = do_cases rem in let mk_new_action _ = action in explode_or_pat argo [] mk_new_action new_ord [] [] orp, new_to_catch | _,_ -> let vars = IdentSet.elements (IdentSet.inter (extract_vars IdentSet.empty orp) (pm_free_variables orpm)) in let or_num = next_raise_count () in let new_patl = Parmatch.omega_list patl in let mk_new_action vs = Lstaticraise (or_num, List.map (fun v -> Lvar v) vs) in let new_ord,new_to_catch = do_cases rem in explode_or_pat argo new_patl mk_new_action new_ord vars [] orp, (([[orp]], or_num, vars , orpm):: new_to_catch) end | cl::rem -> let new_ord,new_to_catch = do_cases rem in cl::new_ord,new_to_catch | [] -> [],[] in let to_match,to_catch = do_cases clor in {to_match = {args=al ; cases=cl@to_match ; default=def} ; to_catch = to_catch} (* Basic grouping predicates *) let group_constant = function | {pat_desc= Tpat_constant _} -> true | _ -> false and group_constructor = function | {pat_desc = Tpat_construct (_, _)} -> true | _ -> false and group_variant = function | {pat_desc = Tpat_variant (_, _, _)} -> true | _ -> false and group_var = function | {pat_desc=Tpat_any} -> true | _ -> false and group_tuple = function | {pat_desc = (Tpat_tuple _|Tpat_any)} -> true | _ -> false and group_record = function | {pat_desc = (Tpat_record _|Tpat_any)} -> true | _ -> false and group_array = function | {pat_desc=Tpat_array _} -> true | _ -> false let get_group p = match p.pat_desc with | Tpat_any -> group_var | Tpat_constant _ -> group_constant | Tpat_construct (_, _) -> group_constructor | Tpat_tuple _ -> group_tuple | Tpat_record _ -> group_record | Tpat_array _ -> group_array | Tpat_variant (_,_,_) -> group_variant | _ -> fatal_error "Matching.get_group" let is_or p = match p.pat_desc with | Tpat_or _ -> true | _ -> false (* Conditions for appending to the Or matrix *) let conda p q = not (compat p q) and condb act ps qs = not (is_guarded act) && Parmatch.le_pats qs ps let or_ok p ps l = List.for_all (function | ({pat_desc=Tpat_or _} as q::qs,act) -> conda p q || condb act ps qs | _ -> true) l (* Insert or append a or-pattern in the Or matrix *) let insert_or_append p ps act ors no = let rec attempt seen = function | (q::qs,act_q) as cl::rem -> if is_or q then begin if compat p q then if IdentSet.is_empty (extract_vars IdentSet.empty p) && IdentSet.is_empty (extract_vars IdentSet.empty q) && equiv_pat p q then (* attempt insert, for equivalent orpats with no variables *) let _, not_e = get_equiv q rem in if or_ok p ps not_e && (* check append condition for head of O *) List.for_all (* check insert condition for tail of O *) (fun cl -> match cl with | (q::_,_) -> not (compat p q) | _ -> assert false) seen then (* insert *) List.rev_append seen ((p::ps,act)::cl::rem), no else (* fail to insert or append *) ors,(p::ps,act)::no else if condb act_q ps qs then (* check condition (b) for append *) attempt (cl::seen) rem else ors,(p::ps,act)::no else (* p # q, go on with append/insert *) attempt (cl::seen) rem end else (* q is not a or-pat, go on with append/insert *) attempt (cl::seen) rem | _ -> (* [] in fact *) (p::ps,act)::ors,no in (* success in appending *) attempt [] ors let separe argo pm = let ex_pat,pm = simplify_matching pm in match pm.cases with | [[{pat_desc=Tpat_any}],_] -> compile_or argo pm.cases [] pm.args pm.default,[] | _ -> let next,nexts = match ex_pat.pat_desc with | Tpat_any -> compile_or argo pm.cases [] pm.args pm.default,[] | _ -> let group = get_group ex_pat in let rec sep_ex yes ors no = function | ((p::ps,act) as cl)::rem -> if group p then begin if up_ok cl no then if up_ok cl ors then sep_ex (cl::yes) ors no rem else if or_ok p ps ors then sep_ex yes (cl::ors) no rem else sep_ex yes ors (cl::no) rem else sep_ex yes ors (cl::no) rem end else if is_or p then begin if up_ok cl no then let ors,no = insert_or_append p ps act ors no in sep_ex yes ors no rem else sep_ex yes ors (cl::no) rem end else (* p is a variable *) sep_ex yes ors (cl::no) rem | _ -> (* [] in fact *) cons_next (List.rev yes) (List.rev ors) (List.rev no) and sep_noex yes no = function | [ps,_ as cl] when List.for_all group_var ps && yes <> [] -> cons_next (List.rev yes) [] (List.rev (cl::no)) | ((p::_,_) as cl)::rem -> if group_var p && up_ok cl no then sep_noex (cl::yes) no rem else sep_noex yes (cl::no) rem | _ -> (* [] in fact *) cons_next (List.rev yes) [] (List.rev no) and sep_next cl rem = match cl with | ((p::_),_) -> if group p then sep_ex [cl] [] [] rem else if is_or p then sep_ex [] [cl] [] rem else sep_noex [cl] [] rem | _ -> assert false and cons_next yes yesor = function | [] -> as_matrix (yes@yesor), compile_or argo yes yesor pm.args pm.default,[] | cl::rem -> let matrix,next,nexts = sep_next cl rem in begin match next with (* Optimisation of jumps to jumps *) | {to_match = {cases=[ps,Lstaticraise (idef,[])]} ; to_catch=[]} when List.for_all group_var ps -> let newdef = cons_default matrix idef next.to_match.default in as_matrix (yes@yesor), compile_or argo yes yesor pm.args newdef, (-1,next)::nexts | _ -> let idef = next_raise_count () in let newdef = cons_default matrix idef next.to_match.default in as_matrix (yes@yesor), compile_or argo yes yesor pm.args newdef, (idef,next)::nexts end in match pm.cases with | ((_::_),_) as cl::rem -> let _,next,nexts = sep_next cl rem in next, nexts | _ -> compile_or argo pm.cases [] pm.args pm.default,[] in (* prerr_endline "SEPARE" ; pretty_ext next ; List.iter (fun (e,p) -> Printf.eprintf "** %d **\n" e ; flush stderr ; pretty_ext p) nexts ; *) (next,nexts) (* General divide functions *) let divide make get_key get_args ctx pm = let rec divide_rec = function | (p::patl,action) :: rem -> let this_match = divide_rec rem in add (make p pm.default ctx) this_match (get_key p) (get_args p patl,action) pm.args | _ -> [] in divide_rec pm.cases let divide_line make_ctx make get_args pat ctx pm = let rec divide_rec = function | (p::patl,action) :: rem -> let this_match = divide_rec rem in add_line (get_args p patl, action) this_match | _ -> make pm.default pm.args in {pm = divide_rec pm.cases ; ctx=make_ctx ctx ; pat=pat} let make_default matcher (exit,l) = let rec make_rec = function | [] -> [] | ([[]],i)::_ -> [[[]],i] | (pss,i)::rem -> let rem = make_rec rem in match filter_matrix matcher pss with | [] -> rem | ([]::_) -> ([[]],i)::rem | pss -> (pss,i)::rem in exit,make_rec l (* Then come various functions, There is one set of functions per matching style (constants, constructors etc.) - matcher function are arguments to make_default (for defaukt handlers) They may raise NoMatch or OrPat and perform the full matching (selection + arguments). - get_args and get_key are for the compiled matrices, note that selection and geting arguments are separed. - make_ _matching combines the previous functions for produicing new ``pattern_matching'' records. *) let rec matcher_const cst p rem = match p.pat_desc with | Tpat_or (p1,p2,_) -> begin try matcher_const cst p1 rem with | NoMatch -> matcher_const cst p2 rem end | Tpat_constant c1 when c1=cst -> rem | Tpat_any -> rem | _ -> raise NoMatch let get_key_constant caller = function | {pat_desc= Tpat_constant cst} as p -> cst | p -> prerr_endline ("BAD: "^caller) ; pretty_pat p ; assert false let get_args_constant _ rem = rem let make_constant_matching p def ctx = function [] -> fatal_error "Matching.make_constant_matching" | (_ :: argl) -> let def = make_default (matcher_const (get_key_constant "make" p)) def and ctx = filter_ctx p ctx in {pm = {cases = []; args = argl ; default = def} ; ctx = ctx ; pat = normalize_pat p} let divide_constant ctx m = divide make_constant_matching (get_key_constant "divide") get_args_constant ctx m (* Matching against a constructor *) let make_field_args binding_kind arg first_pos last_pos argl = let rec make_args pos = if pos > last_pos then argl else (Lprim(Pfield pos, [arg]), binding_kind) :: make_args (pos + 1) in make_args first_pos let get_key_constr = function | {pat_desc=Tpat_construct (cstr,_)} -> cstr.cstr_tag | _ -> assert false let get_args_constr p rem = match p with | {pat_desc=Tpat_construct (_,args)} -> args @ rem | _ -> assert false let pat_as_constr = function | {pat_desc=Tpat_construct (cstr,_)} -> cstr | _ -> fatal_error "Matching.pat_as_constr" let matcher_constr cstr = match cstr.cstr_arity with | 0 -> let rec matcher_rec q rem = match q.pat_desc with | Tpat_or (p1,p2,_) -> begin try matcher_rec p1 rem with | NoMatch -> matcher_rec p2 rem end | Tpat_construct (cstr1, []) when cstr.cstr_tag = cstr1.cstr_tag -> rem | Tpat_any -> rem | _ -> raise NoMatch in matcher_rec | 1 -> let rec matcher_rec q rem = match q.pat_desc with | Tpat_or (p1,p2,_) -> let r1 = try Some (matcher_rec p1 rem) with NoMatch -> None and r2 = try Some (matcher_rec p2 rem) with NoMatch -> None in begin match r1,r2 with | None, None -> raise NoMatch | Some r1, None -> r1 | None, Some r2 -> r2 | Some (a1::rem1), Some (a2::_) -> {a1 with pat_loc = Location.none ; pat_desc = Tpat_or (a1, a2, None)}:: rem | _, _ -> assert false end | Tpat_construct (cstr1, [arg]) when cstr.cstr_tag = cstr1.cstr_tag -> arg::rem | Tpat_any -> omega::rem | _ -> raise NoMatch in matcher_rec | _ -> fun q rem -> match q.pat_desc with | Tpat_or (_,_,_) -> raise OrPat | Tpat_construct (cstr1, args) when cstr.cstr_tag = cstr1.cstr_tag -> args @ rem | Tpat_any -> Parmatch.omegas cstr.cstr_arity @ rem | _ -> raise NoMatch let make_constr_matching p def ctx = function [] -> fatal_error "Matching.make_constr_matching" | ((arg, mut) :: argl) -> let cstr = pat_as_constr p in let newargs = match cstr.cstr_tag with Cstr_constant _ | Cstr_block _ -> make_field_args Alias arg 0 (cstr.cstr_arity - 1) argl | Cstr_exception _ -> make_field_args Alias arg 1 cstr.cstr_arity argl in {pm= {cases = []; args = newargs; default = make_default (matcher_constr cstr) def} ; ctx = filter_ctx p ctx ; pat=normalize_pat p} let divide_constructor ctx pm = divide make_constr_matching get_key_constr get_args_constr ctx pm (* Matching against a variant *) let rec matcher_variant_const lab p rem = match p.pat_desc with | Tpat_or (p1, p2, _) -> begin try matcher_variant_const lab p1 rem with | NoMatch -> matcher_variant_const lab p2 rem end | Tpat_variant (lab1,_,_) when lab1=lab -> rem | Tpat_any -> rem | _ -> raise NoMatch let make_variant_matching_constant p lab def ctx = function [] -> fatal_error "Matching.make_variant_matching_constant" | ((arg, mut) :: argl) -> let def = make_default (matcher_variant_const lab) def and ctx = filter_ctx p ctx in {pm={ cases = []; args = argl ; default=def} ; ctx=ctx ; pat = normalize_pat p} let matcher_variant_nonconst lab p rem = match p.pat_desc with | Tpat_or (_,_,_) -> raise OrPat | Tpat_variant (lab1,Some arg,_) when lab1=lab -> arg::rem | Tpat_any -> omega::rem | _ -> raise NoMatch let make_variant_matching_nonconst p lab def ctx = function [] -> fatal_error "Matching.make_variant_matching_nonconst" | ((arg, mut) :: argl) -> let def = make_default (matcher_variant_nonconst lab) def and ctx = filter_ctx p ctx in {pm= {cases = []; args = (Lprim(Pfield 1, [arg]), Alias) :: argl; default=def} ; ctx=ctx ; pat = normalize_pat p} let get_key_variant p = match p.pat_desc with | Tpat_variant(lab, Some _ , _) -> Cstr_block (Btype.hash_variant lab) | Tpat_variant(lab, None , _) -> Cstr_constant (Btype.hash_variant lab) | _ -> assert false let divide_variant row ctx ({cases = cl; args = al; default=def} as pm) = let row = Btype.row_repr row in let rec divide = function ({pat_desc = Tpat_variant(lab, pato, _)} as p:: patl, action) :: rem -> let variants = divide rem in if try Btype.row_field_repr (List.assoc lab row.row_fields) = Rabsent with Not_found -> true then variants else begin let tag = Btype.hash_variant lab in match pato with None -> add (make_variant_matching_constant p lab def ctx) variants (Cstr_constant tag) (patl, action) al | Some pat -> add (make_variant_matching_nonconst p lab def ctx) variants (Cstr_block tag) (pat :: patl, action) al end | cl -> [] in divide cl (* Three ``no-test'' cases *) (* Matching against a variable *) let get_args_var _ rem = rem let make_var_matching def = function | [] -> fatal_error "Matching.make_var_matching" | _::argl -> {cases=[] ; args = argl ; default= make_default get_args_var def} let divide_var ctx pm = divide_line ctx_lshift make_var_matching get_args_var omega ctx pm (* Matching against a tuple pattern *) let get_args_tuple arity p rem = match p with | {pat_desc = Tpat_any} -> omegas arity @ rem | {pat_desc = Tpat_tuple args} -> args @ rem | _ -> assert false let matcher_tuple arity p rem = match p.pat_desc with | Tpat_or (_,_,_) -> raise OrPat | Tpat_var _ -> get_args_tuple arity omega rem | _ -> get_args_tuple arity p rem let make_tuple_matching arity def = function [] -> fatal_error "Matching.make_tuple_matching" | (arg, mut) :: argl -> let rec make_args pos = if pos >= arity then argl else (Lprim(Pfield pos, [arg]), Alias) :: make_args (pos + 1) in {cases = []; args = make_args 0 ; default=make_default (matcher_tuple arity) def} let divide_tuple arity p ctx pm = divide_line (filter_ctx p) (make_tuple_matching arity) (get_args_tuple arity) p ctx pm (* Matching against a record pattern *) let record_matching_line num_fields lbl_pat_list = let patv = Array.create num_fields omega in List.iter (fun (lbl, pat) -> patv.(lbl.lbl_pos) <- pat) lbl_pat_list; Array.to_list patv let get_args_record num_fields p rem = match p with | {pat_desc=Tpat_any} -> record_matching_line num_fields [] @ rem | {pat_desc=Tpat_record lbl_pat_list} -> record_matching_line num_fields lbl_pat_list @ rem | _ -> assert false let matcher_record num_fields p rem = match p.pat_desc with | Tpat_or (_,_,_) -> raise OrPat | Tpat_var _ -> get_args_record num_fields omega rem | _ -> get_args_record num_fields p rem let make_record_matching all_labels def = function [] -> fatal_error "Matching.make_record_matching" | ((arg, mut) :: argl) -> let rec make_args pos = if pos >= Array.length all_labels then argl else begin let lbl = all_labels.(pos) in let access = match lbl.lbl_repres with Record_regular -> Pfield lbl.lbl_pos | Record_float -> Pfloatfield lbl.lbl_pos in let str = match lbl.lbl_mut with Immutable -> Alias | Mutable -> StrictOpt in (Lprim(access, [arg]), str) :: make_args(pos + 1) end in let nfields = Array.length all_labels in let def= make_default (matcher_record nfields) def in {cases = []; args = make_args 0 ; default = def} let divide_record all_labels p ctx pm = let get_args = get_args_record (Array.length all_labels) in divide_line (filter_ctx p) (make_record_matching all_labels) get_args p ctx pm (* Matching against an array pattern *) let get_key_array = function | {pat_desc=Tpat_array patl} -> List.length patl | _ -> assert false let get_args_array p rem = match p with | {pat_desc=Tpat_array patl} -> patl@rem | _ -> assert false let matcher_array len p rem = match p.pat_desc with | Tpat_or (_,_,_) -> raise OrPat | Tpat_array args when List.length args=len -> args @ rem | Tpat_any -> Parmatch.omegas len @ rem | _ -> raise NoMatch let make_array_matching kind p def ctx = function | [] -> fatal_error "Matching.make_array_matching" | ((arg, mut) :: argl) -> let len = get_key_array p in let rec make_args pos = if pos >= len then argl else (Lprim(Parrayrefu kind, [arg; Lconst(Const_base(Const_int pos))]), StrictOpt) :: make_args (pos + 1) in let def = make_default (matcher_array len) def and ctx = filter_ctx p ctx in {pm={cases = []; args = make_args 0 ; default = def} ; ctx=ctx ; pat = normalize_pat p} let divide_array kind ctx pm = divide (make_array_matching kind) get_key_array get_args_array ctx pm (* To combine sub-matchings together *) let float_compare s1 s2 = let f1 = float_of_string s1 and f2 = float_of_string s2 in Pervasives.compare f1 f2 let sort_lambda_list l = List.sort (fun (x,_) (y,_) -> match x,y with | Const_float f1, Const_float f2 -> float_compare f1 f2 | _, _ -> Pervasives.compare x y) l let rec cut n l = if n = 0 then [],l else match l with [] -> raise (Invalid_argument "cut") | a::l -> let l1,l2 = cut (n-1) l in a::l1, l2 let rec do_tests_fail fail tst arg = function | [] -> fail | (c, act)::rem -> Lifthenelse (Lprim (tst, [arg ; Lconst (Const_base c)]), do_tests_fail fail tst arg rem, act) let rec do_tests_nofail tst arg = function | [] -> fatal_error "Matching.do_tests_nofail" | [_,act] -> act | (c,act)::rem -> Lifthenelse (Lprim (tst, [arg ; Lconst (Const_base c)]), do_tests_nofail tst arg rem, act) let make_test_sequence fail tst lt_tst arg const_lambda_list = let rec make_test_sequence const_lambda_list = if List.length const_lambda_list >= 4 && lt_tst <> Praise then split_sequence const_lambda_list else match fail with | None -> do_tests_nofail tst arg const_lambda_list | Some fail -> do_tests_fail fail tst arg const_lambda_list and split_sequence const_lambda_list = let list1, list2 = cut (List.length const_lambda_list / 2) const_lambda_list in Lifthenelse(Lprim(lt_tst,[arg; Lconst(Const_base (fst(List.hd list2)))]), make_test_sequence list1, make_test_sequence list2) in make_test_sequence (sort_lambda_list const_lambda_list) let make_offset x arg = if x=0 then arg else Lprim(Poffsetint(x), [arg]) let prim_string_notequal = Pccall{prim_name = "string_notequal"; prim_arity = 2; prim_alloc = false; prim_native_name = ""; prim_native_float = false} let rec explode_inter offset i j act k = if i <= j then explode_inter offset i (j-1) act ((j-offset,act)::k) else k let max_vals cases acts = let vals = Array.create (Array.length acts) 0 in for i=Array.length cases-1 downto 0 do let l,h,act = cases.(i) in vals.(act) <- h - l + 1 + vals.(act) done ; let max = ref 0 in for i = Array.length vals-1 downto 0 do if vals.(i) >= vals.(!max) then max := i done ; if vals.(!max) > 1 then !max else -1 let as_int_list cases acts = let default = max_vals cases acts in let min_key,_,_ = cases.(0) and _,max_key,_ = cases.(Array.length cases-1) in let offset = max_key-min_key in let rec do_rec i k = if i >= 0 then let low, high, act = cases.(i) in if act = default then do_rec (i-1) k else do_rec (i-1) (explode_inter min_key low high acts.(act) k) else k in min_key, max_key,do_rec (Array.length cases-1) [], (if default >= 0 then Some acts.(default) else None) let make_switch_offset arg min_key max_key int_lambda_list default = let numcases = max_key - min_key + 1 in let cases = List.map (fun (key, l) -> (key - min_key, l)) int_lambda_list in let offsetarg = make_offset (-min_key) arg in Lswitch(offsetarg, {sw_numconsts = numcases; sw_consts = cases; sw_numblocks = 0; sw_blocks = []; sw_failaction = default}) let make_switch_switcher arg cases acts = let l = ref [] in for i = Array.length cases-1 downto 0 do l := (i,acts.(cases.(i))) :: !l done ; Lswitch(arg, {sw_numconsts = Array.length cases ; sw_consts = !l ; sw_numblocks = 0 ; sw_blocks = [] ; sw_failaction = None}) let full sw = List.length sw.sw_consts = sw.sw_numconsts && List.length sw.sw_blocks = sw.sw_numblocks let make_switch (arg,sw) = match sw.sw_failaction with | None -> let t = Hashtbl.create 17 in let seen l = match l with | Lstaticraise (i,[]) -> let old = try Hashtbl.find t i with Not_found -> 0 in Hashtbl.replace t i (old+1) | _ -> () in List.iter (fun (_,lam) -> seen lam) sw.sw_consts ; List.iter (fun (_,lam) -> seen lam) sw.sw_blocks ; let i_max = ref (-1) and max = ref (-1) in Hashtbl.iter (fun i c -> if c > !max then begin i_max := i ; max := c end) t ; if !i_max >= 0 then let default = !i_max in let rec remove = function | [] -> [] | (_,Lstaticraise (j,[]))::rem when j=default -> remove rem | x::rem -> x::remove rem in Lswitch (arg, {sw with sw_consts = remove sw.sw_consts ; sw_blocks = remove sw.sw_blocks ; sw_failaction = Some (Lstaticraise (default,[]))}) else Lswitch (arg,sw) | _ -> Lswitch (arg,sw) module SArg = struct type primitive = Lambda.primitive let eqint = Pintcomp Ceq let neint = Pintcomp Cneq let leint = Pintcomp Cle let ltint = Pintcomp Clt let geint = Pintcomp Cge let gtint = Pintcomp Cgt type act = Lambda.lambda let make_prim p args = Lprim (p,args) let make_offset arg n = match n with | 0 -> arg | _ -> Lprim (Poffsetint n,[arg]) let bind arg body = let newvar,newarg = match arg with | Lvar v -> v,arg | _ -> let newvar = Ident.create "switcher" in newvar,Lvar newvar in bind Alias newvar arg (body newarg) let make_isout h arg = Lprim (Pisout, [h ; arg]) let make_isin h arg = Lprim (Pnot,[make_isout h arg]) let make_if cond ifso ifnot = Lifthenelse (cond, ifso, ifnot) let make_switch = make_switch_switcher end module Switcher = Switch.Make(SArg) open Switch let lambda_of_int i = Lconst (Const_base (Const_int i)) let rec last def = function | [] -> def | [x,_] -> x | _::rem -> last def rem let get_edges low high l = match l with | [] -> low, high | (x,_)::_ -> x, last high l let as_interval_canfail fail low high l = let store = mk_store equal_action in let rec nofail_rec cur_low cur_high cur_act = function | [] -> if cur_high = high then [cur_low,cur_high,cur_act] else [(cur_low,cur_high,cur_act) ; (cur_high+1,high, 0)] | ((i,act_i)::rem) as all -> let act_index = store.act_store act_i in if cur_high+1= i then if act_index=cur_act then nofail_rec cur_low i cur_act rem else if act_index=0 then (cur_low,i-1, cur_act)::fail_rec i i rem else (cur_low, i-1, cur_act)::nofail_rec i i act_index rem else (cur_low, cur_high, cur_act):: fail_rec ((cur_high+1)) (cur_high+1) all and fail_rec cur_low cur_high = function | [] -> [(cur_low, cur_high, 0)] | (i,act_i)::rem -> let index = store.act_store act_i in if index=0 then fail_rec cur_low i rem else (cur_low,i-1,0):: nofail_rec i i index rem in let rec init_rec = function | [] -> [] | (i,act_i)::rem as all -> let index = store.act_store act_i in if index=0 then fail_rec low i rem else if low < i then (low,i-1,0)::nofail_rec i i index rem else nofail_rec i i index rem in ignore (store.act_store fail) ; (* fail has action index 0 *) let r = init_rec l in Array.of_list r, store.act_get () let as_interval_nofail l = let store = mk_store equal_action in let rec i_rec cur_low cur_high cur_act = function | [] -> [cur_low, cur_high, cur_act] | (i,act)::rem -> let act_index = store.act_store act in if act_index = cur_act then i_rec cur_low i cur_act rem else (cur_low, cur_high, cur_act):: i_rec i i act_index rem in let inters = match l with | (i,act)::rem -> let act_index = store.act_store act in i_rec i i act_index rem | _ -> assert false in Array.of_list inters, store.act_get () let sort_int_lambda_list l = List.sort (fun (i1,_) (i2,_) -> if i1 < i2 then -1 else if i2 < i1 then 1 else 0) l let as_interval fail low high l = let l = sort_int_lambda_list l in get_edges low high l, (match fail with | None -> as_interval_nofail l | Some act -> as_interval_canfail act low high l) let call_switcher konst fail arg low high int_lambda_list = let edges, (cases, actions) = as_interval fail low high int_lambda_list in Switcher.zyva edges konst arg cases actions let exists_ctx ok ctx = List.exists (function | {right=p::_} -> ok p | _ -> assert false) ctx let rec list_as_pat = function | [] -> fatal_error "Matching.list_as_pat" | [pat] -> pat | pat::rem -> {pat with pat_desc = Tpat_or (pat,list_as_pat rem,None)} let rec pat_as_list k = function | {pat_desc=Tpat_or (p1,p2,_)} -> pat_as_list (pat_as_list k p2) p1 | p -> p::k (* Extracting interesting patterns *) exception All let rec extract_pat seen k p = match p.pat_desc with | Tpat_or (p1,p2,_) -> let k1,seen1 = extract_pat seen k p1 in extract_pat seen1 k1 p2 | Tpat_alias (p,_) -> extract_pat seen k p | Tpat_var _|Tpat_any -> raise All | _ -> let q = normalize_pat p in if List.exists (compat q) seen then k, seen else q::k, q::seen let extract_mat seen pss = let r,_ = List.fold_left (fun (k,seen) ps -> match ps with | p::_ -> extract_pat seen k p | _ -> assert false) ([],seen) pss in r let complete_pats_constrs = function | p::_ as pats -> List.map (pat_of_constr p) (complete_constrs p (List.map get_key_constr pats)) | _ -> assert false let mk_res get_key env last_choice idef cant_fail ctx = let env,fail,jumps_fail = match last_choice with | [] -> env, None, jumps_empty | [p] when group_var p -> env, Some (Lstaticraise (idef,[])), jumps_singleton idef ctx | _ -> (idef,cant_fail,last_choice)::env, None, jumps_empty in let klist,jumps = List.fold_right (fun (i,cant_fail,pats) (klist,jumps) -> let act = Lstaticraise (i,[]) and pat = list_as_pat pats in let klist = List.fold_right (fun pat klist -> (get_key pat,act)::klist) pats klist and ctx = if cant_fail then ctx else ctx_lub pat ctx in klist,jumps_add i ctx jumps) env ([],jumps_fail) in fail, klist, jumps (* Aucune optimisation, reflechir apres la release *) let mk_failaction_neg partial ctx (_,def) = match partial with | Partial -> begin match def with | (_,idef)::_ -> Some (Lstaticraise (idef,[])),[],jumps_singleton idef ctx | __ -> assert false end | Total -> None, [], jumps_empty (* Conforme a l'article et plus simple qu'avant *) and mk_failaction_pos partial seen ctx (_,defs) = let rec scan_def env to_test defs = match to_test,defs with | ([],_)|(_,[]) -> List.fold_left (fun (klist,jumps) (pats,i)-> let action = Lstaticraise (i,[]) in let klist = List.fold_right (fun pat r -> (get_key_constr pat,action)::r) pats klist and jumps = jumps_add i (ctx_lub (list_as_pat pats) ctx) jumps in klist,jumps) ([],jumps_empty) env | _,(pss,idef)::rem -> let now, later = List.partition (fun (p,p_ctx) -> ctx_match p_ctx pss) to_test in match now with | [] -> scan_def env to_test rem | _ -> scan_def ((List.map fst now,idef)::env) later rem in scan_def [] (List.map (fun pat -> pat, ctx_lub pat ctx) (complete_pats_constrs seen)) defs let combine_constant arg cst partial ctx def (const_lambda_list, total, pats) = let fail, to_add, local_jumps = mk_failaction_neg partial ctx def in let const_lambda_list = to_add@const_lambda_list in let lambda1 = match cst with | Const_int _ -> let int_lambda_list = List.map (function Const_int n, l -> n,l | _ -> assert false) const_lambda_list in call_switcher lambda_of_int fail arg min_int max_int int_lambda_list | Const_char _ -> let int_lambda_list = List.map (function Const_char c, l -> (Char.code c, l) | _ -> assert false) const_lambda_list in call_switcher (fun i -> Lconst (Const_base (Const_int i))) fail arg 0 255 int_lambda_list | Const_string _ -> make_test_sequence fail prim_string_notequal Praise arg const_lambda_list | Const_float _ -> make_test_sequence fail (Pfloatcomp Cneq) (Pfloatcomp Clt) arg const_lambda_list | Const_int32 _ -> make_test_sequence fail (Pbintcomp(Pint32, Cneq)) (Pbintcomp(Pint32, Clt)) arg const_lambda_list | Const_int64 _ -> make_test_sequence fail (Pbintcomp(Pint64, Cneq)) (Pbintcomp(Pint64, Clt)) arg const_lambda_list | Const_nativeint _ -> make_test_sequence fail (Pbintcomp(Pnativeint, Cneq)) (Pbintcomp(Pnativeint, Clt)) arg const_lambda_list in lambda1,jumps_union local_jumps total let split_cases tag_lambda_list = let rec split_rec = function [] -> ([], []) | (cstr, act) :: rem -> let (consts, nonconsts) = split_rec rem in match cstr with Cstr_constant n -> ((n, act) :: consts, nonconsts) | Cstr_block n -> (consts, (n, act) :: nonconsts) | _ -> assert false in let const, nonconst = split_rec tag_lambda_list in sort_int_lambda_list const, sort_int_lambda_list nonconst let combine_constructor arg ex_pat cstr partial ctx def (tag_lambda_list, total1, pats) = if cstr.cstr_consts < 0 then begin (* Special cases for exceptions *) let cstrs = List.map fst tag_lambda_list in let fail, to_add, local_jumps = mk_failaction_neg partial ctx def in let tag_lambda_list = to_add@tag_lambda_list in let lambda1 = let default, tests = match fail with | None -> begin match tag_lambda_list with | (_, act)::rem -> act,rem | _ -> assert false end | Some fail -> fail, tag_lambda_list in List.fold_right (fun (ex, act) rem -> match ex with | Cstr_exception path -> Lifthenelse(Lprim(Pintcomp Ceq, [Lprim(Pfield 0, [arg]); transl_path path]), act, rem) | _ -> assert false) tests default in lambda1, jumps_union local_jumps total1 end else begin (* Regular concrete type *) let ncases = List.length tag_lambda_list and nconstrs = cstr.cstr_consts + cstr.cstr_nonconsts in let sig_complete = ncases = nconstrs and cstrs = List.map fst tag_lambda_list in let fails,local_jumps = if sig_complete then [],jumps_empty else mk_failaction_pos partial pats ctx def in let tag_lambda_list = fails @ tag_lambda_list in let (consts, nonconsts) = split_cases tag_lambda_list in let lambda1 = match same_actions tag_lambda_list with | Some act -> act | _ -> match (cstr.cstr_consts, cstr.cstr_nonconsts, consts, nonconsts) with | (1, 1, [0, act1], [0, act2]) -> Lifthenelse(arg, act2, act1) | (n,_,_,[]) -> call_switcher (fun i -> Lconst (Const_base (Const_int i))) None arg 0 (n-1) consts | (n, _, _, _) -> match same_actions nonconsts with | None -> make_switch(arg, {sw_numconsts = cstr.cstr_consts; sw_consts = consts; sw_numblocks = cstr.cstr_nonconsts; sw_blocks = nonconsts; sw_failaction = None}) | Some act -> Lifthenelse (Lprim (Pisint, [arg]), call_switcher (fun i -> Lconst (Const_base (Const_int i))) None arg 0 (n-1) consts, act) in lambda1, jumps_union local_jumps total1 end let make_test_sequence_variant_constant fail arg int_lambda_list = let _, (cases, actions) = as_interval fail min_int max_int int_lambda_list in Switcher.test_sequence (fun i -> Lconst (Const_base (Const_int i))) arg cases actions let call_switcher_variant_constant fail arg int_lambda_list = call_switcher (fun i -> Lconst (Const_base (Const_int i))) fail arg min_int max_int int_lambda_list let call_switcher_variant_constr fail arg int_lambda_list = let v = Ident.create "variant" in Llet(Alias, v, Lprim(Pfield 0, [arg]), call_switcher (fun i -> Lconst (Const_base (Const_int i))) fail (Lvar v) min_int max_int int_lambda_list) let combine_variant row arg partial ctx def (tag_lambda_list, total1, pats) = let row = Btype.row_repr row in let num_constr = ref 0 in if row.row_closed then List.iter (fun (_, f) -> match Btype.row_field_repr f with Rabsent | Reither(true, _::_, _, _) -> () | _ -> incr num_constr) row.row_fields else num_constr := max_int; let test_int_or_block arg if_int if_block = Lifthenelse(Lprim (Pisint, [arg]), if_int, if_block) in let sig_complete = List.length tag_lambda_list = !num_constr and one_action = same_actions tag_lambda_list in let fail, to_add, local_jumps = if sig_complete || (match partial with Total -> true | _ -> false) then None, [], jumps_empty else mk_failaction_neg partial ctx def in let tag_lambda_list = to_add@tag_lambda_list in let (consts, nonconsts) = split_cases tag_lambda_list in let lambda1 = match fail, one_action with | None, Some act -> act | _,_ -> match (consts, nonconsts) with | ([n, act1], [m, act2]) when fail=None -> test_int_or_block arg act1 act2 | (_, []) -> (* One can compare integers and pointers *) make_test_sequence_variant_constant fail arg consts | ([], _) -> let lam = call_switcher_variant_constr fail arg nonconsts in (* One must not dereference integers *) begin match fail with | None -> lam | Some fail -> test_int_or_block arg fail lam end | (_, _) -> let lam_const = call_switcher_variant_constant fail arg consts and lam_nonconst = call_switcher_variant_constr fail arg nonconsts in test_int_or_block arg lam_const lam_nonconst in lambda1, jumps_union local_jumps total1 let combine_array arg kind partial ctx def (len_lambda_list, total1, pats) = let fail, to_add, local_jumps = mk_failaction_neg partial ctx def in let len_lambda_list = to_add @ len_lambda_list in let lambda1 = let newvar = Ident.create "len" in let switch = call_switcher lambda_of_int fail (Lvar newvar) 0 max_int len_lambda_list in bind Alias newvar (Lprim(Parraylength kind, [arg])) switch in lambda1, jumps_union local_jumps total1 (* Insertion of debugging events *) let rec event_branch repr lam = begin match lam, repr with (_, None) -> lam | (Levent(lam', ev), Some r) -> incr r; Levent(lam', {lev_pos = ev.lev_pos; lev_kind = ev.lev_kind; lev_repr = repr; lev_env = ev.lev_env}) | (Llet(str, id, lam, body), _) -> Llet(str, id, lam, event_branch repr body) | Lstaticraise _,_ -> lam | (_, Some r) -> Printlambda.lambda Format.str_formatter lam ; fatal_error ("Matching.event_branch: "^Format.flush_str_formatter ()) end exception Unused let compile_list compile_fun division = let rec c_rec totals = function | [] -> [], jumps_unions totals, [] | (key, cell) :: rem -> begin match cell.ctx with | [] -> c_rec totals rem | _ -> try let (lambda1, total1) = compile_fun cell.ctx cell.pm in let c_rem, total, new_pats = c_rec (jumps_map ctx_combine total1::totals) rem in ((key,lambda1)::c_rem), total, (cell.pat::new_pats) with | Unused -> c_rec totals rem end in c_rec [] division let compile_orhandlers compile_fun lambda1 total1 ctx to_catch = let rec do_rec r total_r = function | [] -> r,total_r | (mat,i,vars,pm)::rem -> begin try let ctx = select_columns mat ctx in let handler_i, total_i = compile_fun ctx pm in match raw_action r with | Lstaticraise (j,args) -> if i=j then List.fold_right2 (bind Alias) vars args handler_i, jumps_map (ctx_rshift_num (ncols mat)) total_i else do_rec r total_r rem | _ -> do_rec (Lstaticcatch (r,(i,vars), handler_i)) (jumps_union (jumps_remove i total_r) (jumps_map (ctx_rshift_num (ncols mat)) total_i)) rem with | Unused -> do_rec (Lstaticcatch (r, (i,vars), lambda_unit)) total_r rem end in do_rec lambda1 total1 to_catch let compile_test compile_fun partial divide combine ctx to_match to_catch = let division = divide ctx to_match in let c_div = compile_list compile_fun division in match c_div with | [],_,_ -> begin match mk_failaction_neg partial ctx to_match.default with | None,_,_ -> raise Unused | Some l,_,total -> l,total end | _ -> let lambda1,total1 = combine ctx to_match.default c_div in compile_orhandlers compile_fun lambda1 total1 ctx to_catch (* Attempt to avoid some useless bindinds by lowering them *) (* Approximation of v present in lam *) let rec approx_present v = function | Lconst _ -> false | Lstaticraise (_,args) -> List.exists (fun lam -> approx_present v lam) args | Lprim (_,args) -> List.exists (fun lam -> approx_present v lam) args | Llet (Alias, _, l1, l2) -> approx_present v l1 || approx_present v l2 | Lvar vv -> Ident.same v vv | _ -> true let string_of_lam lam = Printlambda.lambda Format.str_formatter lam ; Format.flush_str_formatter () let rec lower_bind v arg lam = match lam with | Lifthenelse (cond, ifso, ifnot) -> let pcond = approx_present v cond and pso = approx_present v ifso and pnot = approx_present v ifnot in begin match pcond, pso, pnot with | false, false, false -> lam | false, true, false -> Lifthenelse (cond, lower_bind v arg ifso, ifnot) | false, false, true -> Lifthenelse (cond, ifso, lower_bind v arg ifnot) | _,_,_ -> bind Alias v arg lam end | Lswitch (ls,({sw_consts=[i,act] ; sw_blocks = []} as sw)) when not (approx_present v ls) -> Lswitch (ls, {sw with sw_consts = [i,lower_bind v arg act]}) | Lswitch (ls,({sw_consts=[] ; sw_blocks = [i,act]} as sw)) when not (approx_present v ls) -> Lswitch (ls, {sw with sw_blocks = [i,lower_bind v arg act]}) | Llet (Alias, vv, lv, l) -> if approx_present v lv then bind Alias v arg lam else Llet (Alias, vv, lv, lower_bind v arg l) | _ -> bind Alias v arg lam let bind_check str v arg lam = match str,arg with | _, Lvar _ ->bind str v arg lam | Alias,_ -> lower_bind v arg lam | _,_ -> bind str v arg lam let rec comp_exit ctx m = match m.default with | exit,(_,i)::_ -> Lstaticraise (i,[]), jumps_singleton i ctx | _ -> fatal_error "Matching.comp_exit" let rec comp_match_handlers comp_fun partial ctx arg first_match next_matchs = match next_matchs with | [] -> comp_fun partial ctx arg first_match | rem -> let rec c_rec body total_body = function | [] -> body, total_body (* Hum, -1 means never taken, needed for ``partial'' to be correct *) | (-1,pm)::rem -> c_rec body total_body rem | (i,pm)::rem -> let ctx_i,total_rem = jumps_extract i total_body in begin match ctx_i with | [] -> c_rec body total_body rem | _ -> try let li,total_i = comp_fun (match rem with [] -> partial | _ -> Partial) ctx_i arg pm in c_rec (Lstaticcatch (body,(i,[]),li)) (jumps_union total_i total_rem) rem with | Unused -> c_rec (Lstaticcatch (body,(i,[]),lambda_unit)) total_rem rem end in try let first_lam,total = comp_fun Partial ctx arg first_match in c_rec first_lam total rem with Unused -> match next_matchs with | [] -> raise Unused | (_,x)::xs -> comp_match_handlers comp_fun partial ctx arg x xs (* The main compilation function. Input: repr=used for inserting debug events partial=exhaustiveness information from Parmatch ctx=a context m=a pattern matching Output: a lambda term, a jump summary {..., exit number -> context, .. } *) let rec compile_match repr partial ctx m = match m with | { cases = [] } -> comp_exit ctx m | { cases = ([], action) :: rem } -> if is_guarded action then begin let (lambda, total) = compile_match None partial ctx { m with cases = rem } in event_branch repr (patch_guarded lambda action), total end else (event_branch repr action, jumps_empty) | { args = (arg, str)::argl } -> let v,newarg = match arg with | Lvar v -> v,arg | _ -> let v = name_pattern "match" m.cases in v,Lvar v in let first_match,rem = separe (Some v) { m with args = (newarg, Alias) :: argl } in let (lam, total) = comp_match_handlers (do_compile_matching repr) partial ctx newarg first_match rem in bind_check str v arg lam, total | _ -> assert false (* verbose version of do_compile_matching, for debug *) and do_compile_matching_pr repr partial ctx arg x = prerr_string "COMPILE: " ; prerr_endline (match partial with Partial -> "Partial" | Total -> "Total") ; prerr_endline "MATCH" ; pretty_ext x ; prerr_endline "CTX" ; pretty_ctx ctx ; let (_, jumps) as r = do_compile_matching repr partial ctx arg x in prerr_endline "JUMPS" ; pretty_jumps jumps ; r and do_compile_matching repr partial ctx arg {to_match=to_match; to_catch=to_catch} = let pat = what_is_cases to_match.cases in match pat.pat_desc with | Tpat_any -> assert (to_catch=[]) ; compile_no_test divide_var ctx_rshift repr partial ctx to_match to_catch | Tpat_tuple patl -> compile_no_test (divide_tuple (List.length patl) (normalize_pat pat)) ctx_combine repr partial ctx to_match to_catch | Tpat_record ((lbl,_)::_) -> compile_no_test (divide_record lbl.lbl_all (normalize_pat pat)) ctx_combine repr partial ctx to_match to_catch | Tpat_constant cst -> compile_test (compile_match repr partial) partial divide_constant (combine_constant arg cst partial) ctx to_match to_catch | Tpat_construct (cstr, _) -> compile_test (compile_match repr partial) partial divide_constructor (combine_constructor arg pat cstr partial) ctx to_match to_catch | Tpat_array _ -> let kind = Typeopt.array_pattern_kind pat in compile_test (compile_match repr partial) partial (divide_array kind) (combine_array arg kind partial) ctx to_match to_catch | Tpat_variant(lab, _, row) -> compile_test (compile_match repr partial) partial (divide_variant row) (combine_variant row arg partial) ctx to_match to_catch | _ -> fatal_error "Matching.do_compile_matching" and compile_no_test divide up_ctx repr partial ctx to_match to_catch = let {pm=this_match ; ctx=this_ctx } = divide ctx to_match in let lambda,total = compile_match repr partial this_ctx this_match in let total = jumps_map up_ctx total in compile_orhandlers (compile_match repr partial) lambda total ctx to_catch (* The entry points *) (* had toplevel handler when appropriate *) let start_ctx n = [{left=[] ; right = omegas n}] let check_total total lambda i handler_fun = if jumps_is_empty total then lambda else begin Lstaticcatch(lambda, (i,[]), handler_fun()) end let compile_matching loc repr handler_fun arg pat_act_list partial = match partial with | Partial -> let raise_num = next_raise_count () in let pm = { cases = List.map (fun (pat, act) -> ([pat], act)) pat_act_list; args = [arg, Strict] ; default = raise_num,[[[omega]],raise_num]} in begin try let (lambda, total) = compile_match repr partial (start_ctx 1) pm in check_total total lambda raise_num handler_fun with | Unused -> handler_fun() end | Total -> let pm = { cases = List.map (fun (pat, act) -> ([pat], act)) pat_act_list; args = [arg, Strict] ; default = (-1,[])} in let (lambda, total) = compile_match repr partial (start_ctx 1) pm in assert (jumps_is_empty total) ; lambda let partial_function loc () = (* [Location.get_pos_info] is too expensive *) let fname = match loc.Location.loc_start.Lexing.pos_fname with | "" -> !Location.input_name | x -> x in let pos = loc.Location.loc_start in let line = pos.Lexing.pos_lnum in let char = pos.Lexing.pos_cnum - pos.Lexing.pos_bol in Lprim(Praise, [Lprim(Pmakeblock(0, Immutable), [transl_path Predef.path_match_failure; Lconst(Const_block(0, [Const_base(Const_string fname); Const_base(Const_int line); Const_base(Const_int char)]))])]) let for_function loc repr param pat_act_list partial = compile_matching loc repr (partial_function loc) param pat_act_list partial (* In the following two cases, exhaustiveness info is not available! *) let for_trywith param pat_act_list = compile_matching Location.none None (fun () -> Lprim(Praise, [param])) param pat_act_list Partial let for_let loc param pat body = compile_matching loc None (partial_function loc) param [pat, body] Partial (* Handling of tupled functions and matches *) let flatten_pattern size p = match p.pat_desc with Tpat_tuple args -> args | Tpat_any -> omegas size | _ -> raise Cannot_flatten let rec flatten_pat_line size p k = match p.pat_desc with | Tpat_any -> omegas size::k | Tpat_tuple args -> args::k | Tpat_or (p1,p2,_) -> flatten_pat_line size p1 (flatten_pat_line size p2 k) | _ -> fatal_error "Matching.flatten_pat_line" let flatten_cases size cases = List.map (fun (ps,action) -> match ps with | [p] -> flatten_pattern size p,action | _ -> fatal_error "Matching.flatten_case") cases let flatten_matrix size pss = List.fold_right (fun ps r -> match ps with | [p] -> flatten_pat_line size p r | _ -> fatal_error "Matching.flatten_matrix") pss [] let flatten_def size (exit,def) = exit, List.map (fun (pss,i) -> flatten_matrix size pss,i) def let flatten_pm size al pm = {args = al ; cases = flatten_cases size pm.cases ; default = flatten_def size pm.default} let flatten_extended size idl ext = {to_match = flatten_pm size idl ext.to_match ; to_catch = List.map (fun (mat,i,vars,pm) -> flatten_matrix size mat,i,vars,pm) ext.to_catch} let compile_flattened repr partial ctx _ {to_match=to_match ; to_catch=to_catch} = let lambda,total = compile_match repr partial ctx to_match in compile_orhandlers (compile_match repr partial) lambda total ctx to_catch let for_tupled_function loc paraml pats_act_list partial = let raise_num = next_raise_count () in let omegas = [List.map (fun _ -> omega) paraml] in let pm = { cases = pats_act_list; args = List.map (fun id -> (Lvar id, Strict)) paraml ; default = raise_num,[omegas,raise_num] } in try let (lambda, total) = compile_match None partial (start_ctx (List.length paraml)) pm in check_total total lambda raise_num (partial_function loc) with | Unused -> partial_function loc () let for_multiple_match loc paraml pat_act_list partial = let repr = None in let raise_num,pm1 = match partial with | Partial -> let raise_num = next_raise_count () in raise_num, { cases = List.map (fun (pat, act) -> ([pat], act)) pat_act_list; args = [Lprim(Pmakeblock(0, Immutable), paraml), Strict] ; default = raise_num,[[[omega]],raise_num] } | _ -> -1, { cases = List.map (fun (pat, act) -> ([pat], act)) pat_act_list; args = [Lprim(Pmakeblock(0, Immutable), paraml), Strict] ; default = -1,[] } in try try let next,nexts = separe None pm1 in let size = List.length paraml in let idl = List.map (fun _ -> Ident.create "match") paraml in let al = List.map (fun id -> (Lvar id, Alias)) idl in let omegas = [List.map (fun _ -> omega) idl] in let flat_next = flatten_extended size al next and flat_nexts = List.map (fun (i,x) -> i,flatten_extended size al x) nexts in let lambda,total = comp_match_handlers (compile_flattened repr) partial (start_ctx size) staticfail flat_next flat_nexts in List.fold_right2 (bind Strict) idl paraml (match partial with | Partial -> check_total total lambda raise_num (partial_function loc) | Total -> assert (jumps_is_empty total) ; lambda) with Cannot_flatten -> let (lambda, total) = compile_match None partial (start_ctx 1) pm1 in begin match partial with | Partial -> check_total total lambda raise_num (partial_function loc) | Total -> assert (jumps_is_empty total) ; lambda end with Unused -> partial_function loc ()