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*'typesget_expression->cookietypecookies=(string,cookie)Hashtbl.tletcreate_cookies()=Hashtbl.create3letglobal_cookie_table=create_cookies()letget_cookietablenameversion=matchmatchHashtbl.findtablenamewith|result->Someresult|exceptionNot_found->matchAst_mapper.get_cookienamewith|Someexpr->Some(Cookie((moduleOCaml_current),expr))|None->matchHashtbl.findglobal_cookie_tablenamewith|result->Someresult|exceptionNot_found->Nonewith|None->None|Some(Cookie(version',expr))->Some((migrateversion'version).copy_expressionexpr)letset_cookietablenameversionexpr=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_groupletrewriter_group_names(Rewriters(_,l))=List.mapfstlletuniq_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)=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_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_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(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_rightrewriterewriterstreeinrewrite_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_grouplist->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(moduleVersion)=version'inVersion.Ast.map_structure(rewriterconfigcookies)treeinlettree=(migrateversionversion').copy_structuretreeinlettree=List.fold_rightrewriterrewriterstreeinrewrite_structureconfigcookiesStructureversion'treerestletrewrite_structureconfigversionst=letcookies=create_cookies()inletst=rewrite_structureconfigcookiesStructureversionst(all_rewriters())inapply_cookiescookies;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()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_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_infn~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 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_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_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|Somebin->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)withexnwhenembed_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)withexnwhenembed_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_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""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.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_errorbeginfun()->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