123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464(* TyXML
* http://www.ocsigen.org/tyxml
* Copyright (C) 2016 Anton Bachin
*
* This program 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, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program 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 program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02111-1307, USA.
*)(* When dropping support for 4.02, this module can simply be deleted. *)moduleString=structincludeStringletcapitalize_ascii=String.capitalize[@ocaml.warning"-3"]endopenAsttypesopenParsetreetypelang=Common.lang=Html|SvgmoduleLoc=structletshift(pos:Lexing.position)x={poswithpos_cnum=pos.pos_cnum+x}letshrink{Location.loc_start;loc_end;loc_ghost}~xbegin~xend={Location.loc_ghost;loc_start=shiftloc_startxbegin;loc_end=shiftloc_endxend;}(** Returns the real (OCaml) location of the content of a string, taking
delimiters into account. *)letstring_startdelimiterloc=letdelimiter_length=matchdelimiterwith|None->1|Somed->String.lengthd+2inshiftloc.Location.loc_startdelimiter_length(** 0-width locations do not show in the toplevel. We expand them to
one-width.
*)letone_width?(ghost=false)pos={Location.loc_ghost=ghost;loc_start=pos;loc_end=shiftpos1}(** Given a list of input strings for Markup.ml, evaluates to a function that
converts Markup.ml locations of characters within these strings to their
OCaml locations. *)letmake_location_maplocated_strings=(* [source] is a byte stream created from the string list, which calls
[!starting_a_string] each time it moves on to a new string in the
list. *)letstarting_a_string=ref(fun_->())inletsource=letstrings=reflocated_stringsinletoffset=ref0inletrecnext_byte()=match!stringswith|[]->None|(s,loc)::rest->if!offset=0then!starting_a_stringloc;if!offset<String.lengthsthenbeginoffset:=!offset+1;Some(s.[!offset-1])endelsebeginoffset:=0;strings:=rest;next_byte()endinMarkup.fnnext_bytein(* Use Markup.ml to assign locations to characters in [source], and note
the Markup.ml and OCaml locations of the start of each string. *)letlocation_map=letpreprocessed_input_stream,get_markupml_location=source|>Markup.Encoding.decodeMarkup.Encoding.utf_8|>Markup.preprocess_input_streaminletlocation_map=ref[]instarting_a_string:=beginfunocaml_position->location_map:=(get_markupml_location(),ocaml_position)::!location_mapend;Markup.drainpreprocessed_input_stream;List.rev!location_mapin(* The function proper which translates Markup.ml locations into OCaml
locations. *)fungiven_markup_location->(* [bounded_maximum None location_map] evaluates to the greatest Markup.ml
location (and its paired OCaml location) in [location_map] that is less
than or equal to [given_markup_location]. [best] is [Some] of the
greatest candidate found so far, or [None] on the first iteration. *)letrecbounded_maximumbest=function|[]->best|((noted_markup_location,_)asloc)::rest->ifMarkup.compare_locationsnoted_markup_locationgiven_markup_location>0thenbestelsebounded_maximum(Someloc)restinletpreceding_markup_location,preceding_ocaml_position=matchbounded_maximumNonelocation_mapwith|Someloc->loc|None->assertfalseinletline,column=given_markup_locationinletline',column'=preceding_markup_locationinletocaml_position=letopenLexinginifline=line'then{preceding_ocaml_positionwithpos_cnum=preceding_ocaml_position.pos_cnum+column-column'}else{preceding_ocaml_positionwithpos_lnum=preceding_ocaml_position.pos_lnum+line-line';pos_bol=0;pos_cnum=column-1}inone_widthocaml_positionend(** Antiquotations
We replace antiquotations expressions by a dummy token "(tyxmlX)".
We store a table token to expression to retrieve them after parsing.
*)moduleAntiquot=structletfmt_id=Printf.sprintf"(tyxml%i)"letregex_id=Re.(seq[str"(tyxml";repdigit;char')'])letre_id=Re.compileregex_idletmake_id=letr=ref0infun()->incrr;fmt_id!rmoduleH=Hashtbl.Make(structtypet=stringlethash=Hashtbl.hashletequal(x:string)y=x=yend)lettbl=H.create17letcreateexpr=lets=make_id()inH.addtblsexpr;sletgetlocs=ifH.memtblsthenH.findtblselseCommon.errorloc"Internal error: This expression placeholder is not registered"letcontainslocs=matchRe.exec_optre_idswith|None->`No|Someg->let(i,j)=Re.Group.offsetg0inletis_whole=i=0&&j=String.lengthsinifis_wholethen`Whole(getlocs)else`Yes(getloc@@Re.Group.getg0)letassert_no_antiquot~lockind(_namespace,s)=matchcontainslocswith|`No->()|`Yese|`Wholee->Common.errore.pexp_loc"OCaml expressions are not accepted as %s names"kindend(** Building block to rebuild the output with expressions intertwined. *)letmake_txt~loc~langs=lettxt=Common.make~loclang"txt"inletarg=Common.wraplangloc@@Common.stringlocsinAst_helper.Exp.apply~loctxt[Common.Label.nolabel,arg](** Walk the text list to replace placeholders by OCaml expressions when
appropriate. Use {!make_txt} on the rest. *)letmake_text~loc~langss=letbuf=Buffer.create17inletpush_txtbufl=lets=Buffer.contentsbufinBuffer.clearbuf;ifs=""thenlelseCommon.value(make_txt~loc~langs)::linletrecaux~locres=function|[]->push_txtbufres|`Texts::t->Buffer.add_stringbufs;aux~locrest|`Delimg::t->lete=Antiquot.getloc@@Re.getg0inaux~loc(Common.antiquote::push_txtbufres)tinaux~loc[]@@Re.split_fullAntiquot.re_id@@String.concat""ssletreplace_attribute~loc(attr,value)=Antiquot.assert_no_antiquot~loc"attribute"attr;matchAntiquot.containslocvaluewith|`No->(attr,Common.valuevalue)|`Wholee->(attr,Common.antiquote)|`Yes_->Common.errorloc"Mixing literals and OCaml expressions is not supported in attribute values"(** Processing *)(** Takes the ast and transforms it into a Markup.ml char stream.
The payload [expr] is either a single token, or an application (that is, a list).
A token is either a string or an antiquotation. Antiquotations are replaced
by placeholder strings (see {!Antiquot}).
Each token is equipped with a starting (but no ending) position.
*)letast_to_streamexpressions=letstrings=expressions|>List.map@@funexpr->matchAst_convenience.get_str_with_quotation_delimiterexprwith|Some(s,delimiter)->(s,Loc.string_startdelimiterexpr.pexp_loc)|None->(Antiquot.createexpr,expr.pexp_loc.loc_start)inletsource=letitems=refstringsinletoffset=ref0inletrecnext_byte()=match!itemswith|[]->None|(s,_)::rest->if!offset<String.lengthsthenbeginoffset:=!offset+1;Some(s.[!offset-1])endelsebeginoffset:=0;items:=rest;next_byte()endinMarkup.fnnext_byteinsource,Loc.make_location_mapstringsletcontext_of_lang=function|Common.Svg->Some(`Fragment"svg")|Html->None(** Given the payload of a [%html ...] or [%svg ...] expression,
converts it to a TyXML expression representing the markup
contained therein. *)letmarkup_to_exprlanglocexpr=letcontext=context_of_langlanginletinput_stream,adjust_location=ast_to_streamexprinletparser=Markup.parse_html?context~encoding:Markup.Encoding.utf_8~report:(funlocerror->letloc=adjust_locationlocinletmessage=Markup.Error.to_stringerror|>String.capitalize_asciiinCommon.errorloc"%s"message)input_streaminletsignals=Markup.signalsparserinletget_loc()=adjust_location@@Markup.locationparserinletrecassemblelangchildren=matchMarkup.nextsignalswith|None|Some`End_element->List.revchildren|Some(`Textss)->letloc=get_loc()inletnode=make_text~loc~langssinassemblelang(node@children)|Some(`Start_element(name,attributes))->letnewlang=Namespace.to_langloc@@fstnameinletloc=get_loc()inletsub_children=assemblenewlang[]inAntiquot.assert_no_antiquot~loc"element"name;letattributes=List.map(replace_attribute~loc)attributesinletnode=Element.parse~parent_lang:lang~loc~name~attributessub_childreninassemblelang(Common.Valnode::children)|Some(`Comments)->letloc=get_loc()inletnode=Common.value@@Element.comment~loc~langsinassemblelang(node::children)|Some(`Xml_|`Doctype_|`PI_)->assemblelangchildreninletl=Element_content.filter_surrounding_whitespace@@assemblelang[]inmatchlwith|[Valx|Antiquotx]->x|l->Common.list_wrap_valuelangloclletmarkup_to_expr_with_implementationlangmodnamelocexpr=matchmodnamewith|Somemodname->letcurrent_modname=Common.implementationlanginCommon.set_implementationlangmodname;letres=markup_to_exprlanglocexprinCommon.set_implementationlangcurrent_modname;res|_->markup_to_exprlanglocexprletis_capitalizeds=ifString.lengths<0thenfalseelsematchs.[0]with|'A'..'Z'->true|_->false(** Extract and verify the modname in the annotation [%html.Bar.Baz .. ].
We need to fiddle with length to provide a correct location. *)letget_modname~loclenl=lets=String.concat"."linletloc=Loc.shrinkloc~xbegin:(len-String.lengths)~xend:0inifl=[]thenNoneelseifnot(List.for_allis_capitalizedl)thenCommon.errorloc"This identifier is not a module name"elseSomesletre_dot=Re.(compile@@char'.')letdispatch_ext{txt;loc}=letl=Re.splitre_dottxtinletlen=String.lengthtxtinmatchlwith|"html"::l|"tyxml"::"html"::l->Some(Common.Html,get_modname~loclenl)|"svg"::l|"tyxml"::"svg"::l->Some(Common.Svg,get_modname~loclenl)|_->Noneletapplication_to_listexpr=matchexpr.pexp_descwith|Pexp_apply(f,arguments)->f::(List.mapsndarguments)|_->[expr]openAst_mapperopenAst_helperleterror{txt;loc}=Common.errorloc"Invalid payload for [%%%s]"txtletmarkup_cases~lang~modnamecases=letf({pc_rhs}ascase)=letloc=pc_rhs.pexp_locinletpc_rhs=markup_to_expr_with_implementationlangmodnameloc@@application_to_listpc_rhsin{casewithpc_rhs}inList.mapfcasesletrecmarkup_function~lang~modnamee=letloc=e.pexp_locinmatche.pexp_descwith|Pexp_fun(label,def,pat,content)->letcontent=markup_function~lang~modnamecontentin{ewithpexp_desc=Pexp_fun(label,def,pat,content)}|Pexp_functioncases->letcases=markup_cases~lang~modnamecasesin{ewithpexp_desc=Pexp_functioncases}|_->markup_to_expr_with_implementationlangmodnameloc@@application_to_listeletmarkup_bindings~lang~modnamel=letf({pvb_expr}asb)=letpvb_expr=markup_function~lang~modnamepvb_exprin{bwithpvb_expr}inList.mapflletrecexprmappere=matche.pexp_descwith|Pexp_extension(ext,payload)->beginmatchdispatch_extext,payloadwith|Some(lang,modname),PStr[{pstr_desc=Pstr_eval(e,_)}]->beginmatche.pexp_descwith|Pexp_let(recflag,bindings,next)->letbindings=markup_bindings~lang~modnamebindingsin{ewithpexp_desc=Pexp_let(recflag,bindings,exprmappernext)}|_->markup_to_expr_with_implementationlangmodnamee.pexp_loc@@application_to_listeend|Some_,_->errorext|None,_->default_mapper.exprmappereend|_->default_mapper.exprmappereletstructure_itemmapperstri=matchstri.pstr_descwith|Pstr_extension((ext,payload),_attrs)->beginmatchdispatch_extext,payloadwith|Some(lang,modname),PStr[{pstr_desc=Pstr_value(recflag,bindings)}]->letbindings=markup_bindings~lang~modnamebindingsinStr.valuerecflagbindings|Some_,_->errorext|None,_->default_mapper.structure_itemmapperstriend|_->default_mapper.structure_itemmapperstriletmapper__={default_mapperwithexpr;structure_item}