123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753(* 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!Stdlibtype'apack=|Okof'a|Packofstringletunpack=function|Okx->x|Packx->Marshal.from_stringx0letpack=function|Pack_asx->x|Okx->Pack(Marshal.to_stringx[])letto_stringsetutf8_string_set=Javascript.IdentSet.fold(funxacc->matchxwith|S{name=Utf8x;_}->StringSet.addxacc|V_->acc)utf8_string_setStringSet.emptyletlocpi=matchpiwith|{Parse_info.src=Somesrc;line;_}|{Parse_info.name=Somesrc;line;_}->Printf.sprintf"%s:%d"srcline|_->"unknown location"leterrors=Format.ksprintf(funs->failwiths)smoduleArity:sigvalfind:Javascript.program->name:string->intoptionend=structletrecfindp~name=matchpwith|[]->None|(Javascript.Function_declaration(Javascript.S{Javascript.name=Utf8n;_},(_,{list;rest=None},_,_)),_)::_whenString.equalnamen->Some(List.lengthlist)|_::rem->findrem~nameendmoduleNamed_value:sigvalfind_all:Javascript.program->StringSet.tend=structclasstraverse_and_find_named_valuesall=objectinheritJs_traverse.iterasselfmethodexpressionx=letopenJavascriptin(matchxwith|ECall(EVar(S{name=Utf8"caml_named_value";_}),_,[Arg(EStr(Utf8v))],_)->all:=StringSet.addv!all|_->());self#expressionxendletfind_allcode=letall=refStringSet.emptyinletp=newtraverse_and_find_named_valuesallinp#programcode;!allendmoduleCheck=structclasscheck_and_warnnamepi=objectinheritJs_traverse.freeassupermethodmerge_infofrom=letdef=from#get_definletuse=from#get_useinletdiff=Javascript.IdentSet.diffdefuseinletdiff=Javascript.IdentSet.fold(funxacc->matchxwith|S{name=Utf8_string.Utf8s;_}->ifString.is_prefixs~prefix:"_"||String.equalsnamethenaccelses::acc|V_->acc)diff[]in(matchdiffwith|[]->()|l->warn"WARN unused for primitive %s at %s:@. %s@."name(locpi)(String.concat~sep:", "l));super#merge_infofromendletprimitive~namepi~code~requires~has_flags=letfree=ifConfig.Flag.warn_unused()thennewcheck_and_warnnamepielsenewJs_traverse.freeinlet_code=free#programcodeinletfreename=to_stringsetfree#get_freeinletfreename=List.fold_leftrequires~init:freename~f:(funfreenamex->StringSet.removexfreename)inletfreename=StringSet.difffreenameReserved.keywordinletfreename=StringSet.difffreenameReserved.providedinletfreename=StringSet.removeGlobal_constant.global_objectfreenameinletfreename=ifhas_flagsthenStringSet.remove"FLAG"freenameelsefreenameinifStringSet.memGlobal_constant.old_global_objectfreenamethenwarn"warning: %s: 'joo_global_object' is being deprecated, please use `globalThis` \
instead@."(locpi);letfreename=StringSet.removeGlobal_constant.old_global_objectfreenameinletdefname=to_stringsetfree#get_definifnot(StringSet.memnamedefname)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)))endmoduleFragment=structtypeprovides={parse_info:Parse_info.t;name:string;kind:Primitive.kind;kind_arg:Primitive.kind_arglistoption;arity:intoption;named_values:StringSet.t}typefragment_={provides:providesoption;requires:stringlist;has_macro:bool;version_constraint_ok:bool;weakdef:bool;always:bool;code:Javascript.programpack;conditions:boolStringMap.t;fragment_target:Target_env.toption;aliases:StringSet.t;deprecated:stringoption}letallowed_flags=List.fold_left~f:(funm(k,v)->StringMap.addkvm)~init:StringMap.empty["js-string",Config.Flag.use_js_string;"effects",Config.Flag.effects;("wasm",fun()->matchConfig.target()with|`JavaScript->false|`Wasm->true)]typet=|Always_includeofJavascript.programpack|Fragmentoffragment_letprovides=function|Always_include_->[]|Fragment{provides=Somep;_}->[p.name]|Fragment_->[]letanalyze(t:t):t=matchtwith|Always_include_->t|Fragment{provides=None;_}->t|Fragment({provides=Some({parse_info=pi;name;kind=_;kind_arg=_;arity=_;named_values=_}asprovides);_}asfragment)->letcode,has_flags=Macro.f~flags:false(unpackfragment.code)inletnamed_values=Named_value.find_allcodeinletarity=Arity.findcode~nameinCheck.primitive~has_flags~namepi~code~requires:fragment.requires;letprovides=Some{provideswithnamed_values;arity}inFragment{fragmentwithcode=Okcode;provides;has_macro=has_flags}letversion_match=List.for_all~f:(fun(op,str)->opOcaml_version.(comparecurrent(splitstr))0)letparse_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.colinletres=List.mapprogram~f:(fun(annot,code)->matchannotwith|[]->Always_include(Okcode)|annot->letinitial_fragment:fragment_={provides=None;requires=[];version_constraint_ok=true;weakdef=false;always=false;has_macro=false;code=Okcode;conditions=StringMap.empty;fragment_target=None;aliases=StringSet.empty;deprecated=None}inletfragment=List.fold_leftannot~init:initial_fragment~f:(fun(fragment:fragment_)((_,a),pi)->matchawith|`Provides(name,kind,ka)->{fragmentwithprovides=Some{parse_info=pi;name;kind;kind_arg=ka;arity=None;named_values=StringSet.empty}}|`Requiresmn->{fragmentwithrequires=mn@fragment.requires}|`Versionl->{fragmentwithversion_constraint_ok=fragment.version_constraint_ok&&version_matchl}|`Weakdef->{fragmentwithweakdef=true}|`Always->{fragmentwithalways=true}|`Aliasname->{fragmentwithaliases=StringSet.addnamefragment.aliases}|`Deprecatedtxt->{fragmentwithdeprecated=Sometxt}|`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}|(`Ifnotv|`Ifv)whennot(StringMap.memvallowed_flags)->Format.eprintf"Unkown flag %S in %s\n"v(locpi);fragment|(`Ifnotv|`Ifv)asi->ifStringMap.memvfragment.conditionsthenFormat.eprintf"Duplicated %s in %s\n"v(locpi);letb=matchiwith|`If_->true|`Ifnot_->falsein{fragmentwithconditions=StringMap.addvbfragment.conditions})inFragmentfragment)inList.map~f:analyzeresletparse_builtinbuiltin=letfilename=Builtins.File.namebuiltininletcontent=Builtins.File.contentbuiltininmatchBuiltins.File.fragmentsbuiltinwith|None->letlex=Parse_js.Lexer.of_string~filenamecontentinparse_from_lex~filenamelex|Somefragments->Marshal.from_stringfragments0letparse_stringstring=letfilename="<string>"inletlex=Parse_js.Lexer.of_string~filenamestringinparse_from_lex~filenamelexletparse_filef=letfile=matchFindlib.find[]fwith|Somefile->Fs.absolute_pathfile|None->error"cannot find file '%s'. @."finletlex=Parse_js.Lexer.of_filefileinparse_from_lex~filename:filelexletpack=function|Always_includex->Always_include(packx)|Fragmentf->Fragment{fwithcode=packf.code}end(*
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
*)typealways_required'={ar_filename:string;ar_program:Javascript.programpack;ar_requires:stringlist}typealways_required={filename:string;program:Javascript.program;requires:stringlist}typestate={ids:IntSet.t;always_required_codes:always_requiredlist;codes:(Javascript.programpack*bool)list;deprecation:(intlist*string)list;missing:StringSet.t;include_:string->bool}typeoutput={runtime_code:Javascript.program;always_required_codes:always_requiredlist}typeprovided={id:int;pi:Parse_info.t;filename:string;weakdef:bool;target_env:Target_env.t}letalways_included=ref[]letprovided=Hashtbl.create31letprovided_rev=Hashtbl.create31letcode_pieces=Hashtbl.create31letreset()=always_included:=[];Hashtbl.clearprovided;Hashtbl.clearprovided_rev;Hashtbl.clearcode_pieces;Primitive.reset();Generate.init()letlist_all?from()=letinclude_=matchfromwith|None->fun__->true|Somel->funfn_nm->List.memfn~set:linHashtbl.fold(funnmpset->ifinclude_p.filenamenmthenStringSet.addnmsetelseset)providedStringSet.emptyletload_fragment~target_env~filename(f:Fragment.t)=matchfwith|Always_includecode->always_included:={ar_filename=filename;ar_program=code;ar_requires=[]}::!always_included;`Ok|Fragment{provides;requires;version_constraint_ok;weakdef;always;code;fragment_target;aliases;has_macro;conditions;deprecated}->(letshould_ignore=StringMap.exists(funflagb->not(Bool.equalb(StringMap.findflagFragment.allowed_flags())))conditionsinif(notversion_constraint_ok)||should_ignorethen`Ignoredelsematchprovideswith|None->ifnot(StringSet.is_emptyaliases)thenerror"Found JavaScript code with neither `//Alias` and not `//Provides` in \
file %S@."filename;ifalwaysthen(always_included:={ar_filename=filename;ar_program=code;ar_requires=requires}::!always_included;`Ok)elseerror"Found JavaScript code with neither `//Provides` nor `//Always` in file \
%S@."filename|Some{parse_info=pi;name;kind;kind_arg=ka;arity;named_values}->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`Ignoredelselet()=()inletid=Hashtbl.lengthprovidedinPrimitive.registernamekindkaarity;StringSet.iterPrimitive.register_named_valuenamed_values;Hashtbl.addprovidedname{id;pi;filename;weakdef;target_env=fragment_target};Hashtbl.addprovided_revid(name,pi);Hashtbl.addcode_piecesid(code,has_macro,requires,deprecated);StringSet.iter(funalias->Primitive.aliasaliasname)aliases;`Ok)letcheck_deps()=letprovided=list_all()inHashtbl.iter(funid(code,_has_macro,requires,_deprecated)->matchcodewith|Okcode->(lettraverse=newJs_traverse.freeinlet_js=traverse#programcodeinletfree=to_stringsettraverse#get_freeinletrequires=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 *)())|Pack_->(* We only have [Pack] for the builtin runtime, which has
been checked already (before it was embedded *)())code_piecesletload_file~target_envfilename=List.iter(Fragment.parse_filefilename)~f:(funfrag->let(`Ok|`Ignored)=load_fragment~target_env~filenamefragin())letload_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->load_file~target_envfilename);check_deps()(* resolve *)letrecresolve_dep_name_revstatepathnm=matchHashtbl.findprovidednmwith|x->ifstate.include_x.filenamethenresolve_dep_id_revstatepathx.idelse{statewithmissing=StringSet.addnmstate.missing}|exceptionNot_found->{statewithmissing=StringSet.addnmstate.missing}andresolve_dep_id_revstatepathid=ifIntSet.memidstate.idsthen(ifList.memqid~set:paththenerror"circular dependency: %s"(String.concat~sep:", "(List.mappath~f:(funid->fst(Hashtbl.findprovided_revid))));state)elseletpath=id::pathinletcode,has_macro,req,deprecated=Hashtbl.findcode_piecesidinletstate={statewithids=IntSet.addidstate.ids}inletstate=List.fold_leftreq~init:state~f:(funstatenm->resolve_dep_name_revstatepathnm)inletdeprecation=matchdeprecatedwith|None->state.deprecation|Sometxt->(path,txt)::state.deprecationinletstate={statewithcodes=(code,has_macro)::state.codes;deprecation}instateletproj_always_required{ar_filename;ar_requires;ar_program}={filename=ar_filename;requires=ar_requires;program=unpackar_program}letinit?from()=letinclude_=matchfromwith|None->fun_->true|Somel->funfn->List.memfn~set:lin{ids=IntSet.empty;always_required_codes=List.rev(List.filter_map!always_included~f:(funx->ifinclude_x.ar_filenamethenSome(proj_always_requiredx)elseNone));deprecation=[];codes=[];include_;missing=StringSet.empty}letdo_check_missingstate=ifnot(StringSet.is_emptystate.missing)thenerror"missing dependency '%s'@."(StringSet.choosestate.missing)letresolve_deps?(check_missing=true)stateused=(* link the special files *)letmissing,state=StringSet.fold(funnm(missing,visited)->ifHashtbl.memprovidednmthenmissing,resolve_dep_name_revvisited[]nmelseStringSet.addnmmissing,visited)used(StringSet.empty,state)inifcheck_missingthendo_check_missingstate;state,missingletlink?(check_missing=true)program(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=(Okalways.program,false)::state.codes})inifcheck_missingthendo_check_missingstate;List.iterstate.deprecation~f:(fun(path,txt)->matchpathwith|[]->assertfalse|[x]->iffalsethenletname=fst(Hashtbl.findprovided_revx)inwarn"The runtime primitive [%s] is deprecated. %s\n"nametxt|x::path->letname=fst(Hashtbl.findprovided_revx)inletpath=String.concat~sep:"\n"(List.mappath~f:(funid->letnm,loc=Hashtbl.findprovided_revidinPrintf.sprintf"-> %s:%s"nm(Parse_info.to_stringloc)))inwarn"The runtime primitive [%s] is deprecated. %s. Used by:\n%s\n"nametxtpath);letcodes=List.mapstate.codes~f:(fun(x,has_macro)->letc=unpackxinifhas_macrothenletc,_=Macro.f~flags:truecincelsec)inletruntime=List.flatten(List.rev(program::codes))in{runtime_code=runtime;always_required_codes=always_required}letallstate=IntSet.fold(funidacc->tryletname,_=Hashtbl.findprovided_revidinname::accwithNot_found->acc)state.ids[]letmissingstate=StringSet.elementsstate.missingletorigin~name=tryletx=Hashtbl.findprovidednameinx.pi.Parse_info.srcwithNot_found->Noneletdeprecated~name=tryletx=Hashtbl.findprovidednameinlet_,_,_,deprecated=Hashtbl.findcode_piecesx.idinOption.is_somedeprecatedwithNot_found->false