123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260openImportletwith_outputfn~binary~f=matchfnwith|None|Some"-"->(* Flipping back and forth from binary to text is not
a good idea, so we'll make two simplifying assumptions:
1. Assume that nothing is buffered on stdout before
entering [with_output]. That means we don't need to
flush the stdout on entry.
2. Assume that nothing else is sent to stdout after
[with_output]. That means it is safe to leave stdout
channel in binary mode (or text mode if [binary=true])
after the function is done. *)set_binary_mode_outstdoutbinary;fstdout|Somefn->Out_channel.with_filefn~binary~fmoduleKind=structtypet=Intf|Implletof_filenamefn:toption=ifStdlib.Filename.check_suffixfn".ml"thenSomeImplelseifStdlib.Filename.check_suffixfn".mli"thenSomeIntfelseNoneletdescribe=functionImpl->"implementation"|Intf->"interface"letequal:t->t->bool=Poly.equalendmoduleIntf_or_impl=structtypet=Intfofsignature|Implofstructureletmapt(map:Ast_traverse.map)=matchtwith|Implx->Impl(map#structurex)|Intfx->Intf(map#signaturex)letmap_with_contextt(map:_Ast_traverse.map_with_context)ctx=matchtwith|Implx->Impl(map#structurectxx)|Intfx->Intf(map#signaturectxx)letkind:_->Kind.t=functionIntf_->Intf|Impl_->ImplendmoduleAst_io=structtypeinput_version=(moduleOCaml_version)letfall_back_input_version=(moduleCompiler_version:OCaml_version)(* This should only be used when the input version can't be determined due to
loading or preprocessing errors *)typet={input_name:string;input_version:input_version;ast:Intf_or_impl.t;}typeread_error=|Not_a_binary_ast|Unknown_versionofstring*input_version|Source_parse_errorofLocation.Error.t*input_version|System_errorofLocation.Error.t*input_versiontypeinput_source=Stdin|Fileofstringtypeinput_kind=Possibly_sourceofKind.t*string|Necessarily_binaryletread_error_to_string(error:read_error)=matcherrorwith|Not_a_binary_ast->"Error: Not a binary ast"|Unknown_version(s,_)->"Error: Unknown version "^s|Source_parse_error(loc,_)->"Source parse error:"^Location.Error.messageloc|System_error(loc,_)->"System error: "^Location.Error.messagelocletparse_source_code~(kind:Kind.t)~input_name~prefix_read_from_sourceic=(* The input version is determined by the fact that the input will get parsed by
the current compiler Parse module *)letinput_version=(moduleCompiler_version:OCaml_version)intry(* To test if a file is an AST file, we have to read the first few bytes of the
file. If it is not, we have to parse these bytes and the rest of the file as
source code.
The compiler just does [seek_on 0] in this case, however this doesn't work
when the input is a pipe.
What we do is we build a string of the whole source, append the prefix
and built a lexing buffer from that.
We have to put all the source into the lexing buffer at once this way
for source quotation to work in error messages.
See ocaml#12238 and ocaml/driver/pparse.ml. *)letall_source=prefix_read_from_source^In_channel.input_allicinletlexbuf=Lexing.from_stringall_sourceinlexbuf.lex_curr_p<-{lexbuf.lex_curr_pwithpos_fname=input_name};Astlib.Location.set_input_lexbuf(Somelexbuf);Skip_hash_bang.skip_hash_banglexbuf;letast:Intf_or_impl.t=matchkindwith|Intf->Intf(Parse.interfacelexbuf)|Impl->Impl(Parse.implementationlexbuf)inOk{input_name;input_version;ast}withexn->(matchLocation.Error.of_exnexnwith|None->raiseexn|Someerror->Error(Source_parse_error(error,input_version)))letmagic_length=String.lengthAstlib.Config.ast_impl_magic_numberletread_magicic=letbuf=Bytes.createmagic_lengthinletlen=inputicbuf0magic_lengthinlets=Bytes.sub_stringbuf~pos:0~leniniflen=magic_lengththenOkselseErrorsletset_input_lexbufinput_name=letset_input_lexbufic=(* set input lexbuf for error messages. *)letsource=In_channel.input_allicinletlexbuf=Lexing.from_stringsourceinAstlib.Location.set_input_lexbuf(Somelexbuf);lexbufinmatchIn_channel.with_file~binary:trueinput_name~f:set_input_lexbufwith|(_:Lexing.lexbuf)->()|exceptionSys_error_->()letfrom_channelch~input_kind=lethandle_non_binaryprefix_read_from_source=matchinput_kindwith|Possibly_source(kind,input_name)->parse_source_code~kind~input_name~prefix_read_from_sourcech|Necessarily_binary->ErrorNot_a_binary_astin(* Marshalled AST must be read in binary mode. Even though we don't know
before reading the magic number when the file has a marshalled AST,
it is safe to read source files in binary mode. *)set_binary_mode_inchtrue;matchread_magicchwith|Errors->handle_non_binarys|Oks->(matchFind_version.from_magicswith|Intf(moduleInput_version:OCaml_version)->letinput_name:string=input_valuechinletast=input_valuechinletmoduleInput_to_ppxlib=Convert(Input_version)(Js)inset_input_lexbufinput_name;letast=Intf_or_impl.Intf(Input_to_ppxlib.copy_signatureast)inOk{input_name;input_version=(moduleInput_version:OCaml_version);ast;}|Impl(moduleInput_version:OCaml_version)->letinput_name:string=input_valuechinletast=input_valuechinletmoduleInput_to_ppxlib=Convert(Input_version)(Js)inset_input_lexbufinput_name;letast=Intf_or_impl.Impl(Input_to_ppxlib.copy_structureast)inOk{input_name;input_version=(moduleInput_version:OCaml_version);ast;}|Unknown->ifString.equal(String.subs~pos:0~len:9)(String.subAstlib.Config.ast_impl_magic_number~pos:0~len:9)||String.equal(String.subs~pos:0~len:9)(String.subAstlib.Config.ast_intf_magic_number~pos:0~len:9)thenError(Unknown_version(s,fall_back_input_version))elsehandle_non_binarys)letreadinput_source~input_kind=trymatchinput_sourcewith|Stdin->set_binary_mode_instdintrue;from_channelstdin~input_kind|Filefn->In_channel.with_filefn~f:(from_channel~input_kind)withexn->(matchLocation.Error.of_exnexnwith|None->raiseexn|Someerror->Error(System_error(error,fall_back_input_version)))letwriteoc{input_name;input_version=(moduleInput_version);ast}~add_ppx_context=letmodulePpxlib_to_input=Convert(Js)(Input_version)inletmoduleOcaml_to_input=Convert(Compiler_version)(Input_version)inmatchastwith|Intfsg->letsg=ifadd_ppx_contextthenSelected_ast.To_ocaml.copy_signaturesg|>Astlib.Ast_metadata.add_ppx_context_sig~tool_name:"ppx_driver"|>Ocaml_to_input.copy_signatureelsePpxlib_to_input.copy_signaturesginoutput_stringocInput_version.Ast.Config.ast_intf_magic_number;output_valueocinput_name;output_valueocsg|Implst->letst=ifadd_ppx_contextthenSelected_ast.To_ocaml.copy_structurest|>Astlib.Ast_metadata.add_ppx_context_str~tool_name:"ppx_driver"|>Ocaml_to_input.copy_structureelsePpxlib_to_input.copy_structurestinoutput_stringocInput_version.Ast.Config.ast_impl_magic_number;output_valueocinput_name;output_valueocstmoduleRead_bin=structtypeast=Intfofsignature|Implofstructuretypet={ast:ast;input_name:string}letread_binaryfn=matchIn_channel.with_filefn~f:(from_channel~input_kind:Necessarily_binary)with|Ok{ast;input_name;_}->letast=matchastwith|Implstructure->Implstructure|Intfsignature->IntfsignatureinOk{ast;input_name}|Errore->Error(read_error_to_stringe)letget_astt=t.astletget_input_namet=t.input_nameendendmoduleSystem=structletrun_preprocessor~pp~input~output=letcommand=Printf.sprintf"%s %s > %s"pp(ifString.equalinput"-"then""elseStdlib.Filename.quoteinput)(Stdlib.Filename.quoteoutput)inifStdlib.Sys.commandcommand=0thenOk()elseError(command,Ast_io.fall_back_input_version)endletprint_as_compiler_sourceppfast=letmodulePpxlib_to_compiler=Convert(Js)(Compiler_version)inmatch(ast:Intf_or_impl.t)with|Intfsg->letsg=Ppxlib_to_compiler.copy_signaturesginAstlib.Compiler_pprintast.signatureppfsg|Implst->letst=Ppxlib_to_compiler.copy_structurestinAstlib.Compiler_pprintast.structureppfst