123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429(* Js_of_ocaml library
* http://www.ocsigen.org/js_of_ocaml/
* Copyright (C) 2016 OCamlPro
*
* 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!Js_of_ocaml_compileropen!Js_of_ocaml_compiler.StdlibmoduleWrapped_intf=Js_of_ocaml_toplevel_protocol.Wrapped_intf(* The result types are defined in the dependency-free [msg] library (so the
host driver can use them without the toplevel runtime); re-export them here. *)typeloc=Wrapped_intf.loc={loc_start:int*int;loc_end:int*int}typeerror=Wrapped_intf.error={msg:string;locs:loclist}typewarning=errortype'aresult='aWrapped_intf.result=|Successof'a*warninglist|Erroroferror*warninglistletconvert_locloc=let_file1,line1,col1=Location.get_pos_infoloc.Location.loc_startinlet_file2,line2,col2=Location.get_pos_infoloc.Location.loc_endin{loc_start=line1,col1;loc_end=line2,col2}(* The [Location] report printer ends its output with a newline; drop it so a
rendered [msg] is a bare message and consumers control the final formatting
(otherwise a consumer adding its own newline produces a blank line). *)letdrop_trailing_newlines=letn=ref(String.lengths)inwhile!n>0&&Char.equals.[!n-1]'\n'dodecrndone;String.subs~pos:0~len:!n(* Run [f] with a capturing [Location.warning_reporter] and Marshal-safe
[Clflags.error_size] installed; restore both on exit. [f] returns
[`Ok v] or [`Err e]; [with_capture] pairs that with the captured
warning list to produce a [result]. All mutable state is local to
this call — the module exposes no global warning ref. *)letwith_capture(f:unit->[`Okof'a|`Erroferror]):'aresult=(* Reset [Location]'s per-phrase line counter, as the stock toplevel loop
does. Otherwise it accumulates across evaluations and the report printer
prepends a blank-line separator before diagnostics on the second run. *)Location.reset();letwarnings=ref[]inletprev_reporter=!Location.warning_reporterinletprev_error_size=!Clflags.error_sizein(* Capture warnings as first-class data and return [None] so the
default Warnings machinery does not *also* render them through
[report_printer]. Consumers of [Wrapped] get warnings on the
result; if we returned [Some], they would additionally be printed
to the toplevel's error stream (stderr in the worker, [pp_stderr]
on the host), producing duplicated output. *)(Location.warning_reporter:=funlocw->matchprev_reporterlocwwith|Somereport->letbuf=Buffer.create503inletppf=Format.formatter_of_bufferbufinletprinter=!Location.report_printer()inprinter.ppprinterppfreport;Format.pp_print_flushppf();letmsg=drop_trailing_newline(Buffer.contentsbuf)inletloc=convert_loclocinwarnings:={msg;locs=[loc]}::!warnings;(* Rendering the warning into our private buffer bumped [Location]'s
line counter; undo that so the outcome printed afterwards (in the
same phrase) is not prefixed with a separating blank line. *)Location.reset();None|None->None);(* [Includemod] attaches expanded signature context to module-type
mismatch error records. That context can hold closures (e.g. from
[Location.report_printer]), which are not marshallable — so when
[Js_of_ocaml_toplevel_worker_lwt_client] ships an error across a Worker
boundary via [Json.output]
(built on [Marshal]) it raises "function value". Setting
[error_size := 0] disables the enrichment; errors still report
the mismatch, just without the fancy expansion. *)Clflags.error_size:=0;letraw=Fun.protect~finally:(fun()->Location.warning_reporter:=prev_reporter;Clflags.error_size:=prev_error_size)fin(* Warnings are prepended to [warnings] as the typechecker walks
the source, so it's in reverse source order. Reverse here so
consumers iterate top-to-bottom like every other OCaml tool. *)letws=List.rev!warningsinmatchrawwith|`Okv->Success(v,ws)|`Erre->Error(e,ws)(** Error handling *)letreport_error_recppf(report:Location.report)=letlocs=ref[]inletprinter=!Location.report_printer()inletprinter={printerwithpp_main_loc=(funprreportfmtloc->locs:=loc::!locs;printer.pp_main_locprreportfmtloc);pp_submsg_loc=(funprreportfmtloc->locs:=loc::!locs;printer.pp_submsg_locprreportfmtloc)}inprinter.ppprinterppfreport;(* [locs] was built by prepending in callback order (main, then each
submsg), so reverse to hand back [main; sub1; sub2; ...]. *)List.rev_map~f:convert_loc!locsletreport_errorerr=letbuf=Buffer.create503inletppf=Format.formatter_of_bufferbufinletlocs=report_error_recppferrinFormat.pp_print_flushppf();letmsg=drop_trailing_newline(Buffer.contentsbuf)in{msg;locs}leterror_of_exnexn=matchLocation.error_of_exnexnwith|Some(`Okerror)->report_errorerror|Some`Already_displayed|None->letmsg=Printexc.to_stringexnin{msg;locs=[]}(** Enforcing the [check ~setenv:true] / code-execution separation *)(* [check ~setenv:true] installs definitions into the type environment but
discards the translated lambda, so those bindings have no runtime
counterpart: the type environment is left out of sync with the runtime,
and running code afterwards could read uninitialized globals. While such
a "scratch" environment is active we therefore refuse to run code (see
[execute], [use], [use_mod_string], which return [Error]);
[clear_check] restores the environment snapshot taken just before the
first [check ~setenv:true], dropping the type-only definitions while
keeping everything that was actually executed. [check ~setenv:true]
itself ran no code, so there is nothing on the runtime side to undo.
[None] means no scratch env is active; [Some env] both marks it active
and holds the in-sync environment to restore on [clear_check]. *)letsynced_env=refNoneletscratch_env_error={msg="cannot run code while a scratch typing environment from [check ~setenv:true] is \
active; clear it with [clear_check] first.";locs=[]}letclear_check()=(match!synced_envwith|Someenv->Toploop.toplevel_env:=env|None->());synced_env:=None(* Run [f] unless a scratch typing environment is active, in which case
reject with [scratch_env_error] instead of executing code. *)letguard_runf=match!synced_envwith|Some_->Error(scratch_env_error,[])|None->f()(** Execution helpers *)letinit_loclbfilename=Location.initlbfilename;(* [Location.init] points [input_lexbuf] at [lb], which makes the error
printer inline a source excerpt. [Wrapped] reports locations via [locs]
instead, so clear it to keep rendered messages excerpt-free (consumers
show the source themselves) and avoid pinning the lexbuf. *)Location.input_lexbuf:=Noneletexecute()?ppf_code?(print_outcome=true)~ppf_answercode=guard_run@@fun()->with_capture@@fun()->letlb=Lexing.from_function(Refill.lexbufcode(ref0)ppf_code)ininit_loclb"//toplevel//";letrecloop()=letphr=!Toploop.parse_toplevel_phraselbinletphr=Ppx.preprocess_phrasephrinletsuccess=Toploop.execute_phraseprint_outcomeppf_answerphrinFormat.pp_print_flushppf_answer();ifsuccessthenloop()else`Okfalseintryletres=loop()inflush_all();reswith|End_of_file->flush_all();`Oktrue|exn->flush_all();`Err(error_of_exnexn)letmake_lexbuf?ppf_codecode=letlb=Lexing.from_function(Refill.lexbufcode(ref0)ppf_code)ininit_loclb"//toplevel//";lbletstep()?(print_outcome=false)~ppf_answerlb=guard_run@@fun()->with_capture@@fun()->tryletphr=!Toploop.parse_toplevel_phraselbinletphr=Ppx.preprocess_phrasephrinletsuccess=Toploop.execute_phraseprint_outcomeppf_answerphrinFormat.pp_print_flushppf_answer();flush_all();`Ok(`Phrasesuccess)with|End_of_file->flush_all();`Ok`Eof|(Syntaxerr.Error_|Lexer.Error_)asexn->(* A syntax/lex error leaves the lexbuf positioned mid-phrase. Flush the
buffered input so that a caller which keeps stepping makes progress (the
refill feeds one line at a time) instead of re-parsing the same broken
phrase forever. Type/eval errors leave the lexbuf past the [;;], so no
flush is needed there. *)Lexing.flush_inputlb;flush_all();`Err(error_of_exnexn)|exn->flush_all();`Err(error_of_exnexn)letuse()?(filename="//toplevel//")?(print_outcome=false)~ppf_answercode=guard_run@@fun()->with_capture@@fun()->letlb=Lexing.from_stringcodeininit_loclbfilename;tryList.iter~f:(funphr->ifnot(Toploop.execute_phraseprint_outcomeppf_answerphr)thenraiseExitelseFormat.pp_print_flushppf_answer())(List.map~f:Ppx.preprocess_phrase(!Toploop.parse_use_filelb));flush_all();`Oktruewith|Exit->flush_all();Format.pp_print_flushppf_answer();`Okfalse|exn->flush_all();`Err(error_of_exnexn)(* [Ast_helper.Mod.constraint_] differs on OxCaml: the module type became
optional and moved ahead of the module expression, and a [modes] argument was
inserted. Wrap [structure : sig] behind a version-neutral helper. *)letconstrain_structurestrs=letopenAst_helperinMod.constraint_(Mod.structurestr)(Mty.signatures)[@@ifnotoxcaml]letconstrain_structurestrs=letopenAst_helperinMod.constraint_(Some(Mty.signatures))[](Mod.structurestr)[@@ifoxcaml]letparse_mod_stringmodnamesig_codeimpl_code=letopenParsetreeinletopenAst_helperin(* [init_loc] mutates [Location.input_name]/[input_lexbuf]; we reset it
per sub-parse so positions in the Parsetree are tagged with the
right filename. Positions are captured at parse time, so the
leftover .mli state after this function returns is benign. *)letstr=letimpl_lb=Lexing.from_stringimpl_codeininit_locimpl_lb(String.uncapitalize_asciimodname^".ml");Parse.implementationimpl_lbinletm=matchsig_codewith|None->Mod.structurestr|Somesig_code->letsig_lb=Lexing.from_stringsig_codeininit_locsig_lb(String.uncapitalize_asciimodname^".mli");lets=Parse.interfacesig_lbinconstrain_structurestrsinPtop_def[Str.module_(Mb.mk(Location.mknoloc(Somemodname))m)]letuse_mod_string()?(print_outcome=true)~ppf_answer~modname?sig_codeimpl_code=guard_run@@fun()->with_capture@@fun()->tryifnot(String.equal(String.capitalize_asciimodname)modname)theninvalid_arg"Wrapped.use_mod_string: the module name must start with a capital letter.";letphr=Ppx.preprocess_phrase@@parse_mod_stringmodnamesig_codeimpl_codeinletres=Toploop.execute_phraseprint_outcomeppf_answerphrinFormat.pp_print_flushppf_answer();flush_all();`Okreswithexn->flush_all();`Err(error_of_exnexn)(* [Typemod.type_toplevel_phrase] gained a [Shape.t] in its result tuple in
4.14; drop it here so [check_phrase] stays version-agnostic. OxCaml also takes
an extra [Types.signature] argument (the accumulated toplevel signature, empty
here since we type a single scratch phrase). *)lettype_toplevel_phraseenvsstr=letstr,sg,sg_names,newenv=Typemod.type_toplevel_phraseenvsstrinstr,sg,sg_names,newenv[@@if(notoxcaml)&&ocaml_version<(4,14,0)]lettype_toplevel_phraseenvsstr=letstr,sg,sg_names,_shape,newenv=Typemod.type_toplevel_phraseenvsstrinstr,sg,sg_names,newenv[@@if(notoxcaml)&&ocaml_version>=(4,14,0)]lettype_toplevel_phraseenvsstr=letstr,sg,sg_names,_shape,newenv=Typemod.type_toplevel_phraseenv[]sstrinstr,sg,sg_names,newenv[@@ifoxcaml](* [Includemod.signatures]'s [~mark] argument is an [Includemod.mark] variant
before 5.3 and a [bool] from 5.3 on. OxCaml replaces [signatures] with
[check_implementation ~modes] (see its toplevel/topcommon.ml). *)letincludemod_signaturesenvsgsg'=ignore(Includemod.signatures~mark:Includemod.Mark_positiveenvsgsg')[@@if(notoxcaml)&&ocaml_version<(5,3,0)]letincludemod_signaturesenvsgsg'=ignore(Includemod.signatures~mark:trueenvsgsg')[@@if(notoxcaml)&&ocaml_version>=(5,3,0)]letincludemod_signaturesenvsgsg'=Includemod.check_implementationenv~modes:Includemod.modes_toplevelsgsg'[@@ifoxcaml](* Extracted from the "execute" function in "ocaml/toplevel/toploop.ml" *)letcheck_phraseenv=function|Parsetree.Ptop_defsstr->Typecore.reset_delayed_checks();letstr,sg,sg_names,newenv=type_toplevel_phraseenvsstrinletsg'=Typemod.Signature_names.simplifynewenvsg_namessginincludemod_signaturesenvsgsg';Typecore.force_delayed_checks();let_lam=Translmod.transl_toplevel_definitionstrinWarnings.check_fatal();newenv|Parsetree.Ptop_dir_->envletcheck()?(setenv=false)code=with_capture@@fun()->letlb=Lexing.from_stringcodeininit_loclb"//toplevel//";(* Type inference mutates the global type graph in place: unification links
type variables (including the weak variables that live in the toplevel
environment, e.g. the ['_weak] in [let r = ref []]). Snapshot it so a
speculative or failing [check] leaves no residue; [Toploop.execute_phrase]
does the same for [execute]. *)letsnap=Btype.snapshot()intryletenv=List.fold_left~f:check_phrase~init:!Toploop.toplevel_env(List.map~f:Ppx.preprocess_phrase(!Toploop.parse_use_filelb))inifsetenvthen((* Committing the scratch env: keep the inferred mutations. Snapshot the
in-sync environment on the first [setenv] of a run, so [clear_check] can
roll back to it later; consecutive [check ~setenv] keep building on the
scratch env and must not overwrite it. *)(match!synced_envwith|Some_->()|None->synced_env:=Some!Toploop.toplevel_env);Toploop.toplevel_env:=env)else(* A non-committing check: undo the mutations so the real environment's
(weak) type variables are not silently constrained. *)Btype.backtracksnap;`Ok()with|End_of_file->Btype.backtracksnap;`Ok()|exn->(* Drop the partial mutations of the failed check. *)Btype.backtracksnap;`Err(error_of_exnexn)