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;}letmake_config~tool_name?(include_dirs=[])?(load_path=[])?(debug=false)?for_package?(extras=[])()={tool_name;include_dirs;load_path;debug;for_package;extras}typecookie=Cookie:'typesocaml_version*'types get_expression->cookietypecookies=(string,cookie)Hashtbl.tletcreate_cookies()=Hashtbl.create3letglobal_cookie_table=create_cookies()letget_cookietablenameversion=matchmatchHashtbl.findtablenamewith|result->Someresult|exceptionNot_found->match Ast_mapper.get_cookienamewith|Someexpr->Some(Cookie((moduleOCaml_current),expr))|None->matchHashtbl.findglobal_cookie_table namewith|result->Someresult|exceptionNot_found->Nonewith|None ->None|Some(Cookie(version',expr))->Some((migrateversion'version).copy_expression expr)letset_cookietablename version expr=Hashtbl.replacetablename(Cookie (version,expr))letset_global_cookienameversionexpr=set_cookieglobal_cookie_tablenameversionexprletapply_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_grouplet rewriter_group_names(Rewriters(_,l))=List.map fstlletuniq_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(type types)(typeversion)(typerewriter)(Is_rewriter:(types,version,rewriter)is_rewriter)(version:version)name(rewriter:rewriter)=letrecadd_rewriter=function|[]->[Rewriters (version,[name,rewriter])]|(Rewriters(version',rewriters)asx)::xs->matchcompare_ocaml_versionversionversion'with|Eq->Rewriters(version',(name,rewriter)::rewriters)::xs|Lt->Rewriters(version,[name,rewriter])::x::xs|Gt->x::add_rewriterxsinadd_rewriterletregister~name?reset_args?(args=[])?(position=0)versionrewriter=(* Validate name *)ifname=""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_arg arg_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_namename)args;(* Register *)beginmatchreset_argswith|None ->()|Somef->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_grouplist->some_signature=fun(typetypes)(typeversion)(typetree)configcookies(Signature:(types,version,tree)is_signature)(version:version)(tree:tree)->function|[]->let(module Version)=versioninSig((moduleVersion),tree)|Rewriters (version',rewriters)::rest->letrewrite(_name,rewriter)tree =let(moduleVersion)=version' inVersion.Ast.map_signature(rewriterconfigcookies)treeinlettree=(migrate versionversion').copy_signaturetreeinlettree=List.fold_rightrewriterewriterstreeinrewrite_signatureconfigcookiesSignatureversion'treerestletrewrite_signatureconfigversionsg=letcookies=create_cookies()inletsg=rewrite_signatureconfigcookiesSignatureversionsg(all_rewriters())inapply_cookies cookies;sgletrecrewrite_structure:typetypesversion tree.config->cookies->(types,version,tree)is_structure->version->tree->rewriter_grouplist->some_structure=fun(typetypes)(typeversion)(typetree)configcookies(Structure:(types,version,tree)is_structure)(version:version)(tree:tree)->function|[]->let(module Version)=versioninStr((moduleVersion),tree)|Rewriters (version',rewriters)::rest->letrewriter(_name,rewriter)tree=let(moduleVersion)=version' inVersion.Ast.map_structure(rewriterconfigcookies)treeinlettree=(migrate versionversion').copy_structuretreeinlettree=List.fold_rightrewriterrewriterstreeinrewrite_structureconfigcookies Structure version'treerestletrewrite_structureconfigversionst=letcookies=create_cookies()inletst=rewrite_structureconfigcookiesStructureversionst(all_rewriters())inapply_cookies cookies;stletexit_or_raise~exit_on_errorf=ifnotexit_on_errorthenf()elsetryf()with|Arg.Helptext->print_stringtext;exit0|Arg.Badtext->prerr_stringtext;exit2|exn->Location.report_exceptionFormat.err_formatterexn;exit1letrun_as_ast_mapper ?(exit_on_error =true)args=letspec=registered_args ()inlet args,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_in fn~f=protectx(open_in_bin fn)~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_suffixfn ".ml"thenKind_implelseifFilename.check_suffixfn".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_in fn~f:(funic->matchAst_io.from_channelicwith|Ok(fn,Ast_io.Intf((moduleV),sg))->check_kindfn~expected:kind~got:Kind_intf;Location.input_name:=fn;(* We need to convert to the current versionin 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_file0lexbuf.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->letmigrate_back sg=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|Some fn->with_file_outfn~ftypeoutput_mode=|Pretty_print|Dump_ast|Nullletprocess_file~config~output ~output_mode~embed_errorsfile=let fn,ast =load_file fileinletast,binary_ast =match astwith|Intf (sg,migrate_back)->letsg=Ast_mapper.drop_ppx_context_sig~restore:truesginletsg=tryrewrite_signatureconfig(moduleOCaml_current)sg|>migrate_some_signature(moduleOCaml_current)withexnwhenembed_errors->matchMigrate_parsetree_compiler_functions.error_of_exnexnwith|None->raiseexn|Someerror->[Ast_helper.Sig.extension~loc:Location.none(Ast_mapper.extension_of_error error)]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)withexnwhenembed_errors->matchMigrate_parsetree_compiler_functions.error_of_exnexnwith|None->raiseexn|Someerror->[Ast_helper.Str.extension~loc:Location.none(Ast_mapper.extension_of_error error)]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_outputoutput~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""inlet files=ref[]inletembed_errors=reffalseinletembed_errors_arg=ref""inletspec=letfailfmt=Printf.ksprintf (funs->raise(Arg.Bads))fmtinletincompatible ab=fail"%s and %s are incompatible"abinletas_ppx()=fail"--as-ppx must be passedas 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.Stringset_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(funfn->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_transformations thenprint_transformations ()elseletoutput=!outputinletoutput_mode=!output_modeinletembed_errors =!embed_errors inletconfig=(* TODO: wecould 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=argvinlet n=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