123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363(***********************************************************************)(* *)(* Reason *)(* *)(***********************************************************************)(*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)(* Entry points in the parser *)(**
* Provides a simple interface to the most common parsing entrypoints required
* by editor/IDE toolchains, preprocessors, and pretty printers.
*
* The form of this entrypoint includes more than what the standard OCaml
* toolchain (oprof/ocamldoc) expects, but is still compatible.
*
* [implementation_with_comments] and [interface_with_comments] includes
* additional information (about comments) suitable for building pretty
* printers, editor, IDE and VCS integration.
*
* The comments include the full text of the comment (typically in between the
* "(*" and the "*)", as well as location information for that comment.
*
* WARNING: The "end" location is one greater than the actual final position!
* (for both [associatedTextLoc] and [commentLoc]).
*
* Currently, the location information for comments is of the form:
*
* (associatedTextLoc)
*
* But we should quickly change it to be of the form:
*
* (associatedTextLoc, commentLoc)
*
* Where the [commentLoc] is the actual original location of the comment,
* and the [associatedTextLoc] records the location in the file that the
* comment is attached to. If [associatedTextLoc] and [commentLoc] are the
* same, then the comment is "free floating" in that it only attaches to itself.
* The [Reason] pretty printer will try its best to interleave those comments
* in the containing list etc. But if [associatedTextLoc] expands beyond
* the [commentLoc] it means the comment and the AST that is captured by
* the [associatedTextLoc] are related - where "related" is something
* this [reason_toolchain] decides (but in short it handles "end of line
* comments"). Various pretty printers can decide how to preserve this
* relatedness. Ideally, it would preserve end of line comments, but in the
* short term, it might merely use that relatedness to correctly attach
* end of line comments to the "top" of the AST node.
*
* let lst = [
*
* ]; (* Comment *)
* ----commentLoc-----
* ---associatedTextLoc----
*
*
* Ideally that would be formatted as:
*
* let lst = [
*
* ]; (* Comment *)
*
* Or:
*
* let lst = [ ]; (* Comment *)
*
*
* But a shorter term solution would use that [associatedTextLoc] to at least
* correctly attach the comment to the correct node, even if not "end of line".
*
* (* Comment *)
* let lst = [ ];
*)openReason_toolchain_confopenMigrate_parsetreeopenAst_408openLocationopenLexingmoduleComment=Reason_commentletsetup_lexbufuse_stdinfilename=(* Use custom method of lexing from the channel to keep track of the input so that we can
reformat tokens in the toolchain*)letlexbuf=matchuse_stdinwith|true->Lexing.from_channelstdin|false->letfile_chan=open_infilenameinseek_infile_chan0;Lexing.from_channelfile_chaninLocation.initlexbuffilename;lexbufletrecleft_expand_commentshould_scan_prev_linesourceloc_start=ifloc_start=0then(String.unsafe_getsource0,true,0)elseletc=String.unsafe_getsource(loc_start-1)inmatchcwith|'\t'|' '->left_expand_commentshould_scan_prev_linesource(loc_start-1)|'\n'whenshould_scan_prev_line->left_expand_commentshould_scan_prev_linesource(loc_start-1)|'\n'->(c,true,loc_start)|_->(c,false,loc_start)letrecright_expand_commentshould_scan_next_linesourceloc_start=ifloc_start=String.lengthsourcethen(String.unsafe_getsource(String.lengthsource-1),true,String.lengthsource)elseletc=String.unsafe_getsourceloc_startinmatchcwith|'\t'|' '->right_expand_commentshould_scan_next_linesource(loc_start+1)|'\n'whenshould_scan_next_line->right_expand_commentshould_scan_next_linesource(loc_start+1)|'\n'->(c,true,loc_start)|_->(c,false,loc_start)moduleCreate_parse_entrypoint(Toolchain_impl:Toolchain_spec):Toolchain=structletbuffer_add_lexbufbufskiplexbuf=letbytes=lexbuf.Lexing.lex_bufferinletstart=lexbuf.Lexing.lex_start_pos+skipinletstop=lexbuf.Lexing.lex_buffer_leninBuffer.add_subbytesbufbytesstart(stop-start)letrefill_buffbufrefilllb=letskip=lb.Lexing.lex_buffer_len-lb.Lexing.lex_start_posinletresult=refilllbinbuffer_add_lexbufbufskiplb;result(* replaces Lexing.from_channel so we can keep track of the input for comment modification *)letkeep_from_lexbufbufferlexbuf=buffer_add_lexbufbuffer0lexbuf;letrefill_buff=refill_buffbufferlexbuf.Lexing.refill_buffin{lexbufwithrefill_buff}letextensions_of_errorserrors=ignore(Format.flush_str_formatter():string);leterror_extension(err,loc)=Reason_errors.report_errorFormat.str_formatter~locerr;letmsg=Format.flush_str_formatter()inletdue_to_recovery=matcherrwith|Reason_errors.Parsing_error_->true|Reason_errors.Lexing_error_->false|Reason_errors.Ast_error_->falseinifdue_to_recoverythenReason_errors.error_extension_node_from_recoverylocmsgelseReason_errors.error_extension_nodelocmsginList.maperror_extensionerrorsletwrap_with_commentsparsing_funattach_funlexbuf=letinput_copy=Buffer.create0inletlexbuf=keep_from_lexbufinput_copylexbufinToolchain_impl.safeguard_parsinglexbuf(fun()->letlexer=letinsert_completion_ident=!Reason_toolchain_conf.insert_completion_identinToolchain_impl.Lexer.init?insert_completion_identlexbufinletast,invalid_docstrings=letresult=if!Reason_config.recoverablethenReason_errors.recover_non_fatal_errors(fun()->parsing_funlexer)else(Ok(parsing_funlexer),[])inmatchresultwith|Okx,[]->x|Ok(x,ds),errors->(attach_funx(extensions_of_errorserrors),ds)|Errorexn,_->raiseexninletunmodified_comments=Toolchain_impl.Lexer.get_commentslexerinvalid_docstringsinletcontents=Buffer.contentsinput_copyinBuffer.resetinput_copy;ifcontents=""thenlet_=Parsing.clear_parser()inletmake_regular(text,location)=Comment.make~locationComment.Regulartextin(ast,List.mapmake_regularunmodified_comments)elseletrecclassifyAndNormalizeCommentsunmodified_comments=matchunmodified_commentswith|[]->[]|hd::tl->(letclassifiedTail=classifyAndNormalizeCommentstlinlet(txt,physical_loc)=hdin(* When searching for "^" regexp, returns location of newline + 1 *)let(stop_char,eol_start,virtual_start_pos)=left_expand_commentfalsecontentsphysical_loc.loc_start.pos_cnuminifReason_syntax_util.isLineCommenttxtthenletcomment=Comment.make~location:physical_loc(ifeol_startthenSingleLineelseEndOfLine)txtincomment::classifiedTailelseletone_char_before_stop_char=ifvirtual_start_pos<=1then' 'elseString.unsafe_getcontents(virtual_start_pos-2)in(*
*
* The following logic are designed for cases like:
* | (* comment *)
* X => 1
* we want to extend the comment to the next line so it can be
* correctly attached to X
*
* But we don't want it to extend to next line in this case:
*
* true || (* comment *)
* false
*
*)letshould_scan_next_line=stop_char='|'&&(one_char_before_stop_char=' '||one_char_before_stop_char='\n'||one_char_before_stop_char='\t')inlet(_,eol_end,virtual_end_pos)=right_expand_commentshould_scan_next_linecontentsphysical_loc.loc_end.pos_cnuminletend_pos_plus_one=physical_loc.loc_end.pos_cnuminletcomment_length=(end_pos_plus_one-physical_loc.loc_start.pos_cnum-4)inletoriginal_comment_contents=String.subcontents(physical_loc.loc_start.pos_cnum+2)comment_lengthinletlocation={physical_locwithloc_start={physical_loc.loc_startwithpos_cnum=virtual_start_pos};loc_end={physical_loc.loc_endwithpos_cnum=virtual_end_pos}}inletjust_afterloc'=loc'.loc_start.pos_cnum==location.loc_end.pos_cnum-1&&loc'.loc_start.pos_lnum==location.loc_end.pos_lnuminletcategory=match(eol_start,eol_end,classifiedTail)with|(true,true,_)->Comment.SingleLine|(false,true,_)->Comment.EndOfLine|(false,false,comment::_)(* End of line comment is one that has nothing but newlines or
* other comments its right, and has some AST to the left of it.
* For example, there are two end of line comments in:
*
* | Y(int, int); /* eol1 */ /* eol2 */
*)whenComment.categorycomment=Comment.EndOfLine&&just_after(Comment.locationcomment)->Comment.EndOfLine|_->Comment.Regularinletcomment=Comment.make~locationcategoryoriginal_comment_contentsincomment::classifiedTail)inletmodified_and_comment_with_category=classifyAndNormalizeCommentsunmodified_commentsinlet_=Parsing.clear_parser()in(ast,modified_and_comment_with_category))letdefault_errorlexbuferr=if!Reason_config.recoverablethenletloc,msg=matcherrwith|Location.Errorerr->Reason_syntax_util.split_compiler_errorerr|Reason_errors.Reason_error(e,loc)->Reason_errors.report_errorFormat.str_formatter~loce;(loc,Format.flush_str_formatter())|exn->(Location.currlexbuf,"default_error: "^Printexc.to_stringexn)in(loc,Reason_errors.error_extension_nodelocmsg)elseraiseerrletignore_attach_errorsx_extensions=(* FIXME: attach errors in AST *)x(*
* The canonical interface/implementations (with comments) are used with
* recovering mode for IDE integration. The parser itself likely
* implements its own recovery, but we need to recover in the event
* that the file couldn't even lex.
* Note, the location reported here is broken for some lexing errors
* (nested comments or unbalanced strings in comments) but at least we don't
* crash the process. TODO: Report more accurate location in those cases.
*)letimplementation_with_commentslexbuf=letattachimplextensions=(impl@List.mapAst_helper.Str.extensionextensions)intrywrap_with_commentsToolchain_impl.implementationattachlexbufwitherr->letloc,error=default_errorlexbuferrin([Ast_helper.Str.mk~loc(Parsetree.Pstr_extension(error,[]))],[])letcore_type_with_commentslexbuf=trywrap_with_commentsToolchain_impl.core_typeignore_attach_errorslexbufwitherr->letloc,error=default_errorlexbuferrin(Ast_helper.Typ.mk~loc(Parsetree.Ptyp_extensionerror),[])letinterface_with_commentslexbuf=letattachimplextensions=(impl@List.mapAst_helper.Sig.extensionextensions)intrywrap_with_commentsToolchain_impl.interfaceattachlexbufwitherr->letloc,error=default_errorlexbuferrin([Ast_helper.Sig.mk~loc(Parsetree.Psig_extension(error,[]))],[])lettoplevel_phrase_with_commentslexbuf=wrap_with_commentsToolchain_impl.toplevel_phraseignore_attach_errorslexbufletuse_file_with_commentslexbuf=wrap_with_commentsToolchain_impl.use_fileignore_attach_errorslexbuf(** [ast_only] wraps a function to return only the ast component
*)letast_onlyf=(funlexbuf->lexbuf|>f|>fst)letimplementation=ast_onlyimplementation_with_commentsletcore_type=ast_onlycore_type_with_commentsletinterface=ast_onlyinterface_with_commentslettoplevel_phrase=ast_onlytoplevel_phrase_with_commentsletuse_file=ast_onlyuse_file_with_comments(* Printing *)letprint_interface_with_commentsformatterinterface=Toolchain_impl.format_interface_with_commentsinterfaceformatterletprint_implementation_with_commentsformatterimplementation=Toolchain_impl.format_implementation_with_commentsimplementationformatterendmoduleML=Create_parse_entrypoint(Reason_toolchain_ocaml)moduleRE=Create_parse_entrypoint(Reason_toolchain_reason)moduleFrom_current=From_currentmoduleTo_current=To_current