123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355(* {{{ COPYING *(
This file is part of Merlin, an helper for ocaml editors
Copyright (C) 2013 - 2015 Frédéric Bour <frederic.bour(_)lakaban.net>
Thomas Refis <refis.thomas(_)gmail.com>
Simon Castellan <simon.castellan(_)iuwt.fr>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The Software is provided "as is", without warranty of any kind, express or
implied, including but not limited to the warranties of merchantability,
fitness for a particular purpose and noninfringement. In no event shall
the authors or copyright holders be liable for any claim, damages or other
liability, whether in an action of contract, tort or otherwise, arising
from, out of or in connection with the software or the use or other dealings
in the Software.
)* }}} *)openStdtypekeywords=Lexer_raw.keywordstypetriple=Parser_raw.token*Lexing.position*Lexing.positiontypeitem=|Tripleoftriple|Commentof(string*Location.t)|ErrorofLexer_raw.error*Location.ttypet={keywords:keywords;config:Mconfig.t;source:Msource.t;items:itemlist}letget_tokenskeywordspostext=letstate=Lexer_raw.makekeywordsinletlexbuf=Lexing.from_stringtextinLexing.movelexbufpos;letrecauxitems=function|Lexer_raw.Return(Parser_raw.COMMENTcomment)->continue(Commentcomment::items)|Lexer_raw.Refillk->auxitems(k())|Lexer_raw.Returnt->lettriple=(t,lexbuf.Lexing.lex_start_p,lexbuf.Lexing.lex_curr_p)inletitems=Tripletriple::itemsinift=Parser_raw.EOFthenitemselsecontinueitems|Lexer_raw.Fail(err,loc)->continue(Error(err,loc)::items)andcontinueitems=auxitems(Lexer_raw.tokenstatelexbuf)infunction|[]->(* First line: skip #! ... *)aux[](Lexer_raw.skip_sharp_bangstatelexbuf)|items->(* Resume *)continueitemsletinitial_positionconfig={Lexing.pos_fname=Mconfig.filenameconfig;pos_lnum=1;pos_bol=0;pos_cnum=0}letmakewarningskeywordsconfigsource=Msupport.catch_errorswarnings(ref[])@@fun()->letitems=get_tokenskeywords(initial_positionconfig)(Msource.textsource)[]in{keywords;items;config;source}letitem_start=function|Triple(_,s,_)->s|Comment(_,l)|Error(_,l)->l.Location.loc_startletitem_end=function|Triple(_,_,e)->e|Comment(_,l)|Error(_,l)->l.Location.loc_endletinitial_positiont=initial_positiont.configletrev_filter_map~flst=letrecauxacc=function|[]->acc|x::xs->letacc=matchfxwith|Somex'->x'::acc|None->accinauxaccxsinaux[]lstlettokenst=rev_filter_mapt.items~f:(function|Triplet->Somet|_->None)letkeywordst=Lexer_raw.list_keywordst.keywordsleterrorst=rev_filter_mapt.items~f:(function|Error(err,loc)->Some(Lexer_raw.Error(err,loc))|_->None)letcommentst=rev_filter_mapt.items~f:(function|Commentt->Somet|_->None)openParser_rawletis_operator=function|PREFIXOPs|LETOPs|ANDOPs|INFIXOP0s|INFIXOP1s|INFIXOP2s|INFIXOP3s|INFIXOP4s->Somes|BANG->Some"!"|PERCENT->Some"%"|PLUS->Some"+"|PLUSDOT->Some"+."|MINUS->Some"-"|MINUSDOT->Some"-."|STAR->Some"*"|EQUAL->Some"="|LESS->Some"<"|GREATER->Some">"|OR->Some"or"|BARBAR->Some"||"|AMPERSAND->Some"&"|AMPERAMPER->Some"&&"|COLONEQUAL->Some":="|PLUSEQ->Some"+="|_->None(* [reconstruct_identifier] is impossible to read at the moment, here is a
pseudo code version of the function:
(many thanks to Gabriel for this contribution)
00| let h = parse (focus h) with
01| | . { h+1 }
02| | _ { h }
03| in
04| parse h with
05| | BOF x=operator { [x] }
06| | ¬( x=operator { [x] }
07| | ' x=ident { [] }
08| | _ {
09| let acc, h = parse (h ! tail h) with
10| | x=ident ! { [x], h }
11| | ( ! x=operator ) { [x], h }
12| | ( x=operator ! ) { [x], h - 1 }
13| | ( x=operator ) ! { [x], h - 2 }
14| | _ { [], h }
15| in
16| let h = h - 1 in
17| let rec head acc = parse (h !) with
18| | tl x=ident . ! { head (x :: acc) tl }
19| | x=ident . ! { ident :: acc }
20| | _ { acc }
21| in head acc
22| }
Now for the explanations:
line 0-3: if we're on a dot, skip it and move to the right
line 5,6: if we're on an operator not preceded by an opening parenthesis,
just return that.
line 7: if we're on a type variable, don't return anything.
reconstruct_identifier is called when locating and getting the
type of an expression, in both cases there's nothing we can do
with a type variable.
See #317
line 8-22: two step approach:
- line 9-15: retrieve the identifier
OR retrieve the parenthesized operator and move before the
opening parenthesis
- line 16-21: retrieve the "path" prefix of the identifier/operator we
got in the previous step.
Additionally, the message of commit fc0b152 explains what we consider is an
identifier:
«
Interpreting an OCaml identifier out of context is a bit ambiguous.
A prefix of the form (UIDENT DOT)* is the module path,
A UIDENT suffix is either a module name, a module type name (in case the
whole path is a module path), or a value constructor.
A LIDENT suffix is either a value name, a type constructor or a module
type name.
A LPAREN OPERATOR RPAREN suffix is a value name (and soon, maybe a
value constructor if beginning by ':' ?!) .
In the middle, LIDENT DOT (UIDENT DOT)* is projection of the field of a
record. In this case, merlin will drop everything up to the first
UIDENT and complete in the scope of the (UIDENT DOT)* interpreted as a
module path.
Soon, the last UIDENT might also be the type of an inline record.
(Module2.f.Module1.A <- type of the record of the value constructor named A of
type f, defined in Module1 and aliased in Module2, pfffff).
»
*)letreconstruct_identifier_from_tokenstokenspos=letreclook_for_componentacc=function(* Skip 'a and `A *)|((LIDENT_|UIDENT_),_,_)::((BACKQUOTE|QUOTE),_,_)::items->checkaccitems(* UIDENT is a regular a component *)|((UIDENT_,_,_)asitem)::items->look_for_dot(item::acc)items(* LIDENT always begin a new identifier *)|((LIDENT_,_,_)asitem)::items->ifacc=[]thenlook_for_dot[item]itemselsecheckacc(item::items)(* Reified operators behave like LIDENT *)|(RPAREN,_,_)::((token,_,_)asitem)::(LPAREN,_,_)::itemswhenis_operatortoken<>None&&acc=[]->look_for_dot[item]items(* An operator alone is an identifier on its own *)|((token,_,_)asitem)::itemswhenis_operatortoken<>None&&acc=[]->check[item]items(* Otherwise, check current accumulator and scan the rest of the input *)|_::items->checkaccitems|[]->raiseNot_foundandlook_for_dotacc=function|(DOT,_,_)::items->look_for_componentaccitems|items->checkaccitemsandcheckaccitems=ifacc<>[]&&(letstartp=matchaccwith|(_,startp,_)::_->startp|_->assertfalseinLexing.compare_posstartppos<=0)&&letendp=matchList.lastaccwith|Some(_,_,endp)->endp|_->assertfalseinLexing.compare_posposendp<=0thenaccelsematchitemswith|[]->raiseNot_found|(_,_,endp)::_whenLexing.compare_posendppos<0->raiseNot_found|_->look_for_component[]itemsinmatchlook_for_component[]tokenswith|exceptionNot_found->[]|acc->letfmt(token,loc_start,loc_end)=letid=matchtokenwith|UIDENTs|LIDENTs->s|_->(matchis_operatortokenwith|Somet->t|None->assertfalse)inLocation.mklocid{Location.loc_start;loc_end;loc_ghost=false}inletbefore_pos=function|_,s,_->Lexing.compare_posspos<=0inList.map~f:fmt(List.filter~f:before_posacc)letreconstruct_identifierconfigsourcepos=letreclexacclexbuf=lettoken=Lexer_ident.tokenlexbufinletitem=(token,lexbuf.Lexing.lex_start_p,lexbuf.Lexing.lex_curr_p)inmatchtokenwith|EOF->item::acc|EOLwhenLexing.compare_poslexbuf.Lexing.lex_curr_ppos>0->item::acc|EOL->lex[]lexbuf|_->lex(item::acc)lexbufinletlexbuf=Lexing.from_string(Msource.textsource)inLocation.initlexbuf(Mconfig.filenameconfig);lettokens=lex[]lexbufinreconstruct_identifier_from_tokenstokensposletis_uppercase{Location.txt=x;_}=x<>""&&Char.is_uppercasex.[0]letrecdrop_lowercaseacc=function|[x]->List.rev(x::acc)|x::xswhennot(is_uppercasex)->drop_lowercase[]xs|x::xs->drop_lowercase(x::acc)xs|[]->List.revaccletfor_completiontpos=letno_labels=reffalseinletcheck_label=function|Triple((LABEL_|OPTLABEL_),_,_)->no_labels:=true|_->()inletrecauxacc=function(* Cursor is before item: continue *)|item::itemswhenLexing.compare_pos(item_startitem)pos>=0->aux(item::acc)items(* Cursor is in the middle of item: stop *)|item::_whenLexing.compare_pos(item_enditem)pos>0->check_labelitem;raiseExit(* Cursor is at the end *)|(Triple(token,_,loc_end)asitem)::_asitemswhenLexing.compare_posposloc_end=0->check_labelitem;beginmatchtokenwith(* Already on identifier, no need to introduce *)|UIDENT_|LIDENT_->raiseExit|_->(acc,items)end|items->(acc,items)inlett=matchaux[]t.itemswith|exceptionExit->t|acc,items->{twithitems=List.rev_appendacc(Triple(LIDENT"",pos,pos)::items)}in(!no_labels,t)letidentifier_suffixident=matchList.lastidentwith|Somexwhenis_uppercasex->drop_lowercase[]ident|_->ident