CE Source Suggestions.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. namespace EE{
  4. namespace Edit{
  5. /******************************************************************************/
  6. Memc<Str> Suggestions;
  7. const Int OrderUnknown=-2; // to be smaller than 'SuggestionsOrder' result for unknown text
  8. /******************************************************************************/
  9. static Int SuggestionsOrder(C Str &t)
  10. {
  11. REPA(Suggestions)if(Equal(Suggestions[i], t, true))return i;
  12. return -1;
  13. }
  14. void SuggestionsUsed(C Str &t)
  15. {
  16. Suggestions.remove(SuggestionsOrder(t), true);
  17. Suggestions.add(t);
  18. if(Suggestions.elms()>16)Suggestions.remove(0, true);
  19. }
  20. /******************************************************************************/
  21. // Factors:
  22. // -word length (prefer those that are shorter)
  23. // -last usage
  24. // -if shift used the add bonus when matches
  25. // -if string part match starts with case up in suggesion part
  26. // -if string part match corresponds to the same index
  27. // -character equality (use distance in buttons on keyboard?)
  28. static Int EqualChars(CChar *t, CChar *s, Int &p, Bool start_case) // assumes that 't' and 's' != null
  29. {
  30. p=0; // priority
  31. Int e=0; // equal characters
  32. Char start=*s;
  33. for(;;)
  34. {
  35. Char ct=*t++,
  36. cs=*s++;
  37. if(Compare(ct, cs))break;
  38. if(!ct)break;
  39. e++;
  40. if(CaseUp(ct)==ct && ct==cs)p++; // if upper case and matches exactly then increase priority
  41. }
  42. p+=e*2;
  43. if(e && start_case && CaseUp(start)==start)p++; // if found any match and 's' starts with upper case, then increase priority
  44. return e;
  45. }
  46. Int SuggestionsPriority(C Str &s, C Str &t, Bool all_up_case) // 's'=suggestion, 't'=text
  47. {
  48. Int priority=0;
  49. for(Int ti=0; ti<t.length(); ) // check every part of 't' text
  50. {
  51. Int max_p=0, final_ec=0;
  52. for(Int si=0; si<s.length(); si++) // find max number of continuous chars equal in self starting from 't+ti'
  53. {
  54. Int p, ec=EqualChars(t()+ti, s()+si, p, ti!=0 && si!=0 && !all_up_case); // don't test case at start of 't' and 's'
  55. if( p)
  56. {
  57. p=p*p*(20+(si==ti)+(ti==0)*3); // boost priority when at the same place, or starting
  58. if(p>max_p){max_p=p; final_ec=ec;}
  59. }
  60. }
  61. priority+=max_p;
  62. ti+=Max(1, final_ec);
  63. if(final_ec==s.length() && s.length()==t.length() && Equal(s, t, true))priority++; // if this is an exact match then boost priority
  64. }
  65. return priority;
  66. }
  67. /******************************************************************************/
  68. static Int CompareAlphabetical(C Source::Suggestion &a, C Source::Suggestion &b)
  69. {
  70. Int as=0; FREPA(a.text)if(a.text[i]=='_')as++;else break; // level of '_' signs
  71. Int bs=0; FREPA(b.text)if(b.text[i]=='_')bs++;else break; // level of '_' signs
  72. return (as==bs) ? Compare(a.text, b.text, false) : (as<bs) ? -1 : +1;
  73. }
  74. static Int CompareAlphabeticalCS(C Source::Suggestion &a, C Source::Suggestion &b) {return Compare(a.text, b.text, true);}
  75. static Int ComparePriority (C Source::Suggestion &a, C Source::Suggestion &b)
  76. {
  77. if(Int c=Compare(a.priority , b.priority ))return -c;
  78. if(Int c=Compare(a.order() , b.order() ))return -c;
  79. if(Int c=Compare(a.text.length(), b.text.length()))return c; // prefer shorter first
  80. return -Compare(a.text , b.text , true ); // use negative so small case will be on top of upper case
  81. }
  82. static Int CompareSuggPath(C Source::Suggestion &a, C Source::Suggestion &b) {return ComparePath(a.text, b.text);}
  83. /******************************************************************************/
  84. // SOURCE SUGGESTION
  85. /******************************************************************************/
  86. Int Source::Suggestion::order()C {if(_order==OrderUnknown)_order=SuggestionsOrder(text); return _order;}
  87. void Source::Suggestion::set(Int priority, C Str &text, Symbol &symbol ) {T.priority=priority; T._order=OrderUnknown; T.text=text ; T.display=text; T.symbol=&symbol; T.elm_id.zero(); T.is_macro=false; T.macro_params= 0 ; if(symbol.type==Symbol::FUNC || symbol.type==Symbol::FUNC_LIST)T.display+=ELLIPSIS; T.icon=null;}
  88. void Source::Suggestion::set(Int priority, C Macro &macro ) {T.priority=priority; T._order=OrderUnknown; T.text=macro.name; T.display=text; T.symbol= null ; T.elm_id.zero(); T.is_macro=true ; T.macro_params=Mid(macro.params+1, 0, 2); macro_def=macro.def; T.icon=null;}
  89. void Source::Suggestion::set(Int priority, C Str &text, C UID &id, C ImagePtr &icon) {T.priority=priority; T._order=OrderUnknown; T.text=text ; T.display=text; T.symbol= null ; T.elm_id=id ; T.is_macro=false; T.macro_params=0; T.icon=icon;}
  90. /******************************************************************************/
  91. // SOURCE
  92. /******************************************************************************/
  93. void Source::clearSuggestions()
  94. {
  95. cur_text .clear();
  96. suggestions .clear();
  97. suggestions_list .clear().columnVisible(0, false);
  98. suggestions_region .hide ();
  99. suggestions_textline.set(S, QUIET).hide(); // clear the textline so next time it is opened it will be empty
  100. }
  101. /******************************************************************************/
  102. void Source::refreshSuggestions()
  103. {
  104. Memc<CodeEditorInterface::ElmLink> elms; CE.cei().getProjPublishElms(elms);
  105. C Str &t=Replace(suggestions_textline(), '/', '\\'); // use back-slash everywhere because it's used for elm full names
  106. suggestions .clear();
  107. suggestions_list.clear().columnVisible(0, true);
  108. if(t.is()) // if we have some text typed
  109. {
  110. FREPA(elms)
  111. {
  112. CodeEditorInterface::ElmLink &elm=elms[i];
  113. if(Int p=SuggestionsPriority(elm.full_name, t, false))suggestions.New().set(p, elm.full_name, elm.id, elm.icon);
  114. }
  115. suggestions.sort(ComparePriority); // sort by priority
  116. if(suggestions.elms()>64)suggestions.setNum(64); // limit to max 64 suggestions
  117. }else
  118. {
  119. FREPA(elms)suggestions.New().set(0, elms[i].full_name, elms[i].id, elms[i].icon);
  120. suggestions.sort(CompareSuggPath); // sort by path
  121. }
  122. suggestions_list.setData(suggestions);
  123. suggestions_list.cur=(suggestions.elms() ? 0 : -1);
  124. }
  125. void Source::listSuggestions(Int force)
  126. {
  127. if(Const)return;
  128. clearSuggestions();
  129. suggestions_pos=cur;
  130. if(force==-2) // project elements
  131. {
  132. refreshSuggestions();
  133. suggestions_textline.activate();
  134. suggestions_region.show();
  135. suggestionsSetRect();
  136. return;
  137. }
  138. if(!active)return;
  139. if(InRange(cur.y, lines))
  140. {
  141. Line &l=lines[cur.y];
  142. if(l.tokens_preproc_condition_unavailable)return; // if inside inactive block then disable
  143. if(l.Type(cur.x-1)==TOKEN_CODE || l.Type(cur.x-1)==TOKEN_KEYWORD || l.Type(cur.x-1)==TOKEN_PREPROC)
  144. {
  145. suggestions_pos.x=l.wordStart(cur.x-1);
  146. if(l[suggestions_pos.x-1]=='#')suggestions_pos.x--;
  147. for(Int i=suggestions_pos.x; i<cur.x; i++)cur_text+=l[i];
  148. }else
  149. if(l.Type(cur.x-1)==TOKEN_COMMENT)return;else // don't list suggestions if in comment
  150. {
  151. Int i; if(Token *sep=findPrevToken(cur, i))
  152. {
  153. if(*sep=='.'
  154. || *sep=="->"
  155. || *sep=="::")
  156. {
  157. suggestions_pos=cur;
  158. if((force>=0) ? true : (sep->lineIndex()==cur.y && sep->col+sep->length()==cur.x))force=1;
  159. }
  160. }
  161. }
  162. }
  163. if(cur_text.is() || force>0)
  164. {
  165. parseCurFunc();
  166. // detect token at suggestions_pos
  167. Int t; Token *token=findToken(suggestions_pos, t); Symbol *caller=(token ? token->parent : null);
  168. if( token && token->def_decl && force<=0)return; // if token is definition/declaration then skip suggestions
  169. if(!token)
  170. if(token=findPrevToken(suggestions_pos, t))
  171. if(!caller)
  172. if(caller=token->parent)
  173. if(*token=='}')
  174. caller=caller->Parent(); // prev token needs to use caller->parent if '}' is used
  175. // if we're in a preprocess line, and after first token, then disable suggestions
  176. if(InRange(suggestions_pos.y, lines))
  177. {
  178. Line &l=lines[suggestions_pos.y];
  179. if(l.preproc && l.tokens.elms() && suggestions_pos.x>=l.tokens[0].col+l.tokens[0].length()
  180. || l.starts_with_preproc)return;
  181. }
  182. Bool precise_parent=false, ctor_init=false, allow_self=true;
  183. Expr parent; parent.symbol=caller; // set initial parent as the 'caller'
  184. if(token)
  185. {
  186. if(token->ctor_initializer)
  187. {
  188. precise_parent=true;
  189. ctor_init =true;
  190. }else
  191. {
  192. if(!(*token=='.' || *token=="->" || *token=="::"))token=(InRange(t-1, tokens) ? tokens[--t] : null);
  193. if(token)if( *token=='.' || *token=="->" || *token=="::" ) // 't' now points to the separator
  194. {
  195. precise_parent=true;
  196. parent.symbol.clear(true); evaluateSymbol(t, parent); // calculate correct parent, we DO need to calculate from 'i' (and include the '.' separator) because expressions like "(TYPE*)obj." without the '.' separator would look like "(TYPE*)obj" and always return obj already casted to some type and be a pointer, which we don't want, we want the original "obj." first
  197. if(parent=="super" || parent=="__super")
  198. {
  199. allow_self=false;
  200. if(parent.parent.elms())
  201. {
  202. Expr temp;
  203. Swap(temp, parent.parent[0]);
  204. Swap(temp, parent);
  205. }else
  206. {
  207. parent.symbol=(caller ? caller->Class() : null);
  208. }
  209. }
  210. }
  211. }
  212. }
  213. Bool parent_instance=parent.anyInstance();
  214. if(cur_text[0]!='#' && !precise_parent){Memc<Macro> &macros=ProjectMacros/*macrosForLine(suggestions_pos.y)*/; REPA(macros){Macro &m=macros[i]; if(m.use_for_suggestions)if(Int p=(cur_text.is() ? SuggestionsPriority(m.name, cur_text, m.all_up_case) : 1))suggestions.New().set(p, m);}} // list macros
  215. if(cur_text[0]=='#' && !precise_parent){REPA(Symbols){Symbol &s=Symbols.lockedData(i); if(!(s.modifiers&Symbol::MODIF_SKIP_SUGGESTIONS) && (s.valid || s.valid_decl) && s.type==Symbol::PREPROC && ((precise_parent && parent.symbol) ? s.isMemberOf(parent.symbol(), parent.symbol.templates, caller, parent_instance, ctor_init, allow_self, false) : s.canBeAccessedFrom(parent.symbol(), caller, precise_parent, ProjectUsings)))if(Int p=SuggestionsPriority(s, cur_text, FlagTest(s.modifiers, Symbol::MODIF_ALL_UP_CASE)))suggestions.New().set(p, s, s);}}else // list preproc keywords
  216. if(cur_text.is() ){REPA(Symbols){Symbol &s=Symbols.lockedData(i); if(!(s.modifiers&Symbol::MODIF_SKIP_SUGGESTIONS) && (s.valid || s.valid_decl) && s.type!=Symbol::PREPROC && ((precise_parent && parent.symbol) ? s.isMemberOf(parent.symbol(), parent.symbol.templates, caller, parent_instance, ctor_init, allow_self, false) : s.canBeAccessedFrom(parent.symbol(), caller, precise_parent, ProjectUsings)))if(Int p=SuggestionsPriority(s, cur_text, FlagTest(s.modifiers, Symbol::MODIF_ALL_UP_CASE)))suggestions.New().set(p, s, s);}}else // list matching symbols
  217. if(force ){REPA(Symbols){Symbol &s=Symbols.lockedData(i); if(!(s.modifiers&Symbol::MODIF_SKIP_SUGGESTIONS) && (s.valid || s.valid_decl) && s.type!=Symbol::PREPROC && ((precise_parent && parent.symbol) ? s.isMemberOf(parent.symbol(), parent.symbol.templates, caller, parent_instance, ctor_init, allow_self, false) : s.canBeAccessedFrom(parent.symbol(), caller, precise_parent, ProjectUsings))) suggestions.New().set(1, s, s);}} // list all symbols
  218. if(suggestions.elms())
  219. {
  220. suggestions.sort(CompareAlphabeticalCS); // first sort alphabetically, to remove suggestions with the same base names (but from different symbols)
  221. REPA(suggestions)if(i)
  222. {
  223. Suggestion &a=suggestions[i], &b=suggestions[i-1];
  224. if(Equal(a.text, b.text, true)) // if have the same names, use only suggestion which symbol is close to current level
  225. {
  226. Int a_level, b_level;
  227. if(a.symbol && b.symbol) // it's possible we're accessing overloaded members from base and extended classes "class A {void met();} class B {void met() {met|}}" both 'met' symbols can be accessed, however only one is correct
  228. {
  229. Symbol *a_class=a.symbol->Class(), // get classes of both members
  230. *b_class=b.symbol->Class();
  231. if(a_class && b_class) // if both detected
  232. {
  233. if(a_class==b_class ){a_level=a.symbol->level; b_level=b.symbol->level;}else // if this is the same class then it's possible we're accessing local variable "class A {int x; void met() {int x;}}"
  234. if(b_class->hasBase(a_class)){a_level=0; b_level=1;}else // "class A {} class B : A {}" remove A and keep B
  235. if(a_class->hasBase(b_class)){a_level=1; b_level=0;}else // "class B {} class A : B {}" remove B and keep A
  236. {a_level=0; b_level=0;}
  237. }else // maybe one does not belong to class "namespace N {int x;} class X {int x; void method() {x|}}" in such case most probably we're accessing class member, since this suggestion was returned it means we have permission to do so (most likely we're in the class)
  238. {
  239. a_level=(a_class!=null);
  240. b_level=(b_class!=null);
  241. }
  242. }else // macro
  243. {
  244. a_level=(a.symbol==null);
  245. b_level=(b.symbol==null);
  246. }
  247. suggestions.remove((a_level < b_level) ? i : i-1, true); // remove the suggestion which is not deeper (has smaller level) - this is needed if we're referencing 2 symbols from different levels "{int x; {int x; | }}" both symbols can be accessed, however only one is correct
  248. }
  249. }
  250. if(cur_text.is()) // if we have some text typed, then sort again, this time by priority
  251. {
  252. suggestions.sort(ComparePriority);
  253. if(suggestions.elms()>64)suggestions.setNum(64); // limit to max 64 suggestions
  254. }else
  255. {
  256. suggestions.sort(CompareAlphabetical);
  257. }
  258. suggestions_list .setData(suggestions);
  259. suggestions_list .cur=(suggestions.elms() ? 0 : -1);
  260. suggestions_region.show();
  261. suggestionsSetRect();
  262. }
  263. }
  264. }
  265. /******************************************************************************/
  266. void Source::suggestionsSetRect()
  267. {
  268. if(suggestions_region.visible())
  269. {
  270. Flt w=suggestions_region.slidebarSize(); REP(suggestions_list.columns())if(suggestions_list.columnVisible(i))w+=suggestions_list.columnWidth(i);
  271. suggestions_textline.rect(Rect_L (posVisual(suggestions_pos)+Vec2(CE.lineNumberSize(), -0.39f*CE.ts.lineHeight()), w, CE.ts.lineHeight()*1.4f));
  272. suggestions_region .rect(Rect_LU(suggestions_textline.visible() ? suggestions_textline.rect().ld() : posVisual(suggestions_pos+VecI2(0, 1))+Vec2(CE.lineNumberSize()-0.01f, 0), w, D.h()*0.5f));
  273. Flt d=suggestions_region.rect().h()+(suggestions_textline.visible() ? suggestions_textline.rect().h() : CE.ts.lineHeight());
  274. if(suggestions_region.rect().min.y +slidebar[1].offset() < -rect().h()+slidebarSize() // if suggestions rect is below the screen
  275. && suggestions_region.rect().max.y+d+slidebar[1].offset() < 0 ) // moved rect will not be above the top of the source rect
  276. suggestions_region.move(Vec2(0, d));
  277. }
  278. }
  279. /******************************************************************************/
  280. void Source::setSuggestion(Int x)
  281. {
  282. if(Gui.kb()!=&suggestions_list)
  283. {
  284. Clamp(x, 0, suggestions_list.elms()-1);
  285. suggestions_list.cur=x;
  286. suggestions_list.scrollTo(x, true);
  287. }
  288. }
  289. /******************************************************************************/
  290. void Source::autoComplete(Bool auto_space, Bool set_undo, Bool auto_brace, Bool call_changed, Bool ignore_params)
  291. {
  292. if(set_undo)setUndo();
  293. if(sel.y>=0)
  294. {
  295. delSel(false, false);
  296. suggestions_pos=cur;
  297. }
  298. if(Suggestion *sugg=suggestions_list())
  299. {
  300. SuggestionsUsed(sugg->text);
  301. if(!Const)
  302. {
  303. Str text=sugg->text; if(sugg->elm_id.valid())text=sugg->elm_id.asCString();
  304. exist(suggestions_pos.x, suggestions_pos.y);
  305. Line &line=lines[suggestions_pos.y];
  306. if(Equal(text, "else", true) && line[suggestions_pos.x-1]==' ' && line[suggestions_pos.x-2]==';')suggestions_pos.x--; // if we've typed "; else" then remove the space before 'else'
  307. line.remove(suggestions_pos.x, cur.x-suggestions_pos.x).insert(suggestions_pos.x, text);
  308. cur.x=suggestions_pos.x+text.length();
  309. cur.y=suggestions_pos.y;
  310. Bool changed_called=false;
  311. if(auto_space)
  312. {
  313. auto_space=false;
  314. if(sugg->symbol && (sugg->symbol->modifiers&Symbol::MODIF_FOLLOW_BY_SPACE))auto_space=true;else
  315. {
  316. changed(cur.y); changed_called=true; // needed for 'isDeclaration'
  317. Int i; if(Token *token=findPrevToken(cur, i))if(token->symbol && (token->symbol->modifiers&Symbol::MODIF_DATA_TYPE) && !token->symbol->templates.elms() && isDeclaration(token->symbol(), i))auto_space=true; // don't insert space if we're using class with templates (like "Memc<>")
  318. }
  319. if(auto_space)
  320. {
  321. if(Equal(text, "else", true) && line.Type(cur.x)==TOKEN_NONE)cur.x++;else // if we've typed 'else' which is followed by (space or nothing), then just increase cursor position without inserting space, this is needed in case we're typing else at the end of line
  322. if(auto_space)
  323. {
  324. line.insert(cur.x++, ' '); changed_called=false;
  325. }
  326. }
  327. }
  328. if(auto_brace && (Equal(text, "if", true) || Equal(text, "for", true) || Equal(text, "while", true) || Equal(text, "switch", true) || sugg->symbol && (sugg->symbol->type==Symbol::FUNC || sugg->symbol->type==Symbol::FUNC_LIST) || sugg->is_macro && sugg->macro_params))
  329. {
  330. if(line[cur.x]!='(')
  331. {
  332. line.insert(cur.x++, "()", TOKEN_OPERATOR); changed_called=false; // set cursor inside the brackets
  333. // if the function does not have parameters, then set the cursor after brackets
  334. if(sugg->is_macro)
  335. {
  336. if(sugg->macro_params==1)cur.x++;
  337. }else
  338. if(sugg->symbol)
  339. {
  340. if(sugg->symbol->type==Symbol::FUNC && (ignore_params || sugg->symbol->params.elms()==0))cur.x++;else
  341. if(sugg->symbol->type==Symbol::FUNC_LIST)
  342. {
  343. Bool all_no_param=true; if(!ignore_params)REPA(sugg->symbol->funcs)if(sugg->symbol->funcs[i]->params.elms()){all_no_param=false; break;}
  344. if( all_no_param)cur.x++;
  345. }
  346. }
  347. }
  348. }
  349. if(call_changed && !changed_called)changed(cur.y);
  350. }
  351. makeCurVisible();
  352. clearSuggestions();
  353. }
  354. }
  355. /******************************************************************************/
  356. Bool Source::evaluateSymbol(Int start, Expr &out, Int final, Bool allow_func_lists)
  357. {
  358. if(InRange(start, tokens))
  359. {
  360. /*if(parse_templates)
  361. {
  362. Int from=start+1;
  363. for(Int level=0, i=start; i>=0; i--)
  364. {
  365. Token &token=*tokens[i];
  366. switch(token[0])
  367. {
  368. case '(': case '[': level++; break;
  369. case ')': case ']': level--; break;
  370. case '{': case '}': case ';': goto found_start;
  371. }
  372. if(level>0)break;
  373. from=i;
  374. }
  375. found_start:;
  376. for(; from<=start; ){Int s=from; ParseTemplates(tokens, from, temp, expr_parent); MAX(from, s+1);}
  377. }*/
  378. // detect starting position required to obtain desired symbol "return (a+b).x|" -> "return |(a+b).x|"
  379. Symbol *start_parent=tokens[start]->parent;
  380. Int from =start+1;
  381. for(Int level=0, i=start; i>=0; i--)
  382. {
  383. Token &token=*tokens[i];
  384. switch(token[0])
  385. {
  386. case '(': case '[': level++; break;
  387. case ')': case ']': level--; break;
  388. case '{': case '}': case ';': goto found;
  389. default : if(token==TMPL_B)level++;else if(token==TMPL_E)level--; break;
  390. }
  391. if(!(!start_parent || start_parent->contains(token.parent)))break; // break if the token.parent is not a child of start_parent (basically break if we've reached some parent of the start_parent)
  392. if(level>0)break;
  393. if(level<0)from=i;else
  394. {
  395. if(token.type==TOKEN_KEYWORD && (token=="return" || token=="do" || token=="else"))break; // return (a+b).x; - break on encountering command keyword
  396. if(token.type==TOKEN_KEYWORD || token.type==TOKEN_CODE
  397. ||(token.type==TOKEN_OPERATOR && (token=='(' || token==')' || token=='[' || token==']' || token==TMPL_B || token==TMPL_E || token=='.' || token=="->" || token=="::"))
  398. || final>=0) // for 'final' we need full formula
  399. from=i;
  400. else break;
  401. }
  402. }
  403. found:;
  404. Memc<Message> msgs;
  405. Compiler compiler(msgs, tokens, this, null);
  406. return compiler.relax().setFinal(final, allow_func_lists).compileTokens(from, start, out)!=COMPILE_FAILED;
  407. }
  408. return false;
  409. }
  410. /******************************************************************************/
  411. Symbol* Source::finalSymbol(Int final, Bool allow_func_lists)
  412. {
  413. if(InRange(final, tokens))
  414. {
  415. Int start=final;
  416. if(!allow_func_lists) // move cursor from "func|()" to "func()|" to include parameters for 'func' in order to detect correct polymorphic function
  417. {
  418. Token &start_token=*tokens[start];
  419. for(Int level=(start_token=='(' || start_token=='[' || start_token==TMPL_B); start+1<tokens.elms(); )
  420. {
  421. Token &token=*tokens[start+1];
  422. if(token=='(' || token=='[' || token==TMPL_B)level++;else
  423. if(token==')' || token==']' || token==TMPL_E)level--;else
  424. if(token=='{' || token=='}' || token==';')break;
  425. if((token==',' || token==':') && !level)break; // skip ':' for ctor initializers, and for "x ? y : z", but not for "func(x ? y : z)" in case we're calculating 'func'
  426. if(level<0)break;
  427. start++;
  428. }
  429. }
  430. Expr expr;
  431. if(evaluateSymbol(start, expr, final, allow_func_lists))return expr.symbol();
  432. }
  433. return null;
  434. }
  435. /******************************************************************************/
  436. }}
  437. /******************************************************************************/