(*************************************************************************) (* *) (* Objective Caml LablTk library *) (* *) (* Francois Rouaix, Francois Pessaux and Jun Furuse *) (* projet Cristal, INRIA Rocquencourt *) (* Jacques Garrigue, Kyoto University RIMS *) (* *) (* Copyright 1999 Institut National de Recherche en Informatique et *) (* en Automatique and Kyoto University. All rights reserved. *) (* This file is distributed under the terms of the GNU Library *) (* General Public License. *) (* *) (*************************************************************************) (* $Id$ *) open Tables (* CONFIGURE *) (* if you set it true, ImagePhoto and ImageBitmap will annoy you... *) let safetype = true let labeloff :at l = match l with "",t -> t | l ,t -> raise (Failure ("labeloff : " ^ l ^ " at " ^ at)) let labelstring l = match l with "" -> "" | _ -> l ^ ":" let labelprint :w l = w (labelstring l) let small s = let sout = ref "" in for i=0 to String.length s - 1 do let c = if s.[i] >= 'A' && s.[i] <= 'Z' then Char.chr(Char.code(s.[i]) - (Char.code 'A' - Char.code 'a')) else s.[i] in sout := !sout ^ (String.make len:1 c) done; !sout let small_ident s = let idents = ["to"; "raise"; "in"; "class"; "new"] in let s = small s in if List.mem item:s idents then (String.make len:1 s.[0])^s else s let gettklabel fc = match fc.template with ListArg( StringArg s :: _ ) -> if (try s.[0] = '-' with _ -> false) then String.sub s pos:1 len:(String.length s - 1) else if s = "" then small fc.ml_name else small s | _ -> raise (Failure "gettklabel") let count item:x l = let count = ref 0 in List.iter fun:(fun y -> if x = y then incr count) l; !count (* Extract all types from a template *) let rec types_of_template = function StringArg _ -> [] | TypeArg (l,t) -> [l,t] | ListArg l -> List.flatten (List.map fun:types_of_template l) | OptionalArgs (l,tl,_) -> begin match List.flatten (List.map fun:types_of_template tl) with ["",t] -> ["?"^l,t] | [_,_] -> raise (Failure "0 label required") | _ -> raise (Failure "0 or more than 1 args in for optionals") end (* * Pretty print a type * used to write ML type definitions *) let ppMLtype ?(:any=false) ?(:return=false) ?(:def=false) ?(:counter=ref 0) = let rec ppMLtype = function Unit -> "unit" | Int -> "int" | Float -> "float" | Bool -> "bool" | Char -> "char" | String -> "string" (* new *) | List (Subtype (sup,sub)) -> if return then sub^"_"^sup^" list" else begin try let typdef = Hashtbl.find types_table key:sup in let fcl = List.assoc key:sub typdef.subtypes in let tklabels = List.map fun:gettklabel fcl in let l = List.map fcl fun: begin fun fc -> "?" ^ begin let p = gettklabel fc in if count item:p tklabels > 1 then small fc.ml_name else p end ^ ":" ^ let l = types_of_template fc.template in match l with [] -> "unit" | [lt] -> ppMLtype (labeloff lt at:"ppMLtype") | l -> "(" ^ String.concat sep:"*" (List.map l fun:(fun lt -> ppMLtype (labeloff lt at:"ppMLtype"))) ^ ")" end in String.concat sep:" ->\n" l with Not_found -> Printf.eprintf "ppMLtype %s/%s\n" sup sub; exit (-1) end | List ty -> (ppMLtype ty) ^ " list" | Product tyl -> String.concat sep:" * " (List.map fun:ppMLtype tyl) | Record tyl -> String.concat sep:" * " (List.map tyl fun:(fun (l,t) -> labelstring l ^ ppMLtype t)) | Subtype ("widget", sub) -> sub ^ " widget" | UserDefined "widget" -> if any then "any widget" else let c = String.make len:1 (Char.chr(Char.code 'a' + !counter)) in incr counter; "'" ^ c ^ " widget" | UserDefined s -> (* a bit dirty hack for ImageBitmap and ImagePhoto *) begin try let typdef = Hashtbl.find types_table key:s in if typdef.variant then if return then try "[>" ^ String.concat sep:"|" (List.map typdef.constructors fun: begin fun c -> "`" ^ c.var_name ^ (match types_of_template c.template with [] -> "" | l -> " " ^ ppMLtype (Product (List.map l fun:(labeloff at:"ppMLtype UserDefined")))) end) ^ "]" with Not_found -> (prerr_endline ("ppMLtype "^s^ " ?"); s) else if not def & List.length typdef.constructors > 1 then "#" ^ s else s else s with Not_found -> s end | Subtype (s,s') -> s'^"_"^s | Function (Product tyl) -> raise (Failure "Function (Product tyl) ? ppMLtype") | Function (Record tyl) -> "(" ^ String.concat sep:" -> " (List.map tyl fun:(fun (l,t) -> labelstring l ^ ppMLtype t)) ^ " -> unit)" | Function ty -> "(" ^ (ppMLtype ty) ^ " -> unit)" | As (_, s) -> s in ppMLtype (* Produce a documentation version of a template *) let rec ppTemplate = function StringArg s -> s | TypeArg (l,t) -> "<" ^ ppMLtype t ^ ">" | ListArg l -> "{" ^ String.concat sep:" " (List.map fun:ppTemplate l) ^ "}" | OptionalArgs (l,tl,d) -> "?" ^ l ^ "{" ^ String.concat sep:" " (List.map fun:ppTemplate tl) ^ "}[<" ^ String.concat sep:" " (List.map fun:ppTemplate d) ^ ">]" let doc_of_template = function ListArg l -> String.concat sep:" " (List.map fun:ppTemplate l) | t -> ppTemplate t (* * Type definitions *) (* Write an ML constructor *) let write_constructor :w {ml_name = mlconstr; template = t} = w mlconstr; begin match types_of_template t with [] -> () | l -> w " of "; w (ppMLtype any:true (Product (List.map l fun:(labeloff at:"write_constructor")))) end; w " (* tk option: "; w (doc_of_template t); w " *)" (* Write a rhs type decl *) let write_constructors :w = function [] -> fatal_error "empty type" | x::l -> write_constructor :w x; List.iter l fun: begin fun x -> w "\n | "; write_constructor :w x end (* Write an ML variant *) let write_variant :w {ml_name = mlconstr; var_name = varname; template = t} = w "`"; w varname; begin match types_of_template t with [] -> () | l -> w " "; w (ppMLtype any:true def:true (Product (List.map l fun:(labeloff at:"write_variant")))) end; w " (* tk option: "; w (doc_of_template t); w " *)" let write_variants :w = function [] -> fatal_error "empty variants" | x::l -> write_variant :w x; List.iter l fun: begin fun x -> w "\n | "; write_variant :w x end (* Definition of a type *) let write_type intf:w impl:w' name def:typdef = (* Only needed if no subtypes, otherwise use optionals *) if typdef.subtypes = [] then begin w "(* Variant type *)\n"; w ("type "^name^" = [\n "); write_variants :w (sort_components typdef.constructors); w "\n]\n\n" end (************************************************************) (* Converters *) (************************************************************) let rec converterTKtoCAML argname as:ty = match ty with Int -> "int_of_string " ^ argname | Float -> "float_of_string " ^ argname | Bool -> "(match " ^ argname ^" with \"1\" -> true | \"0\" -> false | s -> Pervasives.raise (Invalid_argument (\"cTKtoCAMLbool\" ^ s)))" | Char -> "String.get "^argname ^" 0" | String -> argname | UserDefined s -> "cTKtoCAML"^s^" "^argname | Subtype ("widget",s') -> "(Obj.magic (cTKtoCAMLwidget "^argname^") : "^s'^" widget)" | Subtype (s,s') -> "cTKtoCAML"^s'^"_"^s^" "^argname | List ty -> begin match type_parser_arity ty with OneToken -> "(List.map (function x -> " ^ (converterTKtoCAML "x) " as:ty) ^ argname ^ ")" | MultipleToken -> "iterate_converter (function x -> " ^ (converterTKtoCAML "x) " as:ty) ^ argname ^ ")" end | As (ty, _) -> converterTKtoCAML argname as:ty | t -> (prerr_endline ("ERROR with "^argname^" "^ppMLtype t);fatal_error "converterTKtoCAML") (*******************************) (* Wrappers *) (*******************************) let varnames :prefix n = let rec var i = if i > n then [] else (prefix^(string_of_int i)) :: (var (succ i)) in var 1 (* * generate wrapper source for callbacks * transform a function ... -> unit in a function : unit -> unit * using primitives arg_ ... from the protocol * Warning: sequentiality is important in generated code * TODO: remove arg_ stuff and process lists directly ? *) let rec wrapper_code fname of:ty = match ty with Unit -> "(function _ -> "^fname^" ())" | As (ty, _) -> wrapper_code fname of:ty | ty -> "(function args ->\n " ^ begin match ty with Product tyl -> raise (Failure "Product -> record was done. ???") | Record tyl -> (* variables for each component of the product *) let vnames = varnames prefix:"a" (List.length tyl) in (* getting the arguments *) let readarg = List.map2 vnames tyl fun: begin fun v (l,ty) -> match type_parser_arity ty with OneToken -> "let ("^v^",args) = " ^ converterTKtoCAML "(List.hd args)" as:ty ^ ", List.tl args in\n " | MultipleToken -> "let ("^v^",args) = " ^ converterTKtoCAML "args" as:ty ^ " in\n " end in String.concat sep:"" readarg ^ fname ^ " " ^ String.concat sep:" " (List.map2 fun:(fun v (l,_) -> labelstring l^v) vnames tyl) (* all other types are read in one operation *) | List ty -> fname ^ "(" ^ converterTKtoCAML "args" as:ty ^ ")" | String -> fname ^ "(" ^ converterTKtoCAML "(List.hd args)" as:ty ^ ")" | ty -> begin match type_parser_arity ty with OneToken -> fname ^ "(" ^ converterTKtoCAML "(List.hd args)" as:ty ^ ")" | MultipleToken -> "let (v,_) = " ^ converterTKtoCAML "args" as:ty ^ " in\n " ^ fname ^ " v" end end ^ ")" (*************************************************************) (* Parsers *) (* are required only for values returned by commands and *) (* functions (table is computed by the parser) *) (* Tuples/Lists are Ok if they don't contain strings *) (* they will be returned as list of strings *) (* Can we generate a "parser" ? -> all constructors are unit and at most one int and one string, with null constr *) type parser_pieces = { mutable zeroary : (string * string) list ; (* kw string, ml name *) mutable intpar : string list; (* one at most, mlname *) mutable stringpar : string list (* idem *) } type mini_parser = NoParser | ParserPieces of parser_pieces let can_generate_parser constructors = let pp = {zeroary = []; intpar = []; stringpar = []} in if List.for_all constructors pred: begin fun c -> match c.template with ListArg [StringArg s] -> pp.zeroary <- (s,"`" ^ c.var_name):: pp.zeroary; true | ListArg [TypeArg(_,Int)] | ListArg[TypeArg(_,Float)] -> if pp.intpar <> [] then false else (pp.intpar <- ["`" ^ c.var_name]; true) | ListArg [TypeArg(_,String)] -> if pp.stringpar <> [] then false else (pp.stringpar <- ["`" ^ c.var_name]; true) | _ -> false end then ParserPieces pp else NoParser (* We can generate parsers only for simple types *) (* we should avoid multiple walks *) let write_TKtoCAML :w name def:typdef = if typdef.parser_arity = MultipleToken then prerr_string ("You must write cTKtoCAML" ^ name ^ " : string list ->" ^ name ^ " * string list\n") else let write :consts :name = match can_generate_parser consts with NoParser -> prerr_string ("You must write cTKtoCAML" ^ name ^" : string ->"^name^"\n") | ParserPieces pp -> w ("let cTKtoCAML"^name^" n =\n"); (* First check integer *) if pp.intpar <> [] then begin w (" try " ^ List.hd pp.intpar ^ " (int_of_string n)\n"); w (" with _ ->\n") end; w (" match n with\n"); let first = ref true in List.iter pp.zeroary fun: begin fun (tk,ml) -> if not !first then w " | " else w " "; first := false; w "\""; w tk; w "\" -> "; w ml; w "\n" end; let final = if pp.stringpar <> [] then "n -> " ^ List.hd pp.stringpar ^ " n" else " s -> Pervasives.raise (Invalid_argument (\"cTKtoCAML" ^ name ^ ": \" ^s))" in if not !first then w " | " else w " "; w final; w "\n\n" in begin write :name consts:typdef.constructors; List.iter typdef.subtypes fun: begin fun (subname,consts) -> write name:(subname^"_"^name) :consts end end (******************************) (* Converters *) (******************************) (* Produce an in-lined converter Caml -> Tk for simple types *) (* the converter is a function of type: -> string *) let rec converterCAMLtoTK :context_widget argname as:ty = match ty with Int -> "TkToken (string_of_int " ^ argname ^ ")" | Float -> "TkToken (string_of_float " ^ argname ^ ")" | Bool -> "if "^argname^" then TkToken \"1\" else TkToken \"0\"" | Char -> "TkToken (Char.escaped " ^ argname ^ ")" | String -> "TkToken " ^ argname | As (ty, _) -> converterCAMLtoTK :context_widget argname as:ty | UserDefined s -> let name = "cCAMLtoTK"^s^" " in let args = argname in let args = if requires_widget_context s then context_widget^" "^args else args in name^args | Subtype ("widget",s') -> let name = "cCAMLtoTKwidget" in let args = "("^argname^" : "^s'^" widget)" in name^args | Subtype (s,s') -> let name = "cCAMLtoTK"^s'^"_"^s^" " in let args = if safetype then "("^argname^" : #"^s'^"_"^s^")" else argname in let args = if requires_widget_context s then context_widget^" "^args else args in name^args | Function _ -> fatal_error "unexpected function type in converterCAMLtoTK" | Unit -> fatal_error "unexpected unit type in converterCAMLtoTK" | Product _ -> fatal_error "unexpected product type in converterCAMLtoTK" | Record _ -> fatal_error "unexpected product type in converterCAMLtoTK" | List ty -> fatal_error "unexpected list type in converterCAMLtoTK" (* * Produce a list of arguments from a template * The idea here is to avoid allocation as much as possible * *) let code_of_template :context_widget ?(func:funtemplate=false) template = let catch_opts = ref ("","") in (* class name and first option *) let variables = ref [] in let variables2 = ref [] in let varcnter = ref 0 in let optionvar = ref None in let newvar1 l = match !optionvar with Some v -> optionvar := None; v | None -> incr varcnter; let v = "v" ^ (string_of_int !varcnter) in variables := (l,v) :: !variables; v in let newvar2 l = match !optionvar with Some v -> optionvar := None; v | None -> incr varcnter; let v = "v" ^ (string_of_int !varcnter) in variables2 := (l,v) :: !variables2; v in let newvar = ref newvar1 in let rec coderec = function StringArg s -> "TkToken\"" ^ s ^ "\"" | TypeArg (_,List (Subtype (sup,sub) as ty)) -> let typdef = Hashtbl.find key:sup types_table in let classdef = List.assoc key:sub typdef.subtypes in let lbl = gettklabel (List.hd classdef) in catch_opts := (sub^"_"^sup, lbl); newvar := newvar2; "TkTokenList opts" | TypeArg (l,List ty) -> "TkTokenList (List.map fun:(function x -> " ^ converterCAMLtoTK :context_widget "x" as:ty ^ ") " ^ !newvar l ^ ")" | TypeArg (l,Function tyarg) -> "let id = register_callback " ^context_widget ^ " callback: "^ wrapper_code (!newvar l) of:tyarg ^ " in TkToken (\"camlcb \"^id)" | TypeArg (l,ty) -> converterCAMLtoTK :context_widget (!newvar l) as:ty | ListArg l -> "TkQuote (TkTokenList [" ^ String.concat sep:";\n " (List.map fun:coderec l) ^ "])" | OptionalArgs (l,tl,d) -> let nv = !newvar ("?"^l) in optionvar := Some nv; (* Store *) let argstr = String.concat sep:"; " (List.map fun:coderec tl) in let defstr = String.concat sep:"; " (List.map fun:coderec d) in "TkTokenList (match "^ nv ^" with\n" ^ " Some " ^ nv ^ " -> [" ^ argstr ^ "]\n" ^ " | None -> [" ^ defstr ^ "])" in let code = if funtemplate then match template with ListArg l -> "[|" ^ String.concat sep:";\n " (List.map fun:coderec l) ^ "|]" | _ -> "[|" ^ coderec template ^ "|]" else match template with ListArg [x] -> coderec x | ListArg l -> "TkTokenList [" ^ String.concat sep:";\n " (List.map fun:coderec l) ^ "]" | _ -> coderec template in code , List.rev !variables, List.rev !variables2, !catch_opts (* * Converters for user defined types *) (* For each case of a concrete type *) let write_clause :w :context_widget comp = let warrow () = w " -> " in w "`"; w comp.var_name; let code, variables, variables2, (co, _) = code_of_template :context_widget comp.template in (* no subtype I think ... *) if co <> "" then raise (Failure "write_clause subtype ?"); begin match variables with [] -> warrow() | [x] -> w " "; w (labeloff x at:"write_clause"); warrow() | l -> w " ( "; w (String.concat sep:", " (List.map fun:(labeloff at:"write_clause") l)); w ")"; warrow() end; w code (* The full converter *) let write_CAMLtoTK :w def:typdef ?(safetype:st = true) name = let write_one name constrs = w ("let cCAMLtoTK"^name); let context_widget = if typdef.requires_widget_context then begin w " w"; "w" end else "dummy" in if st then begin w " : "; if typdef.variant then w "#"; w name; w " -> tkArgs " end; w(" = function\n "); write_clause :w :context_widget (List.hd constrs); List.iter (List.tl constrs) fun:(fun c -> w "\n | "; write_clause :w :context_widget c); w "\n\n\n" in (* Only needed if no subtypes, otherwise use optionals *) if typdef.subtypes == [] then write_one name typdef.constructors else List.iter typdef.constructors fun: begin fun fc -> let code, vars, _, (co, _) = code_of_template context_widget:"dummy" fc.template in if co <> "" then fatal_error "optionals in optionals"; let vars = List.map fun:snd vars in w "let ccCAMLtoTK"; w name; w "_"; w (small fc.ml_name); w " ("; w (String.concat sep:"," vars); w ") =\n "; w code; w "\n\n" end (* Tcl does not really return "lists". It returns sp separated tokens *) let rec write_result_parsing :w = function List String -> w "(splitlist res)" | List ty -> w (" List.map fun: "^ converterTKtoCAML "(splitlist res)" as:ty) | Product tyl -> raise (Failure "Product -> record was done. ???") | Record tyl -> (* of course all the labels are "" *) let rnames = varnames prefix:"r" (List.length tyl) in w " let l = splitlist res in"; w ("\n if List.length l <> " ^ string_of_int (List.length tyl)); w ("\n then Pervasives.raise (TkError (\"unexpected result: \" ^ res))"); w ("\n else "); List.iter2 rnames tyl fun: begin fun r (l,ty) -> if l <> "" then raise (Failure "lables in return type!!!"); w (" let " ^ r ^ ", l = "); begin match type_parser_arity ty with OneToken -> w (converterTKtoCAML "(List.hd l)" as:ty); w (", List.tl l") | MultipleToken -> w (converterTKtoCAML "l" as:ty) end; w (" in\n") end; w (String.concat sep:"," rnames) | String -> w (converterTKtoCAML "res" as:String) | As (ty, _) -> write_result_parsing :w ty | ty -> match type_parser_arity ty with OneToken -> w (converterTKtoCAML "res" as:ty) | MultipleToken -> w (converterTKtoCAML "(splitlist res)" as:ty) let write_function :w def = w ("let "^def.ml_name); (* a bit approximative *) let context_widget = match def.template with ListArg (TypeArg(_,UserDefined("widget"))::_) -> "v1" | ListArg (TypeArg(_,Subtype("widget",_))::_) -> "v1" | _ -> "dummy" in let code, variables, variables2, (co, lbl) = code_of_template func:true :context_widget def.template in (* Arguments *) let uv, lv, ov = let rec replace_args :u :l :o = function [] -> u, l, o | ("",x)::ls -> replace_args u:(x::u) :l :o ls | (p,_ as x)::ls when p.[0] = '?' -> replace_args :u :l o:(x::o) ls | x::ls -> replace_args :u l:(x::l) :o ls in replace_args u:[] l:[] o:[] (List.rev (variables @ variables2)) in List.iter (lv@ov) fun:(fun (l,v) -> w " "; w (labelstring l); w v); if co <> "" then begin if lv = [] && ov = [] then w (" ?" ^ lbl ^ ":eta"); w " =\n"; w (co ^ "_optionals"); if lv = [] && ov = [] then w (" ?" ^ lbl ^ ":eta"); w " (fun opts"; if uv = [] then w " ()" else List.iter uv fun:(fun x -> w " "; w x); w " ->\n" end else begin List.iter uv fun:(fun x -> w " "; w x); if (ov <> [] || lv = []) && uv = [] then w " ()"; w " =\n" end; begin match def.result with Unit | As (Unit, _) -> w "tkEval "; w code; w ";()"; | ty -> w "let res = tkEval "; w code ; w " in \n"; write_result_parsing :w ty; end; if co <> "" then w ")"; w "\n\n" let write_create :w clas = (w "let create ?:name =\n" : unit); w (" "^ clas ^ "_options_optionals (fun opts parent ->\n"); w (" let w = new_atom \"" ^ clas ^ "\" :parent ?:name in\n"); w " tkEval [|"; w ("TkToken \"" ^ clas ^ "\";\n"); w (" TkToken (Widget.name w);\n"); w (" TkTokenList opts |];\n"); w (" w)\n\n\n") (* builtin-code: the file (without suffix) is in .template... *) (* not efficient, but hell *) let write_external :w def = match def.template with StringArg fname -> let ic = open_in_bin (fname ^ ".ml") in begin try while true do w (input_line ic); w "\n" done with End_of_file -> close_in ic end | _ -> raise (Compiler_Error "invalid external definition") let write_catch_optionals :w clas def:typdef = if typdef.subtypes = [] then () else List.iter typdef.subtypes fun: begin fun (subclass, classdefs) -> w ("let " ^ subclass ^"_"^ clas ^ "_optionals f = fun\n"); let tklabels = List.map fun:gettklabel classdefs in let l = List.map classdefs fun: begin fun fc -> (* let code, vars, _, (co, _) = code_of_template context_widget:"dummy" fc.template in if co <> "" then fatal_error "optionals in optionals"; *) let p = gettklabel fc in (if count item:p tklabels > 1 then small fc.ml_name else p), small_ident fc.ml_name (* used as labels *), small fc.ml_name end in let p = List.map l fun: begin fun (s, si, _) -> if s = si then " ?:" ^ s else " ?" ^ s ^ ":" ^ si end in let v = List.map l fun: begin fun (_, si, s) -> (* let vars = List.map fun:snd vars in let vars = String.concat sep:"," vars in "(maycons (fun (" ^ vars ^ ") -> " ^ code ^ ") " ^ si *) "(maycons ccCAMLtoTK" ^ clas ^ "_" ^ s ^ " " ^ si end in w (String.concat sep:"\n" p); w " ->\n"; w " f "; w (String.concat sep:"\n " v); w "\n []"; w (String.make len:(List.length v) ')'); w "\n\n" end