1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
|
(* Js_of_ocaml compiler
* http://www.ocsigen.org/js_of_ocaml/
* Copyright (C) 2010 Jérôme Vouillon
* Laboratoire PPS - CNRS Université Paris Diderot
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)
open Util
let parse_annot loc s =
let buf = Lexing.from_string s in
try
match Annot_parser.annot Annot_lexer.initial buf with
| `Requires (_,l) -> Some (`Requires (Some loc,l))
| `Provides (_,n,k,ka) -> Some (`Provides (Some loc,n,k,ka))
| `Version (_,l) -> Some (`Version (Some loc, l))
with
| Not_found -> None
| exc ->
(* Format.eprintf "Not found for %s : %s @." (Printexc.to_string exc) s; *)
None
let error s = Format.kprintf (fun s -> failwith s) s
let parse_file f =
let file =
try
match Util.path_require_findlib f with
| Some f ->
let pkg,f' = match Util.split Filename.dir_sep f with
| [] -> assert false
| [f] -> "js_of_ocaml",f
| pkg::l -> pkg, List.fold_left Filename.concat "" l in
Filename.concat (Util.find_pkg_dir pkg) f'
| None -> f
with
| Not_found ->
error "cannot find file '%s'. @." f
| Sys_error s ->
error "%s@." s
in
let lex = Parse_js.lexer_from_file ~rm_comment:false file in
let status,lexs = Parse_js.lexer_fold (fun (status,lexs) t ->
match t with
| Js_token.TComment (info,str) -> begin
match parse_annot info str with
| None -> (status,lexs)
| Some a ->
match status with
| `Annot annot -> `Annot (a::annot),lexs
| `Code (an,co) -> `Annot [a], ((List.rev an,List.rev co)::lexs)
end
| _ when Js_token.is_comment t -> (status,lexs)
| c -> match status with
| `Code (annot,code) -> `Code (annot,c::code),lexs
| `Annot (annot) -> `Code(annot,[c]),lexs
) (`Annot [],[]) lex in
let lexs = match status with
| `Annot _ -> lexs
| `Code(annot,code) -> (List.rev annot,List.rev code)::lexs in
let res = List.rev_map (fun (annot,code) ->
let lex = Parse_js.lexer_from_list code in
try
let code = Parse_js.parse lex in
let req,has_provide,versions = List.fold_left (fun (req,has_provide,versions) a -> match a with
| `Provides (pi,name,kind,ka) ->
req,Some (pi,name,kind,ka),versions
| `Requires (_,mn) -> (mn@req),has_provide,versions
| `Version (_,l) -> req,has_provide,l::versions
) ([],None,[]) annot in
has_provide,req,versions,code
with Parse_js.Parsing_error pi ->
error "cannot parse file %S (orig:%S from l:%d, c:%d)@."
f pi.Parse_info.name pi.Parse_info.line pi.Parse_info.col)
lexs in
res
let loc pi = match pi with
| None -> "unknown location"
| Some pi -> Printf.sprintf "%s:%d" pi.Parse_info.name pi.Parse_info.line
class check_and_warn name pi = object(m)
inherit Js_traverse.free as super
method merge_info from =
let def = from#get_def_name in
let use = from#get_use_name in
let diff = StringSet.diff def use in
let diff = StringSet.remove name diff in
let diff = StringSet.filter (fun s -> String.length s <> 0 && s.[0] <> '_') diff in
if not (StringSet.is_empty diff)
then Format.eprintf "WARN unused for primitive %s at %s:@. %s@."
name (loc pi) (String.concat ", " (StringSet.elements diff));
super#merge_info from
end
let check_primitive name pi code req =
let free =
if Option.Optim.warn_unused ()
then new check_and_warn name pi
else new Js_traverse.free in
let _code = free#program code in
let freename = free#get_free_name in
let freename = List.fold_left (fun freename x -> StringSet.remove x freename) freename req in
let freename = StringSet.diff freename Reserved.keyword in
let freename = StringSet.diff freename Reserved.provided in
let freename = StringSet.remove Option.global_object freename in
if not(StringSet.mem name free#get_def_name)
then begin
Format.eprintf "warning: primitive code does not define value with the expected name: %s (%s)@." name (loc pi)
end;
if not(StringSet.is_empty freename)
then begin
Format.eprintf "warning: free variables in primitive code %S (%s)@." name (loc pi);
Format.eprintf "vars: %s@." (String.concat ", " (StringSet.elements freename))
end
let version_match =
List.for_all (fun (op,str) ->
op (Util.Version.(compare current (split str))) 0
)
type state = {
ids : IntSet.t;
codes : Javascript.program list ;
}
let last_code_id = ref 0
let provided = Hashtbl.create 31
let provided_rev = Hashtbl.create 31
let code_pieces = Hashtbl.create 31
let always_included = ref []
let add_file f =
List.iter
(fun (provide,req,versions,code) ->
incr last_code_id;
let id = !last_code_id in
let vmatch = match versions with
| [] -> true
| l -> List.exists version_match l in
if vmatch
then begin
(match provide with
| None -> always_included := id :: !always_included
| Some (pi,name,kind,ka) ->
let module J = Javascript in
let rec find = function
| [] -> None
| J.Function_declaration (J.S{J.name=n},l,_,_)::_ when name=n -> Some(List.length l)
| _::rem -> find rem in
let arity = find code in
Primitive.register name kind ka arity;
if Hashtbl.mem provided name
then begin
let ploc = snd(Hashtbl.find provided name) in
Format.eprintf "warning: overriding primitive %S\n old: %s\n new: %s@." name (loc ploc) (loc pi)
end;
Hashtbl.add provided name (id,pi);
Hashtbl.add provided_rev id (name,pi);
check_primitive name pi code req
);
Hashtbl.add code_pieces id (code, req)
end
)
(parse_file f)
let get_provided () =
Hashtbl.fold (fun k _ acc -> StringSet.add k acc) provided StringSet.empty
let check_deps () =
let provided = get_provided () in
Hashtbl.iter (fun id (code,requires) ->
let traverse = new Js_traverse.free in
let _js = traverse#program code in
let free = traverse#get_free_name in
let requires = List.fold_right StringSet.add requires StringSet.empty in
let real = StringSet.inter free provided in
let missing = StringSet.diff real requires in
if not (StringSet.is_empty missing)
then begin
try
let (name,ploc) = Hashtbl.find provided_rev id in
Format.eprintf "code providing %s (%s) may miss dependencies: %s\n"
name
(loc ploc)
(String.concat ", " (StringSet.elements missing))
with Not_found ->
(* there is no //Provides for this piece of code *)
(* FIXME handle missing deps in this case *)
()
end
) code_pieces
let load_files l =
List.iter add_file l;
check_deps ()
(* resolve *)
let rec resolve_dep_name_rev visited path nm =
let id =
try
fst(Hashtbl.find provided nm)
with Not_found ->
error "missing dependency '%s'@." nm
in
resolve_dep_id_rev visited path id
and resolve_dep_id_rev visited path id =
if IntSet.mem id visited.ids then begin
if List.memq id path
then error "circular dependency: %s" (String.concat ", " (List.map (fun id -> fst(Hashtbl.find provided_rev id)) path));
visited
end else begin
let path = id :: path in
let (code, req) = Hashtbl.find code_pieces id in
let visited = {visited with ids = IntSet.add id visited.ids} in
let visited =
List.fold_left
(fun visited nm -> resolve_dep_name_rev visited path nm)
visited req in
let visited = {visited with codes = code::visited.codes} in
visited
end
let init () =
List.fold_left
(fun visited id -> resolve_dep_id_rev visited [] id)
{ids=IntSet.empty; codes=[]} !always_included
let resolve_deps ?(linkall = false) visited_rev used =
(* link the special files *)
let missing,visited_rev =
if linkall
then
begin
(* link all primitives *)
let prog,set =
Hashtbl.fold (fun nm (id,_) (visited,set) ->
resolve_dep_name_rev visited [] nm,
StringSet.add nm set
)
provided
(visited_rev,StringSet.empty) in
let missing = StringSet.diff used set in
missing,prog
end
else (* link used primitives *)
StringSet.fold
(fun nm (missing, visited)->
if Hashtbl.mem provided nm then
(missing, resolve_dep_name_rev visited [] nm)
else
(StringSet.add nm missing, visited))
used (StringSet.empty, visited_rev) in
visited_rev, missing
let link program state = List.flatten (List.rev (program::state.codes))
|