123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339(* Copyright (C) 2015-2016 Bloomberg Finance L.P.
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)openImporttypeerror=|Invalid_code_point|Unterminated_backslash|Unterminated_variable|Unmatched_paren|Invalid_syntax_of_varofstringtypecontent=|Stringofstring|Varof{lident:Longident.t;content:string;loffset:int;roffset:int;}(* [Var (loffset, roffset)]
For parens it used to be (2,-1)
for non-parens it used to be (1,0) *)(* Note the position is about code point *)typepos={lnum:int;offset:int;byte_bol:int;(* Note it actually needs to be in sync with OCaml's lexing semantics *)}typesegment={start:pos;finish:pos;content:content}typecxt={mutablesegment_start:pos;buf:Buffer.t;s_len:int;mutablesegments:segmentlist;pos_bol:int;(* record the abs position of current beginning line *)byte_bol:int;pos_lnum:int;(* record the line number *)}exceptionErrorofpos*pos*errorletpp_error=letto_string=function|Invalid_code_point->"Invalid code point"|Unterminated_backslash->"\\ ended unexpectedly"|Unterminated_variable->"$ unterminated"|Unmatched_paren->"Unmatched paren"|Invalid_syntax_of_vars->"`"^s^"' is not a valid syntax of interpolated identifer"infunfmterr->Format.pp_print_stringfmt(to_stringerr)letvalid_lead_identifier_char=function|'a'..'z'|'_'->true|_->falseletvalid_identifier_char=function|'a'..'z'|'A'..'Z'|'0'..'9'|'_'|'\''|'.'->true|_->false(* Invariant: [valid_lead_identifier] has to be [valid_identifier] *)letvalid_identifiers=matchString.length(String.trims)with|0->None|_n->(matchString.for_alls~f:valid_identifier_charwith|true->(matchLongident.parseswithli->Someli|exception_->None)|false->None)(* FIXME: multiple line offset
if there is no line offset. Note {|{j||} border will never trigger a new
line *)letupdate_positionborder{lnum;offset;byte_bol}(pos:Lexing.position)=matchlnumwith|0->{poswithpos_cnum=pos.pos_cnum+border+offset}|_->(* When no newline, the column number is [border + offset] *){poswithpos_lnum=pos.pos_lnum+lnum;pos_bol=pos.pos_cnum+border+byte_bol;pos_cnum=pos.pos_cnum+border+byte_bol+offset;(* when newline, the column number is [offset] *)}letupdateborderstartfinish(loc:Location.t)={locwithloc_start=update_positionborderstartloc.loc_start;loc_end=update_positionborderfinishloc.loc_start;}letpos_errorcxt~locerror=raise(Error(cxt.segment_start,{lnum=cxt.pos_lnum;offset=loc-cxt.pos_bol;byte_bol=cxt.byte_bol;},error))letadd_var_segmentcxt~loc~loffset~roffset=letcontent=Buffer.contentscxt.bufinBuffer.clearcxt.buf;letnext_loc={lnum=cxt.pos_lnum;offset=loc-cxt.pos_bol;byte_bol=cxt.byte_bol}inmatchvalid_identifiercontentwith|Somelident->cxt.segments<-{start=cxt.segment_start;finish=next_loc;content=Var{content;loffset;roffset;lident};}::cxt.segments;cxt.segment_start<-next_loc|None->letcxt=matchString.trimcontentwith|""->(* Move the position back 2 characters "$(" if this is the empty
interpolation. *){cxtwithsegment_start={cxt.segment_startwithoffset=(matchcxt.segment_start.offsetwith0->0|n->n-3);byte_bol=(matchcxt.segment_start.byte_bolwith|0->0|n->n-3);};pos_bol=cxt.pos_bol+3;byte_bol=cxt.byte_bol+3;}|_->cxtinpos_errorcxt~loc(Invalid_syntax_of_varcontent)letadd_str_segmentcxtloc=letcontent=Buffer.contentscxt.bufinBuffer.clearcxt.buf;letnext_loc={lnum=cxt.pos_lnum;offset=loc-cxt.pos_bol;byte_bol=cxt.byte_bol}incxt.segments<-{start=cxt.segment_start;finish=next_loc;content=Stringcontent}::cxt.segments;cxt.segment_start<-next_locletreccheck_and_transformlocsbyte_offset({s_len;buf;_}ascxt)=matchInt.equalbyte_offsets_lenwith|true->add_str_segmentcxtloc|false->(letcurrent_char=s.[byte_offset]inmatchMelange_ffi.Utf8_string.classifycurrent_charwith|Single92(* '\\' *)->letloc=loc+1inletoffset=byte_offset+1inifoffset>=s_lenthenpos_errorcxt~locUnterminated_backslashelseBuffer.add_charbuf'\\';letcur_char=s.[offset]inBuffer.add_charbufcur_char;check_and_transform(loc+1)s(offset+1)cxt|Single36->(* $ *)add_str_segmentcxtloc;letoffset=byte_offset+1inifoffset>=s_lenthenpos_error~loccxtUnterminated_variableelseletcur_char=s.[offset]inifcur_char='('thenexpect_var_paren(loc+2)s(offset+1)cxtelseexpect_simple_var(loc+1)soffsetcxt|Single_|Leading_|Cont_->Buffer.add_charbufcurrent_char;check_and_transform(loc+1)s(byte_offset+1)cxt|Invalid->pos_error~loccxtInvalid_code_point)(* Lets keep identifier simple, so that we could generating a function easier
in the future for example
let f = [%fn{| $x + $y = $x_add_y |}] *)andexpect_simple_varlocsoffset({buf;s_len;_}ascxt)=matchoffset<s_len&&valid_lead_identifier_chars.[offset]with|false->pos_errorcxt~loc(Invalid_syntax_of_varString.empty)|true->letv=refoffsetinwhile!v<s_len&&valid_identifier_chars.[!v]do(* TODO *)letcur_char=s.[!v]inBuffer.add_charbufcur_char;incrvdone;letadded_length=!v-offsetinletloc=added_length+locinadd_var_segmentcxt~loc~loffset:1~roffset:0;check_and_transformlocs(added_length+offset)cxtandexpect_var_parenlocsoffset({buf;s_len;_}ascxt)=letv=refoffsetinwhile!v<s_len&&s.[!v]<>')'doletcur_char=s.[!v]inBuffer.add_charbufcur_char;incrvdone;letadded_length=!v-offsetinletloc=added_length+1+locinmatch!v<s_len&&s.[!v]=')'with|true->add_var_segmentcxt~loc~loffset:2~roffset:(-1);check_and_transformlocs(added_length+1+offset)cxt|false->pos_errorcxt~locUnmatched_paren(* TODO: Allow identifers x.A.y *)letborder=String.length"{j|"letrechandle_segments=letmoduleExp=Ast_helper.Expinletconcat_ident:Longident.t=Ldot(Lident"Stdlib","^")inletescaped_js_delimiter=(* syntax not allowed at the user level *)Some"js"inletmerge_loc(l:Location.t)(r:Location.t)=ifl.loc_ghostthenrelseifr.loc_ghostthenlelse{loc_start=l.loc_start;loc_end=r.loc_end;loc_ghost=false}inletauxloc{start;finish;content}=matchcontentwith|Stringcontent->letloc=updateborderstartfinishlocinExp.constant(Pconst_string(content,loc,escaped_js_delimiter))|Var{loffset=soffset;roffset=foffset;content=_;lident}->letloc={locwithloc_start=update_position(soffset+border)startloc.loc_start;loc_end=update_position(foffset+border)finishloc.loc_start;}inExp.ident~loc{loc;txt=lident}inletconcat_expa_locx~(lhs:expression)=letloc=merge_loca_loclhs.pexp_locinExp.apply(Exp.ident{txt=concat_ident;loc})[(Nolabel,lhs);(Nolabel,auxlocx)]infunlocrev_segments->matchrev_segmentswith|[]->Exp.constant(Pconst_string("",loc,escaped_js_delimiter))|[segment]->auxlocsegment(* string literal *)|{content=String"";_}::rest->handle_segmentslocrest|a::rest->concat_exploca~lhs:(handle_segmentslocrest)lettransform=letunescaped_j_delimiter="j"inlettransform(e:expression)s~delim=matchString.equaldelimunescaped_j_delimiterwith|true->lets_len=String.lengthsinletcxt={segment_start={lnum=0;offset=0;byte_bol=0};buf=Buffer.create(s_len*2);s_len;segments=[];pos_lnum=0;byte_bol=0;pos_bol=0;}incheck_and_transform0s0cxt;handle_segmentse.pexp_loccxt.segments|false->einfun~loc~delimexprs->trytransformexprs~delimwithError(start,pos,error)->letloc=updateborderstartposlocinLocation.raise_errorf~loc"%a"pp_errorerrormodulePrivate=structtypenonrecsegment=segment={start:pos;finish:pos;content:content;}lettransform_tests=lets_len=String.lengthsinletcxt={segment_start={lnum=0;offset=0;byte_bol=0};buf=Buffer.create(s_len*2);s_len;segments=[];pos_lnum=0;byte_bol=0;pos_bol=0;}incheck_and_transform0s0cxt;List.revcxt.segmentsend