Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file pa_comprehension.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227(* Pa_comprehension, a syntax extension for comprehension expressions
-----------------------------------------------------------------------------
Copyright (C) 2007, Nicolas Pouillard
2008, David Teller
2008, Gabriel Scherer
License:
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2.1, as published by the Free Software Foundation.
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 Library General Public License version 2.1 for more details
(enclosed in LICENSE.txt).
*)openCamlp4;moduleId:Sig.Id=structvaluename="pa_comprehension";valueversion="0.4";end;moduleMake(Syntax:Sig.Camlp4Syntax)=structopenSig;includeSyntax;(* "[?" and "?]" are not recognized as delimiters by the Camlp4
lexer; This token parser will spot "["; "?" and "?"; "]" token
and insert "[?" and "?]" instead.
Thanks to Jérémie Dimino for the idea. *)valuerecdelim_filterolder_filterstream=letrecfilter=parser[[:`(KEYWORD"[",loc);rest:]->matchrestwithparser[[:`(KEYWORD"?",_):]->[:`(KEYWORD"[?",loc);filterrest:]|[::]->[:`(KEYWORD"[",loc);filterrest:]]|[:`(KEYWORD"?",loc);rest:]->matchrestwithparser[[:`(KEYWORD"]",loc):]->[:`(KEYWORD"?]",loc);filterrest:]|[::]->[:`(KEYWORD"?",loc);filterrest:]]|[:`other;rest:]->[:`other;filterrest:]]inolder_filter(filterstream);value_=Token.Filter.define_filter(Gram.get_filter())delim_filter;valuerecloopn=fun[[]->None|[(x,_)]->ifn=0thenSomexelseNone|[_::l]->loop(n-1)l];valuestream_peek_nthnstrm=loopn(Stream.npeek(n+1)strm);(* copied from Camlp4ListComprehension *)valuetest_patt_lessminus=Gram.Entry.of_parser"test_patt_lessminus"(funstrm->letrecskip_pattn=matchstream_peek_nthnstrmwith[Some(KEYWORD"<-")->n|Some(KEYWORD("["|"[<"))->skip_patt(ignore_upto"]"(n+1)+1)|Some(KEYWORD"(")->skip_patt(ignore_upto")"(n+1)+1)|Some(KEYWORD"{")->skip_patt(ignore_upto"}"(n+1)+1)|Some(KEYWORD("as"|"::"|","|"_"))|Some(LIDENT_|UIDENT_)->skip_patt(n+1)|Some_|None->raiseStream.Failure]andignore_uptoend_kwdn=matchstream_peek_nthnstrmwith[Some(KEYWORDprm)whenprm=end_kwd->n|Some(KEYWORD("["|"[<"))->ignore_uptoend_kwd(ignore_upto"]"(n+1)+1)|Some(KEYWORD"(")->ignore_uptoend_kwd(ignore_upto")"(n+1)+1)|Some(KEYWORD"{")->ignore_uptoend_kwd(ignore_upto"}"(n+1)+1)|None|SomeEOI->raiseStream.Failure|Some_->ignore_uptoend_kwd(n+1)]inskip_patt0);valuetest_custom_module=Gram.Entry.of_parser"test_comprehension_custom_module"(funstrm->letrecafter_longidentn=matchstream_peek_nthnstrmwith[Some(UIDENT_)->matchstream_peek_nth(n+1)strmwith[Some(KEYWORD".")->letn'=after_longident(n+2)in(* if n = n + 2, the last longident token is the '.' : Failure *)ifn'>n+2thenn'elseraiseStream.Failure|_->n+1]|_->n]inmatchafter_longident0with[0->raiseStream.Failure|n->matchstream_peek_nthnstrmwith[Some(KEYWORD":")->()|_->raiseStream.Failure]]);(* map, filter, concat are generalized version of
Camlp4ListComprehension, abstracted over the module name *)valuemap_locmpel=match(p,e)with[(<:patt<$lid:x$>>,<:expr<$lid:y$>>)whenx=y->l|_->ifAst.is_irrefut_pattpthen<:expr<$id:m$.map(fun$p$->$e$)$l$>>else<:expr<$id:m$.filter_map(fun[$p$->Some$e$|_->None])$l$>>];valuefilter_locmpbl=ifAst.is_irrefut_pattpthen<:expr<$id:m$.filter(fun$p$->$b$)$l$>>else<:expr<$id:m$.filter(fun[$p$whenTrue->$b$|_->False])$l$>>;valueconcat_locml=<:expr<$id:m$.concat$l$>>;(** An item of a data structure comprehension *)typecomprehension_item=[GuardofAst.expr|GenofoptionAst.identandAst.pattandAst.expr];valuereceq_identab=match(a,b)with[(<:ident<$a$$b$>>,<:ident<$a'$$b'$>>)|(<:ident<$a$.$b$>>,<:ident<$a'$.$b'$>>)->eq_identaa'&&eq_identbb'|(<:ident<$lid:a$>>,<:ident<$lid:a'$>>)|(<:ident<$uid:a$>>,<:ident<$uid:a'$>>)->a=a'|_->False];(* comprehension building function :
comprehension may use numerous data structures modules, eg.
[? List : (a,b) | a <- List : foo; b <- Array : bar ]
When different modules are used, the "lingua franca" is enum :
the input module (here Array) is converted to Enum, and the
enumeration is then converted back into the output module (here
the second List module). All comprehension operations in the
between (map, filter, concat) are performed on enumerations (it
is assumed that they are generally more efficient than the unkown
data structure modules, when the structure of the data need not
to be preserved).
In the special case were the input module and output module are
the same (here the second List and the first List module), we use
the internal map/filter/filter_map/concat operations. That means
that the user should not use the "Module : value" syntax with
a module wich doesn't support map, filter and concat; She can
still use "Module.enum value" (in generator position) or
"Module.of_enum [? ... ]" (in output position) instead
Guards are accumulated and combined in one filter with "&&"
instead of several filters *)valuecompr_locmoduexprcomp_items=letenum=<:ident<Enum>>inlet(to_enum,of_enum)=letwrapperstr=funme->ifeq_identenummtheneelse<:expr<$id:m$.$lid:str$$e$>>in(wrapper"enum",wrapper"of_enum")inletget=fun[None->enum|Somem->m]inletapply_guardsmpgse=matchgswith[[]->e|[hd::tl]->letg=List.fold_left(funeg-><:expr<$g$&&$e$>>)hdtlinfilter_locmpge]inletrecbuildmexprguards=fun[[Genm'pgen]->(* final output, last generator : map *)letm'=getm'inifeq_identmm'thenmap_locmpexpr(apply_guardsmpguardsgen)elseletfiltered=apply_guardsenumpguards(to_enumm'gen)inof_enumm(map_locenumpexprfiltered)|[(Gen___asgen);Guardg::tail]->buildmexpr[g::guards][gen::tail]|[(Genm'pgen)::tail]->(* middle generator (map + concat) *)letm'=getm'inifeq_identmm'thenconcat_locm(map_locmp(buildmexpr[]tail)gen)elseletfiltered=apply_guardsenumpguards(to_enumm'gen)inletproduct=map_locenump(buildenumexpr[]tail)filteredinof_enumm(concat_locenumproduct)|_->raiseStream.Failure]inbuild(getmodu)expr[]comp_items;(* proper syntax extension code *)valuecomp_item=Gram.Entry.mk"comprehension item";valuecomp_expr=Gram.Entry.mk"comp_expr";EXTENDGramexpr:LEVEL"simple"[["[?";(m,output)=comp_expr;"|";comp=LIST1comp_itemSEP";";"?]"->compr_locmoutputcomp]];comp_item:[[test_patt_lessminus;p=patt;"<-";(m,gen)=comp_expr->Gen(m,p,gen)|guard=exprLEVEL"top"->Guardguard]];comp_expr:[[test_custom_module;m=module_longident;":";e=exprLEVEL"top"->(Somem,e)|e=exprLEVEL"top"->(None,e)]];END;end;letmoduleM=Register.OCamlSyntaxExtension(Id)(Make)in();