You can search for identifiers within the package.
in-package search v0.2.0
Source file js_of_ocaml_devtools.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599(* Js_of_ocaml library
* http://www.ocsigen.org/js_of_ocaml/
* Copyright (C) 2026 Hugo Heuzard
*
* 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.
*)(* The formatter only exists when the compiler ships [Introspect] (Introcaml);
elsewhere [register_formatters] does nothing. *)letregister_formatters()=()[@@ifnotintrospect]moduleFormatter=structmoduleJs=Jsoo_runtime.JsmoduleDyn=Introspect.Dyn(* Untyped JavaScript values, through the runtime primitives only *)letany(x:'a):Js.t=Obj.magicxletstrs=Js.stringsletundefined:Js.t=Js.pure_js_expr"undefined"letnull:Js.t=Js.pure_js_expr"null"letglobal:Js.t=Js.pure_js_expr"globalThis"letis_undefinedv=Js.strict_equalsvundefinedletis_nullv=Js.strict_equalsvnull(* [null] or [undefined] *)letis_nullishv=Js.equalsvnulllettypeofv=Js.to_string(Js.typeofv)letgetvkey=Js.getv(Js.stringkey)(* JsonML, the format expected by DevTools: [[tag, attributes, children...]],
where the "object" tag embeds a reference to be formatted in turn. *)letjsonmltag?(attrs=[])children=letattrs=Js.obj(Array.of_listattrs)inJs.array(Array.of_list(strtag::attrs::children))letspan?styles=letattrs=matchstylewith|None->[]|Somestyle->["style",strstyle]injsonml"span"~attrs[strs]letlabel_style="color: rgb(136, 19, 145)"letbody_style="list-style: none; padding-left: 12px; margin: 0"(* Values nested in a body are handed back to DevTools as "object" nodes; the
configuration tells [header] the object comes from us (it may lack a
descriptor of its own, e.g. a block built by the runtime) and carries the
approximation known from the enclosing type. *)letconfig_of_dyn?(offset=0)d=Js.obj[|"jsoo",Js.booltrue;"approx",any(Obj.repr(Dyn.get_approxd));"offset",anyoffset|](* The config DevTools passes along: ours, another formatter's, or none *)typeconfig=Js.tletour_config(config:config)=ifis_nullishconfig||is_undefined(getconfig"jsoo")thenNoneelseSomeconfigletis_our_configconfig=Option.is_some(our_configconfig)(* Arrays are expanded by chunks: the entry for the rest of an array is the
array itself, with the offset of the chunk in its config. *)letconfig_offsetconfig=matchour_configconfigwith|Someconfig->letoffset=getconfig"offset"inifis_undefinedoffsetthen0else(Obj.magicoffset:int)|None->0letlift(obj:Js.t)config:Dyn.t=letapprox=Option.map(funconfig->(Obj.magic(getconfig"approx"):Introspect.Desc.approx))(our_configconfig)inDyn.lift?approx(Obj.reprobj)(* Values with a representation of their own under js_of_ocaml, which
[Introspect] cannot know about: [bytes] are [MlBytes] objects while
strings are JavaScript strings (unless strings are MlBytes too, in which
case the two cannot be told apart, as in native code), boxed integers and
bigarrays are custom objects. *)letjs_strings=String.equal(typeof(any(Sys.opaque_identity"")))"string"letis_bytes(raw:Obj.t)=js_strings&&Obj.is_blockraw&&Obj.tagraw=Obj.string_tag&¬(String.equal(typeof(anyraw))"string")letcustom_name(raw:Obj.t)=letname=get(anyraw)"caml_custom"inifis_undefinednamethenNoneelseSome(Js.to_stringname)letis_int64raw=matchcustom_namerawwith|Some"_j"->true|_->falseletis_bigarrayraw=matchcustom_namerawwith|Some"_bigarr02"->true|_->falseletcustom_repr(raw:Obj.t)=matchcustom_namerawwith|Some"_j"->Int64.to_string(Obj.objraw)^"L"|Some"_bigarr02"->letdims:intarray=Js.to_array(get(anyraw)"dims")inPrintf.sprintf"<bigarray %s>"(String.concat"x"(List.mapstring_of_int(Array.to_listdims)))|_->"<custom>"(* JavaScript values embedded in OCaml ones (DOM nodes, promises, [Js.t]
objects, [null]...), which [Introspect] cannot know about either: the
console renders them itself when they are handed back to it as "object"
nodes. *)letis_js_object(raw:Obj.t)=letv=anyrawinString.equal(typeofv)"object"&&(not(is_nullv))&¬(Js.to_bool(Js.meth_call(getglobal"Array")"isArray"[|v|]))(* [Introspect] sizes every block before looking at it, which under
js_of_ocaml reads [.length]: [null] and [undefined] would throw. *)letview(d:Dyn.t):Dyn.view=ifis_nullish(any(Dyn.get_objd))thenUnknownelseDyn.viewdletconstructor_namev=tryletctor=getv"constructor"inifis_undefinedctorthen"Object"elseletname=getctor"name"inifString.equal(typeofname)"string"&¬(String.equal(Js.to_stringname)"")thenJs.to_stringnameelse"Object"with_->"Object"letunknown_repr(raw:Obj.t)=letv=anyrawinmatchtypeofvwith|"undefined"->"undefined"|"boolean"->ifJs.to_boolvthen"true"else"false"|"object"whenis_nullv->"null"|"object"whenis_js_objectraw->"<"^constructor_namev^">"|"object"whenObj.tagraw=Obj.object_tag->"<object>"|"object"->"<unknown>"|t->"<"^t^">"letstring_repr(raw:Obj.t)s=ifis_bytesrawthenPrintf.sprintf"Bytes.of_string %S"(Bytes.to_string(Obj.objraw))elsePrintf.sprintf"%S"s(* Float printing from typing/oprint.ml, as in [Introspect] *)letfloat_represf=matchclassify_floatfwith|FP_nan->"nan"|FP_infinite->iff<0.0then"neg_infinity"else"infinity"|_->lets=lets1=Printf.sprintf"%.12g"finifFloat.equalf(float_of_strings1)thens1elselets2=Printf.sprintf"%.15g"finifFloat.equalf(float_of_strings2)thens2elsePrintf.sprintf"%.18g"finletl=String.lengthsinletrecloopi=ifi>=lthens^"."elsematchs.[i]with|'0'..'9'|'-'->loop(i+1)|_->sinloop0(* One-line printing in OCaml syntax, after [Introspect.Dyn.pp], but aware
of the representations above and bounded by a character budget rather
than a step budget, so that huge values cost their visible prefix only. *)(* Lists are expanded by chunks: past this many elements, the rest of the
list is one entry, expandable in turn; the one-line header shows the same
elements. *)letmax_list_elements=10moduleH=Hashtbl.Make(structtypet=Obj.tletequal=(==)lethash=Hashtbl.hashend)exceptionBudgettypeprinter={buf:Buffer.t;max_chars:int;seen:unitH.t}letaddps=ifBuffer.lengthp.buf>=p.max_charsthenraiseBudget;Buffer.add_stringp.bufsletis_reffields=Dyn.field_countfields=1&&String.equal(fst(Dyn.field_getfields0))"contents"letrecpp?(offset=0)pdepthfragile(d:Dyn.t)=ifdepth<=0thenaddp"..."elseletraw=Dyn.get_objdinletprotect=Obj.is_blockraw&&Obj.tagraw<Obj.no_scan_taginifprotect&&H.memp.seenrawthenaddp"<cycle>"else(ifprotectthenH.addp.seenraw();pp_viewpdepthfragile~offsetraw(viewd);ifprotectthenH.removep.seenraw)andpp_viewpdepthfragile~offsetraw(view:Dyn.view)=letselffragiled=ppp(depth-1)fragiledinletparencondf=ifcondthenaddp"(";f();ifcondthenaddp")"inletfieldssepffs=fori=0toDyn.field_countfs-1doifi>0thenaddpsep;f(Dyn.field_getfsi)doneinlettuple_field(k,v)=ifnot(String.equalk"")thenaddp("~"^k^":");selffalsevinletrecord_field(k,v)=addp(k^" = ");selffalsevinmatchviewwith|Strings->addp(string_reprraws)|Floatf->addp(float_represf)|Charc->addp(Printf.sprintf"%C"c)(* Floats are unboxed numbers, which [Introspect] takes for integers:
print those that cannot be integers as floats. Never reached in native
code, where the formatter is not installed. *)|Int_or_constant(i,[])whennot(Float.is_integer(Obj.magici:float))->addp(float_repres(Obj.magici:float))|Int_or_constant(i,keys)->paren(fragile&&matchkeyswith|[]->false|_::_->true)(fun()->addp(string_of_inti);List.iter(funk->addp(" or `"^k))keys)|Constant[]->addp"<invalid constant>"|Constant[name]->addpname|Constant(name::names)->parenfragile(fun()->addpname;List.iter(funn->addp(" or "^n))names)|Arrayfs->letn=Dyn.field_countfsinletlast=minn(offset+max_list_elements)inaddp"[|";fori=offsettolast-1doifi>offsetthenaddp"; ";selffalse(Dyn.field_getfsi)done;iflast<nthenaddp(iflast>offsetthen"; ..."else"...");addp"|]"|Tuple{name="::";fields=fs}whenDyn.field_countfs=2->addp"[";list_elementspdepth0fs;addp"]"|Tuple{name="";fields=fs}->addp"(";fields", "tuple_fieldfs;addp")"|Tuple{name;fields=fs}whenDyn.field_countfs=1&&(notfragile)&&String.equal(fst(Dyn.field_getfs0))""->addp(name^" ");selftrue(snd(Dyn.field_getfs0))|Tuple{name;fields=fs}->parenfragile(fun()->ifnot(String.equalname"")thenaddp(name^" ");addp"(";fields", "tuple_fieldfs;addp")")(* A record with the single field [contents] is a ['a ref] (or a record
defined exactly like it): print it as it is built *)|Record{name="";fields=fs}whenis_reffs->parenfragile(fun()->addp"ref ";selftrue(snd(Dyn.field_getfs0)))|Record{name;fields=fs}->paren(fragile&¬(String.equalname""))(fun()->ifnot(String.equalname"")thenaddp(name^" ");addp"{";fields"; "record_fieldfs;addp"}")|Extension(name,uid,fs)->addp(name^"/"^string_of_intuid);ifDyn.field_countfs>0then(addp" (";fields", "(selffalse)fs;addp")")|Polymorphic_variant(name,payload)->parenfragile(fun()->addp("`"^name^" ");selftruepayload)|Closure->addp"<closure>"|Abstract->addp"<abstract>"|Custom->addp(custom_reprraw)|Unknown->addp(unknown_reprraw)|Lazy_unforced->addp"<lazy>"|Lazy_forcing->addp"<lazy (forcing)>"|Lazy_forwardd->parenfragile(fun()->addp"lazy ";selftrued)(* Elements of a list, the cells not counting towards the depth; as many
as the body lists, then "..." *)andlist_elementspdepthifs=letlabel,car=Dyn.field_getfs0inifnot(String.equallabel"")thenaddp("~"^label^":");ppp(depth-1)falsecar;let_,cdr=Dyn.field_getfs1inmatchviewcdrwith|Constant["[]"]|Int_or_constant(0,_)->()|Tuple{name="::";fields=fs}whenDyn.field_countfs=2->addp"; ";ifi+1>=max_list_elementsthenaddp"..."elseletraw=Dyn.get_objcdrinifH.memp.seenrawthenaddp"<cycle>"else(H.addp.seenraw();list_elementspdepth(i+1)fs;H.removep.seenraw)|_->addp"<malformed list>"letto_string?(depth=6)?(max_chars=160)?offsetd=letp={buf=Buffer.create64;max_chars;seen=H.create7}inmatchpp?offsetpdepthfalsedwith|()->Buffer.contentsp.buf|exceptionBudget->Buffer.contentsp.buf^"..."(* Claim OCaml blocks carrying a descriptor, blocks recognized from their
shape alone (extensions such as exceptions and lazy values, which the
runtime allocates without descriptor, bytes and boxed int64), and
anything we referenced from a body. *)letclaims(obj:Js.t)(config:config)=is_our_configconfig||letobj=Obj.reprobjinObj.is_blockobj&&(Obj.get_reservedobj<>0||matchview(Dyn.liftobj)with|Extension_|Lazy_forward_|Lazy_unforced|Lazy_forcing->true|String_->is_bytesobj|Custom->is_int64obj|Float_|Char_|Int_or_constant_|Constant_|Array_|Tuple_|Record_|Polymorphic_variant_|Closure|Abstract|Unknown->false)(* The entries listed when a value is expanded. *)typechild={label:string;value:Dyn.t;offset:int(* of the chunk shown, for the rest of an array *)}letchildlabelvalue={label;value;offset=0}letindexedfields=List.init(Dyn.field_countfields)(funi->child(string_of_inti)(Dyn.field_getfieldsi))(* A chunk of an array, then the array again from the next chunk on *)letarray_elementsd~offsetfields=letn=Dyn.field_countfieldsinletlast=minn(offset+max_list_elements)inletelements=List.init(last-offset)(funi->child(string_of_int(offset+i))(Dyn.field_getfields(offset+i)))iniflast<nthenelements@[{label="...";value=d;offset=last}]elseelements(* The single unnamed argument of a constructor is not numbered. *)letlabelledfields=matchDyn.field_countfieldswith|1whenString.equal(fst(Dyn.field_getfields0))""->[child""(snd(Dyn.field_getfields0))]|n->List.initn(funi->letlabel,value=Dyn.field_getfieldsiinchild(ifString.equallabel""thenstring_of_intielselabel)value)(* Lists are shown flat, elements numbered; past [max_list_elements] (or on
a malformed list) the tail is shown as a single entry. *)letlist_elementsfields=letrecloopaccifields=let_,car=Dyn.field_getfields0inlet_,cdr=Dyn.field_getfields1inletacc=child(string_of_inti)car::accinletrest()=List.rev(child"..."cdr::acc)inifi+1>=max_list_elementsthenrest()elsematchviewcdrwith|Tuple{name="::";fields}whenDyn.field_countfields=2->loopacc(i+1)fields|Constant_|Int_or_constant_->List.revacc|_->rest()inloop[]0fieldsletchildren?(offset=0)(d:Dyn.t)=matchviewdwith|Arrayfields->array_elementsd~offsetfields|Tuple{name="::";fields}whenDyn.field_countfields=2->list_elementsfields|Record{name="";fields}whenis_reffields->[child""(snd(Dyn.field_getfields0))]|Tuple{fields;_}|Record{fields;_}->labelledfields|Extension(_,_,fields)->indexedfields|Polymorphic_variant(_,value)|Lazy_forwardvalue->[child""value]|String_|Float_|Char_|Int_or_constant_|Constant_|Closure|Abstract|Custom|Unknown|Lazy_unforced|Lazy_forcing->[](* Strings longer than this are handed to the console rather than cut *)letinline_string_length=60letrender_value~offsetd=matchviewdwith|(Array_|Tuple_|Record_|Polymorphic_variant_|Lazy_forward_|Extension_)whenObj.is_block(Dyn.get_objd)->jsonml"object"~attrs:["object",any(Dyn.get_objd);"config",any(config_of_dyn~offsetd)][]|Unknownwhenis_js_object(Dyn.get_objd)->(* No config: the console formats the value itself *)jsonml"object"~attrs:["object",any(Dyn.get_objd)][]|ClosurewhenString.equal(typeof(any(Dyn.get_objd)))"function"->(* Likewise: the console shows the function with a link to its source *)jsonml"object"~attrs:["object",any(Dyn.get_objd)][]|Customwhenis_bigarray(Dyn.get_objd)->(* Likewise: the console shows the underlying typed array *)jsonml"object"~attrs:["object",any(Dyn.get_objd)][]|StringswhenString.lengths>inline_string_length&¬(is_bytes(Dyn.get_objd))->(* Long strings in full, with copy, rather than cut *)jsonml"object"~attrs:["object",any(Dyn.get_objd)][]|_->span(to_string~depth:3~max_chars:80d)letrender_child{label;value;offset}=letvalue=render_value~offsetvalueinjsonml"li"(ifString.equallabel""then[value]else[span~style:label_style(label^": ");value])(* A throwing formatter breaks the console, so never let an exception out. *)letguarddefaultfobjconfig=tryfobjconfigwith_->defaultletheader=guardnull(funobjconfig->ifclaimsobjconfigthenspan~style:"font-family: monospace"(to_string~offset:(config_offsetconfig)(liftobjconfig))elsenull)lethas_body=guard(Js.boolfalse)(funobjconfig->Js.bool(claimsobjconfig&&matchchildren~offset:(config_offsetconfig)(liftobjconfig)with|[]->false|_::_->true))letbody=guardnull(funobjconfig->jsonml"ol"~attrs:["style",strbody_style](List.maprender_child(children~offset:(config_offsetconfig)(liftobjconfig))))letformatter()=Js.obj[|"header",any(Js.callback_with_arity2header);"hasBody",any(Js.callback_with_arity2has_body);"body",any(Js.callback_with_arity2body)|]letregistered=reffalseletregister_formatters()=matchSys.backend_typewith|Other"js_of_ocaml"whennot!registered->registered:=true;letkey="devtoolsFormatters"inletformatters=letformatters=getglobalkeyinifis_nullishformattersthen(letformatters=Js.array[||]inJs.setglobal(Js.stringkey)formatters;formatters)elseformattersinignore(Js.meth_callformatters"push"[|formatter()|]:Js.t)|_->()end[@@ifintrospect]letregister_formatters=Formatter.register_formatters[@@ifintrospect]