123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597(**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the "hack" directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*)(**
* Hh_json parsing and pretty printing library.
*)(*
<value> ::=
| <object>
| <array>
| <string>
| <number>
| 'true'
| 'false'
| 'null'
<object> ::= '{' <members>* '}'
<members> ::= <pair> { ',' <pair> }* [',']
<pair> ::= <string> ':' <value>
<array> ::= '{' <elements>* '}'
<elements> ::= <value> { ',' <value> }* [',']
<number> ::=
Caveats:
(+) No handling of Unicode yet
(+) Numbers are just stored as strings
*)moduleList=Hack_core_listtypejson=|JSON_Objectof(string*json)list|JSON_Arrayofjsonlist|JSON_Stringofstring|JSON_Numberofstring|JSON_Boolofbool|JSON_Nullletis_digit=function'0'..'9'->true|_->falseletis_whitespace=function' '|'\n'|'\r'|'\t'->true|_->falsetypeenv={allow_trailing_comma:bool;data:string;mutablepos:int;}exceptionSyntax_errorofstring(* Ignore whitespace in peek/eat/next/has_more to make code that uses them
cleaner
*)letpeekenv=String.getenv.dataenv.poslethas_moreenv=String.lengthenv.data>env.posletsyntax_errorenvmsg=leterr_msg=ifhas_moreenvthenPrintf.sprintf"%s at char[%d]=%c"msgenv.posenv.data.[env.pos]elsePrintf.sprintf"%s after the last character"msginraise(Syntax_errorerr_msg)(* skip all blank and new line characters *)letskip_blank_charsenv=whilehas_moreenv&&is_whitespace(peekenv)doenv.pos<-env.pos+1doneletcreate_envstricts=letenv={allow_trailing_comma=notstrict;data=s;pos=0}inskip_blank_charsenv;envleteat_wsenvc=letc'=peekenvinifc'=cthenenv.pos<-env.pos+1elseleterr_msg=Printf.sprintf"eat_ws: expected %c, saw %c"cc'insyntax_errorenverr_msgleteatenvc=skip_blank_charsenv;letc'=peekenvinifc'=cthenbeginenv.pos<-env.pos+1;skip_blank_charsenvendelseleterr_msg=Printf.sprintf"eat: expected %c, saw %c"cc'insyntax_errorenverr_msgletmatch_substring_atsoffsetss=letss_len=String.lengthssinifString.lengths-offset>=ss_lenthentryfori=0toss_len-1doifs.[i+offset]<>ss.[i]thenraiseExitdone;truewithExit->falseelsefalseletjs_literalenvsjs=skip_blank_charsenv;ifmatch_substring_atenv.dataenv.possthenbeginenv.pos<-env.pos+String.lengths;jsendelseleterr_msg=Printf.sprintf"expected '%s'"sinsyntax_errorenverr_msgletjs_trueenv=js_literalenv"true"(JSON_Bool(true))letjs_falseenv=js_literalenv"false"(JSON_Bool(false))letjs_nullenv=js_literalenv"null"JSON_Nullletbuf_eatbufenvc=(eatenvc;Buffer.add_charbufc)letbuf_eat_allbufenvc=(eat_wsenvc;Buffer.add_charbufc)letchar_codeenv=letrecchar_code_(acc:int)envlen=iflen=0thenaccelsebeginenv.pos<-env.pos+1;letc=peekenvinleti=if'0'<=c&&c<='9'then(Char.codec)-(Char.code'0')elseif'a'<=c&&c<='f'then10+(Char.codec)-(Char.code'a')elsesyntax_errorenv"expected hexadecimal digit"inchar_code_(16*acc+i)env(len-1)endinchar_code_0env4letjs_stringenv=letbuf=Buffer.create128inletrecloopenv=letc=peekenvinmatchcwith|'"'->JSON_String(Buffer.contentsbuf)|'\\'->env.pos<-env.pos+1;letc'=peekenvinletc'=matchc'with|'n'->'\n'|'r'->'\r'|'t'->'\t'|'u'->letcode=char_codeenvinChar.chrcode|x->xinenv.pos<-env.pos+1;Buffer.add_charbufc';loopenv|_->buf_eat_allbufenvc;loopenvin(matchpeekenvwith|'"'->env.pos<-env.pos+1|_->syntax_errorenv"expected '\"' character");ifpeekenv='"'thenbegineatenv'"';JSON_String("")endelseletres=loopenvineatenv'"';resletrecbuf_eat_digitsbufenv=ifhas_moreenvthenletc=peekenvinifis_digitcthenbeginbuf_eatbufenvc;buf_eat_digitsbufenvendelse()(* encountered a non-digit char, stop *)else()(* end of string, stop *)letbuf_eat_expbufenv=letc=peekenvinifc='e'||c='E'thenbeginbuf_eatbufenvc;letsign=peekenvinifsign='+'||sign='-'thenbuf_eatbufenvsign;buf_eat_digitsbufenv;endletjs_numberenv=letbuf=Buffer.create32inletc=peekenvinifc='-'thenbuf_eatbufenv'-';buf_eat_digitsbufenv;(* ['-'] digits *)letc=peekenvinifc='.'then(* ['.' digits ] *)beginbuf_eatbufenv'.';buf_eat_digitsbufenv;end;buf_eat_expbufenv;(* [exp digits] *)JSON_Number(Buffer.contentsbuf)(* The recursive rules *)letrecjs_valueenv=letjs_value_syntax_error()=leterr_msg="expected '{[\"0123456789' or {t,f,n}"insyntax_errorenverr_msginifnot(has_moreenv)thenjs_value_syntax_error()elsematchpeekenvwith|'{'->js_objectenv|'['->js_arrayenv|'"'->js_stringenv|cwhenis_digitc||c='-'->js_numberenv|'t'->js_trueenv|'f'->js_falseenv|'n'->js_nullenv|_->js_value_syntax_error()andjs_objectenv=letrecloopmembers=letp=js_pairenvinifpeekenv<>','thenJSON_Object(List.rev(p::members))elsebegineatenv',';ifpeekenv='}'thenifenv.allow_trailing_commathenJSON_Object(List.rev(p::members))elsesyntax_errorenv"Hh_json.object: trailing comma"elseloop(p::members)endineatenv'{';ifpeekenv='}'thenbegineatenv'}';JSON_Object([])endelseletres=loop[]ineatenv'}';resandjs_arrayenv=letrecelementsaccum=letv=js_valueenvinifpeekenv<>','thenJSON_Array(List.rev(v::accum))elsebegineatenv',';ifpeekenv=']'thenifenv.allow_trailing_commathenJSON_Array(List.rev(v::accum))elsesyntax_errorenv"Hh_json.array: trailing comma"elseelements(v::accum)endineatenv'[';ifpeekenv=']'thenbegineatenv']';JSON_Array([])endelseletres=elements[]inbegineatenv']';resendandjs_pairenv=skip_blank_charsenv;letk=js_stringenvinskip_blank_charsenv;eatenv':';letv=js_valueenvinmatchkwith|JSON_Strings->(s,v)|_->syntax_errorenv"Hh_json.js_pair: expected a JSON String"letstring_of_filefilename=letic=open_infilenameinletbuf=Buffer.create5096inletrecloop()=matchtrySome(input_lineic)with_->Nonewith|None->Buffer.contentsbuf|Somel->beginBuffer.add_stringbufl;Buffer.add_charbuf'\n';loop();endinloop()(* Writing JSON *)(* buf_concat :
* Designed as a substitute for String.concat that passes a buffer
* into which intermediate strings are added, and also includes left
* and right bracket (lb and rb) in addition to sep. They are strings,
* despite common case of (), [],{}, or even <>, to handle missing brackets,
* brackets with spacing and multichar brackets like OCaml's arrays ([| and |]).
* The A conc_elt function parameter performs the operation of transforming
* the list element to a string and adding it to the buffer, the simplest
* example would be fun x -> Buffer.add_string (to_string x)
*)letbuf_concat~buf~lb~rb~sep~concat_eltl=Buffer.add_stringbuflb;(matchlwith|[]->()|elt::elts->concat_eltbufelt;List.iterelts~f:beginfune->Buffer.add_stringbufsep;concat_eltbufeend);Buffer.add_stringbufrbletadd_charbufc=Buffer.add_charbufcletadd_stringbufs=Buffer.add_stringbufsletescapes=letb=Buffer.create((String.lengths)+2)inBuffer.add_charb'"';s|>String.iterbeginfunc->letcode=Char.codecinmatchc,codewith|'\\',_->Buffer.add_stringb"\\\\"|'"',_->Buffer.add_stringb"\\\""|'\n',_->Buffer.add_stringb"\\n"|'\r',_->Buffer.add_stringb"\\r"|'\t',_->Buffer.add_stringb"\\t"|_,_whencode<=0x1f->Printf.sprintf"\\u%04x"code|>Buffer.add_stringb|_->Buffer.add_charbcend;Buffer.add_charb'"';Buffer.contentsbletrecadd_json_to_buffer(buf:Buffer.t)(json:json):unit=matchjsonwith|JSON_Objectl->buf_concat~buf~lb:"{"~rb:"}"~sep:","~concat_elt:add_assoc_to_bufferl|JSON_Arrayl->buf_concat~buf~lb:"["~rb:"]"~sep:","~concat_elt:add_json_to_bufferl|JSON_Strings->add_stringbuf(escapes)|JSON_Numbern->add_stringbufn|JSON_Boolb->ifbthenadd_stringbuf"true"elseadd_stringbuf"false"|JSON_Null->add_stringbuf"null"andadd_assoc_to_buffer(buf:Buffer.t)(k,v)=add_stringbuf(escapek);add_charbuf':';add_json_to_bufferbufvletrecjson_to_string?(pretty=false)(json:json):string=ifprettythenjson_to_multilinejsonelseletbuf=Buffer.create1024in(* need a better estimate! *)add_json_to_bufferbufjson;Buffer.contentsbufandjson_to_multilinejson=letrecloopindentjson=letsingle=json_to_stringjsoninifString.lengthsingle<80thensingleelsematchjsonwith|JSON_Arrayl->letnl=List.mapl~f:(loop(indent^" "))in"[\n"^indent^" "^(String.concat(",\n"^indent^" ")nl)^"\n"^indent^"]"|JSON_Objectl->letnl=List.mapl~f:(fun(k,v)->indent^" "^(json_to_string(JSON_Stringk))^":"^(loop(indent^" ")v))in"{\n"^(String.concat",\n"nl)^"\n"^indent^"}"|_->singleinloop""jsonletrecoutput_listocelemsoutput_elem:unit=matchelemswith|[]->()|[elem]->output_elemocelem|elem::other_elems->output_elemocelem;output_stringoc",";output_listocother_elemsoutput_elemletrecjson_to_outputoc(json:json):unit=matchjsonwith|JSON_Objectl->output_stringoc"{";output_listocljson_assoc_to_output;output_stringoc"}";|JSON_Arrayl->output_stringoc"[";output_listocljson_to_output;output_stringoc"]";|JSON_Strings->output_stringoc(escapes)|JSON_Numbern->output_stringocn|JSON_Boolb->output_stringoc(ifbthen"true"else"false")|JSON_Null->output_stringoc"null"andjson_assoc_to_outputoc(k,v):unit=output_stringoc(escapek);output_stringoc":";json_to_outputocvletjson_of_string?(strict=true)s=letlb=create_envstrictsinjs_valuelbletjson_of_file?strictfilename=json_of_string?strict(string_of_filefilename)letint_n=JSON_Number(string_of_intn)letstring_s=JSON_Stringsletget_object_exn=function|JSON_Objecto->o|_->assertfalseletget_array_exn=function|JSON_Arraya->a|_->assertfalseletget_string_exn=function|JSON_Strings->s|_->assertfalseletget_number_exn=function|JSON_Numbers->s|_->assertfalseletget_number_int_exn=function|JSON_Numbers->int_of_strings|_->assertfalseletget_bool_exn=function|JSON_Boolb->b|_->assertfalseletopt_string_to_json=function|Somex->JSON_Stringx|None->JSON_Nullletopt_int_to_json=function|Somex->JSON_Number(string_of_intx)|None->JSON_Nulltypejson_type=|Object_t|Array_t|String_t|Number_t|Integer_t|Bool_tletjson_type_to_string=function|Object_t->"Object"|Array_t->"Array"|String_t->"String"|Number_t->"Number"|Integer_t->"Integer"|Bool_t->"Bool"moduletypeAccess=sigtypekeytrace=stringlisttypeaccess_failure=|Not_an_objectofkeytrace|Missing_key_errorofstring*keytrace|Wrong_type_errorofkeytrace*json_typetype'am=(('a*keytrace),access_failure)Hack_result.tvalaccess_failure_to_string:access_failure->stringvalreturn:'a->'amval(>>=):'am->(('a*keytrace)->'bm)->'bmvalcounit_with:(access_failure->'a)->'am->'avalget_obj:string->json*keytrace->jsonmvalget_bool:string->json*keytrace->boolmvalget_string:string->json*keytrace->stringmvalget_number:string->json*keytrace->stringmvalget_number_int:string->json*keytrace->intmvalget_array:string->json*keytrace->(jsonlist)mvalget_val:string->json*keytrace->jsonmendmoduleAccess=structtypekeytrace=stringlisttypeaccess_failure=|Not_an_objectofkeytrace|Missing_key_errorofstring*keytrace|Wrong_type_errorofkeytrace*json_typetype'am=(('a*keytrace),access_failure)Hack_result.tletkeytrace_to_stringx=ifx=[]then""elseletres=List.mapx~f:(funx->"["^x^"]")|>String.concat" "in" (at field "^res^")"letaccess_failure_to_string=function|Not_an_objectx->Printf.sprintf"Value is not an object %s"(keytrace_to_stringx)|Missing_key_error(x,y)->Printf.sprintf"Missing key: %s%s"x(keytrace_to_stringy)|Wrong_type_error(x,y)->Printf.sprintf"Value expected to be %s%s"(json_type_to_stringy)(keytrace_to_stringx)letreturnv=Hack_result.Ok(v,[])let(>>=)mf=Hack_result.bindmfletcounit_withfm=matchmwith|Hack_result.Ok(v,_)->v|Hack_result.Errore->feletcatch_type_errorexpf(v,keytrace)=tryHack_result.Ok(fv,keytrace)with|Failuremsgwhen(String.equal"int_of_string"msg)->Hack_result.Error(Wrong_type_error(keytrace,exp))|Assert_failure_->Hack_result.Error(Wrong_type_error(keytrace,exp))letget_valk(v,keytrace)=trybeginletobj=get_object_exnvinletcandidate=List.fold_leftobj~init:None~f:(funopt(key,json)->ifopt<>Nonethenoptelseifkey=kthen(Somejson)elseNone)inmatchcandidatewith|None->Hack_result.Error(Missing_key_error(k,keytrace))|Someobj->Hack_result.Ok(obj,k::keytrace)endwith|Assert_failure_->Hack_result.Error(Not_an_object(keytrace))letmake_object_jsonv=JSON_Object(get_object_exnv)letget_objk(v,keytrace)=get_valk(v,keytrace)>>=catch_type_errorObject_tmake_object_jsonletget_boolk(v,keytrace)=get_valk(v,keytrace)>>=catch_type_errorBool_tget_bool_exnletget_stringk(v,keytrace)=get_valk(v,keytrace)>>=catch_type_errorString_tget_string_exnletget_numberk(v,keytrace)=get_valk(v,keytrace)>>=catch_type_errorNumber_tget_number_exnletget_number_intk(v,keytrace)=get_valk(v,keytrace)>>=catch_type_errorInteger_tget_number_int_exnletget_arrayk(v,keytrace)=get_valk(v,keytrace)>>=catch_type_errorArray_tget_array_exnend