123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700# 1 "reason_syntax_util.cppo.ml"(* Hello! Welcome to the Reason syntax util logic.
This file's shared between the Reason repo and the BuckleScript repo. In
Reason, it's in src/reason-parser. In BuckleScript, it's in
jscomp/outcome_printer. We periodically copy this file from Reason (the source
of truth) to BuckleScript, then uncomment the #if #else #end cppo macros you
see in the file. That's because BuckleScript's on OCaml 4.02 while Reason's on
4.04; so the #if macros surround the pieces of code that are different between
the two compilers.
When you modify this file, please make sure you're not dragging in too many
things. You don't necessarily have to test the file on both Reason and
BuckleScript; ping @chenglou and a few others and we'll keep them synced up by
patching the right parts, through the power of types(tm)
*)# 18 "reason_syntax_util.cppo.ml"openReason_migrate_parsetreeopenAst_408# 22 "reason_syntax_util.cppo.ml"openAsttypesopenAst_mapperopenParsetreeopenLongident(* Rename labels in function definition/application and records *)letrename_labels=reffalse(** Check to see if the string `s` is made up of `keyword` and zero or more
trailing `_` characters. *)letpotentially_conflicts_with~keywords=lets_length=String.lengthsinletkeyword_length=String.lengthkeywordin(* It can't be a match if s is shorter than keyword *)s_length>=keyword_length&&(try(* Ensure s starts with keyword... *)fori=0tokeyword_length-1doifkeyword.[i]<>s.[i]thenraiseExit;done;(* ...and contains nothing else except trailing _ characters *)fori=keyword_lengthtos_length-1doifs.[i]<>'_'thenraiseExit;done;(* If we've made it this far there's a potential conflict *)truewith|Exit->false)(** Add/remove an appropriate suffix when mangling potential keywords *)letstring_add_suffixx=x^"_"letstring_drop_suffixx=String.subx0(String.lengthx-1)(** What do these *_swap functions do? Here's an example: Reason code uses `!`
for logical not, while ocaml uses `not`. So, for converting between reason
and ocaml syntax, ocaml `not` converts to `!`, reason `!` converts to
`not`.
In more complicated cases where a reserved keyword exists in one syntax but
not the other, these functions translate any potentially conflicting
identifier into the same identifier with a suffix attached, or remove the
suffix when converting back. Two examples:
reason to ocaml:
pub: invalid in reason to begin with
pub_: pub
pub__: pub_
ocaml to reason:
pub: pub_
pub_: pub__
pub__: pub___
=====
reason to ocaml:
match: match_
match_: match__
match__: match___
ocaml to reason:
match: invalid in ocaml to begin with
match_: match
match__: match_
*)letreason_to_ml_swap=function|"!"->"not"|"^"->"!"|"++"->"^"|"==="->"=="|"=="->"="(* ===\/ and !==\/ are not representable in OCaml but
* representable in Reason
*)|"\\!=="->"!=="|"\\==="->"==="|"!="->"<>"|"!=="->"!="|xwhen(potentially_conflicts_with~keyword:"match"x||potentially_conflicts_with~keyword:"method"x||potentially_conflicts_with~keyword:"private"x||potentially_conflicts_with~keyword:"not"x)->string_add_suffixx|xwhen(potentially_conflicts_with~keyword:"switch_"x||potentially_conflicts_with~keyword:"pub_"x||potentially_conflicts_with~keyword:"pri_"x)->string_drop_suffixx|everything_else->everything_elseletml_to_reason_swap=function|"not"->"!"|"!"->"^"|"^"->"++"|"=="->"==="|"="->"=="(* ===\/ and !==\/ are not representable in OCaml but
* representable in Reason
*)|"!=="->"\\!=="|"==="->"\\==="|"<>"->"!="|"!="->"!=="|xwhen(potentially_conflicts_with~keyword:"match_"x||potentially_conflicts_with~keyword:"method_"x||potentially_conflicts_with~keyword:"private_"x||potentially_conflicts_with~keyword:"not_"x)->string_drop_suffixx|xwhen(potentially_conflicts_with~keyword:"switch"x||potentially_conflicts_with~keyword:"pub"x||potentially_conflicts_with~keyword:"pri"x)->string_add_suffixx|everything_else->everything_elseletescape_stringstr=letbuf=Buffer.create(String.lengthstr)inString.iter(func->matchcwith|'\t'->Buffer.add_stringbuf"\\t"|'\r'->Buffer.add_stringbuf"\\r"|'\n'->Buffer.add_stringbuf"\\n"|'\\'->Buffer.add_stringbuf"\\\\"|'"'->Buffer.add_stringbuf"\\\""|cwhenc<' '->Buffer.add_stringbuf(Char.escapedc)|c->Buffer.add_charbufc)str;Buffer.contentsbuf(* the stuff below contains side-effects and are not used by BuckleScript's
vendored version of reason_syntax_util.ml. So we can neglect it *)# 160 "reason_syntax_util.cppo.ml"(*
UTF-8 characters are encoded like this (most editors are UTF-8)
0xxxxxxx (length 1)
110xxxxx 10xxxxxx (length 2)
1110xxxx 10xxxxxx 10xxxxxx (length 3)
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (length 4)
Numbers over 127 cannot be encoded in UTF in a single byte, so they use two
bytes. That means we can use any characters between 128-255 to encode special
characters that would never be written by the user and thus never be confused
for our special formatting characters.
*)(* Logic for handling special behavior that only happens if things break. We
use characters that will never appear in the printed output if actually
written in source code. The OCaml formatter will replace them with the escaped
versions When moving to a new formatter, the formatter may *not* escape these
an in that case we need the formatter to accept blacklists of characters to
escape, but more likely is that the new formatter allows us to do these kinds
of if-break logic without writing out special characters for post-processing.
*)moduleTrailingCommaMarker=struct(* TODO: You can detect failed parsings by *NOT* omitting the final comma *ever*. *)(* A trailing comma will only be rendered if it is not immediately
* followed by a closing paren, bracket, or brace *)letchar=Char.chr249(* ˘ *)letstring=String.make1charend(* Special character marking the end of a line. Nothing should be printed
* after this marker. Example usage: // comments shouldn't have content printed
* at the end of the comment. By attaching an EOLMarker.string at the end of the
* comment our postprocessing step will ensure a linebreak at the position
* of the marker. *)moduleEOLMarker=structletchar=Char.chr248letstring=String.make1charend(** [is_prefixed prefix i str] checks if prefix is the prefix of str
* starting from position i
*)letis_prefixedprefixstri=letlen=String.lengthprefixinletj=ref0inwhile!j<len&&String.unsafe_getprefix!j=String.unsafe_getstr(i+!j)doincrjdone;(!j=len)(**
* pick_while returns a tuple where first element is longest prefix (possibly empty) of the list of elements that satisfy p
* and second element is the remainder of the list
*)letrecpick_whilep=function|[]->[],[]|hd::tlwhenphd->let(satisfied,not_satisfied)=pick_whileptlinhd::satisfied,not_satisfied|l->([],l)(** [find_substring sub str i]
returns the smallest [j >= i] such that [sub = str.[j..length sub - 1]]
raises [Not_found] if there is no such j
behavior is not defined if [sub] is the empty string
*)letfind_substringsubstri=letlen=String.lengthstr-String.lengthsubinletfound=reffalseandi=refiinwhilenot!found&&!i<=lendoifis_prefixedsubstr!ithenfound:=trueelseincri;done;ifnot!foundthenraiseNot_found;!i(** [replace_string old_str new_str str] replaces old_str to new_str in str *)letreplace_stringold_strnew_strstr=matchfind_substringold_strstr0with|exceptionNot_found->str|occurrence->letbuffer=Buffer.create(String.lengthstr+15)inletrecloopij=Buffer.add_substringbufferstri(j-i);Buffer.add_stringbuffernew_str;leti=j+String.lengthold_strinmatchfind_substringold_strstriwith|j->loopij|exceptionNot_found->Buffer.add_substringbufferstri(String.lengthstr-i)inloop0occurrence;Buffer.contentsbuffer(* This is lifted from https://github.com/bloomberg/bucklescript/blob/14d94bb9c7536b4c5f1208c8e8cc715ca002853d/jscomp/ext/ext_string.ml#L32
Thanks @bobzhang and @hhugo! *)letsplit_by?(keep_empty=false)is_delimstr=letlen=String.lengthstrinletrecloopacclast_pospos=ifpos=-1theniflast_pos=0&¬keep_emptythen(*
{[ split " test_unsafe_obj_ffi_ppx.cmi" ~keep_empty:false ' ']}
*)accelseString.substr0last_pos::accelseifis_delimstr.[pos]thenletnew_len=(last_pos-pos-1)inifnew_len<>0||keep_emptythenletv=String.substr(pos+1)new_leninloop(v::acc)pos(pos-1)elseloopaccpos(pos-1)elseloopacclast_pos(pos-1)inloop[]len(len-1)letrectrim_right_idxstridx=ifidx=-1then0elsematchString.getstridxwith|'\t'|' '|'\n'|'\r'->trim_right_idxstr(idx-1)|_->idx+1lettrim_rightstr=letlength=String.lengthstriniflength=0then""elseletindex=trim_right_idxstr(length-1)inifindex=0then""elseifindex=lengththenstrelseString.substr0indexletprocessLineline=letrightTrimmed=trim_rightlineinlettrimmedLen=String.lengthrightTrimmediniftrimmedLen=0thenrightTrimmedelseletsegments=split_by~keep_empty:false(func->c=TrailingCommaMarker.char)rightTrimmedin(* Now we concat the portions back together without any trailing comma markers
- except we detect if there was a final trailing comma marker which we know
must be before a newline so we insert a regular comma. This achieves
"intelligent" trailing commas. *)lethadTrailingCommaMarkerBeforeNewline=String.getrightTrimmed(trimmedLen-1)=TrailingCommaMarker.charinletalmostEverything=String.concat""segmentsinletlineBuilder=ifhadTrailingCommaMarkerBeforeNewlinethenalmostEverything^","elsealmostEverythingin(* Ensure EOLMarker.char is replaced by a newline *)split_by~keep_empty:false(func->c=EOLMarker.char)lineBuilder|>List.maptrim_right|>String.concat"\n"letprocessLineEndingsAndStartsstr=split_by~keep_empty:true(funx->x='\n')str|>List.mapprocessLine|>String.concat"\n"|>String.trimletisLineCommentstr=(* true iff the first \n is the last character *)matchString.indexstr'\n'with|exceptionNot_found->false|n->n=String.lengthstr-1letmap_lidentflid=letswapped=matchlid.txtwith|Lidents->Lident(fs)|Ldot(longPrefix,s)->Ldot(longPrefix,fs)|Lapply(y,s)->Lapply(y,s)in{lidwithtxt=swapped}letmap_arg_labelf=function|Nolabel->Nolabel|Labelledlbl->Labelled(flbl)|Optionallbl->Optional(flbl)letmap_class_exprfclass_expr={class_exprwithpcl_desc=matchclass_expr.pcl_descwith|Pcl_constr(lid,ts)->Pcl_constr(map_lidentflid,ts)|e->e}letmap_class_typefclass_type={class_typewithpcty_desc=matchclass_type.pcty_descwith|Pcty_constr(lid,ct)->Pcty_constr(map_lidentflid,ct)|Pcty_arrow(arg_lbl,ct,cls_type)->Pcty_arrow(map_arg_labelfarg_lbl,ct,cls_type)|x->x}letmap_core_typeftyp={typwithptyp_desc=matchtyp.ptyp_descwith|Ptyp_varvar->Ptyp_var(fvar)|Ptyp_arrow(lbl,t1,t2)->letlbl'=matchlblwith|Labelledswhen!rename_labels->Labelled(fs)|Optionalswhen!rename_labels->Optional(fs)|lbl->lblinPtyp_arrow(lbl',t1,t2)|Ptyp_constr(lid,typs)->Ptyp_constr(map_lidentflid,typs)|Ptyp_object(fields,closed_flag)when!rename_labels->Ptyp_object(List.map(function|{pof_desc=Otag(s,typ);_}aspof->{pofwithpof_desc=Otag({swithtxt=fs.txt},typ)}|other->other)fields,closed_flag)|Ptyp_class(lid,typs)->Ptyp_class(map_lidentflid,typs)|Ptyp_alias(typ,s)->Ptyp_alias(typ,fs)|Ptyp_variant(rfs,closed,lbls)->Ptyp_variant(List.map(function|{prf_desc=Rtag(lbl,b,cts)}asprf->{prfwithprf_desc=Rtag({lblwithtxt=flbl.txt},b,cts)}|t->t)rfs,closed,lbls)|Ptyp_poly(vars,typ)->Ptyp_poly(List.map(funli->{liwithtxt=fli.txt})vars,typ)|Ptyp_package(lid,typs)->Ptyp_package(map_lidentflid,List.map(fun(lid,typ)->(map_lidentflid,typ))typs)|other->other}(** identifier_mapper maps all identifiers in an AST with a mapping function f
this is used by swap_operator_mapper right below, to traverse the whole AST
and swapping the symbols listed above.
*)letidentifier_mapperfsuper=letmap_fieldsfields=List.map(fun(lid,x)->(map_lidentflid,x))fieldsinletmap_name({txt}asname)={namewithtxt=(ftxt)}inletmap_lidlid=map_lidentflidinletmap_labellabel=map_arg_labelflabelin{superwithexpr=beginfunmapperexpr->letexpr=matchexprwith|{pexp_desc=Pexp_identlid}->{exprwithpexp_desc=Pexp_ident(map_lidlid)}|{pexp_desc=Pexp_fun(label,eo,pat,e)}when!rename_labels->{exprwithpexp_desc=Pexp_fun(map_labellabel,eo,pat,e)}|{pexp_desc=Pexp_apply(e,args)}when!rename_labels->{exprwithpexp_desc=Pexp_apply(e,List.map(fun(label,e)->(map_labellabel),e)args)}|{pexp_desc=Pexp_variant(s,e)}->{exprwithpexp_desc=Pexp_variant(fs,e)}|{pexp_desc=Pexp_record(fields,closed)}when!rename_labels->{exprwithpexp_desc=Pexp_record(map_fieldsfields,closed)}|{pexp_desc=Pexp_field(e,lid)}when!rename_labels->{exprwithpexp_desc=Pexp_field(e,map_lidlid)}|{pexp_desc=Pexp_setfield(e1,lid,e2)}when!rename_labels->{exprwithpexp_desc=Pexp_setfield(e1,map_lidlid,e2)}|{pexp_desc=Pexp_send(e,s)}->{exprwithpexp_desc=Pexp_send(e,{swithtxt=fs.txt})}|{pexp_desc=Pexp_newlid}->{exprwithpexp_desc=Pexp_new(map_lidlid)}|{pexp_desc=Pexp_setinstvar(name,e)}->{exprwithpexp_desc=Pexp_setinstvar(map_namename,e)}|{pexp_desc=Pexp_overridename_exp_list}->letname_exp_list=List.map(fun(name,e)->(map_namename,e))name_exp_listin{exprwithpexp_desc=Pexp_overridename_exp_list}|{pexp_desc=Pexp_newtype(s,e)}->{exprwithpexp_desc=Pexp_newtype({swithtxt=fs.txt},e)}|_->exprinsuper.exprmapperexprend;pat=beginfunmapperpat->letpat=matchpatwith|{ppat_desc=Ppat_varname}->{patwithppat_desc=Ppat_var(map_namename)}|{ppat_desc=Ppat_alias(p,name)}->{patwithppat_desc=Ppat_alias(p,map_namename)}|{ppat_desc=Ppat_variant(s,po)}->{patwithppat_desc=Ppat_variant(fs,po)}|{ppat_desc=Ppat_record(fields,closed)}when!rename_labels->{patwithppat_desc=Ppat_record(map_fieldsfields,closed)}|{ppat_desc=Ppat_typelid}->{patwithppat_desc=Ppat_type(map_lidlid)}|_->patinsuper.patmapperpatend;value_description=beginfunmapperdesc->letdesc'={descwithpval_name=map_namedesc.pval_name}insuper.value_descriptionmapperdesc'end;type_declaration=beginfunmappertype_decl->lettype_decl'={type_declwithptype_name=map_nametype_decl.ptype_name}inlettype_decl''=matchtype_decl'.ptype_kindwith|Ptype_recordlstwhen!rename_labels->{type_decl'withptype_kind=Ptype_record(List.map(funlbl->{lblwithpld_name=map_namelbl.pld_name})lst)}|_->type_decl'insuper.type_declarationmappertype_decl''end;typ=beginfunmappertyp->super.typmapper(map_core_typeftyp)end;class_declaration=beginfunmapperclass_decl->letclass_decl'={class_declwithpci_name=map_nameclass_decl.pci_name;pci_expr=map_class_exprfclass_decl.pci_expr}insuper.class_declarationmapperclass_decl'end;class_field=beginfunmapperclass_field->letclass_field_desc'=matchclass_field.pcf_descwith|Pcf_inherit(ovf,e,lo)->Pcf_inherit(ovf,map_class_exprfe,lo)|Pcf_val(lbl,mut,kind)->Pcf_val({lblwithtxt=flbl.txt},mut,kind)|Pcf_method(lbl,priv,kind)->Pcf_method({lblwithtxt=flbl.txt},priv,kind)|x->xinsuper.class_fieldmapper{class_fieldwithpcf_desc=class_field_desc'}end;class_type_field=beginfunmapperclass_type_field->letclass_type_field_desc'=matchclass_type_field.pctf_descwith|Pctf_inheritclass_type->Pctf_inherit(map_class_typefclass_type)|Pctf_val(lbl,mut,vf,ct)->Pctf_val({lblwithtxt=flbl.txt},mut,vf,ct)|Pctf_method(lbl,pf,vf,ct)->Pctf_method({lblwithtxt=flbl.txt},pf,vf,ct)|x->xinsuper.class_type_fieldmapper{class_type_fieldwithpctf_desc=class_type_field_desc'}end;class_type_declaration=beginfunmapperclass_type_decl->letclass_type_decl'={class_type_declwithpci_name=map_nameclass_type_decl.pci_name}insuper.class_type_declarationmapperclass_type_decl'end;module_type_declaration=beginfunmappermodule_type_decl->letmodule_type_decl'={module_type_declwithpmtd_name=map_namemodule_type_decl.pmtd_name}insuper.module_type_declarationmappermodule_type_decl'end;}letremove_stylistic_attrs_mapper_makersuper=letopenAst_408inletopenAst_mapperin{superwithexpr=beginfunmapperexpr->let{Reason_attributes.stylisticAttrs;arityAttrs;docAttrs;stdAttrs;jsxAttrs}=Reason_attributes.partitionAttributes~allowUncurry:falseexpr.pexp_attributesinletexpr=ifstylisticAttrs!=[]then{exprwithpexp_attributes=arityAttrs@docAttrs@stdAttrs@jsxAttrs}elseexprinsuper.exprmapperexprend;pat=beginfunmapperpat->let{Reason_attributes.stylisticAttrs;arityAttrs;docAttrs;stdAttrs;jsxAttrs}=Reason_attributes.partitionAttributes~allowUncurry:falsepat.ppat_attributesinletpat=ifstylisticAttrs!=[]then{patwithppat_attributes=arityAttrs@docAttrs@stdAttrs@jsxAttrs}elsepatinsuper.patmapperpatend;}letremove_stylistic_attrs_mapper=remove_stylistic_attrs_mapper_makerAst_mapper.default_mapper# 586 "reason_syntax_util.cppo.ml"letnoop_mapper=letnoop=fun_mapperx->xin{Ast_mapper.default_mapperwithexpr=noop;structure=noop;structure_item=noop;signature=noop;signature_item=noop;}(* Don't need to backport past 4.08 *)letbackport_letopt_mapper=noop_mapper# 662 "reason_syntax_util.cppo.ml"letescape_stars_slashesstr=ifString.containsstr'/'thenreplace_string"/*""/\\*"@@replace_string"*/""*\\/"@@replace_string"//""/\\/"@@strelsestr(** escape_stars_slashes_mapper escapes all stars and slashes in an AST *)letescape_stars_slashes_mapper=identifier_mapperescape_stars_slashes(* To be used in parser, transform a token into an ast node with different identifier
*)letreason_to_ml_swap_operator_mapper=identifier_mapperreason_to_ml_swap(* To be used in printer, transform an ast node into a token with different identifier
*)letml_to_reason_swap_operator_mapper=identifier_mapperml_to_reason_swap(* attribute_equals tests an attribute is txt
*)letattribute_equalsto_compare=function|{attr_name={txt};_}->txt=to_compare(* attribute_exists tests if an attribute exists in a list
*)letattribute_existstxtattributes=List.exists(attribute_equalstxt)attributes(* conflicted_attributes tests if both attribute1 and attribute2
* exist
*)letattributes_conflictedattribute1attribute2attributes=attribute_existsattribute1attributes&&attribute_existsattribute2attributes(* normalized_attributes removes attribute from a list of attributes
*)letnormalized_attributesattributeattributes=List.filter(funx->not(attribute_equalsattributex))attributes(* apply_mapper family applies an ast_mapper to an ast *)letapply_mapper_to_structuresmapper=mapper.structuremappersletapply_mapper_to_signaturesmapper=mapper.signaturemappersletapply_mapper_to_typesmapper=mapper.typmappersletapply_mapper_to_exprsmapper=mapper.exprmappersletapply_mapper_to_patternsmapper=mapper.patmappersletapply_mapper_to_toplevel_phrasetoplevel_phrasemapper=matchtoplevel_phrasewith|Ptop_defx->Ptop_def(apply_mapper_to_structurexmapper)|x->xletapply_mapper_to_use_fileuse_filemapper=List.map(funx->apply_mapper_to_toplevel_phrasexmapper)use_fileletmap_firstf=function|[]->invalid_arg"Syntax_util.map_first: empty list"|x::xs->fx::xsletmap_lastfl=matchList.revlwith|[]->invalid_arg"Syntax_util.map_last: empty list"|x::xs->List.rev(fx::xs)letlocation_is_beforeloc1loc2=letopenLocationinloc1.loc_end.Lexing.pos_cnum<=loc2.loc_start.Lexing.pos_cnumletlocation_containsloc1loc2=letopenLocationinloc1.loc_start.Lexing.pos_cnum<=loc2.loc_start.Lexing.pos_cnum&&loc1.loc_end.Lexing.pos_cnum>=loc2.loc_end.Lexing.pos_cnum# 737 "reason_syntax_util.cppo.ml"letsplit_compiler_error(err:Location.error)=(err.main.loc,Format.asprintf"%t"err.main.txt)# 744 "reason_syntax_util.cppo.ml"letexplode_strstr=letrecloopacci=ifi<0thenaccelseloop(str.[i]::acc)(i-1)inloop[](String.lengthstr-1)# 752 "reason_syntax_util.cppo.ml"moduleClflags=structincludeClflags# 756 "reason_syntax_util.cppo.ml"letfast=unsafe# 758 "reason_syntax_util.cppo.ml"endletparse_lids=# 762 "reason_syntax_util.cppo.ml"matchLongident.unflatten(String.split_on_char'.'s)with|Somelid->lid|None->failwith(Format.asprintf"parse_lid: unable to parse '%s' to longident"s)