123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599openMigrate_parsetree_versionsmoduleAst_io=Migrate_parsetree_ast_io(** {1 State a rewriter can access} *)typeextra=..typeconfig={tool_name:string;include_dirs:stringlist;load_path:stringlist;debug:bool;for_package :stringoption;extras:extralist;}let make_config ~tool_name?(include_dirs=[])?(load_path=[])?(debug=false)?for_package?(extras=[])()={tool_name;include_dirs;load_path;debug;for_package;extras}typecookie =Cookie:'types ocaml_version*'types get_expression->cookietypecookies=(string,cookie)Hashtbl.tletcreate_cookies()=Hashtbl.create 3letglobal_cookie_table=create_cookies ()letget_cookie tablenameversion=matchmatchHashtbl.findtablenamewith|result->Someresult|exceptionNot_found->matchAst_mapper.get_cookienamewith|Someexpr->Some(Cookie((moduleOCaml_current),expr))|None->match Hashtbl.findglobal_cookie_tablenamewith|result->Someresult|exception Not_found->Nonewith|None->None|Some(Cookie (version',expr))->Some((migrateversion'version).copy_expressionexpr)letset_cookietablenameversionexpr=Hashtbl.replace tablename(Cookie(version,expr))letset_global_cookienameversionexpr=set_cookieglobal_cookie_tablename versionexprletapply_cookiestable=Hashtbl.iter(funname(Cookie(version,expr))->Ast_mapper.set_cookiename((migrateversion(moduleOCaml_current)).copy_expressionexpr))tableletinitial_state()={tool_name=Ast_mapper.tool_name();include_dirs=!Clflags.include_dirs;load_path=Migrate_parsetree_compiler_functions.get_load_paths();debug=!Clflags.debug;for_package=!Clflags.for_package;extras =[];}(** {1 Registering rewriters} *)type'typesrewriter=config ->cookies->'typesget_mappertyperewriter_group=Rewriters:'typesocaml_version*(string*'typesrewriter)list->rewriter_groupletrewriter_group_names(Rewriters(_,l))=List.mapfst lletuniq_rewriter=Hashtbl.create7modulePos_map=Map.Make(structtypet=intletcompare:int->int->t=compareend)letregistered_rewriters =refPos_map.emptyletall_rewriters ()=Pos_map.bindings !registered_rewriters|>List.map(fun(_,r)->!r)|>List.concatletuniq_arg=Hashtbl.create7letregistered_args_reset=ref[]letregistered_args=ref[]let()=letset_cookies=matchString.indexs'='with|exception_->raise(Arg.Bad"invalid cookie, must be of the form \"<name>=<expr>\"")|i->letname=String.subs0iinletvalue=String.subs(i+1)(String.lengths-i-1)inletinput_name="<command-line>"inLocation.input_name:=input_name;letlexbuf=Lexing.from_stringvalueinlexbuf.Lexing.lex_curr_p<-{Lexing.pos_fname=input_name;pos_lnum=1;pos_bol=0;pos_cnum=0};letexpr=Parse.expressionlexbufinset_global_cookiename(moduleOCaml_current)exprinregistered_args:=("--cookie",Arg.Stringset_cookie,"NAME=EXPR Set the cookie NAME to EXPR")::!registered_argstype('types,'version,'rewriter)is_rewriter=|Is_rewriter:('types,'typesocaml_version,'typesrewriter)is_rewriterletadd_rewriter(typetypes)(typeversion)(typerewriter)(Is_rewriter :(types,version,rewriter)is_rewriter)(version:version)name(rewriter:rewriter)=let recadd_rewriter =function|[]->[Rewriters (version,[name,rewriter])]|(Rewriters(version',rewriters)asx)::xs->match compare_ocaml_versionversionversion'with|Eq ->Rewriters(version',(name,rewriter)::rewriters)::xs|Lt->Rewriters(version,[name,rewriter])::x::xs|Gt->x::add_rewriterxsinadd_rewriterlet register~name?reset_args?(args=[])?(position=0)versionrewriter=(* Validate name *)if name=""theninvalid_arg "Migrate_parsetree_driver.register: name is empty";ifHashtbl.memuniq_rewriternametheninvalid_arg("Migrate_parsetree_driver.register: rewriter "^name^" has already been registered")elseHashtbl.adduniq_rewritername();(* Validate arguments *)List.iter(fun(arg_name,_,_)->matchHashtbl.finduniq_argarg_namewith|other_rewriter->invalid_arg(Printf.sprintf"Migrate_parsetree_driver.register: argument %s is used by %s and %s"arg_namenameother_rewriter)|exceptionNot_found->Hashtbl.adduniq_argarg_name name)args;(* Register *)beginmatchreset_argswith|None->()|Some f->registered_args_reset:=f::!registered_args_resetend;registered_args:=List.rev_appendargs!registered_args;letr=tryPos_map.findposition!registered_rewriterswithNot_found->letr=ref[]inregistered_rewriters :=Pos_map.addpositionr!registered_rewriters;rinr:=add_rewriterIs_rewriterversionnamerewriter!rletregistered_args()=List.rev!registered_argsletreset_args()=List.iter(funf->f())!registered_args_reset(** {1 Accessing or running registered rewriters} *)type('types,'version,'tree)is_signature=Signature:('types,'typesocaml_version,'typesget_signature)is_signaturetype('types,'version,'tree)is_structure=Structure:('types,'typesocaml_version,'typesget_structure)is_structuretypesome_structure=|Str:(moduleMigrate_parsetree_versions.OCaml_versionwithtypeAst.Parsetree.structure='concrete)*'concrete->some_structuretypesome_signature=|Sig:(moduleMigrate_parsetree_versions.OCaml_versionwithtypeAst.Parsetree.signature='concrete)*'concrete->some_signatureletmigrate_some_structuredst(Str((moduleVersion),st))=(migrate(moduleVersion)dst).copy_structurestletmigrate_some_signaturedst(Sig((moduleVersion),sg))=(migrate(moduleVersion)dst).copy_signaturesgletrecrewrite_signature:typetypesversiontree.config ->cookies->(types,version,tree)is_signature->version->tree->rewriter_group list->some_signature=fun(typetypes)(typeversion)(typetree)configcookies(Signature:(types,version,tree)is_signature)(version:version)(tree:tree)->function|[]->let(moduleVersion)=versioninSig((moduleVersion),tree)|Rewriters(version',rewriters)::rest->letrewrite(_name,rewriter)tree=let(moduleVersion)=version'inVersion.Ast.map_signature (rewriterconfigcookies)treeinlettree=(migrateversionversion').copy_signaturetreeinlettree=List.fold_right rewrite rewriters tree inrewrite_signatureconfigcookiesSignatureversion'treerestletrewrite_signatureconfigversionsg=letcookies =create_cookies()inletsg=rewrite_signatureconfigcookiesSignatureversionsg(all_rewriters())inapply_cookiescookies;sgletrecrewrite_structure:typetypesversiontree.config->cookies->(types,version,tree)is_structure->version->tree->rewriter_group list->some_structure=fun(typetypes)(typeversion)(typetree)configcookies(Structure:(types,version,tree)is_structure)(version:version)(tree:tree)->function|[]->let(moduleVersion)=versioninStr((moduleVersion),tree)|Rewriters(version',rewriters)::rest->letrewriter(_name,rewriter)tree=let(module Version)=version'inVersion.Ast.map_structure (rewriterconfigcookies)treeinlettree=(migrateversionversion').copy_structuretreeinlettree=List.fold_right rewriter rewriters tree inrewrite_structureconfigcookiesStructureversion'treerestletrewrite_structureconfigversionst=letcookies =create_cookies()inletst=rewrite_structureconfigcookiesStructureversionst(all_rewriters())inapply_cookiescookies;stletexit_or_raise~exit_on_error f=ifnotexit_on_errorthenf()elsetryf()with|Arg.Helptext->print_string text;exit0|Arg.Badtext->prerr_stringtext;exit2|exn ->Location.report_exceptionFormat.err_formatterexn;exit 1letrun_as_ast_mapper ?(exit_on_error=true)args=letspec=registered_args()inletargs,usage=letme=Filename.basenameSys.executable_nameinletargs=matchargswith"--as-ppx"::args->args|args->argsin(Array.of_list(me::args),Printf.sprintf"%s [options] <input ast file> <output ast file>"me)inreset_args();exit_or_raise~exit_on_errorbeginfun()->Arg.parse_argv~current:(ref0)argsspec(funarg->raise (Arg.Bad(Printf.sprintf"invalid argument %S" arg)))usage;OCaml_current.Ast.make_top_mapper~signature:(funsg->letconfig=initial_state()inrewrite_signatureconfig(moduleOCaml_current)sg|>migrate_some_signature(moduleOCaml_current))~structure:(funstr->letconfig=initial_state()inrewrite_structureconfig(moduleOCaml_current)str|>migrate_some_structure(moduleOCaml_current))endletprotectxx~finally~f=matchfxwith|y->finallyx;y|exceptione->finallyx;raiseeletwith_file_infn~f=protectx(open_in_binfn)~finally:close_in~fletwith_file_outfn~f=protectx(open_out_binfn)~finally:close_out~ftype('a,'b)intf_or_impl=|Intfof'a|Implof'btypefile_kind=|Kind_intf|Kind_impl|Kind_unknownletguess_file_kindfn=ifFilename.check_suffix fn".ml"thenKind_implelseifFilename.check_suffix fn".mli"thenKind_intfelseKind_unknownletcheck_kindfn~expected~got =letdescribe=function|Kind_intf->"interface"|Kind_impl -> "implementation"|Kind_unknown->"unknown file"inmatchexpected,gotwith|Kind_impl,Kind_impl|Kind_intf,Kind_intf|Kind_unknown,_->()|_->Location.raise_errorf~loc:(Location.in_filefn)"Expected an %s got an %s instead"(describeexpected)(describegot)letload_file(kind,fn)=with_file_infn~f:(funic->matchAst_io.from_channelicwith|Ok(fn,Ast_io.Intf((module V),sg))->check_kindfn~expected:kind~got:Kind_intf;Location.input_name:=fn;(* We need to convert to the current version in order to interpret the cookies using
[Ast_mapper.drop_ppx_context_*] from the compiler *)letsg=(migrate(moduleV)(moduleOCaml_current)).copy_signaturesginletmigrate_backsg=Ast_io.Intf((moduleV),(migrate(moduleOCaml_current)(moduleV)).copy_signaturesg)in(fn,Intf(sg,migrate_back))|Ok(fn,Ast_io.Impl((moduleV),st))->check_kindfn ~expected:kind ~got:Kind_impl;Location.input_name:=fn;letst=(migrate (moduleV)(moduleOCaml_current)).copy_structurestinletmigrate_backst=Ast_io.Impl((moduleV),(migrate(moduleOCaml_current)(moduleV)).copy_structurest)in(fn,Impl(st,migrate_back))|Error(Ast_io.Unknown_version_)->Location.raise_errorf~loc:(Location.in_filefn)"File is a binary ast for an unknown version of OCaml"|Error(Ast_io.Not_a_binary_astprefix_read_from_file)->(* To test if a file is a binary AST file, we have to read the first few bytes of
the file.
If it is not a binary AST, we have to parse these bytes and the rest of the file
as source code. To do that, we prefill the lexbuf buffer with what we read from
the file to do the test. *)letlexbuf=Lexing.from_channelicinletlen=String.lengthprefix_read_from_fileinString.blitprefix_read_from_file 0lexbuf.Lexing.lex_buffer0len;lexbuf.Lexing.lex_buffer_len<-len;lexbuf.Lexing.lex_curr_p<-{Lexing.pos_fname=fn;pos_lnum=1;pos_bol=0;pos_cnum=0};Location.input_name:=fn;letkind=matchkindwith|Kind_impl->Kind_impl|Kind_intf->Kind_intf|Kind_unknown->guess_file_kindfninmatchkindwith|Kind_impl->letmigrate_backst=Ast_io.Impl((moduleOCaml_current),st)in(fn,Impl(Parse.implementationlexbuf,migrate_back))|Kind_intf->let migrate_backsg=Ast_io.Intf((moduleOCaml_current),sg)in(fn,Intf(Parse.interfacelexbuf,migrate_back))|Kind_unknown ->Location.raise_errorf~loc:(Location.in_filefn)"I can't decide whether %s is an implementation or interface file"fn)letwith_output?binoutput~f=matchoutputwith|None->beginmatchbinwith|Some bin->set_binary_mode_outstdoutbin|None->()end;fstdout|Somefn->with_file_outfn~ftypeoutput_mode=|Pretty_print|Dump_ast|Nullletprocess_file~config~output~output_mode~embed_errorsfile=letfn,ast=load_filefileinletast,binary_ast=matchastwith|Intf(sg,migrate_back)->letsg=Ast_mapper.drop_ppx_context_sig~restore:truesginletsg=tryrewrite_signatureconfig(moduleOCaml_current)sg|>migrate_some_signature(moduleOCaml_current)withexnwhen embed_errors->matchMigrate_parsetree_compiler_functions.error_of_exnexnwith|None->raiseexn|Someerror->[Ast_helper.Sig.extension~loc:Location.none(Ast_mapper.extension_of_errorerror)]inletbinary_sg =Ast_mapper.add_ppx_context_sig~tool_name:config.tool_namesgin(Intfsg,migrate_backbinary_sg)|Impl(st,migrate_back)->letst=Ast_mapper.drop_ppx_context_str ~restore:truestinletst=tryrewrite_structureconfig(moduleOCaml_current)st|>migrate_some_structure(moduleOCaml_current)withexnwhen embed_errors->matchMigrate_parsetree_compiler_functions.error_of_exnexnwith|None->raiseexn|Someerror->[Ast_helper.Str.extension~loc:Location.none(Ast_mapper.extension_of_errorerror)]inletbinary_st =Ast_mapper.add_ppx_context_str~tool_name:config.tool_namestin(Implst,migrate_backbinary_st)inmatchoutput_modewith|Dump_ast->with_output~bin:trueoutput~f:(funoc->Ast_io.to_channelocfnbinary_ast)|Pretty_print->with_output output~f:(funoc->letppf=Format.formatter_of_out_channelocin(matchastwith|Intfsg->Pprintast.signatureppfsg|Implst->Pprintast.structureppfst);Format.pp_print_newlineppf())|Null->()letprint_transformations ()=letprint_groupname=function|[]->()|names->Printf.printf"%s:\n"name;List.iter(Printf.printf"%s\n")namesinall_rewriters()|>List.maprewriter_group_names|>List.concat|>print_group"Registered Transformations";Ppx_derivers.derivers()|>List.map(fun(x,_)->x)|>print_group"Registered Derivers"letrun_as_standalone_driver~exit_on_errorargv=letrequest_print_transformations=reffalseinletoutput =refNoneinletoutput_mode=refPretty_printinletoutput_mode_arg=ref""inletfiles=ref[]inletembed_errors=reffalseinletembed_errors_arg =ref""inletspec=letfailfmt=Printf.ksprintf(funs->raise(Arg.Bads))fmtinletincompatibleab=fail"%s and %s are incompatible"abinletas_ppx ()=fail"--as-ppx must be passed as first argument"inletset_embed_errorsarg=if!output_mode=Nullthenincompatible!output_mode_argarg;embed_errors:=true;embed_errors_arg:=arginletset_output_modeargmode=match!output_mode,modewith|Pretty_print,_->ifmode=Null&&!embed_errorsthenincompatible!embed_errors_argarg;ifmode=Null&&!output <>Nonethenincompatible "-o"arg;output_mode:=mode;output_mode_arg :=arg|_,Pretty_print->assertfalse|Dump_ast,Dump_ast|Null,Null->()|_->incompatible!output_mode_argarginletset_outputfn=if!output_mode=Nullthenincompatible!output_mode_arg "-o";output:=Somefninletas_pp()=letarg="--as-pp"inset_output_modeargDump_ast;set_embed_errorsargin["--as-ppx",Arg.Unitas_ppx," Act as a -ppx rewriter";"--as-pp",Arg.Unitas_pp," Shorthand for: --dump-ast --embed-errors";"--dump-ast",Arg.Unit(fun()->set_output_mode"--dump-ast"Dump_ast)," Output a binary AST instead of source code";"--null",Arg.Unit(fun()->set_output_mode"--null"Null)," Output nothing, just report errors";"-o",Arg.String set_output,"FILE Output to this file instead of the standard output";"--intf",Arg.String(funfn->files:=(Kind_intf,fn)::!files),"FILE Treat FILE as a .mli file";"--impl",Arg.String(fun fn->files :=(Kind_impl,fn)::!files),"FILE Treat FILE as a .ml file";"--embed-errors",Arg.Unit(fun()->set_embed_errors"--embed-errors")," Embed error reported by rewriters into the AST";"--print-transformations",Arg.Setrequest_print_transformations," Print registered transformations in their order of executions"]inletspec=Arg.align(spec@registered_args())inletme=Filename.basenameSys.executable_nameinletusage=Printf.sprintf"%s [options] [<files>]"meinexit_or_raise~exit_on_error beginfun()->reset_args();Arg.parse_argv~current:(ref0)argvspec(funanon->files:=(Kind_unknown,anon)::!files)usage;if!request_print_transformationsthenprint_transformations()elseletoutput=!outputinletoutput_mode=!output_modeinletembed_errors=!embed_errorsinletconfig=(* TODO: we could add -I, -L and -g options to populate these fields. *){tool_name="migrate_driver";include_dirs=[];load_path=[];debug=false;for_package=None;extras=[]}inList.iter(process_file~config~output~output_mode~embed_errors)(List.rev!files)endletrun_as_ppx_rewriter?(exit_on_error=true)?(argv=Sys.argv)()=leta=argvinletn=Array.lengthainexit_or_raise~exit_on_errorbeginfun()->ifn<=2thenbeginletme=Filename.basenameSys.executable_nameinArg.usage_string(registered_args())(Printf.sprintf"%s [options] <input ast file> <output ast file>" me);|>funs->raise(Arg.Bads)end;Ast_mapper.apply~source:a.(n-2)~target:a.(n-1)(run_as_ast_mapper(Array.to_list(Array.suba1(n-3))))endletrun_main?(exit_on_error=true)?(argv=Sys.argv)()=ifArray.lengthargv>=2&&argv.(1)="--as-ppx"thenrun_as_ppx_rewriter ~exit_on_error~argv()elserun_as_standalone_driver~exit_on_errorargv