123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581(* 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!StdlibmoduleFragment=structtypefragment_={provides:(Parse_info.t*string*Primitive.kind*Primitive.kind_arglistoption)option;requires:stringlist;version_constraint:((int->int->bool)*string)listlist;weakdef:bool;always:bool;code:Javascript.program;js_string:booloption;fragment_target:Target_env.toption}typet=[`Always_includeofJavascript.program|`Someoffragment_]letprovides=function|`Always_include_->[]|`Some{provides=Some(_,name,_,_);_}->[name]|`Some_->[]endletlocpi=matchpiwith|{Parse_info.src=Somesrc;line;_}|{Parse_info.name=Somesrc;line;_}->Printf.sprintf"%s:%d"srcline|_->"unknown location"leterrors=Format.ksprintf(funs->failwiths)sletparse_from_lex~filenamelex=letprogram,_=tryParse_js.parse'lexwithParse_js.Parsing_errorpi->letname=matchpiwith|{Parse_info.src=Somex;_}|{Parse_info.name=Somex;_}->x|_->"??"inerror"cannot parse file %S (orig:%S from l:%d, c:%d)@."filenamenamepi.Parse_info.linepi.Parse_info.colinletreccollect_without_annotacc=function|[]->List.revacc,[]|(x,[])::program->collect_without_annot(x::acc)program|(_,_::_)::_asprogram->List.revacc,programinletreccollectaccprogram=matchprogramwith|[]->List.revacc|(x,[])::program->letcode,program=collect_without_annot[x]programincollect(([],code)::acc)program|(x,annots)::program->letcode,program=collect_without_annot[x]programincollect((annots,code)::acc)programinletblocks=collect[]programinletres=List.rev_mapblocks~f:(fun(annot,code)->matchannotwith|[]->`Always_includecode|annot->letinitial_fragment:Fragment.fragment_={provides=None;requires=[];version_constraint=[];weakdef=false;always=false;code;js_string=None;fragment_target=None}inletfragment=List.fold_leftannot~init:initial_fragment~f:(fun(fragment:Fragment.fragment_)(_,pi,a)->matchawith|`Provides(name,kind,ka)->{fragmentwithprovides=Some(pi,name,kind,ka)}|`Requiresmn->{fragmentwithrequires=mn@fragment.requires}|`Versionl->{fragmentwithversion_constraint=l::fragment.version_constraint}|`Weakdef->{fragmentwithweakdef=true}|`Always->{fragmentwithalways=true}|(`Ifnot"js-string"|`If"js-string")asi->letb=matchiwith|`If_->true|`Ifnot_->falseinifOption.is_somefragment.js_stringthenFormat.eprintf"Duplicated js-string in %s\n"(locpi);{fragmentwithjs_string=Someb}|`IfnamewhenOption.is_some(Target_env.of_stringname)->ifOption.is_somefragment.fragment_targetthenFormat.eprintf"Duplicated target_env in %s\n"(locpi);{fragmentwithfragment_target=Target_env.of_stringname}|`Ifname|`Ifnotname->Format.eprintf"Unkown flag %S in %s\n"name(locpi);fragment)in`Somefragment)inresletparse_builtinbuiltin=letfilename=Builtins.File.namebuiltininletcontent=Builtins.File.contentbuiltininletlexbuf=Lexing.from_stringcontentinletlexbuf={lexbufwithlex_curr_p={lexbuf.lex_curr_pwithpos_fname=filename}}inletlex=Parse_js.Lexer.of_lexbuflexbufinparse_from_lex~filenamelexletparse_stringstring=letlexbuf=Lexing.from_stringstringinletlex=Parse_js.Lexer.of_lexbuflexbufinparse_from_lex~filename:"<dummy>"lexletparse_filef=letfile=trymatchFindlib.path_require_findlibfwith|Somef->letpkg,f'=matchString.split~sep:Filename.dir_sepfwith|[]->assertfalse|pkg::l->pkg,List.fold_leftl~init:""~f:Filename.concatinFs.absolute_path(Filename.concat(Findlib.find_pkg_dirpkg)f')|None->Fs.absolute_pathfwith|Not_found->error"cannot find file '%s'. @."f|Sys_errors->error"%s@."sinletlex=Parse_js.Lexer.of_filefileinparse_from_lex~filename:filelexclasscheck_and_warnnamepi=objectinheritJs_traverse.freeassupermethodmerge_infofrom=letdef=from#get_def_nameinletuse=from#get_use_nameinletdiff=StringSet.diffdefuseinletdiff=StringSet.removenamediffinletdiff=StringSet.filter(funs->not(String.is_prefixs~prefix:"_"))diffinifnot(StringSet.is_emptydiff)thenwarn"WARN unused for primitive %s at %s:@. %s@."name(locpi)(String.concat~sep:", "(StringSet.elementsdiff));super#merge_infofromend(*
exception May_not_return
let all_return p =
let open Javascript in
let rec loop_st = function
| [] -> raise May_not_return
| [Return_statement (Some _), _] -> ()
| [Return_statement None, _] -> raise May_not_return
| [If_statement(_,th,el), _] ->
loop_st [th];
(match el with
| None -> raise May_not_return
| Some x -> loop_st [x])
| [Do_while_statement(st,_), _] -> loop_st [st]
| [While_statement(_,st), _] -> loop_st [st]
| [For_statement (_,_,_,st), _] -> loop_st [st]
| [Switch_statement (_,l,def), _] ->
List.iter (fun (_,sts) -> loop_st sts) l
| [Try_statement(b,_,_),_] -> loop_st b
| [Throw_statement _, _] -> ()
| x::xs -> loop_st xs
in
let rec loop_sources = function
| [] -> raise May_not_return
| [(Statement x, loc)] -> loop_st [(x, loc)]
| [_] -> raise May_not_return
| x::xs -> loop_sources xs
in
let rec loop_all_sources = function
| [] -> ()
| Statement x :: xs -> loop_all_sources xs
| Function_declaration(_,_,b,_) :: xs ->
loop_sources b;
loop_all_sources xs in
try loop_all_sources p; true with May_not_return -> false
*)letcheck_primitive~namepi~code~requires=letfree=ifConfig.Flag.warn_unused()thennewcheck_and_warnnamepielsenewJs_traverse.freeinlet_code=free#programcodeinletfreename=free#get_free_nameinletfreename=List.fold_leftrequires~init:freename~f:(funfreenamex->StringSet.removexfreename)inletfreename=StringSet.difffreenameReserved.keywordinletfreename=StringSet.difffreenameReserved.providedinletfreename=StringSet.removeConstant.global_objectfreenameinifStringSet.memConstant.old_global_objectfreename&&false(* Don't warn yet, we want to give a transition period where both
"globalThis" and "joo_global_object" are allowed without extra
noise *)thenwarn"warning: %s: 'joo_global_object' is being deprecated, please use `globalThis` \
instead@."(locpi);letfreename=StringSet.removeConstant.old_global_objectfreenameinifnot(StringSet.memnamefree#get_def_name)thenwarn"warning: primitive code does not define value with the expected name: %s (%s)@."name(locpi);ifnot(StringSet.is_emptyfreename)then(warn"warning: free variables in primitive code %S (%s)@."name(locpi);warn"vars: %s@."(String.concat~sep:", "(StringSet.elementsfreename)))letversion_match=List.for_all~f:(fun(op,str)->opOcaml_version.(comparecurrent(splitstr))0)typealways_required={filename:string;program:Javascript.program;requires:stringlist}typestate={ids:IntSet.t;always_required_codes:always_requiredlist;codes:Javascript.programlist}typeoutput={runtime_code:Javascript.program;always_required_codes:always_requiredlist}typeprovided={id:int;pi:Parse_info.t;weakdef:bool;target_env:Target_env.t}letlast_code_id=ref0letalways_included=ref[]letprovided=Hashtbl.create31letprovided_rev=Hashtbl.create31letcode_pieces=Hashtbl.create31letreset()=last_code_id:=0;always_included:=[];Hashtbl.clearprovided;Hashtbl.clearprovided_rev;Hashtbl.clearcode_piecesclasstraverse_and_find_named_valuesall=objectinheritJs_traverse.mapasselfmethodexpressionx=letopenJavascriptin(matchxwith|ECall(EVar(S{name="caml_named_value";_}),[(EStr(v,_),`Not_spread)],_)->all:=StringSet.addv!all|_->());self#expressionxendletfind_named_valuecode=letall=refStringSet.emptyinletp=newtraverse_and_find_named_valuesallinignore(p#programcode);!allletload_fragment~target_env~filename(f:Fragment.t)=matchfwith|`Always_includecode->always_included:={filename;program=code;requires=[]}::!always_included;`Ok|`Some{provides;requires;version_constraint;weakdef;always;code;js_string;fragment_target}->(letignore_because_of_js_string=matchjs_string,Config.Flag.use_js_string()with|Sometrue,false|Somefalse,true->true|None,_|Sometrue,true|Somefalse,false->falseinletignore_because_of_version_constraint=matchversion_constraintwith|[]->false|l->not(List.existsl~f:version_match)inifignore_because_of_version_constraint||ignore_because_of_js_stringthen`Ignoredelsematchprovideswith|None->ifalwaysthen(always_included:={filename;program=code;requires}::!always_included;`Ok)elseerror"Found JavaScript code with neither `//Provides` nor `//Always` in file \
%S@."filename|Some(pi,name,kind,ka)->letfragment_target=Option.value~default:Target_env.Isomorphicfragment_targetinletexists=try`Exists(Hashtbl.findprovidedname)withNot_found->`Newinletis_updating=matchexists,(target_env:Target_env.t),(fragment_target:Target_env.t)with(* permit default, un-annotated symbols *)|`New,_,Isomorphic->true(* permit env specializations *)|`New,Nodejs,Nodejs|`New,Browser,Browser|`Exists{target_env=Isomorphic;_},Nodejs,Nodejs|`Exists{target_env=Isomorphic;_},Browser,Browser->true(* ignore non target matched envs *)|(`Exists_|`New),Isomorphic,(Browser|Nodejs)|(`Exists_|`New),Browser,Nodejs|(`Exists_|`New),Nodejs,Browser->false(* Ignore env unspecializations *)|`Exists{target_env=Nodejs;_},Nodejs,Isomorphic|`Exists{target_env=Browser;_},Browser,Isomorphic->false(* The following are impossible *)|`Exists{target_env=Nodejs;_},Browser,_|`Exists{target_env=Browser;_},Nodejs,_|`Exists{target_env=Nodejs|Browser;_},Isomorphic,_->assertfalse(* collision detected *)|`Exists({target_env=Nodejs;_}asp),Nodejs,Nodejs|`Exists({target_env=Isomorphic;_}asp),Nodejs,Isomorphic|`Exists({target_env=Browser;_}asp),Browser,Browser|`Exists({target_env=Isomorphic;_}asp),Browser,Isomorphic|`Exists({target_env=Isomorphic;_}asp),Isomorphic,Isomorphic->ifp.weakdefthentrueelse(warn"warning: overriding primitive %S\n old: %s\n new: %s@."name(locp.pi)(locpi);true)inifnotis_updatingthen`Ignoredelseletrecfind_arity=function|[]->None|(Javascript.Function_declaration(Javascript.S{Javascript.name=n;_},l,_,_),_)::_whenString.equalnamen->Some(List.lengthl)|_::rem->find_arityreminincrlast_code_id;letid=!last_code_idinletcode=Macro.fcodeinletarity=find_aritycodeinletnamed_values=find_named_valuecodeincheck_primitive~namepi~code~requires;Primitive.registernamekindkaarity;StringSet.iterPrimitive.register_named_valuenamed_values;Hashtbl.addprovidedname{id;pi;weakdef;target_env=fragment_target};Hashtbl.addprovided_revid(name,pi);Hashtbl.addcode_piecesid(code,requires);`Ok)letadd_file~target_envfilename=List.iter(parse_filefilename)~f:(funfrag->let(`Ok|`Ignored)=load_fragment~target_env~filenamefragin())letget_provided()=Hashtbl.fold(funk_acc->StringSet.addkacc)providedStringSet.emptyletcheck_deps()=letprovided=get_provided()inHashtbl.iter(funid(code,requires)->lettraverse=newJs_traverse.freeinlet_js=traverse#programcodeinletfree=traverse#get_free_nameinletrequires=List.fold_rightrequires~init:StringSet.empty~f:StringSet.addinletreal=StringSet.interfreeprovidedinletmissing=StringSet.diffrealrequiresinifnot(StringSet.is_emptymissing)thentryletname,ploc=Hashtbl.findprovided_revidinwarn"code providing %s (%s) may miss dependencies: %s\n"name(locploc)(String.concat~sep:", "(StringSet.elementsmissing))withNot_found->(* there is no //Provides for this piece of code *)(* FIXME handle missing deps in this case *)())code_piecesletload_fragments~target_env~filenamel=List.iterl~f:(funfrag->let(`Ok|`Ignored)=load_fragment~target_env~filenamefragin());check_deps()letload_files~target_envl=List.iterl~f:(funfilename->add_file~target_envfilename);check_deps()(* resolve *)letrecresolve_dep_name_revvisitedpathnm=letid=tryletx=Hashtbl.findprovidednminx.idwithNot_found->error"missing dependency '%s'@."nminresolve_dep_id_revvisitedpathidandresolve_dep_id_revvisitedpathid=ifIntSet.memidvisited.idsthen(ifList.memqid~set:paththenerror"circular dependency: %s"(String.concat~sep:", "(List.mappath~f:(funid->fst(Hashtbl.findprovided_revid))));visited)elseletpath=id::pathinletcode,req=Hashtbl.findcode_piecesidinletvisited={visitedwithids=IntSet.addidvisited.ids}inletvisited=List.fold_leftreq~init:visited~f:(funvisitednm->resolve_dep_name_revvisitedpathnm)inletvisited={visitedwithcodes=code::visited.codes}invisitedletinit()={ids=IntSet.empty;always_required_codes=List.rev!always_included;codes=[]}letresolve_deps?(linkall=false)visited_revused=(* link the special files *)letmissing,visited_rev=iflinkallthen(* link all primitives *)letprog,set=Hashtbl.fold(funnm_(visited,set)->resolve_dep_name_revvisited[]nm,StringSet.addnmset)provided(visited_rev,StringSet.empty)inletmissing=StringSet.diffusedsetinmissing,progelse(* link used primitives *)StringSet.fold(funnm(missing,visited)->ifHashtbl.memprovidednmthenmissing,resolve_dep_name_revvisited[]nmelseStringSet.addnmmissing,visited)used(StringSet.empty,visited_rev)invisited_rev,missingletlinkprogram(state:state)=letalways,always_required=List.partition~f:(function|{requires=[];_}->false|_->true)state.always_required_codesinletstate=List.fold_leftalways~init:state~f:(fun(state:state)always->letstate=List.fold_leftalways.requires~init:state~f:(funstatenm->resolve_dep_name_revstate[]nm)in{statewithcodes=always.program::state.codes})inletruntime=List.flatten(List.rev(program::state.codes))in{runtime_code=runtime;always_required_codes=always_required}letallstate=IntSet.fold(funidacc->tryletname,_=Hashtbl.findprovided_revidinname::accwithNot_found->acc)state.ids[]letorigin~name=tryletx=Hashtbl.findprovidednameinx.pi.Parse_info.srcwithNot_found->None