123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347(* 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_varofstringtypekind=String|Varofint*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;kind:kind;content:string}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_errorfmterr=Format.pp_print_stringfmt@@matcherrwith|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"letvalid_lead_identifier_charx=matchxwith'a'..'z'|'_'->true|_->falseletvalid_identifier_charx=matchxwith|'a'..'z'|'A'..'Z'|'0'..'9'|'_'|'\''->true|_->false(* Invariant: [valid_lead_identifier] has to be [valid_identifier] *)letvalid_identifier=letfor_all_from=letrecunsafe_for_all_ranges~start~finishp=start>finish||p(String.unsafe_getsstart)&&unsafe_for_all_ranges~start:(start+1)~finishpinfunsstartp->letlen=String.lengthsinifstart<0theninvalid_arg"for_all_from"elseunsafe_for_all_ranges~start~finish:(len-1)pinfuns->lets_len=String.lengthsinifs_len=0thenfalseelsevalid_lead_identifier_chars.[0]&&for_all_froms1valid_identifier_char(* 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)=iflnum=0then{poswithpos_cnum=pos.pos_cnum+border+offset}(* When no newline, the column number is [border + offset] *)else{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)=letstart_pos=loc.loc_startin{locwithloc_start=update_positionborderstartstart_pos;loc_end=update_positionborderfinishstart_pos;}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_segmentcxtlocloffsetroffset=letcontent=Buffer.contentscxt.bufinBuffer.clearcxt.buf;letnext_loc={lnum=cxt.pos_lnum;offset=loc-cxt.pos_bol;byte_bol=cxt.byte_bol}inifvalid_identifiercontentthen(cxt.segments<-{start=cxt.segment_start;finish=next_loc;kind=Var(loffset,roffset);content;}::cxt.segments;cxt.segment_start<-next_loc)elseletcxt=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_bolwith0->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;kind=String;content}::cxt.segments;cxt.segment_start<-next_locletreccheck_and_transformlocsbyte_offset({s_len;buf;_}ascxt)=ifbyte_offset=s_lenthenadd_str_segmentcxtlocelseletcurrent_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)=letv=refoffsetinifnot(offset<s_len&&valid_lead_identifier_chars.[offset])thenpos_errorcxt~loc(Invalid_syntax_of_varString.empty)else(while!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_segmentcxtloc10;check_and_transformlocs(added_length+offset)cxt)andexpect_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+locinif!v<s_len&&s.[!v]=')'then(add_var_segmentcxtloc2(-1);check_and_transformlocs(added_length+1+offset)cxt)elsepos_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 *)letunescaped_js_delimiter="js"inSomeunescaped_js_delimiterinletmerge_loc(l:Location.t)(r:Location.t)=ifl.loc_ghostthenrelseifr.loc_ghostthenlelsematch(l,r)with|{loc_start;_},{loc_end;_}(* TODO: improve*)->{loc_start;loc_end;loc_ghost=false}inletauxlocsegment=matchsegmentwith|{start;finish;kind;content}->(matchkindwith|String->letloc=updateborderstartfinishlocinExp.constant(Pconst_string(content,loc,escaped_js_delimiter))|Var(soffset,foffset)->letloc={locwithloc_start=update_position(soffset+border)startloc.loc_start;loc_end=update_position(foffset+border)finishloc.loc_start;}inExp.ident~loc{loc;txt=Lidentcontent})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="";_}::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.lengthsinletbuf=Buffer.create(s_len*2)inletcxt={segment_start={lnum=0;offset=0;byte_bol=0};buf;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;kind:kind;content:string;}lettransform_tests=lets_len=String.lengthsinletbuf=Buffer.create(s_len*2)inletcxt={segment_start={lnum=0;offset=0;byte_bol=0};buf;s_len;segments=[];pos_lnum=0;byte_bol=0;pos_bol=0;}incheck_and_transform0s0cxt;List.revcxt.segmentsend