123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262(* Copyright (C) 2017 Petter A. Urkedal <paurkedal@gmail.com>
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version, with the OCaml static compilation exception.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*)openMigrate_parsetreeopenAst_409letocaml_version=Versions.ocaml_409openAst_mapperopenAst_helperopenAsttypesopenLongidentopenParsetreeopenPrintfleterror~locmsg=raise(Location.Error(Location.error~locmsg))letwarn~locmsge=lete_msg=Exp.constant(Const.stringmsg)inletstructure={pstr_desc=Pstr_eval(e_msg,[]);pstr_loc=loc}inExp.attre(Attr.mk~loc{txt="ocaml.ppwarning";loc}(PStr[structure]))moduleList=structincludeListletrecfoldf=function|[]->funacc->acc|x::xs->funacc->foldfxs(fxacc)endmoduleRegexp=structincludeRegexpletbindings=letrecrecursemust_match(e':_Location.loc)=letloc=e'.Location.locin(matche'.Location.txtwith|Code_->funacc->acc|Seqes->List.fold(recursemust_match)es|Altes->List.fold(recursefalse)es|Opte->recursefalsee|Repeat({Location.txt=(i,_);_},e)->recurse(must_match&&i>0)e|Nongreedye->recursemust_matche|Capture_->error~loc"Unnamed capture is not allowed for %pcre."|Capture_as(idr,e)->fun(nG,bs)->recursemust_matche(nG+1,(idr,SomenG,must_match)::bs)|Call_->error~loc"(&...) is not implemented for %pcre.")in(function|{Location.txt=Capture_as(idr,e);_}->recursetruee(0,[idr,None,true])|e->recursetruee(0,[]))letto_string=letp_alt,p_seq,p_suffix,p_atom=0,1,2,3inletdelimit_ifbs=ifbthen"(?:"^s^")"elsesinletrecrecursep(e':_Location.loc)=letloc=e'.Location.locin(matche'.Location.txtwith|Codes->(* Delimiters not needed as Regexp.parse_exn only returns single
* chars, csets, and escape sequences. *)s|Seqes->delimit_if(p>p_seq)(String.concat""(List.map(recursep_seq)es))|Altes->delimit_if(p>p_alt)(String.concat"|"(List.map(recursep_alt)es))|Opte->delimit_if(p>p_suffix)(recursep_atome^"?")|Repeat({Location.txt=(i,j_opt);_},e)->letj_str=matchj_optwithNone->""|Somej->string_of_intjindelimit_if(p>p_suffix)(sprintf"%s{%d,%s}"(recursep_atome)ij_str)|Nongreedye->recursep_suffixe^"?"|Capture_->error~loc"Unnamed capture is not allowed for %pcre."|Capture_as(_,e)->"("^recursep_alte^")"|Call_->error~loc"(&...) is not implemented for %pcre.")in(function|{Location.txt=Capture_as(_,e);_}->recurse0e|e->recurse0e)endletdyn_bindings=ref[]letclear_bindings()=dyn_bindings:=[]letadd_bindingbinding=dyn_bindings:=binding::!dyn_bindingsletget_bindings()=!dyn_bindingsletfresh_var=letc=ref0infun()->incrc;Printf.sprintf"_ppx_regexp_%d"!cletrecis_zeropk=(matchp.[k]with|'0'->is_zerop(k+1)|'1'..'9'->false|_->true)letrecmust_matchpi=letl=String.lengthpinifi=lthentrueelseifp.[i]='?'||p.[i]='*'thenfalseelseifp.[i]='{'thenletj=String.index_fromp(i+1)'}'innot(is_zerop(i+1))&&must_matchp(j+1)elsetrueletextract_bindings~poss=letr=Regexp.parse_exn~possinletnG,bs=Regexp.bindingsrinletre_str=Regexp.to_stringrin(Exp.constant(Const.stringre_str),bs,nG)letrecwrap_group_bindings~locrhsoffG=function|[]->rhs|(varG,iG,mustG)::bs->leteG=matchiGwith|None->[%exprRe.Group.get_g0]|SomeiG->[%exprRe.Group.get_g[%eExp.constant(Const.int(offG+iG+1))]]inleteG=ifmustGtheneGelse[%exprtrySome[%eeG]withNot_found->None]in[%exprlet[%pPat.varvarG]=[%eeG]in[%ewrap_group_bindings~locrhsoffGbs]]lettransform_cases~mapper~loccases=letauxcase=ifcase.pc_guard<>Nonethenerror~loc"Guards are not implemented for match%pcre."else(matchcase.pc_lhswith|{ppat_desc=Ppat_constant(Pconst_string(re_src,re_delim));ppat_loc={loc_start;_};_}->letre_offset=(matchre_delimwithSomes->String.lengths+2|None->1)inletpos={loc_startwithpos_cnum=loc_start.pos_cnum+re_offset}inletre,bs,nG=extract_bindings~posre_srcin(re,nG,bs,case.pc_rhs)(*
| {ppat_desc = Ppat_alias
({ ppat_desc = Ppat_constant (Pconst_string (re_src,_));
ppat_loc = loc; _ },
var); _} ->
let re, bs, nG = extract_bindings ~loc re_src in
let rhs =
(* TODO: Should this be (_ppx_regexp_v or Re.Group.get _g 0? *)
[%expr let [%p Pat.var var] = _ppx_regexp_v in [%e case.pc_rhs]] in
(re, nG, bs, rhs)
*)|{ppat_desc=Ppat_any;_}->error~loc"Universal wildcard must be the last pattern."|{ppat_loc=loc;_}->error~loc"Regular expression pattern should be a string.")inletrewrite_casecase={casewithpc_rhs=mapper.exprmappercase.pc_rhs}inletcases,default_rhs=(matchList.rev_maprewrite_casecaseswith|{pc_lhs={ppat_desc=Ppat_any;_};pc_rhs;pc_guard=None}::cases->(cases,pc_rhs)|{pc_lhs={ppat_desc=Ppat_varvar;_};pc_rhs;pc_guard=None}::cases->(cases,[%exprlet[%pPat.varvar]=_ppx_regexp_vin[%epc_rhs]])|cases->letopenLexinginletpos=loc.Location.loc_startinlete0=Exp.constant(Const.stringpos.pos_fname)inlete1=Exp.constant(Const.intpos.pos_lnum)inlete2=Exp.constant(Const.int(pos.pos_cnum-pos.pos_bol))inlete=[%exprraise(Match_failure([%ee0],[%ee1],[%ee2]))]in(cases,warn~loc"A universal case is recommended for %pcre."e))inletcases=List.rev_mapauxcasesinletres=Exp.array(List.map(fun(re,_,_,_)->re)cases)inletcomp=[%exprleta=Array.map(funs->Re.mark(Re.Perl.res))[%eres]inletmarks=Array.mapfstainletre=Re.compile(Re.alt(Array.to_list(Array.mapsnda)))in(re,marks)]inletvar=fresh_var()inadd_binding(Vb.mk(Pat.var{txt=var;loc})comp);lete_comp=Exp.ident{txt=Lidentvar;loc}inletrechandle_casesioffG=function|[]->[%exprassertfalse]|(_,nG,bs,rhs)::cases->lete_i=Exp.constant(Const.inti)in[%exprifRe.Mark.test_g(snd[%ee_comp]).([%ee_i])then[%ewrap_group_bindings~locrhsoffGbs]else[%ehandle_cases(i+1)(offG+nG)cases]]in[%expr(matchRe.exec_opt(fst[%ee_comp])_ppx_regexp_vwith|None->[%edefault_rhs]|Some_g->[%ehandle_cases00cases])]letrewrite_exprmappere_ext=(matche_ext.pexp_descwith|Pexp_extension({txt="pcre";_},PStr[{pstr_desc=Pstr_eval(e,_);_}])->letloc=e.pexp_locin(matche.pexp_descwith|Pexp_match(e,cases)->[%exprlet_ppx_regexp_v=[%ee]in[%etransform_cases~mapper~loccases]]|Pexp_function(cases)->[%exprfun_ppx_regexp_v->[%etransform_cases~mapper~loccases]]|_->error~loc"[%pcre] only applies to match an function.")|_->default_mapper.exprmappere_ext)letrewrite_structure_mappersis=letmapper={default_mapperwithexpr=rewrite_expr}inletsis'=default_mapper.structuremappersisin(matchget_bindings()|>List.revwith|[]->sis'|bindings->clear_bindings();letlocal_sis=[%strmodulePpx_regexp__local=struct[%%s[{pstr_desc=Pstr_value(Nonrecursive,bindings);pstr_loc=Location.none;}]]endopenPpx_regexp__local]inlocal_sis@sis')let()=Driver.register~name:"ppx_regexp"ocaml_version(fun_config_cookies->{default_mapperwithstructure=rewrite_structure;expr=rewrite_expr})