123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238(* 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_404letocaml_version=Versions.ocaml_404openAst_mapperopenAst_helperopenAsttypesopenParsetreeopenLongidentleterror~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({txt="ocaml.ppwarning";loc},PStr[structure])letdyn_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~locp=letl=String.lengthpinletbuf=Buffer.createlinletrecparse_normalnGstackbsi=ifi=lthenifstack=[]then(bs,nG)elseerror~loc"Unmatched start of group."elsebeginBuffer.add_charbufp.[i];(matchp.[i]with|'('->parse_bgroupnGstackbs(i+1)|')'->parse_egroupnGstackbs(i+1)|'\\'->parse_escapenGstackbs(i+1)|_->parse_normalnGstackbs(i+1))endandparse_escapenGstackbsi=ifi=lthen(bs,nG)elsebeginBuffer.add_charbufp.[i];parse_normalnGstackbs(i+1)endandparse_bgroupnGstackbsi=ifi+2>=l||p.[i]<>'?'||p.[i+1]<>'<'thenparse_normal(nG+1)((None,nG,bs)::stack)[]ielseletj=String.index_fromp(i+2)'>'inletvarG=String.subp(i+2)(j-i-2)inparse_normal(nG+1)((SomevarG,nG,bs)::stack)[](j+1)andparse_egroupnGstackbsi=letbs,bs',stack'=(matchstackwith|[]->error~loc"Unmached end of group."|((SomevarG,iG,bs')::stack')->letbs=(varG,SomeiG,true)::bsin(bs,bs',stack')|((None,_,bs')::stack')->(bs,bs',stack'))inletbs=ifmust_matchpithenbselseList.map(fun(varG,iG,_)->(varG,iG,false))bsinparse_normalnGstack'(List.rev_appendbsbs')iinletparse_first()=ifl>=4&&p.[0]='?'&&p.[1]='<'thenletj=String.index_fromp2'>'inletvarG=String.subp2(j-2)inparse_normal0[][varG,None,true](j+1)elseparse_normal0[][]0inletbs,nG=parse_first()inletre_str=Buffer.contentsbufin(tryignore(Re_pcre.regexpre_str)with|Re_perl.Not_supported->error~loc"Unsupported regular expression."|Re_perl.Parse_error->error~loc"Invalid regular expression.");(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.var{txt=varG;loc}]=[%eeG]in[%ewrap_group_bindings~locrhsoffGbs]]lettransform_cases~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,_));ppat_loc=loc;_}->letre,bs,nG=extract_bindings~locre_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.")inletcases,default_rhs=(matchList.revcaseswith|{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_pcre.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~loccases]]|Pexp_function(cases)->[%exprfun_ppx_regexp_v->[%etransform_cases~loccases]]|_->error~loc"[%pcre] only applies to match an function.")|_->default_mapper.exprmappere_ext)letrewrite_structure_mappersis=letsis'=default_mapper.structure{default_mapperwithexpr=rewrite_expr}sisin(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})