PageRenderTime 55ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/libs/codeintel2/lang_php.py

https://github.com/MinerCrafter/SublimeCodeIntel
Python | 3309 lines | 2996 code | 88 blank | 225 comment | 255 complexity | 552fa497b347d494869fd91c766ef9e6 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. #!/usr/bin/env python
  2. # ***** BEGIN LICENSE BLOCK *****
  3. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4. #
  5. # The contents of this file are subject to the Mozilla Public License
  6. # Version 1.1 (the "License"); you may not use this file except in
  7. # compliance with the License. You may obtain a copy of the License at
  8. # http://www.mozilla.org/MPL/
  9. #
  10. # Software distributed under the License is distributed on an "AS IS"
  11. # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
  12. # License for the specific language governing rights and limitations
  13. # under the License.
  14. #
  15. # The Original Code is Komodo code.
  16. #
  17. # The Initial Developer of the Original Code is ActiveState Software Inc.
  18. # Portions created by ActiveState Software Inc are Copyright (C) 2000-2007
  19. # ActiveState Software Inc. All Rights Reserved.
  20. #
  21. # Contributor(s):
  22. # ActiveState Software Inc
  23. #
  24. # Alternatively, the contents of this file may be used under the terms of
  25. # either the GNU General Public License Version 2 or later (the "GPL"), or
  26. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27. # in which case the provisions of the GPL or the LGPL are applicable instead
  28. # of those above. If you wish to allow use of your version of this file only
  29. # under the terms of either the GPL or the LGPL, and not to allow others to
  30. # use your version of this file under the terms of the MPL, indicate your
  31. # decision by deleting the provisions above and replace them with the notice
  32. # and other provisions required by the GPL or the LGPL. If you do not delete
  33. # the provisions above, a recipient may use your version of this file under
  34. # the terms of any one of the MPL, the GPL or the LGPL.
  35. #
  36. # ***** END LICENSE BLOCK *****
  37. #
  38. # Contributors:
  39. # Shane Caraveo (ShaneC@ActiveState.com)
  40. # Trent Mick (TrentM@ActiveState.com)
  41. # Todd Whiteman (ToddW@ActiveState.com)
  42. """codeintel support for PHP"""
  43. import os
  44. from os.path import isdir, join, basename, splitext, exists, dirname
  45. import sys
  46. import re
  47. import logging
  48. import time
  49. import warnings
  50. from cStringIO import StringIO
  51. import weakref
  52. from glob import glob
  53. from SilverCity.ScintillaConstants import (SCE_UDL_SSL_DEFAULT,
  54. SCE_UDL_SSL_OPERATOR,
  55. SCE_UDL_SSL_IDENTIFIER,
  56. SCE_UDL_SSL_WORD,
  57. SCE_UDL_SSL_VARIABLE,
  58. SCE_UDL_SSL_STRING,
  59. SCE_UDL_SSL_NUMBER,
  60. SCE_UDL_SSL_COMMENT,
  61. SCE_UDL_SSL_COMMENTBLOCK)
  62. from codeintel2.parseutil import *
  63. from codeintel2.phpdoc import phpdoc_tags
  64. from codeintel2.citadel import ImportHandler, CitadelLangIntel
  65. from codeintel2.udl import UDLBuffer, UDLLexer, UDLCILEDriver, is_udl_csl_style, XMLParsingBufferMixin
  66. from codeintel2.common import *
  67. from codeintel2 import util
  68. from codeintel2.indexer import PreloadBufLibsRequest, PreloadLibRequest
  69. from codeintel2.gencix_utils import *
  70. from codeintel2.tree_php import PHPTreeEvaluator
  71. from codeintel2.langintel import (ParenStyleCalltipIntelMixin,
  72. ProgLangTriggerIntelMixin)
  73. from codeintel2.accessor import AccessorCache, KoDocumentAccessor
  74. if _xpcom_:
  75. from xpcom.server import UnwrapObject
  76. #---- global data
  77. lang = "PHP"
  78. log = logging.getLogger("codeintel.php")
  79. #log.setLevel(logging.DEBUG)
  80. util.makePerformantLogger(log)
  81. #---- language support
  82. class PHPLexer(UDLLexer):
  83. lang = lang
  84. def _walk_php_symbols(elem, _prefix=None):
  85. if _prefix:
  86. lpath = _prefix + (elem.get("name"), )
  87. else:
  88. lpath = (elem.get("name"), )
  89. yield lpath
  90. if not (elem.tag == "scope" and elem.get("ilk") == "function"):
  91. for child in elem:
  92. for child_lpath in _walk_php_symbols(child, lpath):
  93. yield child_lpath
  94. class PHPLangIntel(CitadelLangIntel, ParenStyleCalltipIntelMixin,
  95. ProgLangTriggerIntelMixin):
  96. lang = lang
  97. # Used by ProgLangTriggerIntelMixin.preceding_trg_from_pos()
  98. trg_chars = tuple('$>:(,@"\' \\')
  99. calltip_trg_chars = tuple('(') # excluded ' ' for perf (bug 55497)
  100. # named styles used by the class
  101. whitespace_style = SCE_UDL_SSL_DEFAULT
  102. operator_style = SCE_UDL_SSL_OPERATOR
  103. identifier_style = SCE_UDL_SSL_IDENTIFIER
  104. keyword_style = SCE_UDL_SSL_WORD
  105. variable_style = SCE_UDL_SSL_VARIABLE
  106. string_style = SCE_UDL_SSL_STRING
  107. comment_styles = (SCE_UDL_SSL_COMMENT, SCE_UDL_SSL_COMMENTBLOCK)
  108. comment_styles_or_whitespace = comment_styles + (whitespace_style, )
  109. def _functionCalltipTrigger(self, ac, pos, DEBUG=False):
  110. # Implicit calltip triggering from an arg separater ",", we trigger a
  111. # calltip if we find a function open paren "(" and function identifier
  112. # http://bugs.activestate.com/show_bug.cgi?id=70470
  113. if DEBUG:
  114. print "Arg separater found, looking for start of function"
  115. # Move back to the open paren of the function
  116. paren_count = 0
  117. p = pos
  118. min_p = max(0, p - 200) # look back max 200 chars
  119. while p > min_p:
  120. p, c, style = ac.getPrecedingPosCharStyle(ignore_styles=self.comment_styles)
  121. if style == self.operator_style:
  122. if c == ")":
  123. paren_count += 1
  124. elif c == "(":
  125. if paren_count == 0:
  126. # We found the open brace of the func
  127. trg_from_pos = p+1
  128. p, ch, style = ac.getPrevPosCharStyle()
  129. if DEBUG:
  130. print "Function start found, pos: %d" % (p, )
  131. if style in self.comment_styles_or_whitespace:
  132. # Find previous non-ignored style then
  133. p, c, style = ac.getPrecedingPosCharStyle(style, self.comment_styles_or_whitespace)
  134. if style in (self.identifier_style, self.keyword_style):
  135. return Trigger(lang, TRG_FORM_CALLTIP,
  136. "call-signature",
  137. trg_from_pos, implicit=True)
  138. else:
  139. paren_count -= 1
  140. elif c in ";{}":
  141. # Gone too far and noting was found
  142. if DEBUG:
  143. print "No function found, hit stop char: %s at p: %d" % (c, p)
  144. return None
  145. # Did not find the function open paren
  146. if DEBUG:
  147. print "No function found, ran out of chars to look at, p: %d" % (p,)
  148. return None
  149. #@util.hotshotit
  150. def trg_from_pos(self, buf, pos, implicit=True, DEBUG=False, ac=None):
  151. #DEBUG = True
  152. if pos < 4:
  153. return None
  154. #DEBUG = True
  155. # Last four chars and styles
  156. if ac is None:
  157. ac = AccessorCache(buf.accessor, pos, fetchsize=4)
  158. last_pos, last_char, last_style = ac.getPrevPosCharStyle()
  159. prev_pos, prev_char, prev_style = ac.getPrevPosCharStyle()
  160. # Bump up how much text is retrieved when cache runs out
  161. ac.setCacheFetchSize(20)
  162. if DEBUG:
  163. print "\nphp trg_from_pos"
  164. print " last_pos: %s" % last_pos
  165. print " last_char: %s" % last_char
  166. print " last_style: %r" % last_style
  167. ac.dump()
  168. try:
  169. # Note: If a "$" exists by itself, it's styled as whitespace.
  170. # Generally we want it to be indicating a variable instead.
  171. if last_style == self.whitespace_style and last_char != "$":
  172. if DEBUG:
  173. print "Whitespace style"
  174. WHITESPACE = tuple(" \t\n\r\v\f")
  175. if not implicit:
  176. # If we're not already at the keyword style, find it
  177. if prev_style != self.keyword_style:
  178. prev_pos, prev_char, prev_style = ac.getPrecedingPosCharStyle(last_style, self.comment_styles)
  179. if DEBUG:
  180. print "Explicit: prev_pos: %d, style: %d, ch: %r" % (prev_pos, prev_style, prev_char)
  181. else:
  182. prev_pos = pos - 2
  183. if last_char in WHITESPACE and \
  184. (prev_style == self.keyword_style or
  185. (prev_style == self.operator_style and prev_char == ",")):
  186. p = prev_pos
  187. style = prev_style
  188. ch = prev_char
  189. #print "p: %d" % p
  190. while p > 0 and style == self.operator_style and ch == ",":
  191. p, ch, style = ac.getPrecedingPosCharStyle(style, self.comment_styles_or_whitespace)
  192. #print "p 1: %d" % p
  193. if p > 0 and style == self.identifier_style:
  194. # Skip the identifier too
  195. p, ch, style = ac.getPrecedingPosCharStyle(style, self.comment_styles_or_whitespace)
  196. #print "p 2: %d" % p
  197. if DEBUG:
  198. ac.dump()
  199. p, text = ac.getTextBackWithStyle(style, self.comment_styles, max_text_len=len("implements"))
  200. if DEBUG:
  201. print "ac.getTextBackWithStyle:: pos: %d, text: %r" % (p, text)
  202. if text in ("new", "extends"):
  203. return Trigger(lang, TRG_FORM_CPLN, "classes", pos, implicit)
  204. elif text in ("implements", ):
  205. return Trigger(lang, TRG_FORM_CPLN, "interfaces", pos, implicit)
  206. elif text in ("use", ):
  207. return Trigger(lang, TRG_FORM_CPLN, "use", pos, implicit)
  208. elif prev_style == self.operator_style and \
  209. prev_char == "," and implicit:
  210. return self._functionCalltipTrigger(ac, prev_pos, DEBUG)
  211. elif last_style == self.operator_style:
  212. if DEBUG:
  213. print " lang_style is operator style"
  214. print "Prev char: %r" % (prev_char)
  215. ac.dump()
  216. if last_char == ":":
  217. if not prev_char == ":":
  218. return None
  219. ac.setCacheFetchSize(10)
  220. p, c, style = ac.getPrecedingPosCharStyle(prev_style, self.comment_styles)
  221. if DEBUG:
  222. print "Preceding: %d, %r, %d" % (p, c, style)
  223. if style is None:
  224. return None
  225. elif style == self.keyword_style:
  226. # Check if it's a "self::" or "parent::" expression
  227. p, text = ac.getTextBackWithStyle(self.keyword_style,
  228. # Ensure we don't go too far
  229. max_text_len=6)
  230. if DEBUG:
  231. print "Keyword text: %d, %r" % (p, text)
  232. ac.dump()
  233. if text not in ("parent", "self", "static"):
  234. return None
  235. return Trigger(lang, TRG_FORM_CPLN, "static-members",
  236. pos, implicit)
  237. elif last_char == ">":
  238. if prev_char == "-":
  239. p, c, style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles_or_whitespace)
  240. if style in (self.variable_style, self.identifier_style) or \
  241. (style == self.operator_style and c == ')'):
  242. return Trigger(lang, TRG_FORM_CPLN, "object-members",
  243. pos, implicit)
  244. elif DEBUG:
  245. print "Preceding style is not a variable, pos: %d, style: %d" % (p, style)
  246. elif last_char in "(,":
  247. # where to trigger from, updated by "," calltip handler
  248. if DEBUG:
  249. print "Checking for function calltip"
  250. # Implicit calltip triggering from an arg separater ","
  251. # http://bugs.activestate.com/show_bug.cgi?id=70470
  252. if implicit and last_char == ',':
  253. return self._functionCalltipTrigger(ac, prev_pos, DEBUG)
  254. if prev_style in self.comment_styles_or_whitespace:
  255. # Find previous non-ignored style then
  256. p, c, prev_style = ac.getPrecedingPosCharStyle(prev_style, self.comment_styles_or_whitespace)
  257. if prev_style in (self.identifier_style, self.keyword_style):
  258. return Trigger(lang, TRG_FORM_CALLTIP, "call-signature",
  259. pos, implicit)
  260. elif last_char == "\\":
  261. # Ensure does not trigger when defining a new namespace,
  262. # i.e., do not trigger for:
  263. # namespace foo\<|>
  264. style = last_style
  265. while style in (self.operator_style, self.identifier_style):
  266. p, c, style = ac.getPrecedingPosCharStyle(style, max_look_back=30)
  267. if style == self.whitespace_style:
  268. p, c, style = ac.getPrecedingPosCharStyle(self.whitespace_style, max_look_back=30)
  269. if style is None:
  270. if DEBUG:
  271. print "Triggering namespace completion"
  272. return Trigger(lang, TRG_FORM_CPLN, "namespace-members",
  273. pos, implicit)
  274. prev_text = ac.getTextBackWithStyle(style, max_text_len=15)
  275. if DEBUG:
  276. print "prev_text: %r" % (prev_text, )
  277. if prev_text[1] == "use":
  278. if DEBUG:
  279. print "Triggering use-namespace completion"
  280. return Trigger(lang, TRG_FORM_CPLN, "use-namespace",
  281. pos, implicit)
  282. elif prev_text[1] != "namespace":
  283. if DEBUG:
  284. print "Triggering namespace completion"
  285. return Trigger(lang, TRG_FORM_CPLN, "namespace-members",
  286. pos, implicit)
  287. elif last_style == self.variable_style or \
  288. (not implicit and last_char == "$"):
  289. if DEBUG:
  290. print "Variable style"
  291. # Completion for variables (builtins and user defined variables),
  292. # must occur after a "$" character.
  293. if not implicit and last_char == '$':
  294. # Explicit call, move ahead one for real trigger position
  295. pos += 1
  296. if not implicit or prev_char == "$":
  297. # Ensure we are not triggering over static class variables.
  298. # Do this by checking that the preceding text is not "::"
  299. # http://bugs.activestate.com/show_bug.cgi?id=78099
  300. p, c, style = ac.getPrecedingPosCharStyle(last_style,
  301. max_look_back=30)
  302. if c == ":" and style == self.operator_style and \
  303. ac.getTextBackWithStyle(style, max_text_len=3)[1] == "::":
  304. return None
  305. return Trigger(lang, TRG_FORM_CPLN, "variables",
  306. pos-1, implicit)
  307. elif last_style in (self.identifier_style, self.keyword_style):
  308. if DEBUG:
  309. if last_style == self.identifier_style:
  310. print "Identifier style"
  311. else:
  312. print "Identifier keyword style"
  313. # Completion for keywords,function and class names
  314. # Works after first 3 characters have been typed
  315. #if DEBUG:
  316. # print "identifier_style: pos - 4 %s" % (accessor.style_at_pos(pos - 4))
  317. #third_char, third_style = last_four_char_and_styles[2]
  318. #fourth_char, fourth_style = last_four_char_and_styles[3]
  319. if prev_style == last_style:
  320. trig_pos, ch, style = ac.getPrevPosCharStyle()
  321. if style == last_style:
  322. p, ch, style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles)
  323. # style is None if no change of style (not ignored) was
  324. # found in the last x number of chars
  325. #if not implicit and style == last_style:
  326. # if DEBUG:
  327. # print "Checking back further for explicit call"
  328. # p, c, style = ac.getPrecedingPosCharStyle(style, max_look_back=100)
  329. # if p is not None:
  330. # trg_pos = p + 3
  331. if style in (None, self.whitespace_style,
  332. self.operator_style):
  333. # Ensure we are not in another trigger zone, we do
  334. # this by checking that the preceeding text is not
  335. # one of "->", "::", "new", "function", "class", ...
  336. if style == self.whitespace_style:
  337. p, c, style = ac.getPrecedingPosCharStyle(self.whitespace_style, max_look_back=30)
  338. if style is None:
  339. return Trigger(lang, TRG_FORM_CPLN, "functions",
  340. trig_pos, implicit)
  341. prev_text = ac.getTextBackWithStyle(style, max_text_len=15)
  342. if DEBUG:
  343. print "prev_text: %r" % (prev_text, )
  344. if (prev_text[1] not in ("new", "function", "use",
  345. "class", "interface", "implements",
  346. "public", "private", "protected",
  347. "final", "abstract", "instanceof",)
  348. # For the operator styles, we must use
  349. # endswith, as it could follow a "()",
  350. # bug 90846.
  351. and prev_text[1][-2:] not in ("->", "::",)
  352. # Don't trigger when accessing a
  353. # namespace - bug 88736.
  354. and not prev_text[1].endswith("\\")):
  355. return Trigger(lang, TRG_FORM_CPLN, "functions",
  356. trig_pos, implicit)
  357. # If we want implicit triggering on more than 3 chars
  358. #elif style == self.identifier_style:
  359. # p, c, style = ac.getPrecedingPosCharStyle(self.identifier_style)
  360. # return Trigger(lang, TRG_FORM_CPLN, "functions",
  361. # p+1, implicit)
  362. elif DEBUG:
  363. print "identifier preceeded by an invalid style: " \
  364. "%r, p: %r" % (style, p, )
  365. elif last_char == '_' and prev_char == '_' and \
  366. style == self.whitespace_style:
  367. # XXX - Check the php version, magic methods only
  368. # appeared in php 5.
  369. p, ch, style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles)
  370. if style == self.keyword_style and \
  371. ac.getTextBackWithStyle(style, max_text_len=9)[1] == "function":
  372. if DEBUG:
  373. print "triggered:: complete magic-methods"
  374. return Trigger(lang, TRG_FORM_CPLN, "magic-methods",
  375. prev_pos, implicit)
  376. # PHPDoc completions
  377. elif last_char == "@" and last_style in self.comment_styles:
  378. # If the preceeding non-whitespace character is a "*" or newline
  379. # then we complete for phpdoc tag names
  380. p = last_pos - 1
  381. min_p = max(0, p - 50) # Don't look more than 50 chars
  382. if DEBUG:
  383. print "Checking match for phpdoc completions"
  384. accessor = buf.accessor
  385. while p >= min_p and \
  386. accessor.style_at_pos(p) in self.comment_styles:
  387. ch = accessor.char_at_pos(p)
  388. p -= 1
  389. #if DEBUG:
  390. # print "Looking at ch: %r" % (ch)
  391. if ch in "*\r\n":
  392. break
  393. elif ch not in " \t\v":
  394. # Not whitespace, not a valid tag then
  395. return None
  396. else:
  397. # Nothing found in the specified range
  398. if DEBUG:
  399. print "trg_from_pos: not a phpdoc"
  400. return None
  401. if DEBUG:
  402. print "Matched trigger for phpdoc completion"
  403. return Trigger("PHP", TRG_FORM_CPLN,
  404. "phpdoc-tags", pos, implicit)
  405. # PHPDoc calltip
  406. elif last_char in " \t" and last_style in self.comment_styles:
  407. # whitespace in a comment, see if it matches for phpdoc calltip
  408. p = last_pos - 1
  409. min_p = max(0, p - 50) # Don't look more than 50 chars
  410. if DEBUG:
  411. print "Checking match for phpdoc calltip"
  412. ch = None
  413. ident_found_pos = None
  414. accessor = buf.accessor
  415. while p >= min_p and \
  416. accessor.style_at_pos(p) in self.comment_styles:
  417. ch = accessor.char_at_pos(p)
  418. p -= 1
  419. if ident_found_pos is None:
  420. #print "phpdoc: Looking for identifier, ch: %r" % (ch)
  421. if ch in " \t":
  422. pass
  423. elif _isident(ch):
  424. ident_found_pos = p+1
  425. else:
  426. if DEBUG:
  427. print "No phpdoc, whitespace not preceeded " \
  428. "by an identifer"
  429. return None
  430. elif ch == "@":
  431. # This is what we've been looking for!
  432. phpdoc_field = accessor.text_range(p+2,
  433. ident_found_pos+1)
  434. if DEBUG:
  435. print "Matched trigger for phpdoc calltip: '%s'" % (
  436. phpdoc_field, )
  437. return Trigger("PHP", TRG_FORM_CALLTIP,
  438. "phpdoc-tags", ident_found_pos, implicit,
  439. phpdoc_field=phpdoc_field)
  440. elif not _isident(ch):
  441. if DEBUG:
  442. print "No phpdoc, identifier not preceeded by '@'"
  443. # Not whitespace, not a valid tag then
  444. return None
  445. # Nothing found in the specified range
  446. if DEBUG:
  447. print "No phpdoc, ran out of characters to look at."
  448. # Array completions
  449. elif last_style == self.string_style and last_char in '\'"':
  450. if prev_char != '[':
  451. if prev_style in self.comment_styles_or_whitespace:
  452. # Look back further.
  453. prev_pos, prev_char, prev_style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles_or_whitespace)
  454. if prev_char == '[':
  455. # We're good to go.
  456. if DEBUG:
  457. print "Matched trigger for array completions"
  458. return Trigger("PHP", TRG_FORM_CPLN,
  459. "array-members", pos, implicit,
  460. bracket_pos=prev_pos,
  461. trg_char=last_char)
  462. # Variable completions inside of comments
  463. elif prev_char == "$" and last_style in self.comment_styles:
  464. if DEBUG:
  465. print "Comment variable style"
  466. # Completion for variables (builtins and user defined variables),
  467. # must occur after a "$" character.
  468. return Trigger(lang, TRG_FORM_CPLN, "comment-variables",
  469. pos-1, implicit)
  470. elif DEBUG:
  471. print "trg_from_pos: no handle for style: %d" % last_style
  472. except IndexError:
  473. # Not enough chars found, therefore no trigger
  474. pass
  475. return None
  476. #@util.hotshotit
  477. def preceding_trg_from_pos(self, buf, pos, curr_pos,
  478. preceding_trg_terminators=None, DEBUG=False):
  479. #DEBUG = True
  480. # Try the default preceding_trg_from_pos handler
  481. trg = ProgLangTriggerIntelMixin.preceding_trg_from_pos(
  482. self, buf, pos, curr_pos, preceding_trg_terminators,
  483. DEBUG=DEBUG)
  484. if trg is not None:
  485. return trg
  486. # Else, let's try to work out some other options
  487. accessor = buf.accessor
  488. prev_style = accessor.style_at_pos(curr_pos - 1)
  489. if prev_style in (self.identifier_style, self.keyword_style):
  490. # We don't know what to trigger here... could be one of:
  491. # functions:
  492. # apache<$><|>_getenv()...
  493. # if(get_e<$><|>nv()...
  494. # classes:
  495. # new Exce<$><|>ption()...
  496. # extends Exce<$><|>ption()...
  497. # interfaces:
  498. # implements apache<$><|>_getenv()...
  499. ac = AccessorCache(accessor, curr_pos)
  500. pos_before_identifer, ch, prev_style = \
  501. ac.getPrecedingPosCharStyle(prev_style)
  502. if DEBUG:
  503. print "\nphp preceding_trg_from_pos, first chance for identifer style"
  504. print " curr_pos: %d" % (curr_pos)
  505. print " pos_before_identifer: %d" % (pos_before_identifer)
  506. print " ch: %r" % ch
  507. print " prev_style: %d" % prev_style
  508. ac.dump()
  509. if pos_before_identifer < pos:
  510. resetPos = min(pos_before_identifer + 4, accessor.length() - 1)
  511. ac.resetToPosition(resetPos)
  512. if DEBUG:
  513. print "preceding_trg_from_pos:: reset to position: %d, ac now:" % (resetPos)
  514. ac.dump()
  515. # Trigger on the third identifier character
  516. return self.trg_from_pos(buf, resetPos,
  517. implicit=False, DEBUG=DEBUG, ac=ac)
  518. elif DEBUG:
  519. print "Out of scope of the identifier"
  520. elif prev_style in self.comment_styles:
  521. # Check if there is a PHPDoc to provide a calltip for, example:
  522. # /** @param $foo foobar - This is field for <|>
  523. if DEBUG:
  524. print "\nphp preceding_trg_from_pos::phpdoc: check for calltip"
  525. comment = accessor.text_range(max(0, curr_pos-200), curr_pos)
  526. at_idx = comment.rfind("@")
  527. if at_idx >= 0:
  528. if DEBUG:
  529. print "\nphp preceding_trg_from_pos::phpdoc: contains '@'"
  530. space_idx = comment[at_idx:].find(" ")
  531. if space_idx >= 0:
  532. # Trigger after the space character.
  533. trg_pos = (curr_pos - len(comment)) + at_idx + space_idx + 1
  534. if DEBUG:
  535. print "\nphp preceding_trg_from_pos::phpdoc: calltip at %d" % (trg_pos, )
  536. return self.trg_from_pos(buf, trg_pos,
  537. implicit=False, DEBUG=DEBUG)
  538. _phpdoc_cplns = [ ("variable", t) for t in sorted(phpdoc_tags) ]
  539. #@util.hotshotit
  540. def async_eval_at_trg(self, buf, trg, ctlr):
  541. if _xpcom_:
  542. trg = UnwrapObject(trg)
  543. ctlr = UnwrapObject(ctlr)
  544. pos = trg.pos
  545. ctlr.start(buf, trg)
  546. #print "trg.type: %r" % (trg.type)
  547. # PHPDoc completions
  548. if trg.id == ("PHP", TRG_FORM_CPLN, "phpdoc-tags"):
  549. #TODO: Would like a "javadoc tag" completion image name.
  550. ctlr.set_cplns(self._phpdoc_cplns)
  551. ctlr.done("success")
  552. return
  553. # PHPDoc calltip
  554. elif trg.id == ("PHP", TRG_FORM_CALLTIP, "phpdoc-tags"):
  555. phpdoc_field = trg.extra.get("phpdoc_field")
  556. if phpdoc_field:
  557. #print "phpdoc_field: %r" % (phpdoc_field, )
  558. calltip = phpdoc_tags.get(phpdoc_field)
  559. if calltip:
  560. ctlr.set_calltips([calltip])
  561. ctlr.done("success")
  562. return
  563. elif trg.type in ("classes", "interfaces"):
  564. # Triggers from zero characters, thus calling citdl_expr_from_trg
  565. # is no help
  566. line = buf.accessor.line_from_pos(pos)
  567. evalr = PHPTreeEvaluator(ctlr, buf, trg, "", line)
  568. buf.mgr.request_eval(evalr)
  569. else:
  570. try:
  571. citdl_expr = self.citdl_expr_from_trg(buf, trg)
  572. except CodeIntelError, ex:
  573. ctlr.error(str(ex))
  574. ctlr.done("error")
  575. return
  576. line = buf.accessor.line_from_pos(pos)
  577. evalr = PHPTreeEvaluator(ctlr, buf, trg, citdl_expr, line)
  578. buf.mgr.request_eval(evalr)
  579. def _citdl_expr_from_pos(self, trg, buf, pos, implicit=True,
  580. include_forwards=False, DEBUG=False):
  581. #DEBUG = True
  582. #PERF: Would dicts be faster for all of these?
  583. WHITESPACE = tuple(" \t\n\r\v\f")
  584. EOL = tuple("\r\n")
  585. BLOCKCLOSES = tuple(")}]")
  586. STOPOPS = tuple("({[,&$+=^|%/<;:->!.@?")
  587. EXTRA_STOPOPS_PRECEDING_IDENT = BLOCKCLOSES # Might be others.
  588. #TODO: This style picking is a problem for the LangIntel move.
  589. if trg.type == "comment-variables":
  590. # Dev note: skip_styles in the other cases below will be a dict.
  591. skip_styles = set()
  592. elif implicit:
  593. skip_styles = buf.implicit_completion_skip_styles
  594. else:
  595. skip_styles = buf.completion_skip_styles
  596. citdl_expr = []
  597. accessor = buf.accessor
  598. # Use a cache of characters, easy to keep track this way
  599. i = pos
  600. ac = AccessorCache(accessor, i)
  601. if include_forwards:
  602. try:
  603. # Move ahead to include forward chars as well
  604. lastch_was_whitespace = False
  605. while 1:
  606. i, ch, style = ac.getNextPosCharStyle()
  607. if DEBUG:
  608. print "include_forwards:: i now: %d, ch: %r" % (i, ch)
  609. if ch in WHITESPACE:
  610. lastch_was_whitespace = True
  611. continue
  612. lastch_was_whitespace = False
  613. if ch in STOPOPS:
  614. if DEBUG:
  615. print "include_forwards:: ch in STOPOPS, i:%d ch:%r" % (i, ch)
  616. break
  617. elif ch in BLOCKCLOSES:
  618. if DEBUG:
  619. print "include_forwards:: ch in BLOCKCLOSES, i:%d ch:%r" % (i, ch)
  620. break
  621. elif lastch_was_whitespace:
  622. # Two whitespace separated words
  623. if DEBUG:
  624. print "include_forwards:: ch separated by whitespace, i:%d ch:%r" % (i, ch)
  625. break
  626. # Move back to last valid char
  627. i -= 1
  628. if DEBUG:
  629. if i > pos:
  630. print "include_forwards:: Including chars from pos %d up to %d" % (pos, i)
  631. else:
  632. print "include_forwards:: No valid chars forward from pos %d, i now: %d" % (pos, i)
  633. except IndexError:
  634. # Nothing forwards, user what we have then
  635. i = min(i, accessor.length() - 1)
  636. if DEBUG:
  637. print "include_forwards:: No more buffer, i now: %d" % (i)
  638. ac.resetToPosition(i)
  639. ch = None
  640. try:
  641. while i >= 0:
  642. if ch == None and include_forwards:
  643. i, ch, style = ac.getCurrentPosCharStyle()
  644. else:
  645. i, ch, style = ac.getPrevPosCharStyle()
  646. if DEBUG:
  647. print "i now: %d, ch: %r" % (i, ch)
  648. if ch in WHITESPACE:
  649. if trg.type in ("use-namespace", "namespace-members"):
  650. # Namespaces cannot be split over whitespace.
  651. break
  652. while ch in WHITESPACE:
  653. # drop all whitespace
  654. next_char = ch
  655. i, ch, style = ac.getPrevPosCharStyle()
  656. if ch in WHITESPACE \
  657. or (ch == '\\' and next_char in EOL):
  658. if DEBUG:
  659. print "drop whitespace: %r" % ch
  660. # If there are two whitespace-separated words then this is
  661. # (likely or always?) a language keyword or declaration
  662. # construct at which we want to stop. E.g.
  663. # if foo<|> and ...
  664. # def foo<|>(...
  665. # if \foo<|>(... # uses a namespace
  666. if citdl_expr \
  667. and (_isident(citdl_expr[-1]) or citdl_expr[-1] == '\\') \
  668. and (_isident(ch) or _isdigit(ch)):
  669. if DEBUG:
  670. print "stop at (likely?) start of keyword or "\
  671. "declaration: %r" % ch
  672. break
  673. # Not whitespace anymore, move into the main checks below
  674. if DEBUG:
  675. print "Out of whitespace: i now: %d, ch: %s" % (i, ch)
  676. if style in skip_styles: # drop styles to ignore
  677. while i >= 0 and style in skip_styles:
  678. i, ch, style = ac.getPrevPosCharStyle()
  679. if DEBUG:
  680. print "drop char of style to ignore: %r" % ch
  681. elif ch in ":>" and i > 0:
  682. # Next char has to be ":" or "-" respectively
  683. prev_pos, prev_ch, prev_style = ac.getPrevPosCharStyle()
  684. if (ch == ">" and prev_ch == "-") or \
  685. (ch == ":" and prev_ch == ":"):
  686. citdl_expr.append(".")
  687. if DEBUG:
  688. print "Turning member accessor '%s%s' into '.'" % (prev_ch, ch)
  689. i -= 2
  690. else:
  691. if DEBUG:
  692. print "citdl_expr: %r" % (citdl_expr)
  693. print "stop at special stop-operator %d: %r" % (i, ch)
  694. break
  695. elif (ch in STOPOPS or ch in EXTRA_STOPOPS_PRECEDING_IDENT) and \
  696. (ch != ")" or (citdl_expr and citdl_expr[-1] != ".")):
  697. if ch == '$':
  698. # This may not be the end of the road, given static
  699. # variables are accessed through "Class::$static".
  700. prev_pos, prev_ch, prev_style = ac.peekPrevPosCharStyle()
  701. if prev_ch == ":":
  702. # Continue building up the citdl then.
  703. continue
  704. if DEBUG:
  705. print "citdl_expr: %r" % (citdl_expr)
  706. print "stop at stop-operator %d: %r" % (i, ch)
  707. break
  708. elif ch in BLOCKCLOSES:
  709. if DEBUG:
  710. print "found block at %d: %r" % (i, ch)
  711. citdl_expr.append(ch)
  712. BLOCKS = { # map block close char to block open char
  713. ')': '(',
  714. ']': '[',
  715. '}': '{',
  716. }
  717. stack = [] # stack of blocks: (<block close char>, <style>)
  718. stack.append( (ch, style, BLOCKS[ch], i) )
  719. while i >= 0:
  720. i, ch, style = ac.getPrevPosCharStyle()
  721. if DEBUG:
  722. print "finding matching brace: ch %r (%s), stack %r"\
  723. % (ch, ', '.join(buf.style_names_from_style_num(style)), stack)
  724. if ch in BLOCKS and style not in skip_styles:
  725. stack.append( (ch, style, BLOCKS[ch]) )
  726. elif ch == stack[-1][2] and style not in skip_styles:
  727. #XXX Replace the second test with the following
  728. # when LexPython+SilverCity styling bugs are fixed
  729. # (spurious 'stderr' problem):
  730. # and style == stack[-1][1]:
  731. stack.pop()
  732. if not stack:
  733. if DEBUG:
  734. print "jump to matching brace at %d: %r" % (i, ch)
  735. citdl_expr.append(ch)
  736. break
  737. else:
  738. # Didn't find the matching brace.
  739. if DEBUG:
  740. print "couldn't find matching brace"
  741. raise EvalError("could not find matching brace for "
  742. "'%s' at position %d"
  743. % (stack[-1][0], stack[-1][3]))
  744. else:
  745. if DEBUG:
  746. style_names = buf.style_names_from_style_num(style)
  747. print "add char: %r (%s)" % (ch, ', '.join(style_names))
  748. citdl_expr.append(ch)
  749. i -= 1
  750. except IndexError:
  751. # Nothing left to consume, return what we have
  752. pass
  753. # Remove any unecessary starting dots
  754. while citdl_expr and citdl_expr[-1] == ".":
  755. citdl_expr.pop()
  756. citdl_expr.reverse()
  757. citdl_expr = ''.join(citdl_expr)
  758. if DEBUG:
  759. print "return: %r" % citdl_expr
  760. print util.banner("done")
  761. return citdl_expr
  762. def citdl_expr_from_trg(self, buf, trg):
  763. """Return a PHP CITDL expression preceding the given trigger.
  764. The expression drops newlines, whitespace, and function call
  765. arguments -- basically any stuff that is not used by the codeintel
  766. database system for determining the resultant object type of the
  767. expression. For example (in which <|> represents the given position):
  768. GIVEN RETURN
  769. ----- ------
  770. foo-<|>> foo
  771. Foo:<|>: Foo
  772. foo(bar-<|>> bar
  773. foo(bar,blam)-<|>> foo()
  774. foo(bar, foo()
  775. blam)-<|>>
  776. foo(arg1, arg2)->bar-<|>> foo().bar
  777. Foo(arg1, arg2)::bar-<|>> Foo().bar
  778. Foo\bar:<|>: Foo\bar
  779. Foo\bar::bam-<|>> Foo\bar.bam
  780. Foo\bar(arg1, arg2)::bam-<|>> Foo\bar().bam
  781. """
  782. #DEBUG = True
  783. DEBUG = False
  784. if DEBUG:
  785. print util.banner("%s citdl_expr_from_trg @ %r" % (buf.lang, trg))
  786. if trg.form == TRG_FORM_CPLN:
  787. # "->" or "::"
  788. if trg.type == "classes":
  789. i = trg.pos + 1
  790. elif trg.type == "functions":
  791. i = trg.pos + 3 # 3-char trigger, skip over it
  792. elif trg.type in ("variables", "comment-variables"):
  793. i = trg.pos + 1 # triggered on the $, skip over it
  794. elif trg.type == "array-members":
  795. i = trg.extra.get("bracket_pos") # triggered on foo['
  796. elif trg.type == "use":
  797. i = trg.pos + 1
  798. elif trg.type == "namespace-members" or \
  799. trg.type == "use-namespace":
  800. i = trg.pos - 1
  801. else:
  802. i = trg.pos - 2 # skip past the trigger char
  803. return self._citdl_expr_from_pos(trg, buf, i, trg.implicit,
  804. DEBUG=DEBUG)
  805. elif trg.form == TRG_FORM_DEFN:
  806. return self.citdl_expr_under_pos(trg, buf, trg.pos, DEBUG)
  807. else: # trg.form == TRG_FORM_CALLTIP:
  808. # (<|>
  809. return self._citdl_expr_from_pos(trg, buf, trg.pos-1, trg.implicit,
  810. DEBUG=DEBUG)
  811. def citdl_expr_under_pos(self, trg, buf, pos, DEBUG=False):
  812. """Return a PHP CITDL expression around the given pos.
  813. Similar to citdl_expr_from_trg(), but looks forward to grab additional
  814. characters.
  815. GIVEN RETURN
  816. ----- ------
  817. foo-<|>> foo
  818. F<|>oo:: Foo
  819. foo->ba<|>r foo.bar
  820. f<|>oo->bar foo
  821. foo(bar-<|>> bar
  822. foo(bar,blam)-<|>> foo()
  823. foo(bar, foo()
  824. blam)-<|>>
  825. foo(arg1, arg2)->bar-<|>> foo().bar
  826. Foo(arg1, arg2)::ba<|>r-> Foo().bar
  827. Fo<|>o(arg1, arg2)::bar-> Foo
  828. """
  829. #DEBUG = True
  830. expr = self._citdl_expr_from_pos(trg, buf, pos-1, implicit=True,
  831. include_forwards=True, DEBUG=DEBUG)
  832. if expr:
  833. # Chop off any trailing "." characters
  834. return expr.rstrip(".")
  835. return expr
  836. def libs_from_buf(self, buf):
  837. env = buf.env
  838. # A buffer's libs depend on its env and the buf itself so
  839. # we cache it on the env and key off the buffer.
  840. if "php-buf-libs" not in env.cache:
  841. env.cache["php-buf-libs"] = weakref.WeakKeyDictionary()
  842. cache = env.cache["php-buf-libs"] # <buf-weak-ref> -> <libs>
  843. if buf not in cache:
  844. # - curdirlib
  845. # Using the dirname of this buffer isn't always right, but
  846. # hopefully is a good first approximation.
  847. cwd = dirname(buf.path)
  848. if cwd == "<Unsaved>":
  849. libs = []
  850. else:
  851. libs = [ self.mgr.db.get_lang_lib("PHP", "curdirlib", [cwd], "PHP")]
  852. libs += self._buf_indep_libs_from_env(env)
  853. cache[buf] = libs
  854. return cache[buf]
  855. def lpaths_from_blob(self, blob):
  856. """Return <lpaths> for this blob
  857. where,
  858. <lpaths> is a set of externally referencable lookup-paths, e.g.
  859. [("MyOwnClass",), ("MyOwnClass", "function1"), ...]
  860. """
  861. return set(lpath for child in blob
  862. for lpath in _walk_php_symbols(child))
  863. def _php_from_env(self, env):
  864. import which
  865. path = [d.strip()
  866. for d in env.get_envvar("PATH", "").split(os.pathsep)
  867. if d.strip()]
  868. for exe_name in ("php", "php4", "php-cgi", "php-cli"):
  869. try:
  870. return which.which(exe_name, path=path)
  871. except which.WhichError:
  872. pass
  873. return None
  874. def _php_info_from_php(self, php, env):
  875. """Call the given PHP and return:
  876. (<version>, <include_path>)
  877. Returns (None, []) if could not determine.
  878. """
  879. import process
  880. import tempfile
  881. # Use a marker to separate the start of output from possible
  882. # leading lines of PHP loading errors/logging.
  883. marker = "--- Start of Good Stuff ---"
  884. info_cmd = (r'<?php '
  885. + r'echo("%s\n");' % marker
  886. + r'echo(phpversion()."\n");'
  887. + r'echo(ini_get("include_path")."\n");'
  888. + r' ?>')
  889. argv = [php]
  890. envvars = env.get_all_envvars()
  891. php_ini_path = env.get_pref("phpConfigFile")
  892. if php_ini_path:
  893. envvars["PHPRC"] = php_ini_path
  894. fd, filepath = tempfile.mkstemp(suffix=".php")
  895. try:
  896. os.write(fd, info_cmd)
  897. os.close(fd)
  898. argv.append(filepath)
  899. p = process.ProcessOpen(argv, env=env.get_all_envvars())
  900. stdout, stderr = p.communicate()
  901. finally:
  902. os.remove(filepath)
  903. stdout_lines = stdout.splitlines(0)
  904. retval = p.returncode
  905. if retval:
  906. log.warn("failed to determine PHP info:\n"
  907. " path: %s\n"
  908. " retval: %s\n"
  909. " stdout:\n%s\n"
  910. " stderr:\n%s\n",
  911. php, retval, util.indent('\n'.join(stdout_lines)),
  912. util.indent(stderr))
  913. return None, []
  914. stdout_lines = stdout_lines[stdout_lines.index(marker)+1:]
  915. php_ver = stdout_lines[0]
  916. include_path = [p.strip() for p in stdout_lines[1].split(os.pathsep)
  917. if p.strip()]
  918. return php_ver, include_path
  919. def _extra_dirs_from_env(self, env):
  920. extra_dirs = set()
  921. include_project = env.get_pref("codeintel_scan_files_in_project", True)
  922. if include_project:
  923. proj_base_dir = env.get_proj_base_dir()
  924. if proj_base_dir is not None:
  925. extra_dirs.add(proj_base_dir) # Bug 68850.
  926. for pref in env.get_all_prefs("phpExtraPaths"):
  927. if not pref: continue
  928. extra_dirs.update(d.strip() for d in pref.split(os.pathsep)
  929. if exists(d.strip()))
  930. if extra_dirs:
  931. log.debug("PHP extra lib dirs: %r", extra_dirs)
  932. max_depth = env.get_pref("codeintel_max_recursive_dir_depth", 10)
  933. php_assocs = env.assoc_patterns_from_lang("PHP")
  934. extra_dirs = tuple(
  935. util.gen_dirs_under_dirs(extra_dirs,
  936. max_depth=max_depth,
  937. interesting_file_patterns=php_assocs)
  938. )
  939. else:
  940. extra_dirs = () # ensure retval is a tuple
  941. return extra_dirs
  942. def _buf_indep_libs_from_env(self, env):
  943. """Create the buffer-independent list of libs."""
  944. cache_key = "php-libs"
  945. if cache_key not in env.cache:
  946. env.add_pref_observer("php", self._invalidate_cache)
  947. env.add_pref_observer("phpExtraPaths",
  948. self._invalidate_cache_and_rescan_extra_dirs)
  949. env.add_pref_observer("phpConfigFile",
  950. self._invalidate_cache)
  951. env.add_pref_observer("codeintel_selected_catalogs",
  952. self._invalidate_cache)
  953. env.add_pref_observer("codeintel_max_recursive_dir_depth",
  954. self._invalidate_cache)
  955. env.add_pref_observer("codeintel_scan_files_in_project",
  956. self._invalidate_cache)
  957. # (Bug 68850) Both of these 'live_*' prefs on the *project*
  958. # prefset can result in a change of project base dir. It is
  959. # possible that we can false positives here if there is ever
  960. # a global pref of this name.
  961. env.add_pref_observer("import_live",
  962. self._invalidate_cache_and_rescan_extra_dirs)
  963. env.add_pref_observer("import_dirname",
  964. self._invalidate_cache_and_rescan_extra_dirs)
  965. db = self.mgr.db
  966. # Gather information about the current php.
  967. php = None
  968. if env.has_pref("php"):
  969. php = env.get_pref("php").strip() or None
  970. if not php or not exists(php):
  971. php = self._php_from_env(env)
  972. if not php:
  973. log.warn("no PHP was found from which to determine the "
  974. "import path")
  975. php_ver, include_path = None, []
  976. else:
  977. php_ver, include_path \
  978. = self._php_info_from_php(php, env)
  979. libs = []
  980. # - extradirslib
  981. extra_dirs = self._extra_dirs_from_env(env)
  982. if extra_dirs:
  983. libs.append( db.get_lang_lib("PHP", "extradirslib",
  984. extra_dirs, "PHP") )
  985. # - inilib (i.e. dirs in the include_path in PHP.ini)
  986. include_dirs = [d for d in include_path
  987. if d != '.' # handled separately
  988. if exists(d)]
  989. if include_dirs:
  990. max_depth = env.get_pref("codeintel_max_recursive_dir_depth", 10)
  991. php_assocs = env.assoc_patterns_from_lang("PHP")
  992. include_dirs = tuple(
  993. util.gen_dirs_under_dirs(include_dirs,
  994. max_depth=max_depth,
  995. interesting_file_patterns=php_assocs)
  996. )
  997. if include_dirs:
  998. libs.append( db.get_lang_lib("PHP", "inilib",
  999. include_dirs, "PHP") )
  1000. # Warn the user if there is a huge number of import dirs that
  1001. # might slow down completion.
  1002. all_dirs = list(extra_dirs) + list(include_dirs)
  1003. num_import_dirs = len(all_dirs)
  1004. if num_import_dirs > 100:
  1005. msg = "This buffer is configured with %d %s import dirs: " \
  1006. "this may result in poor completion performance" % \
  1007. (num_import_dirs, self.lang)
  1008. self.mgr.report_message(msg, "\n".join(all_dirs))
  1009. # - cataloglib, stdlib
  1010. catalog_selections = env.get_pref("codeintel_selected_catalogs")
  1011. libs += [
  1012. db.get_catalog_lib("PHP", catalog_selections),
  1013. db.get_stdlib("PHP", php_ver)
  1014. ]
  1015. env.cache[cache_key] = libs
  1016. return env.cache[cache_key]
  1017. def _invalidate_cache(self, env, pref_name):
  1018. for key in ("php-buf-libs", "php-libs"):
  1019. if key in env.cache:
  1020. log.debug("invalidate '%s' cache on %r", key, env)
  1021. del env.cache[key]
  1022. def _invalidate_cache_and_rescan_extra_dirs(self, env, pref_name):
  1023. self._invalidate_cache(env, pref_name)
  1024. extra_dirs = self._extra_dirs_from_env(env)
  1025. if extra_dirs:
  1026. extradirslib = self.mgr.db.get_lang_lib(
  1027. "PHP", "extradirslib", extra_dirs, "PHP")
  1028. request = PreloadLibRequest(extradirslib)
  1029. self.mgr.idxr.stage_request(request, 1.0)
  1030. #---- code browser integration
  1031. cb_import_group_title = "Includes and Requires"
  1032. def cb_import_data_from_elem(self, elem):
  1033. alias = elem.get("alias")
  1034. symbol = elem.get("symbol")
  1035. module = elem.get("module")
  1036. if alias is not None:
  1037. if symbol is not None:
  1038. name = "%s (%s\%s)" % (alias, module, symbol)
  1039. detail = "from %(module)s import %(symbol)s as %(alias)s" % locals()
  1040. else:
  1041. name = "%s (%s)" % (alias, module)
  1042. detail = "import %(module)s as %(alias)s" % locals()
  1043. elif symbol is not None:
  1044. if module == "\\":
  1045. name = '\\%s' % (symbol)
  1046. else:
  1047. name = '%s\\%s' % (module, symbol)
  1048. detail = "from %(module)s import %(symbol)s" % locals()
  1049. else:
  1050. name = module
  1051. detail = 'include "%s"' % module
  1052. return {"name": name, "detail": detail}
  1053. def cb_variable_data_from_elem(self, elem):
  1054. """Use the 'constant' image in the Code Browser for a variable constant.
  1055. """
  1056. data = CitadelLangIntel.cb_variable_data_from_elem(self, elem)
  1057. if elem.get("ilk") == "constant":
  1058. data["img"] = "constant"
  1059. return data
  1060. class PHPBuffer(UDLBuffer, XMLParsingBufferMixin):
  1061. lang = lang
  1062. m_lang = "HTML"
  1063. css_lang = "CSS"
  1064. csl_lang = "JavaScript"
  1065. ssl_lang = "PHP"
  1066. cb_show_if_empty = True
  1067. # Fillup chars for PHP: basically, any non-identifier char.
  1068. # - dropped '#' to prevent annoying behavior with $form['#foo']
  1069. # - dropped '@' I could find no common use of "@" following func/variable
  1070. # - dropped '$' It gets in the way of common usage: "$this->$"
  1071. # - dropped '\\' I could find no common use of "\" following func/variable
  1072. # - dropped '?' It gets in the way of common usage: "<?php "
  1073. # - dropped '/', it gets in the way of "</" closing an XML/HTML tag
  1074. # - dropped '!' It gets in the way of "<!" in XML/HTML tag (bug 78632)
  1075. # - dropped '=' It gets in the way of "<a href=" in XML/HTML cpln (bug 78632)
  1076. # - dropped ':' It gets in the way of "<a d:blah=" in XML/HTML cpln
  1077. # - dropped '>' It gets in the way of "<p>asdf</" in XML/HTML tag cpln (bug 80348)
  1078. cpln_fillup_chars = "~`%^&*()-+{}[]|;'\",.< "
  1079. #TODO: c.f. cpln_stop_chars stuff in lang_html.py
  1080. # - dropping '[' because need for "<!<|>" -> "<![CDATA[" cpln
  1081. # - dropping '#' because we need it for $form['#foo']
  1082. # - dropping '$' because: MyClass::$class_var
  1083. # - dropping '-' because causes problem with CSS (bug 78312)
  1084. # - dropping '!' because causes problem with CSS "!important" (bug 78312)
  1085. cpln_stop_chars = "~`@%^&*()=+{}]|\\;:'\",.<>?/ "
  1086. def __init__(self, *args, **kwargs):
  1087. super(PHPBuffer, self).__init__(*args, **kwargs)
  1088. if isinstance(self.accessor, KoDocumentAccessor):
  1089. # Encourage the database to pre-scan dirs relevant to completion
  1090. # for this buffer -- because of recursive-dir-include-everything
  1091. # semantics for PHP this first-time scan can take a while.
  1092. request = PreloadBufLibsRequest(self)
  1093. self.mgr.idxr.stage_request(request, 1.0)
  1094. @property
  1095. def libs(self):
  1096. return self.langintel.libs_from_buf(self)
  1097. @property
  1098. def stdlib(self):
  1099. return self.libs[-1]
  1100. class PHPImportHandler(ImportHandler):
  1101. sep = '/'
  1102. def setCorePath(self, compiler=None, extra=None):
  1103. #XXX To do this independent of Komodo this would need to do all
  1104. # the garbage that koIPHPInfoEx is doing to determine this. It
  1105. # might also require adding a "rcfile" argument to this method
  1106. # so the proper php.ini file is used in the "_shellOutForPath".
  1107. # This is not crucial now because koCodeIntel._Manager() handles
  1108. # this for us.
  1109. if not self.corePath:
  1110. raise CodeIntelError("Do not know how to determine the core "
  1111. "PHP include path. 'corePath' must be set "
  1112. "manually.")
  1113. def _findScannableFiles(self, (files, searchedDirs), dirname, names):
  1114. if sys.platform.startswith("win"):
  1115. cpath = dirname.lower()
  1116. else:
  1117. cpath = dirname
  1118. if cpath in searchedDirs:
  1119. while names:
  1120. del names[0]
  1121. return
  1122. else:
  1123. searchedDirs[cpath] = 1
  1124. for i in range(len(names)-1, -1, -1): # backward so can del from list
  1125. path = os.path.join(dirname, names[i])
  1126. if os.path.isdir(path):
  1127. pass
  1128. elif os.path.splitext(names[i])[1] in (".php", ".inc",
  1129. ".module", ".tpl"):
  1130. #XXX The list of extensions should be settable on
  1131. # the ImportHandler and Komodo should set whatever is
  1132. # set in prefs. ".module" and ".tpl" are for
  1133. # drupal-nerds until CodeIntel gets this right.
  1134. #XXX This check for files should probably include
  1135. # scripts, which might likely not have the
  1136. # extension: need to grow filetype-from-content smarts.
  1137. files.append(path)
  1138. def find_importables_in_dir(self, dir):
  1139. """See citadel.py::ImportHandler.find_importables_in_dir() for
  1140. details.
  1141. Importables for PHP look like this:
  1142. {"foo.php": ("foo.php", None, False),
  1143. "bar.inc": ("bar.inc", None, False),
  1144. "somedir": (None, None, True)}
  1145. TODO: log the fs-stat'ing a la codeintel.db logging.
  1146. """
  1147. from os.path import join, isdir
  1148. from fnmatch import fnmatch
  1149. if dir == "<Unsaved>":
  1150. #TODO: stop these getting in here.
  1151. return {}
  1152. try:
  1153. names = os.listdir(dir)
  1154. except OSError, ex:
  1155. return {}
  1156. dirs, nondirs = set(), set()
  1157. for name in names:
  1158. try:
  1159. if isdir(join(dir, name)):
  1160. dirs.add(name)
  1161. else:
  1162. nondirs.add(name)
  1163. except UnicodeDecodeError:
  1164. # Hit a filename that cannot be encoded in the default encoding.
  1165. # Just skip it. (Bug 82268)
  1166. pass
  1167. importables = {}
  1168. patterns = self.mgr.env.assoc_patterns_from_lang("PHP")
  1169. for name in nondirs:
  1170. for pattern in patterns:
  1171. if fnmatch(name, pattern):
  1172. break
  1173. else:
  1174. continue
  1175. if name in dirs:
  1176. importables[name] = (name, None, True)
  1177. dirs.remove(name)
  1178. else:
  1179. importables[name] = (name, None, False)
  1180. for name in dirs:
  1181. importables[name] = (None, None, True)
  1182. return importables
  1183. class PHPCILEDriver(UDLCILEDriver):
  1184. lang = lang
  1185. ssl_lang = "PHP"
  1186. csl_lang = "JavaScript"
  1187. def scan_multilang(self, buf, csl_cile_driver=None):
  1188. #try:
  1189. """Scan the given multilang (UDL-based) buffer and return a CIX
  1190. element tree.
  1191. "buf" is the multi-lang Buffer instance (e.g.
  1192. lang_rhtml.RHTMLBuffer for RHTML).
  1193. "csl_cile_driver" (optional) is the CSL (client-side language)
  1194. CILE driver. While scanning, CSL tokens should be gathered and,
  1195. if any, passed to the CSL scanner like this:
  1196. csl_cile_driver.scan_csl_tokens(
  1197. file_elem, blob_name, csl_tokens)
  1198. The CSL scanner will append a CIX <scope ilk="blob"> element
  1199. to the <file> element.
  1200. """
  1201. # Create the CIX tree.
  1202. mtime = "XXX"
  1203. fullpath = buf.path
  1204. cixtree = createCixRoot()
  1205. cixfile = createCixFile(cixtree, fullpath, lang=buf.lang)
  1206. if sys.platform.startswith("win"):
  1207. fullpath = fullpath.replace('\\', '/')
  1208. basepath = os.path.basename(fullpath)
  1209. cixblob = createCixModule(cixfile, basepath, "PHP", src=fullpath)
  1210. phpciler = PHPParser(fullpath, buf.accessor.text, mtime)
  1211. csl_tokens = phpciler.scan_multilang_content(buf.accessor.text)
  1212. phpciler.convertToElementTreeModule(cixblob)
  1213. # Hand off the csl tokens if any
  1214. if csl_cile_driver and csl_tokens:
  1215. csl_cile_driver.scan_csl_tokens(cixfile, basepath, csl_tokens)
  1216. return cixtree
  1217. #except Exception, e:
  1218. # print "\nPHP cile exception"
  1219. # import traceback
  1220. # traceback.print_exc()
  1221. # print
  1222. # raise
  1223. #---- internal routines and classes
  1224. # States used by PHP scanner when parsing information
  1225. S_DEFAULT = 0
  1226. S_IN_ARGS = 1
  1227. S_IN_ASSIGNMENT = 2
  1228. S_IGNORE_SCOPE = 3
  1229. S_OBJECT_ARGUMENT = 4
  1230. S_GET_HEREDOC_MARKER = 5
  1231. S_IN_HEREDOC = 6
  1232. S_TRAIT_RESOLUTION = 7
  1233. # Special tags for multilang handling (i.e. through UDL)
  1234. S_OPEN_TAG = 10
  1235. S_CHECK_CLOSE_TAG = 11
  1236. S_IN_SCRIPT = 12
  1237. # Types used by JavaScriptScanner when parsing information
  1238. TYPE_NONE = 0
  1239. TYPE_FUNCTION = 1
  1240. TYPE_VARIABLE = 2
  1241. TYPE_GETTER = 3
  1242. TYPE_SETTER = 4
  1243. TYPE_MEMBER = 5
  1244. TYPE_OBJECT = 6
  1245. TYPE_CLASS = 7
  1246. TYPE_PARENT = 8
  1247. def _sortByLineCmp(val1, val2):
  1248. try:
  1249. #if hasattr(val1, "line") and hasattr(val2, "line"):
  1250. return cmp(val1.linestart, val2.linestart)
  1251. except AttributeError:
  1252. return cmp(val1, val2)
  1253. def sortByLine(seq):
  1254. seq.sort(_sortByLineCmp)
  1255. return seq
  1256. class PHPArg:
  1257. def __init__(self, name, citdl=None, signature=None, default=None):
  1258. """Set details for a function argument"""
  1259. self.name = name
  1260. self.citdl = citdl
  1261. if signature:
  1262. self.signature = signature
  1263. else:
  1264. if citdl:
  1265. self.signature = "%s $%s" % (citdl, name)
  1266. else:
  1267. self.signature = "$%s" % (name, )
  1268. self.default = default
  1269. def __repr__(self):
  1270. return self.signature
  1271. def updateCitdl(self, citdl):
  1272. self.citdl = citdl
  1273. if self.signature.startswith("$") or self.signature.startswith("&") or \
  1274. " " not in self.signature:
  1275. self.signature = "%s %s" % (citdl, self.signature)
  1276. else:
  1277. self.signature = "%s %s" % (citdl, self.signature.split(" ", 1)[1])
  1278. def toElementTree(self, cixelement):
  1279. cixarg = addCixArgument(cixelement, self.name, argtype=self.citdl)
  1280. if self.default:
  1281. cixarg.attrib["default"] = self.default
  1282. class PHPVariable:
  1283. # PHPDoc variable type sniffer.
  1284. _re_var = re.compile(r'^\s*@var\s+(\$(?P<variable>\w+)\s+)?(?P<type>[\w\\]+)(?:\s+(?P<doc>.*?))?', re.M|re.U)
  1285. _ignored_php_types = ("object", "mixed")
  1286. def __init__(self, name, line, vartype='', attributes='', doc=None,
  1287. fromPHPDoc=False, namespace=None):
  1288. self.name = name
  1289. self.types = [(line, vartype, fromPHPDoc)]
  1290. self.linestart = line
  1291. if attributes:
  1292. if not isinstance(attributes, list):
  1293. attributes = attributes.strip().split()
  1294. self.attributes = ' '.join(attributes)
  1295. else:
  1296. self.attributes = None
  1297. self.doc = doc
  1298. self.created_namespace = None
  1299. if namespace:
  1300. self.created_namespace = namespace.name
  1301. def addType(self, line, type, fromPHPDoc=False):
  1302. self.types.append((line, type, fromPHPDoc))
  1303. def __repr__(self):
  1304. return "var %s line %s type %s attributes %s\n"\
  1305. % (self.name, self.linestart, self.types, self.attributes)
  1306. def toElementTree(self, cixblob):
  1307. # Work out the best vartype
  1308. vartype = None
  1309. doc = None
  1310. if self.doc:
  1311. # We are only storing the doc string for cases where we have an
  1312. # "@var" phpdoc tag, we should actually store the docs anyway, but
  1313. # we don't yet have a clean way to ensure the doc is really meant
  1314. # for this specific variable (i.e. the comment was ten lines before
  1315. # the variable definition).
  1316. if "@var" in self.doc:
  1317. doc = uncommentDocString(self.doc)
  1318. # get the variable citdl type set by "@var"
  1319. all_matches = re.findall(self._re_var, doc)
  1320. if len(all_matches) >= 1:
  1321. #print "all_matches[0]: %r" % (all_matches[0], )
  1322. vartype = all_matches[0][2]
  1323. if vartype and vartype.lower() in self._ignored_php_types:
  1324. # Ignore these PHP types, they don't help codeintel.
  1325. # http://bugs.activestate.com/show_bug.cgi?id=77602
  1326. vartype = None
  1327. if not vartype and self.types:
  1328. d = {}
  1329. max_count = 0
  1330. for line, vtype, fromPHPDoc in self.types:
  1331. if vtype:
  1332. if fromPHPDoc:
  1333. if vtype.lower() in self._ignored_php_types:
  1334. # Ignore these PHP types, they don't help codeintel.
  1335. continue
  1336. # The doc gets priority.
  1337. vartype = vtype
  1338. break
  1339. count = d.get(vtype, 0) + 1
  1340. d[vtype] = count
  1341. if count > max_count:
  1342. # Best found so far
  1343. vartype = vtype
  1344. max_count = count
  1345. cixelement = createCixVariable(cixblob, self.name, vartype=vartype,
  1346. attributes=self.attributes)
  1347. if doc:
  1348. setCixDoc(cixelement, doc)
  1349. cixelement.attrib["line"] = str(self.linestart)
  1350. if self.created_namespace:
  1351. # Need to remember that the object was created in a namespace, so
  1352. # that the starting lookup scope can start in the given namespace.
  1353. cixelement.attrib["namespace"] = self.created_namespace
  1354. return cixelement
  1355. class PHPConstant(PHPVariable):
  1356. def __init__(self, name, line, vartype=''):
  1357. PHPVariable.__init__(self, name, line, vartype)
  1358. def __repr__(self):
  1359. return "constant %s line %s type %s\n"\
  1360. % (self.name, self.linestart, self.types)
  1361. def toElementTree(self, cixblob):
  1362. cixelement = PHPVariable.toElementTree(self, cixblob)
  1363. cixelement.attrib["ilk"] = "constant"
  1364. return cixelement
  1365. class PHPFunction:
  1366. def __init__(self, funcname, phpArgs, lineno, depth=0,
  1367. attributes=None, doc=None, classname='', classparent='',
  1368. returnType=None, returnByRef=False):
  1369. self.name = funcname
  1370. self.args = phpArgs
  1371. self.linestart = lineno
  1372. self.lineend = None
  1373. self.depth = depth
  1374. self.classname = classname
  1375. self.classparent = classparent
  1376. self.returnType = returnType
  1377. self.returnByRef = returnByRef
  1378. self.variables = {} # all variables used in class
  1379. # build the signature before we add any attributes that are not part
  1380. # of the signature
  1381. if returnByRef:
  1382. self.signature = '&%s' % (self.name)
  1383. else:
  1384. self.signature = '%s' % (self.name)
  1385. if attributes:
  1386. attrs = ' '.join(attributes)
  1387. self.shortSig = '%s %s' % (attrs, self.name)
  1388. else:
  1389. self.shortSig = self.name
  1390. # both php 4 and 5 constructor methods
  1391. if funcname == '__construct' or (classname and funcname.lower() == classname.lower()):
  1392. attributes.append('__ctor__')
  1393. # if we add destructor attributes...
  1394. # elif funcname == '__destruct':
  1395. # attributes += ['__dtor__']
  1396. self.attributes = attributes and ' '.join(attributes) or ''
  1397. self.doc = None
  1398. if doc:
  1399. if isinstance(doc, list):
  1400. doc = "".join(doc)
  1401. docinfo = parseDocString(doc)
  1402. self.doc = docinfo[0]
  1403. # See if there are any PHPDoc arguments defined in the docstring.
  1404. if docinfo[1]:
  1405. for argInfo in docinfo[1]:
  1406. for phpArg in self.args:
  1407. if phpArg.name == argInfo[1]:
  1408. phpArg.updateCitdl(argInfo[0])
  1409. break
  1410. else:
  1411. self.args.append(PHPArg(argInfo[1], citdl=argInfo[0]))
  1412. if docinfo[2]:
  1413. self.returnType = docinfo[2][0]
  1414. if self.returnType:
  1415. self.signature = '%s %s' % (self.returnType, self.signature, )
  1416. self.signature += "("
  1417. if self.args:
  1418. self.signature += ", ".join([x.signature for x in self.args])
  1419. self.signature += ")"
  1420. def addReturnType(self, returnType):
  1421. if self.returnType is None:
  1422. self.returnType = returnType
  1423. def __str__(self):
  1424. return self.signature
  1425. # The following is busted and outputting multiple lines from __str__
  1426. # and __repr__ is bad form: make debugging prints hard.
  1427. #if self.doc:
  1428. # if self.args:
  1429. # return "%s(%s)\n%s" % (self.shortSig, self.args.argline, self.doc)
  1430. # else:
  1431. # return "%s()\n%s" % (self.shortSig, self.doc)
  1432. #return "%s(%s)" % (self.shortSig, self.argline)
  1433. def __repr__(self):
  1434. return self.signature
  1435. def hasArgumentWithName(self, name):
  1436. if self.args:
  1437. for phpArg in self.args:
  1438. if phpArg.name == name:
  1439. return True
  1440. return False
  1441. def toElementTree(self, cixblob):
  1442. cixelement = createCixFunction(cixblob, self.name,
  1443. attributes=self.attributes)
  1444. cixelement.attrib["line"] = str(self.linestart)
  1445. if self.lineend is not None:
  1446. cixelement.attrib['lineend'] = str(self.lineend)
  1447. setCixSignature(cixelement, self.signature)
  1448. if self.doc:
  1449. setCixDoc(cixelement, self.doc)
  1450. if self.args:
  1451. for phpArg in self.args:
  1452. phpArg.toElementTree(cixelement)
  1453. if self.returnType:
  1454. addCixReturns(cixelement, self.returnType)
  1455. # Add a "this" and "self" member for class functions
  1456. #if self.classname:
  1457. # createCixVariable(cixelement, "this", vartype=self.classname)
  1458. # createCixVariable(cixelement, "self", vartype=self.classname)
  1459. # Add a "parent" member for class functions that have a parent
  1460. #if self.classparent:
  1461. # createCixVariable(cixelement, "parent", vartype=self.classparent)
  1462. # XXX for variables inside functions
  1463. for v in self.variables.values():
  1464. v.toElementTree(cixelement)
  1465. class PHPInterface:
  1466. def __init__(self, name, extends, lineno, depth, doc=None):
  1467. self.name = name
  1468. self.extends = extends
  1469. self.linestart = lineno
  1470. self.lineend = None
  1471. self.depth = depth
  1472. self.constants = {} # declared class constants
  1473. self.members = {} # declared class variables
  1474. self.variables = {} # all variables used in class
  1475. self.functions = {}
  1476. self.doc = None
  1477. if doc:
  1478. self.doc = uncommentDocString(doc)
  1479. def __repr__(self):
  1480. # dump our contents to human readable form
  1481. r = "INTERFACE %s" % self.name
  1482. if self.extends:
  1483. r += " EXTENDS %s" % self.extends
  1484. r += '\n'
  1485. if self.constants:
  1486. r += "Constants:\n"
  1487. for m in self.constants:
  1488. r += " var %s line %s\n" % (m, self.constants[m])
  1489. if self.members:
  1490. r += "Members:\n"
  1491. for m in self.members:
  1492. r += " var %s line %s\n" % (m, self.members[m])
  1493. if self.functions:
  1494. r += "functions:\n"
  1495. for f in self.functions.values():
  1496. r += " %r" % f
  1497. if self.variables:
  1498. r += "variables:\n"
  1499. for v in self.variables.values():
  1500. r += " %r" % v
  1501. return r + '\n'
  1502. def toElementTree(self, cixblob):
  1503. cixelement = createCixInterface(cixblob, self.name)
  1504. cixelement.attrib["line"] = str(self.linestart)
  1505. if self.lineend is not None:
  1506. cixelement.attrib["lineend"] = str(self.lineend)
  1507. signature = "%s" % (self.name)
  1508. if self.extends:
  1509. signature += " extends %s" % (self.extends)
  1510. for name in self.extends.split(","):
  1511. addInterfaceRef(cixelement, name.strip())
  1512. #SubElement(cixelement, "classref", name=self.extends)
  1513. cixelement.attrib["signature"] = signature
  1514. if self.doc:
  1515. setCixDoc(self.doc)
  1516. allValues = self.functions.values() + self.constants.values() + \
  1517. self.members.values() + self.variables.values()
  1518. for v in sortByLine(allValues):
  1519. v.toElementTree(cixelement)
  1520. class PHPClass:
  1521. cixtype = "CLASS"
  1522. # PHPDoc magic property sniffer.
  1523. _re_magic_property = re.compile(r'^\s*@property(-(?P<type>read|write))?\s+((?P<citdl>[\w\\]+)\s+)?(?P<name>\$\w+)(?:\s+(?P<doc>.*?))?', re.M|re.U)
  1524. _re_magic_method = re.compile(r'^\s*@method\s+((?P<citdl>[\w\\]+)\s+)?(?P<name>\w+)(\(\))?(?P<doc>.*?)$', re.M|re.U)
  1525. def __init__(self, name, extends, lineno, depth, attributes=None,
  1526. interfaces=None, doc=None):
  1527. self.name = name
  1528. self.extends = extends
  1529. self.linestart = lineno
  1530. self.lineend = None
  1531. self.depth = depth
  1532. self.constants = {} # declared class constants
  1533. self.members = {} # declared class variables
  1534. self.variables = {} # all variables used in class
  1535. self.functions = {}
  1536. self.traits = {}
  1537. self.traitOverrides = {}
  1538. if interfaces:
  1539. self.interfaces = interfaces.split(',')
  1540. else:
  1541. self.interfaces = []
  1542. if attributes:
  1543. self.attributes = ' '.join(attributes)
  1544. else:
  1545. self.attributes = None
  1546. self.doc = None
  1547. if doc:
  1548. if isinstance(doc, list):
  1549. doc = "".join(doc)
  1550. self.doc = uncommentDocString(doc)
  1551. if self.doc.find("@property") >= 0:
  1552. all_matches = re.findall(self._re_magic_property, self.doc)
  1553. for match in all_matches:
  1554. varname = match[4][1:] # skip "$" in the name.
  1555. v = PHPVariable(varname, lineno, match[3], doc=match[5])
  1556. self.members[varname] = v
  1557. if self.doc.find("@method") >= 0:
  1558. all_matches = re.findall(self._re_magic_method, self.doc)
  1559. for match in all_matches:
  1560. citdl = match[1] or None
  1561. fnname = match[2]
  1562. fndoc = match[4]
  1563. phpArgs = []
  1564. fn = PHPFunction(fnname, phpArgs, lineno, depth=self.depth+1,
  1565. doc=fndoc, returnType=citdl)
  1566. self.functions[fnname] = fn
  1567. def __repr__(self):
  1568. # dump our contents to human readable form
  1569. r = "%s %s" % (self.cixtype, self.name)
  1570. if self.extends:
  1571. r += " EXTENDS %s" % self.extends
  1572. r += '\n'
  1573. if self.constants:
  1574. r += "Constants:\n"
  1575. for m in self.constants:
  1576. r += " var %s line %s\n" % (m, self.constants[m])
  1577. if self.members:
  1578. r += "Members:\n"
  1579. for m in self.members:
  1580. r += " var %s line %s\n" % (m, self.members[m])
  1581. if self.functions:
  1582. r += "functions:\n"
  1583. for f in self.functions.values():
  1584. r += " %r" % f
  1585. if self.variables:
  1586. r += "variables:\n"
  1587. for v in self.variables.values():
  1588. r += " %r" % v
  1589. if self.traits:
  1590. r += "traits:\n"
  1591. for k, v in self.traits.items():
  1592. r += " %r" % k
  1593. if self.traitOverrides:
  1594. r += "trait overrides:\n"
  1595. for k, v in self.traitOverrides.items():
  1596. r += " %r, %r" % (k, v)
  1597. return r + '\n'
  1598. def addTraitReference(self, name):
  1599. self.traits[name] = []
  1600. def addTraitOverride(self, namelist, alias, visibility=None, insteadOf=False):
  1601. self.traitOverrides[".".join(namelist)] = (alias, visibility, insteadOf)
  1602. def _toElementTree(self, cixblob, cixelement):
  1603. cixelement.attrib["line"] = str(self.linestart)
  1604. if self.lineend is not None:
  1605. cixelement.attrib["lineend"] = str(self.lineend)
  1606. if self.attributes:
  1607. cixelement.attrib["attributes"] = self.attributes
  1608. if self.doc:
  1609. setCixDoc(cixelement, self.doc)
  1610. if self.extends:
  1611. addClassRef(cixelement, self.extends)
  1612. if self.traits:
  1613. cixelement.attrib["traitrefs"] = " ".join(self.traits)
  1614. for citdl, data in self.traitOverrides.items():
  1615. alias, vis, insteadOf = data
  1616. if alias and not insteadOf:
  1617. name = alias
  1618. else:
  1619. name = citdl.split(".")[-1]
  1620. override_elem = SubElement(cixelement, "alias", name=name, citdl=citdl)
  1621. if insteadOf:
  1622. override_elem.attrib["insteadof"] = alias
  1623. if vis:
  1624. override_elem.attrib["attributes"] = vis
  1625. for i in self.interfaces:
  1626. addInterfaceRef(cixelement, i.strip())
  1627. allValues = self.functions.values() + self.constants.values() + \
  1628. self.members.values() + self.variables.values()
  1629. for v in sortByLine(allValues):
  1630. v.toElementTree(cixelement)
  1631. def toElementTree(self, cixblob):
  1632. cixelement = createCixClass(cixblob, self.name)
  1633. self._toElementTree(cixblob, cixelement)
  1634. class PHPTrait(PHPClass):
  1635. cixtype = "TRAIT"
  1636. def toElementTree(self, cixblob):
  1637. cixelement = SubElement(cixblob, "scope", ilk="trait", name=self.name)
  1638. self._toElementTree(cixblob, cixelement)
  1639. class PHPImport:
  1640. def __init__(self, name, lineno, alias=None, symbol=None):
  1641. self.name = name
  1642. self.lineno = lineno
  1643. self.alias = alias
  1644. self.symbol = symbol
  1645. def __repr__(self):
  1646. # dump our contents to human readable form
  1647. if self.alias:
  1648. return "IMPORT %s as %s\n" % (self.name, self.alias)
  1649. else:
  1650. return "IMPORT %s\n" % self.name
  1651. def toElementTree(self, cixmodule):
  1652. elem = SubElement(cixmodule, "import", module=self.name, line=str(self.lineno))
  1653. if self.alias:
  1654. elem.attrib["alias"] = self.alias
  1655. if self.symbol:
  1656. elem.attrib["symbol"] = self.symbol
  1657. return elem
  1658. def qualifyNamespacePath(namespace_path):
  1659. # Ensure the namespace does not begin or end with a backslash.
  1660. return namespace_path.strip("\\")
  1661. class PHPNamespace:
  1662. def __init__(self, name, lineno, depth, doc=None):
  1663. assert not name.startswith("\\")
  1664. assert not name.endswith("\\")
  1665. self.name = name
  1666. self.linestart = lineno
  1667. self.lineend = None
  1668. self.depth = depth
  1669. self.doc = None
  1670. if doc:
  1671. self.doc = uncommentDocString(doc)
  1672. self.functions = {} # functions declared in file
  1673. self.classes = {} # classes declared in file
  1674. self.constants = {} # all constants used in file
  1675. self.interfaces = {} # interfaces declared in file
  1676. self.includes = [] # imported files/namespaces
  1677. def __repr__(self):
  1678. # dump our contents to human readable form
  1679. r = "NAMESPACE %s\n" % self.name
  1680. for v in self.includes:
  1681. r += " %r" % v
  1682. r += "constants:\n"
  1683. for v in self.constants.values():
  1684. r += " %r" % v
  1685. r += "interfaces:\n"
  1686. for v in self.interfaces.values():
  1687. r += " %r" % v
  1688. r += "functions:\n"
  1689. for f in self.functions.values():
  1690. r += " %r" % f
  1691. r += "classes:\n"
  1692. for c in self.classes.values():
  1693. r += repr(c)
  1694. return r + '\n'
  1695. def toElementTree(self, cixblob):
  1696. cixelement = createCixNamespace(cixblob, self.name)
  1697. cixelement.attrib["line"] = str(self.linestart)
  1698. if self.lineend is not None:
  1699. cixelement.attrib["lineend"] = str(self.lineend)
  1700. if self.doc:
  1701. setCixDoc(cixelement, self.doc)
  1702. for v in self.includes:
  1703. v.toElementTree(cixelement)
  1704. allValues = self.functions.values() + self.constants.values() + \
  1705. self.interfaces.values() + self.classes.values()
  1706. for v in sortByLine(allValues):
  1707. v.toElementTree(cixelement)
  1708. class PHPFile:
  1709. """CIX specifies that a <file> tag have zero or more
  1710. <scope ilk="blob"> children. In PHP this is a one-to-one
  1711. relationship, so this class represents both (and emits the XML tags
  1712. for both).
  1713. """
  1714. def __init__(self, filename, content=None, mtime=None):
  1715. self.filename = filename
  1716. self.content = content
  1717. self.mtime = mtime
  1718. self.error = None
  1719. self.content = content
  1720. if mtime is None:
  1721. self.mtime = int(time.time())
  1722. self.functions = {} # functions declared in file
  1723. self.classes = {} # classes declared in file
  1724. self.variables = {} # all variables used in file
  1725. self.constants = {} # all constants used in file
  1726. self.includes = [] # imported files/namespaces
  1727. self.interfaces = {} # interfaces declared in file
  1728. self.namespaces = {} # namespaces declared in file
  1729. def __repr__(self):
  1730. # dump our contents to human readable form
  1731. r = "FILE %s\n" % self.filename
  1732. for v in self.includes:
  1733. r += " %r" % v
  1734. r += "constants:\n"
  1735. for v in self.constants.values():
  1736. r += " %r" % v
  1737. r += "interfaces:\n"
  1738. for v in self.interfaces.values():
  1739. r += " %r" % v
  1740. r += "functions:\n"
  1741. for f in self.functions.values():
  1742. r += " %r" % f
  1743. r += "variables:\n"
  1744. for v in self.variables.values():
  1745. r += " %r" % v
  1746. r += "classes:\n"
  1747. for c in self.classes.values():
  1748. r += repr(c)
  1749. r += "namespaces:\n"
  1750. for v in self.namespaces.values():
  1751. r += " %r" % v
  1752. return r + '\n'
  1753. def convertToElementTreeModule(self, cixmodule):
  1754. for v in self.includes:
  1755. v.toElementTree(cixmodule)
  1756. allValues = self.constants.values() + self.functions.values() + \
  1757. self.interfaces.values() + self.variables.values() + \
  1758. self.classes.values() + self.namespaces.values()
  1759. for v in sortByLine(allValues):
  1760. v.toElementTree(cixmodule)
  1761. def convertToElementTreeFile(self, cix):
  1762. if sys.platform.startswith("win"):
  1763. path = self.filename.replace('\\', '/')
  1764. else:
  1765. path = self.filename
  1766. cixfile = createCixFile(cix, path, lang="PHP", mtime=str(self.mtime))
  1767. if self.error:
  1768. cixfile.attrib["error"] = self.error
  1769. cixmodule = createCixModule(cixfile, os.path.basename(self.filename),
  1770. "PHP")
  1771. self.convertToElementTreeModule(cixmodule)
  1772. class PHPcile:
  1773. def __init__(self):
  1774. # filesparsed contains all files parsed
  1775. self.filesparsed={}
  1776. def clear(self, filename):
  1777. # clear include links from the cache
  1778. if filename not in self.filesparsed:
  1779. return
  1780. del self.filesparsed[filename]
  1781. def __repr__(self):
  1782. r = ''
  1783. for f in self.filesparsed:
  1784. r += repr(self.filesparsed[f])
  1785. return r + '\n'
  1786. #def toElementTree(self, cix):
  1787. # for f in self.filesparsed.values():
  1788. # f.toElementTree(cix)
  1789. def convertToElementTreeModule(self, cixmodule):
  1790. for f in self.filesparsed.values():
  1791. f.convertToElementTreeModule(cixmodule)
  1792. def convertToElementTreeFile(self, cix):
  1793. for f in self.filesparsed.values():
  1794. f.convertToElementTreeFile(cix)
  1795. class PHPParser:
  1796. PHP_COMMENT_STYLES = (SCE_UDL_SSL_COMMENT, SCE_UDL_SSL_COMMENTBLOCK)
  1797. # lastText, lastStyle are use to remember the previous tokens.
  1798. lastText = None
  1799. lastStyle = None
  1800. def __init__(self, filename, content=None, mtime=None):
  1801. self.filename = filename
  1802. self.cile = PHPcile()
  1803. self.fileinfo = PHPFile(self.filename, content, mtime)
  1804. # Working variables, used in conjunction with state
  1805. self.classStack = []
  1806. self.currentClass = None
  1807. self.currentNamespace = None
  1808. self.currentFunction = None
  1809. self.csl_tokens = []
  1810. self.lineno = 0
  1811. self.depth = 0
  1812. self.styles = []
  1813. self.linenos = []
  1814. self.text = []
  1815. self.comment = None
  1816. self.comments = []
  1817. self.heredocMarker = None
  1818. # state : used to store the current JS lexing state
  1819. # return_to_state : used to store JS state to return to
  1820. # multilang_state : used to store the current UDL lexing state
  1821. self.state = S_DEFAULT
  1822. self.return_to_state = S_DEFAULT
  1823. self.multilang_state = S_DEFAULT
  1824. self.PHP_WORD = SCE_UDL_SSL_WORD
  1825. self.PHP_IDENTIFIER = SCE_UDL_SSL_IDENTIFIER
  1826. self.PHP_VARIABLE = SCE_UDL_SSL_VARIABLE
  1827. self.PHP_OPERATOR = SCE_UDL_SSL_OPERATOR
  1828. self.PHP_STRINGS = (SCE_UDL_SSL_STRING,)
  1829. self.PHP_NUMBER = SCE_UDL_SSL_NUMBER
  1830. # XXX bug 44775
  1831. # having the next line after scanData below causes a crash on osx
  1832. # in python's UCS2 to UTF8. leaving this here for later
  1833. # investigation, see bug 45362 for details.
  1834. self.cile.filesparsed[self.filename] = self.fileinfo
  1835. # parses included files
  1836. def include_file(self, filename):
  1837. # XXX Very simple prevention of include looping. Really should
  1838. # recurse the indices to make sure we are not creating a loop
  1839. if self.filename == filename:
  1840. return ""
  1841. # add the included file to our list of included files
  1842. self.fileinfo.includes.append(PHPImport(filename, self.lineno))
  1843. def incBlock(self):
  1844. self.depth = self.depth+1
  1845. # log.debug("depth at %d", self.depth)
  1846. def decBlock(self):
  1847. self.depth = self.depth-1
  1848. # log.debug("depth at %d", self.depth)
  1849. if self.currentClass and self.currentClass.depth == self.depth:
  1850. # log.debug("done with class %s at depth %d", self.currentClass.name, self.depth)
  1851. self.currentClass.lineend = self.lineno
  1852. log.debug("done with %s %s at depth %r",
  1853. isinstance(self.currentClass, PHPInterface) and "interface" or "class",
  1854. self.currentClass.name, self.depth)
  1855. self.currentClass = self.classStack.pop()
  1856. if self.currentNamespace and self.currentNamespace.depth == self.depth:
  1857. log.debug("done with namespace %s at depth %r", self.currentNamespace.name, self.depth)
  1858. self.currentNamespace.lineend = self.lineno
  1859. self.currentNamespace = None
  1860. elif self.currentFunction and self.currentFunction.depth == self.depth:
  1861. self.currentFunction.lineend = self.lineno
  1862. # XXX stacked functions used to work in php, need verify still is
  1863. self.currentFunction = None
  1864. def addFunction(self, name, phpArgs=None, attributes=None, doc=None,
  1865. returnByRef=False):
  1866. log.debug("FUNC: %s(%r) on line %d", name, phpArgs, self.lineno)
  1867. classname = ''
  1868. extendsName = ''
  1869. if self.currentClass:
  1870. classname = self.currentClass.name
  1871. extendsName = self.currentClass.extends
  1872. self.currentFunction = PHPFunction(name,
  1873. phpArgs,
  1874. self.lineno,
  1875. self.depth,
  1876. attributes=attributes,
  1877. doc=doc,
  1878. classname=classname,
  1879. classparent=extendsName,
  1880. returnByRef=returnByRef)
  1881. if self.currentClass:
  1882. self.currentClass.functions[self.currentFunction.name] = self.currentFunction
  1883. elif self.currentNamespace:
  1884. self.currentNamespace.functions[self.currentFunction.name] = self.currentFunction
  1885. else:
  1886. self.fileinfo.functions[self.currentFunction.name] = self.currentFunction
  1887. if isinstance(self.currentClass, PHPInterface) or self.currentFunction.attributes.find('abstract') >= 0:
  1888. self.currentFunction.lineend = self.lineno
  1889. self.currentFunction = None
  1890. def addReturnType(self, typeName):
  1891. if self.currentFunction:
  1892. log.debug("RETURN TYPE: %r on line %d", typeName, self.lineno)
  1893. self.currentFunction.addReturnType(typeName)
  1894. else:
  1895. log.debug("addReturnType: No current function for return value!?")
  1896. def addClass(self, name, extends=None, attributes=None, interfaces=None, doc=None, isTrait=False):
  1897. toScope = self.currentNamespace or self.fileinfo
  1898. if name not in toScope.classes:
  1899. # push the current class onto the class stack
  1900. self.classStack.append(self.currentClass)
  1901. # make this class the current class
  1902. cixClass = isTrait and PHPTrait or PHPClass
  1903. self.currentClass = cixClass(name,
  1904. extends,
  1905. self.lineno,
  1906. self.depth,
  1907. attributes,
  1908. interfaces,
  1909. doc=doc)
  1910. toScope.classes[self.currentClass.name] = self.currentClass
  1911. log.debug("%s: %s extends %s interfaces %s attributes %s on line %d in %s at depth %d\nDOCS: %s",
  1912. self.currentClass.cixtype,
  1913. self.currentClass.name, self.currentClass.extends,
  1914. self.currentClass.interfaces, self.currentClass.attributes,
  1915. self.currentClass.linestart, self.filename, self.depth,
  1916. self.currentClass.doc)
  1917. else:
  1918. # shouldn't ever get here
  1919. pass
  1920. def addClassMember(self, name, vartype, attributes=None, doc=None, forceToClass=False):
  1921. if self.currentFunction and not forceToClass:
  1922. if name not in self.currentFunction.variables:
  1923. phpVariable = self.currentClass.members.get(name)
  1924. if phpVariable is None:
  1925. log.debug("Class FUNC variable: %r", name)
  1926. self.currentFunction.variables[name] = PHPVariable(name,
  1927. self.lineno,
  1928. vartype,
  1929. doc=doc)
  1930. elif vartype:
  1931. log.debug("Adding type information for VAR: %r, vartype: %r",
  1932. name, vartype)
  1933. phpVariable.addType(self.lineno, vartype)
  1934. elif self.currentClass:
  1935. phpVariable = self.currentClass.members.get(name)
  1936. if phpVariable is None:
  1937. log.debug("CLASSMBR: %r", name)
  1938. self.currentClass.members[name] = PHPVariable(name, self.lineno,
  1939. vartype,
  1940. attributes,
  1941. doc=doc)
  1942. elif vartype:
  1943. log.debug("Adding type information for CLASSMBR: %r, vartype: %r",
  1944. name, vartype)
  1945. phpVariable.addType(self.lineno, vartype)
  1946. def addClassConstant(self, name, vartype, doc=None):
  1947. """Add a constant variable into the current class."""
  1948. if self.currentClass:
  1949. phpConstant = self.currentClass.constants.get(name)
  1950. if phpConstant is None:
  1951. log.debug("CLASS CONST: %r", name)
  1952. self.currentClass.constants[name] = PHPConstant(name, self.lineno,
  1953. vartype)
  1954. elif vartype:
  1955. log.debug("Adding type information for CLASS CONST: %r, "
  1956. "vartype: %r", name, vartype)
  1957. phpConstant.addType(self.lineno, vartype)
  1958. def addInterface(self, name, extends=None, doc=None):
  1959. toScope = self.currentNamespace or self.fileinfo
  1960. if name not in toScope.interfaces:
  1961. # push the current interface onto the class stack
  1962. self.classStack.append(self.currentClass)
  1963. # make this interface the current interface
  1964. self.currentClass = PHPInterface(name, extends, self.lineno, self.depth)
  1965. toScope.interfaces[name] = self.currentClass
  1966. log.debug("INTERFACE: %s extends %s on line %d, depth %d",
  1967. name, extends, self.lineno, self.depth)
  1968. else:
  1969. # shouldn't ever get here
  1970. pass
  1971. def setNamespace(self, namelist, usesBracketedStyle, doc=None):
  1972. """Create and set as the current namespace."""
  1973. if self.currentNamespace:
  1974. # End the current namespace before starting the next.
  1975. self.currentNamespace.lineend = self.lineno -1
  1976. if not namelist:
  1977. # This means to use the global namespace, i.e.:
  1978. # namespace { // global code }
  1979. # http://ca3.php.net/manual/en/language.namespaces.definitionmultiple.php
  1980. self.currentNamespace = None
  1981. else:
  1982. depth = self.depth
  1983. if not usesBracketedStyle:
  1984. # If the namespacing does not uses brackets, then there is no
  1985. # good way to find out when the namespace end, we can only
  1986. # guarentee that the namespace ends if another namespace starts.
  1987. # Using None as the depth will ensure these semantics hold.
  1988. depth = None
  1989. namespace_path = qualifyNamespacePath("\\".join(namelist))
  1990. namespace = self.fileinfo.namespaces.get(namespace_path)
  1991. if namespace is None:
  1992. namespace = PHPNamespace(namespace_path, self.lineno, depth,
  1993. doc=doc)
  1994. self.fileinfo.namespaces[namespace_path] = namespace
  1995. self.currentNamespace = namespace
  1996. log.debug("NAMESPACE: %r on line %d in %s at depth %r",
  1997. namespace_path, self.lineno, self.filename, depth)
  1998. def addNamespaceImport(self, namespace, alias):
  1999. """Import the namespace."""
  2000. namelist = namespace.split("\\")
  2001. namespace_path = "\\".join(namelist[:-1])
  2002. if namespace.startswith("\\") and not namespace_path.startswith("\\"):
  2003. namespace_path = "\\%s" % (namespace_path, )
  2004. symbol = namelist[-1]
  2005. toScope = self.currentNamespace or self.fileinfo
  2006. toScope.includes.append(PHPImport(namespace_path, self.lineno,
  2007. alias=alias, symbol=symbol))
  2008. log.debug("IMPORT NAMESPACE: %s\%s as %r on line %d",
  2009. namespace_path, symbol, alias, self.lineno)
  2010. def addVariable(self, name, vartype='', attributes=None, doc=None,
  2011. fromPHPDoc=False):
  2012. log.debug("VAR: %r type: %r on line %d", name, vartype, self.lineno)
  2013. phpVariable = None
  2014. already_existed = True
  2015. if self.currentFunction:
  2016. phpVariable = self.currentFunction.variables.get(name)
  2017. # Also ensure the variable is not a function argument.
  2018. if phpVariable is None and \
  2019. not self.currentFunction.hasArgumentWithName(name):
  2020. phpVariable = PHPVariable(name, self.lineno, vartype,
  2021. attributes, doc=doc,
  2022. fromPHPDoc=fromPHPDoc)
  2023. self.currentFunction.variables[name] = phpVariable
  2024. already_existed = False
  2025. elif self.currentClass:
  2026. pass
  2027. # XXX this variable is local to a class method, what to do with it?
  2028. #if m.group('name') not in self.currentClass.variables:
  2029. # self.currentClass.variables[m.group('name')] =\
  2030. # PHPVariable(m.group('name'), self.lineno)
  2031. else:
  2032. # Variables cannot get defined in a namespace, so if it's not a
  2033. # function or a class, then it goes into the global scope.
  2034. phpVariable = self.fileinfo.variables.get(name)
  2035. if phpVariable is None:
  2036. phpVariable = PHPVariable(name, self.lineno, vartype,
  2037. attributes, doc=doc,
  2038. fromPHPDoc=fromPHPDoc,
  2039. namespace=self.currentNamespace)
  2040. self.fileinfo.variables[name] = phpVariable
  2041. already_existed = False
  2042. if phpVariable and already_existed:
  2043. if doc:
  2044. if phpVariable.doc:
  2045. phpVariable.doc += doc
  2046. else:
  2047. phpVariable.doc = doc
  2048. if vartype:
  2049. log.debug("Adding type information for VAR: %r, vartype: %r",
  2050. name, vartype)
  2051. phpVariable.addType(self.lineno, vartype, fromPHPDoc=fromPHPDoc)
  2052. return phpVariable
  2053. def addConstant(self, name, vartype='', doc=None):
  2054. """Add a constant at the global or namelisted scope level."""
  2055. log.debug("CONSTANT: %r type: %r on line %d", name, vartype, self.lineno)
  2056. toScope = self.currentNamespace or self.fileinfo
  2057. phpConstant = toScope.constants.get(name)
  2058. # Add it if it's not already defined
  2059. if phpConstant is None:
  2060. if vartype and isinstance(vartype, (list, tuple)):
  2061. vartype = ".".join(vartype)
  2062. toScope.constants[name] = PHPConstant(name, self.lineno, vartype)
  2063. def addDefine(self, name, vartype='', doc=None):
  2064. """Add a define at the global or namelisted scope level."""
  2065. log.debug("DEFINE: %r type: %r on line %d", name, vartype, self.lineno)
  2066. # Defines always go into the global scope unless explicitly defined
  2067. # with a namespace:
  2068. # http://ca3.php.net/manual/en/language.namespaces.definition.php
  2069. toScope = self.fileinfo
  2070. namelist = name.split("\\")
  2071. if len(namelist) > 1:
  2072. namespace_path = "\\".join(namelist[:-1])
  2073. namespace_path = qualifyNamespacePath(namespace_path)
  2074. log.debug("defined in namespace: %r", namespace_path)
  2075. namespace = toScope.namespaces.get(namespace_path)
  2076. # Note: This does not change to the namespace, it just creates
  2077. # it when it does not already exist!
  2078. if namespace is None:
  2079. namespace = PHPNamespace(namespace_path, self.lineno,
  2080. self.depth)
  2081. self.fileinfo.namespaces[namespace_path] = namespace
  2082. toScope = namespace
  2083. const_name = namelist[-1]
  2084. phpConstant = toScope.constants.get(const_name)
  2085. # Add it if it's not already defined
  2086. if phpConstant is None:
  2087. if vartype and isinstance(vartype, (list, tuple)):
  2088. vartype = ".".join(vartype)
  2089. toScope.constants[const_name] = PHPConstant(const_name, self.lineno, vartype)
  2090. def _parseOneArgument(self, styles, text):
  2091. """Create a PHPArg object from the given text"""
  2092. # Arguments can be of the form:
  2093. # foo($a, $b, $c)
  2094. # foo(&$a, &$b, &$c)
  2095. # foo($a, &$b, $c)
  2096. # foo($a = "123")
  2097. # makecoffee($types = array("cappuccino"), $coffeeMaker = NULL)
  2098. # Arguments can be statically typed declarations too, bug 79003:
  2099. # foo(MyClass $a)
  2100. # foo(string $a = "123")
  2101. # foo(MyClass &$a)
  2102. # References the inner class:
  2103. # static function bar($x=self::X)
  2104. pos = 0
  2105. name = None
  2106. citdl = None
  2107. default = None
  2108. sig_parts = []
  2109. log.debug("_parseOneArgument: text: %r", text)
  2110. while pos < len(styles):
  2111. sig_parts.append(text[pos])
  2112. if name is None:
  2113. if styles[pos] == self.PHP_VARIABLE:
  2114. name = self._removeDollarSymbolFromVariableName(text[pos])
  2115. elif styles[pos] in (self.PHP_IDENTIFIER, self.PHP_WORD):
  2116. # Statically typed argument.
  2117. citdl = text[pos]
  2118. sig_parts.append(" ")
  2119. elif text[pos] == '&':
  2120. sig_parts.append(" ")
  2121. elif not citdl:
  2122. if text[pos] == "=":
  2123. sig_parts[-1] = " = "
  2124. # It's an optional argument
  2125. default = "".join(text[pos+1:])
  2126. valueType, pos = self._getVariableType(styles, text, pos+1)
  2127. if valueType:
  2128. citdl = valueType[0]
  2129. break
  2130. else:
  2131. pos += 1
  2132. break
  2133. pos += 1
  2134. sig_parts += text[pos:]
  2135. if name is not None:
  2136. return PHPArg(name, citdl=citdl, signature="".join(sig_parts),
  2137. default=default)
  2138. def _getArgumentsFromPos(self, styles, text, pos):
  2139. """Return a list of PHPArg objects"""
  2140. p = pos
  2141. log.debug("_getArgumentsFromPos: text: %r", text[p:])
  2142. phpArgs = []
  2143. if p < len(styles) and styles[p] == self.PHP_OPERATOR and text[p] == "(":
  2144. p += 1
  2145. paren_count = 0
  2146. start_pos = p
  2147. while p < len(styles):
  2148. if styles[p] == self.PHP_OPERATOR:
  2149. if text[p] == "(":
  2150. paren_count += 1
  2151. elif text[p] == ")":
  2152. if paren_count <= 0:
  2153. # End of the arguments.
  2154. break
  2155. paren_count -= 1
  2156. elif text[p] == "," and paren_count == 0:
  2157. # End of the current argument.
  2158. phpArg = self._parseOneArgument(styles[start_pos:p],
  2159. text[start_pos:p])
  2160. if phpArg:
  2161. phpArgs.append(phpArg)
  2162. start_pos = p + 1
  2163. p += 1
  2164. if start_pos < p:
  2165. phpArg = self._parseOneArgument(styles[start_pos:p],
  2166. text[start_pos:p])
  2167. if phpArg:
  2168. phpArgs.append(phpArg)
  2169. return phpArgs, p
  2170. def _getOneIdentifierFromPos(self, styles, text, pos, identifierStyle=None):
  2171. if identifierStyle is None:
  2172. identifierStyle = self.PHP_IDENTIFIER
  2173. log.debug("_getIdentifiersFromPos: text: %r", text[pos:])
  2174. start_pos = pos
  2175. ids = []
  2176. last_style = self.PHP_OPERATOR
  2177. isNamespace = False
  2178. while pos < len(styles):
  2179. style = styles[pos]
  2180. #print "Style: %d, Text[%d]: %r" % (style, pos, text[pos])
  2181. if style == identifierStyle:
  2182. if last_style != self.PHP_OPERATOR:
  2183. break
  2184. if isNamespace:
  2185. ids[-1] += text[pos]
  2186. else:
  2187. ids.append(text[pos])
  2188. elif style == self.PHP_OPERATOR:
  2189. t = text[pos]
  2190. isNamespace = False
  2191. if t == "\\":
  2192. isNamespace = True
  2193. if ids:
  2194. ids[-1] += "\\"
  2195. else:
  2196. ids.append("\\")
  2197. elif ((t != "&" or last_style != self.PHP_OPERATOR) and \
  2198. (t != ":" or last_style != identifierStyle)):
  2199. break
  2200. else:
  2201. break
  2202. pos += 1
  2203. last_style = style
  2204. return ids, pos
  2205. def _getIdentifiersFromPos(self, styles, text, pos, identifierStyle=None):
  2206. typeNames, p = self._getOneIdentifierFromPos(styles, text, pos, identifierStyle)
  2207. if typeNames:
  2208. typeNames[0] = self._removeDollarSymbolFromVariableName(typeNames[0])
  2209. log.debug("typeNames: %r, p: %d, text left: %r", typeNames, p, text[p:])
  2210. # Grab additional fields
  2211. # Example: $x = $obj<p>->getFields()->field2
  2212. while p+2 < len(styles) and styles[p] == self.PHP_OPERATOR and \
  2213. text[p] in (":->\\"):
  2214. isNamespace = False
  2215. if text[p] == "\\":
  2216. isNamespace = True
  2217. p += 1
  2218. log.debug("while:: p: %d, text left: %r", p, text[p:])
  2219. if styles[p] == self.PHP_IDENTIFIER or \
  2220. (styles[p] == self.PHP_VARIABLE and text[p-1] == ":"):
  2221. additionalNames, p = self._getOneIdentifierFromPos(styles, text, p, styles[p])
  2222. log.debug("p: %d, additionalNames: %r", p, additionalNames)
  2223. if additionalNames:
  2224. if isNamespace:
  2225. if typeNames:
  2226. typeNames[-1] += "\\%s" % (additionalNames[0])
  2227. else:
  2228. typeNames.append("\\%s" % (additionalNames[0]))
  2229. else:
  2230. typeNames.append(additionalNames[0])
  2231. if p < len(styles) and \
  2232. styles[p] == self.PHP_OPERATOR and text[p][0] == "(":
  2233. typeNames[-1] += "()"
  2234. p = self._skipPastParenArguments(styles, text, p+1)
  2235. log.debug("_skipPastParenArguments:: p: %d, text left: %r", p, text[p:])
  2236. return typeNames, p
  2237. def _skipPastParenArguments(self, styles, text, p):
  2238. paren_count = 1
  2239. while p < len(styles):
  2240. if styles[p] == self.PHP_OPERATOR:
  2241. if text[p] == "(":
  2242. paren_count += 1
  2243. elif text[p] == ")":
  2244. if paren_count == 1:
  2245. return p+1
  2246. paren_count -= 1
  2247. p += 1
  2248. return p
  2249. _citdl_type_from_cast = {
  2250. "int": "int",
  2251. "integer": "int",
  2252. "bool": "boolean",
  2253. "boolean": "boolean",
  2254. "float": "int",
  2255. "double": "int",
  2256. "real": "int",
  2257. "string": "string",
  2258. "binary": "string",
  2259. "array": "array()", # array(), see bug 32896.
  2260. "object": "object",
  2261. }
  2262. def _getVariableType(self, styles, text, p, assignmentChar="="):
  2263. """Set assignmentChar to None to skip over looking for this char first"""
  2264. log.debug("_getVariableType: text: %r", text[p:])
  2265. typeNames = []
  2266. if p+1 < len(styles) and (assignmentChar is None or \
  2267. (styles[p] == self.PHP_OPERATOR and \
  2268. text[p] == assignmentChar)):
  2269. # Assignment to the variable
  2270. if assignmentChar is not None:
  2271. p += 1
  2272. if p+1 >= len(styles):
  2273. return typeNames, p
  2274. if styles[p] == self.PHP_OPERATOR and text[p] == '&':
  2275. log.debug("_getVariableType: skipping over reference char '&'")
  2276. p += 1
  2277. if p+1 >= len(styles):
  2278. return typeNames, p
  2279. elif p+3 <= len(styles) and styles[p] == self.PHP_OPERATOR and \
  2280. text[p+2] == ')' and text[p+1] in self._citdl_type_from_cast:
  2281. # Looks like a casting:
  2282. # http://ca.php.net/manual/en/language.types.type-juggling.php#language.types.typecasting
  2283. # $bar = (boolean) $foo;
  2284. typeNames = [self._citdl_type_from_cast.get(text[p+1])]
  2285. log.debug("_getVariableType: casted to type: %r", typeNames)
  2286. p += 3
  2287. return typeNames, p
  2288. if styles[p] == self.PHP_WORD:
  2289. # Keyword
  2290. keyword = text[p].lower()
  2291. p += 1
  2292. if keyword == "new":
  2293. typeNames, p = self._getIdentifiersFromPos(styles, text, p)
  2294. #if not typeNames:
  2295. # typeNames = ["object"]
  2296. elif keyword in ("true", "false"):
  2297. typeNames = ["boolean"];
  2298. elif keyword == "array":
  2299. typeNames = ["array()"];
  2300. elif keyword == "clone":
  2301. # clone is a special method - bug 85534.
  2302. typeNames, p = self._getIdentifiersFromPos(styles, text, p,
  2303. identifierStyle=self.PHP_VARIABLE)
  2304. elif styles[p] in self.PHP_STRINGS:
  2305. p += 1
  2306. typeNames = ["string"]
  2307. elif styles[p] == self.PHP_NUMBER:
  2308. p += 1
  2309. typeNames = ["int"]
  2310. elif styles[p] == self.PHP_IDENTIFIER:
  2311. # PHP Uses mixed upper/lower case for boolean values.
  2312. if text[p].lower() in ("true", "false"):
  2313. p += 1
  2314. typeNames = ["boolean"]
  2315. else:
  2316. typeNames, p = self._getIdentifiersFromPos(styles, text, p)
  2317. # Don't record null, as it doesn't help us with anything
  2318. if typeNames == ["NULL"]:
  2319. typeNames = []
  2320. elif typeNames and p < len(styles) and \
  2321. styles[p] == self.PHP_OPERATOR and text[p][0] == "(":
  2322. typeNames[-1] += "()"
  2323. elif styles[p] == self.PHP_VARIABLE:
  2324. typeNames, p = self._getIdentifiersFromPos(styles, text, p, self.PHP_VARIABLE)
  2325. elif styles[p] == self.PHP_OPERATOR and text[p] == "\\":
  2326. typeNames, p = self._getIdentifiersFromPos(styles, text, p, self.PHP_IDENTIFIER)
  2327. return typeNames, p
  2328. def _getKeywordArguments(self, styles, text, p, keywordName):
  2329. arguments = None
  2330. while p < len(styles):
  2331. if styles[p] == self.PHP_WORD and text[p] == keywordName:
  2332. # Grab the definition
  2333. p += 1
  2334. arguments = []
  2335. last_style = self.PHP_OPERATOR
  2336. namespaced = False
  2337. while p < len(styles):
  2338. if styles[p] == self.PHP_IDENTIFIER and \
  2339. last_style == self.PHP_OPERATOR:
  2340. if namespaced:
  2341. arguments[-1] += text[p]
  2342. namespaced = False
  2343. else:
  2344. arguments.append(text[p])
  2345. elif styles[p] == self.PHP_OPERATOR and text[p] == "\\":
  2346. if not arguments or last_style != self.PHP_IDENTIFIER:
  2347. arguments.append(text[p])
  2348. else:
  2349. arguments[-1] += text[p]
  2350. namespaced = True
  2351. elif styles[p] != self.PHP_OPERATOR or text[p] != ",":
  2352. break
  2353. last_style = styles[p]
  2354. p += 1
  2355. arguments = ", ".join(arguments)
  2356. break
  2357. p += 1
  2358. return arguments
  2359. def _getExtendsArgument(self, styles, text, p):
  2360. return self._getKeywordArguments(styles, text, p, "extends")
  2361. def _getImplementsArgument(self, styles, text, p):
  2362. return self._getKeywordArguments(styles, text, p, "implements")
  2363. def _unquoteString(self, s):
  2364. """Return the string without quotes around it"""
  2365. if len(s) >= 2 and s[0] in "\"'":
  2366. return s[1:-1]
  2367. return s
  2368. def _removeDollarSymbolFromVariableName(self, name):
  2369. if name[0] == "$":
  2370. return name[1:]
  2371. return name
  2372. def _getIncludePath(self, styles, text, p):
  2373. """Work out the include string and return it (without the quotes)"""
  2374. # Some examples (include has identical syntax):
  2375. # require 'prepend.php';
  2376. # require $somefile;
  2377. # require ('somefile.txt');
  2378. # From bug: http://bugs.activestate.com/show_bug.cgi?id=64208
  2379. # We just find the first string and use that
  2380. # require_once(CEON_CORE_DIR . 'core/datatypes/class.CustomDT.php');
  2381. # Skip over first brace if it exists
  2382. if p < len(styles) and \
  2383. styles[p] == self.PHP_OPERATOR and text[p] == "(":
  2384. p += 1
  2385. while p < len(styles):
  2386. if styles[p] in self.PHP_STRINGS:
  2387. requirename = self._unquoteString(text[p])
  2388. if requirename:
  2389. # Return with the first string found, we could do better...
  2390. return requirename
  2391. p += 1
  2392. return None
  2393. def _unescape_string(self, s):
  2394. """Unescape a PHP string."""
  2395. return s.replace("\\\\", "\\")
  2396. def _getConstantNameAndType(self, styles, text, p):
  2397. """Work out the constant name and type is, returns these as tuple"""
  2398. # Some examples (include has identical syntax):
  2399. # define('prepend', 1);
  2400. # define ('somefile', "file.txt");
  2401. # define('\namespace\CONSTANT', True);
  2402. # define(__NAMESPACE__ . '\CONSTANT', True);
  2403. constant_name = ""
  2404. constant_type = None
  2405. if styles[p] == self.PHP_OPERATOR and text[p] == "(":
  2406. p += 1
  2407. while p < len(styles):
  2408. if styles[p] in self.PHP_STRINGS:
  2409. constant_name += self._unquoteString(text[p])
  2410. elif styles[p] == self.PHP_WORD and \
  2411. text[p] == "__NAMESPACE__" and self.currentNamespace:
  2412. # __NAMESPACE__ is a special constant - we can expand this as we
  2413. # know what the current namespace is.
  2414. constant_name += self.currentNamespace.name
  2415. elif text[p] == ",":
  2416. constant_type, p = self._getVariableType(styles, text, p+1,
  2417. assignmentChar=None)
  2418. break
  2419. p += 1
  2420. # We must ensure the name (which came from a PHP string is unescaped),
  2421. # bug 90795.
  2422. return self._unescape_string(constant_name), constant_type
  2423. def _addAllVariables(self, styles, text, p):
  2424. while p < len(styles):
  2425. if styles[p] == self.PHP_VARIABLE:
  2426. namelist, p = self._getIdentifiersFromPos(styles, text, p, self.PHP_VARIABLE)
  2427. if len(namelist) == 1:
  2428. name = self._removeDollarSymbolFromVariableName(namelist[0])
  2429. # Don't add special internal variable names
  2430. if name in ("this", "self"):
  2431. # Lets see what we are doing with this
  2432. if p+3 < len(styles) and "".join(text[p:p+2]) in ("->", "::"):
  2433. # Get the variable the code is accessing
  2434. namelist, p = self._getIdentifiersFromPos(styles, text, p+2)
  2435. typeNames, p = self._getVariableType(styles, text, p)
  2436. if len(namelist) == 1 and typeNames:
  2437. log.debug("Assignment through %r for variable: %r", name, namelist)
  2438. self.addClassMember(namelist[0],
  2439. ".".join(typeNames),
  2440. doc=self.comment,
  2441. forceToClass=True)
  2442. elif name is not "parent":
  2443. # If next text/style is not an "=" operator, then add
  2444. # __not_defined__, which means the variable was not yet
  2445. # defined at the position it was ciled.
  2446. attributes = None
  2447. if p < len(styles) and text[p] != "=":
  2448. attributes = "__not_yet_defined__"
  2449. self.addVariable(name, attributes=attributes)
  2450. p += 1
  2451. def _handleVariableComment(self, namelist, comment):
  2452. """Determine any necessary information from the provided comment.
  2453. Returns true when the comment was used to apply variable info, false
  2454. otherwise.
  2455. """
  2456. log.debug("_handleVariableComment:: namelist: %r, comment: %r",
  2457. namelist, comment)
  2458. if "@var" in comment:
  2459. doc = uncommentDocString(comment)
  2460. # get the variable citdl type set by "@var"
  2461. all_matches = re.findall(PHPVariable._re_var, doc)
  2462. if len(all_matches) >= 1:
  2463. #print all_matches[0]
  2464. varname = all_matches[0][1]
  2465. vartype = all_matches[0][2]
  2466. php_variable = None
  2467. if varname:
  2468. # Optional, defines the variable this is applied to.
  2469. php_variable = self.addVariable(varname, vartype,
  2470. doc=comment,
  2471. fromPHPDoc=True)
  2472. return True
  2473. elif namelist:
  2474. php_variable = self.addVariable(namelist[0], vartype,
  2475. doc=comment,
  2476. fromPHPDoc=True)
  2477. return True
  2478. return False
  2479. def _variableHandler(self, styles, text, p, attributes, doc=None,
  2480. style="variable"):
  2481. log.debug("_variableHandler:: style: %r, text: %r, attributes: %r",
  2482. style, text[p:], attributes)
  2483. classVar = False
  2484. if attributes:
  2485. classVar = True
  2486. if "var" in attributes:
  2487. attributes.remove("var") # Don't want this in cile output
  2488. if style == "const":
  2489. if self.currentClass is not None:
  2490. classVar = True
  2491. elif self.currentNamespace is not None:
  2492. classVar = False
  2493. else:
  2494. log.debug("Ignoring const %r, as not defined in a "
  2495. "class or namespace context.", text)
  2496. return
  2497. looped = False
  2498. while p < len(styles):
  2499. if looped:
  2500. if text[p] != ",": # Variables need to be comma delimited.
  2501. p += 1
  2502. continue
  2503. p += 1
  2504. else:
  2505. looped = True
  2506. if style == "const":
  2507. namelist, p = self._getIdentifiersFromPos(styles, text, p,
  2508. self.PHP_IDENTIFIER)
  2509. elif text[p:p+3] == ["self", ":", ":"]:
  2510. # Handle things like: "self::$instance = FOO", bug 92813.
  2511. classVar = True
  2512. namelist, p = self._getIdentifiersFromPos(styles, text, p+3,
  2513. self.PHP_VARIABLE)
  2514. else:
  2515. namelist, p = self._getIdentifiersFromPos(styles, text, p,
  2516. self.PHP_VARIABLE)
  2517. if not namelist:
  2518. break
  2519. log.debug("namelist:%r, p:%d", namelist, p)
  2520. # Remove the dollar sign
  2521. name = self._removeDollarSymbolFromVariableName(namelist[0])
  2522. # Parse special internal variable names
  2523. if name == "parent":
  2524. continue
  2525. thisVar = False
  2526. if name in ("this", "self", ):
  2527. classVar = True
  2528. thisVar = True # need to distinguish between class var types.
  2529. if len(namelist) <= 1:
  2530. continue
  2531. # We don't need the this/self piece of the namelist.
  2532. namelist = namelist[1:]
  2533. name = namelist[0]
  2534. if len(namelist) != 1:
  2535. # Example: "item->foo;" translates to namelist: [item, foo]
  2536. if self.comment:
  2537. # We may be able to get some PHPDoc out of the comment.
  2538. if self._handleVariableComment(namelist, self.comment):
  2539. self.comment = None
  2540. log.info("multiple part variable namelist (ignoring): "
  2541. "%r, line: %d in file: %r", namelist,
  2542. self.lineno, self.filename)
  2543. continue
  2544. if name.endswith("()"):
  2545. # Example: "foo(x);" translates to namelist: [foo()]
  2546. if self.comment:
  2547. # We may be able to get some PHPDoc out of the comment.
  2548. if self._handleVariableComment(namelist, self.comment):
  2549. self.comment = None
  2550. log.info("variable is making a method call (ignoring): "
  2551. "%r, line: %d in file: %r", namelist,
  2552. self.lineno, self.filename)
  2553. continue
  2554. assignChar = text[p]
  2555. typeNames = []
  2556. mustCreateVariable = False
  2557. # Work out the citdl, we also ensure this is not just a comparison,
  2558. # i.e. not "$x == 2".
  2559. if p+1 < len(styles) and styles[p] == self.PHP_OPERATOR and \
  2560. assignChar in "=" and \
  2561. (p+2 >= len(styles) or text[p+1] != "="):
  2562. # Assignment to the variable
  2563. mustCreateVariable = True
  2564. typeNames, p = self._getVariableType(styles, text, p, assignChar)
  2565. log.debug("typeNames: %r", typeNames)
  2566. # Skip over paren arguments from class, function calls.
  2567. if typeNames and p < len(styles) and \
  2568. styles[p] == self.PHP_OPERATOR and text[p] == "(":
  2569. p = self._skipPastParenArguments(styles, text, p+1)
  2570. # Create the variable cix information.
  2571. if mustCreateVariable or (not thisVar and p < len(styles) and
  2572. styles[p] == self.PHP_OPERATOR and \
  2573. text[p] in ",;"):
  2574. log.debug("Line %d, variable definition: %r",
  2575. self.lineno, namelist)
  2576. if style == "const":
  2577. if classVar:
  2578. self.addClassConstant(name, ".".join(typeNames),
  2579. doc=self.comment)
  2580. else:
  2581. self.addConstant(name, ".".join(typeNames),
  2582. doc=self.comment)
  2583. elif classVar and self.currentClass is not None:
  2584. self.addClassMember(name, ".".join(typeNames),
  2585. attributes=attributes, doc=self.comment,
  2586. forceToClass=classVar)
  2587. else:
  2588. self.addVariable(name, ".".join(typeNames),
  2589. attributes=attributes, doc=self.comment)
  2590. def _useKeywordHandler(self, styles, text, p):
  2591. log.debug("_useKeywordHandler:: text: %r", text[p:])
  2592. looped = False
  2593. while p < len(styles):
  2594. if looped:
  2595. if text[p] != ",": # Use statements need to be comma delimited.
  2596. p += 1
  2597. continue
  2598. p += 1
  2599. else:
  2600. looped = True
  2601. namelist, p = self._getIdentifiersFromPos(styles, text, p)
  2602. log.debug("use:%r, p:%d", namelist, p)
  2603. if namelist:
  2604. alias = None
  2605. if p+1 < len(styles):
  2606. if styles[p] == self.PHP_WORD and \
  2607. text[p] == "as":
  2608. # Uses an alias
  2609. alias, p = self._getIdentifiersFromPos(styles, text, p+1)
  2610. if alias:
  2611. alias = alias[0]
  2612. if self.currentClass:
  2613. # Must be a trait.
  2614. self.currentClass.addTraitReference(namelist[0])
  2615. else:
  2616. # Must be a namespace reference.
  2617. self.addNamespaceImport(namelist[0], alias)
  2618. def _handleTraitResolution(self, styles, text, p, doc=None):
  2619. log.debug("_handleTraitResolution:: text: %r", text[p:])
  2620. # Examples:
  2621. # B::smallTalk insteadof A;
  2622. # B::bigTalk as talk;
  2623. # sayHello as protected;
  2624. # sayHello as private myPrivateHello;
  2625. # Can only be defined on a trait or a class.
  2626. if not self.currentClass:
  2627. log.warn("_handleTraitResolution:: not in a class|trait definition")
  2628. return
  2629. # Look for the identifier first.
  2630. #
  2631. namelist, p = self._getIdentifiersFromPos(styles, text, p,
  2632. self.PHP_IDENTIFIER)
  2633. log.debug("namelist:%r, p:%d", namelist, p)
  2634. if not namelist or p+2 >= len(text):
  2635. log.warn("Not enough arguments in trait use statement: %r", text)
  2636. return
  2637. # Get the keyword "as", "insteadof"
  2638. keyword = text[p]
  2639. log.debug("keyword:%r", keyword)
  2640. p += 1
  2641. # Get the settings.
  2642. alias = None
  2643. visibility = None
  2644. # Get special attribute keywords.
  2645. if keyword == "as" and \
  2646. text[p] in ("public", "protected", "private"):
  2647. visibility = text[p]
  2648. p += 1
  2649. log.debug("_handleTraitResolution: visibility %r", visibility)
  2650. if p < len(text):
  2651. # Get the alias name.
  2652. names, p = self._getIdentifiersFromPos(styles, text, p)
  2653. if names:
  2654. alias = names[0]
  2655. if len(names) > 1:
  2656. log.warn("Ignoring multiple alias identifiers in text: %r",
  2657. text)
  2658. if alias or visibility:
  2659. # Set override.
  2660. self.currentClass.addTraitOverride(namelist, alias,
  2661. visibility=visibility,
  2662. insteadOf=(keyword=="insteadof"))
  2663. else:
  2664. self.warn("Unknown trait resolution: %r", text)
  2665. def _addCodePiece(self, newstate=S_DEFAULT, varnames=None):
  2666. styles = self.styles
  2667. if len(styles) == 0:
  2668. return
  2669. text = self.text
  2670. lines = self.linenos
  2671. log.debug("*** Line: %d ********************************", self.lineno)
  2672. #log.debug("Styles: %r", self.styles)
  2673. log.debug("Text: %r", self.text)
  2674. #log.debug("Comment: %r", self.comment)
  2675. #log.debug("")
  2676. pos = 0
  2677. attributes = []
  2678. firstStyle = styles[pos]
  2679. try:
  2680. # We may be able to get some PHPDoc out of the comment already,
  2681. # such as targeted "@var " comments.
  2682. # http://bugs.activestate.com/show_bug.cgi?id=76676
  2683. if self.comment and self._handleVariableComment(None, self.comment):
  2684. self.comment = None
  2685. # Eat special attribute keywords
  2686. while firstStyle == self.PHP_WORD and \
  2687. text[pos] in ("var", "public", "protected", "private",
  2688. "final", "static", "abstract"):
  2689. attributes.append(text[pos])
  2690. pos += 1
  2691. firstStyle = styles[pos]
  2692. if firstStyle == self.PHP_WORD:
  2693. keyword = text[pos].lower()
  2694. pos += 1
  2695. if pos >= len(lines):
  2696. # Nothing else here, go home
  2697. return
  2698. self.lineno = lines[pos]
  2699. if keyword in ("require", "include", "require_once", "include_once"):
  2700. # Some examples (include has identical syntax):
  2701. # require 'prepend.php';
  2702. # require $somefile;
  2703. # require ('somefile.txt');
  2704. # XXX - Below syntax is not handled...
  2705. # if ((include 'vars.php') == 'OK') {
  2706. namelist = None
  2707. if pos < len(styles):
  2708. requirename = self._getIncludePath(styles, text, pos)
  2709. if requirename:
  2710. self.include_file(requirename)
  2711. else:
  2712. log.debug("Could not work out requirename. Text: %r",
  2713. text[pos:])
  2714. elif keyword == "define":
  2715. # Defining a constant
  2716. # define("FOO", "something");
  2717. # define('TEST_CONSTANT', FALSE);
  2718. name, citdl = self._getConstantNameAndType(styles, text, pos)
  2719. if name:
  2720. self.addDefine(name, citdl)
  2721. elif keyword == "const":
  2722. # Defining a class constant
  2723. # const myconstant = x;
  2724. self._variableHandler(styles, text, pos, attributes,
  2725. doc=self.comment, style="const")
  2726. elif keyword == "function":
  2727. namelist, p = self._getIdentifiersFromPos(styles, text, pos)
  2728. log.debug("namelist:%r, p:%d", namelist, p)
  2729. if namelist:
  2730. returnByRef = (text[pos] == "&")
  2731. phpArgs, p = self._getArgumentsFromPos(styles, text, p)
  2732. log.debug("Line %d, function: %r(%r)",
  2733. self.lineno, namelist, phpArgs)
  2734. if len(namelist) != 1:
  2735. log.info("warn: invalid function name (ignoring): "
  2736. "%r, line: %d in file: %r", namelist,
  2737. self.lineno, self.filename)
  2738. return
  2739. self.addFunction(namelist[0], phpArgs, attributes,
  2740. doc=self.comment,
  2741. returnByRef=returnByRef)
  2742. elif keyword == "class" or keyword == "trait":
  2743. # Examples:
  2744. # class SimpleClass {
  2745. # class SimpleClass2 extends SimpleClass {
  2746. # class MyClass extends AbstractClass implements TestInterface, TestMethodsInterface {
  2747. #
  2748. namelist, p = self._getIdentifiersFromPos(styles, text, pos)
  2749. if namelist and "{" in text:
  2750. if len(namelist) != 1:
  2751. log.info("warn: invalid class name (ignoring): %r, "
  2752. "line: %d in file: %r", namelist,
  2753. self.lineno, self.filename)
  2754. return
  2755. extends = self._getExtendsArgument(styles, text, p)
  2756. implements = self._getImplementsArgument(styles, text, p)
  2757. #print "extends: %r" % (extends)
  2758. #print "implements: %r" % (implements)
  2759. self.addClass(namelist[0], extends=extends,
  2760. attributes=attributes,
  2761. interfaces=implements, doc=self.comment,
  2762. isTrait=(keyword == "trait"))
  2763. elif keyword == "interface":
  2764. # Examples:
  2765. # interface Foo {
  2766. # interface SQL_Result extends SeekableIterator, Countable {
  2767. #
  2768. namelist, p = self._getIdentifiersFromPos(styles, text, pos)
  2769. if namelist and "{" in text:
  2770. if len(namelist) != 1:
  2771. log.info("warn: invalid interface name (ignoring): "
  2772. "%r, line: %d in file: %r", namelist,
  2773. self.lineno, self.filename)
  2774. return
  2775. extends = self._getExtendsArgument(styles, text, p)
  2776. self.addInterface(namelist[0], extends, doc=self.comment)
  2777. elif keyword == "return":
  2778. # Returning value for a function call
  2779. # return 123;
  2780. # return $x;
  2781. typeNames, p = self._getVariableType(styles, text, pos, assignmentChar=None)
  2782. log.debug("typeNames:%r", typeNames)
  2783. if typeNames:
  2784. self.addReturnType(".".join(typeNames))
  2785. elif keyword == "catch" and pos+3 >= len(text):
  2786. # catch ( Exception $e)
  2787. pos += 1 # skip the paren
  2788. typeNames, p = self._getVariableType(styles, text, pos, assignmentChar=None)
  2789. namelist, p = self._getIdentifiersFromPos(styles, text, p, self.PHP_VARIABLE)
  2790. if namelist and typeNames:
  2791. self.addVariable(namelist[0], ".".join(typeNames))
  2792. elif keyword == "namespace":
  2793. namelist, p = self._getIdentifiersFromPos(styles, text, pos)
  2794. log.debug("namelist:%r, p:%d", namelist, p)
  2795. if namelist:
  2796. usesBraces = "{" in text
  2797. self.setNamespace(namelist, usesBraces,
  2798. doc=self.comment)
  2799. elif keyword == "use":
  2800. self._useKeywordHandler(styles, text, pos)
  2801. if text and text[-1] == "{":
  2802. self.return_to_state = newstate
  2803. newstate = S_TRAIT_RESOLUTION
  2804. else:
  2805. log.debug("Ignoring keyword: %s", keyword)
  2806. self._addAllVariables(styles, text, pos)
  2807. elif firstStyle == self.PHP_IDENTIFIER:
  2808. if text[0] == "self":
  2809. self._variableHandler(styles, text, pos, attributes,
  2810. doc=self.comment)
  2811. elif self.state == S_TRAIT_RESOLUTION:
  2812. self._handleTraitResolution(styles, text, pos, doc=self.comment)
  2813. log.debug("Trait resolution: text: %r, pos: %d", text, pos)
  2814. # Stay in this state.
  2815. newstate = S_TRAIT_RESOLUTION
  2816. else:
  2817. log.debug("Ignoring when starting with identifier")
  2818. elif firstStyle == self.PHP_VARIABLE:
  2819. # Defining scope for action
  2820. self._variableHandler(styles, text, pos, attributes,
  2821. doc=self.comment)
  2822. else:
  2823. log.debug("Unhandled first style:%d", firstStyle)
  2824. finally:
  2825. self._resetState(newstate)
  2826. def _resetState(self, newstate=S_DEFAULT):
  2827. self.state = newstate
  2828. self.styles = []
  2829. self.linenos = []
  2830. self.text = []
  2831. self.comment = None
  2832. self.comments = []
  2833. def token_next(self, style, text, start_column, start_line, **other_args):
  2834. """Loops over the styles in the document and stores important info.
  2835. When enough info is gathered, will perform a call to analyze the code
  2836. and generate subsequent language structures. These language structures
  2837. will later be used to generate XML output for the document."""
  2838. #log.debug("text: %r", text)
  2839. #print "text: %r, style: %r" % (text, style)
  2840. if self.state == S_GET_HEREDOC_MARKER:
  2841. if not text.strip():
  2842. log.debug("Ignoring whitespace after <<<: %r", text)
  2843. return
  2844. self.heredocMarker = self._unquoteString(text)
  2845. log.debug("getting heredoc marker: %r, now in heredoc state", text)
  2846. self._resetState(S_IN_HEREDOC)
  2847. elif self.state == S_IN_HEREDOC:
  2848. # Heredocs *must* be on the start of a newline
  2849. if text == self.heredocMarker and self.lastText and \
  2850. self.lastText[-1] in "\r\n":
  2851. log.debug("end of heredoc: %r", self.heredocMarker)
  2852. self._resetState(self.return_to_state)
  2853. else:
  2854. log.debug("ignoring heredoc material")
  2855. elif (style in (self.PHP_WORD, self.PHP_IDENTIFIER,
  2856. self.PHP_OPERATOR, self.PHP_NUMBER, self.PHP_VARIABLE) or
  2857. style in (self.PHP_STRINGS)):
  2858. # We keep track of these styles and the text associated with it.
  2859. # When we gather enough info, these will be sent to the
  2860. # _addCodePiece() function which will analyze the info.
  2861. self.lineno = start_line + 1
  2862. if style != self.PHP_OPERATOR:
  2863. # Have to trim whitespace, as the identifier style is
  2864. # also the default whitespace style... ugly!
  2865. if style == self.PHP_IDENTIFIER:
  2866. text = text.strip()
  2867. if text:
  2868. self.text.append(text)
  2869. self.styles.append(style)
  2870. self.linenos.append(self.lineno)
  2871. #print "Text:", text
  2872. else:
  2873. # Do heredoc parsing, since UDL cannot as yet
  2874. if text == "<<<":
  2875. self.return_to_state = self.state
  2876. self.state = S_GET_HEREDOC_MARKER
  2877. # Remove out any "<?php" and "?>" tags, see syntax description:
  2878. # http://www.php.net/manual/en/language.basic-syntax.php
  2879. elif text.startswith("<?"):
  2880. if text[:5].lower() == "<?php":
  2881. text = text[5:]
  2882. elif text.startswith("<?="):
  2883. text = text[len("<?="):]
  2884. else:
  2885. text = text[len("<?"):]
  2886. elif text.startswith("<%"):
  2887. if text.startswith("<%="):
  2888. text = text[len("<%="):]
  2889. else:
  2890. text = text[len("<%"):]
  2891. if text.endswith("?>"):
  2892. text = text[:-len("?>")]
  2893. elif text.endswith("<%"):
  2894. text = text[:-len("%>")]
  2895. col = start_column + 1
  2896. #for op in text:
  2897. # self.styles.append(style)
  2898. # self.text.append(op)
  2899. #log.debug("token_next: line %d, %r" % (self.lineno, text))
  2900. for op in text:
  2901. self.styles.append(style)
  2902. self.text.append(op)
  2903. self.linenos.append(self.lineno)
  2904. if op == "(":
  2905. # We can start defining arguments now
  2906. #log.debug("Entering S_IN_ARGS state")
  2907. self.return_to_state = self.state
  2908. self.state = S_IN_ARGS
  2909. elif op == ")":
  2910. #log.debug("Entering state %d", self.return_to_state)
  2911. self.state = self.return_to_state
  2912. elif op == "=":
  2913. if text == op:
  2914. #log.debug("Entering S_IN_ASSIGNMENT state")
  2915. self.state = S_IN_ASSIGNMENT
  2916. elif op == "{":
  2917. # Increasing depth/scope, could be an argument object
  2918. self._addCodePiece()
  2919. self.incBlock()
  2920. elif op == "}":
  2921. # Decreasing depth/scope
  2922. if len(self.text) == 1:
  2923. self._resetState()
  2924. else:
  2925. self._addCodePiece()
  2926. self.decBlock()
  2927. elif op == ":":
  2928. # May be an alternative syntax
  2929. if len(self.text) > 0 and \
  2930. self.styles[0] == self.PHP_WORD and \
  2931. self.text[0].lower() in ("if", "elseif", "else", "while", "for", "foreach", "switch"):
  2932. #print "Alt syntax? text: %r" % (self.text, )
  2933. self._addCodePiece()
  2934. elif "case" in self.text or "default" in self.text:
  2935. # Part of a switch statement - bug 86927.
  2936. self._addCodePiece()
  2937. elif op == ";":
  2938. # Statement is done
  2939. if len(self.text) > 0 and \
  2940. self.styles[0] == self.PHP_WORD and \
  2941. self.text[-1].lower() in ("endif", "endwhile", "endfor", "endforeach", "endswitch"):
  2942. # Alternative syntax, remove this from the text.
  2943. self.text = self.text[:-1]
  2944. self._addCodePiece()
  2945. col += 1
  2946. elif style in self.PHP_COMMENT_STYLES:
  2947. # Use rstrip to remove any trailing spaces or newline characters.
  2948. comment = text.rstrip()
  2949. # Check if it's a continuation from the last comment. If we have
  2950. # already collected text then this is a comment in the middle of a
  2951. # statement, so do not set self.comment, but rather just add it to
  2952. # the list of known comment sections (self.comments).
  2953. if not self.text:
  2954. if style == SCE_UDL_SSL_COMMENT and self.comment and \
  2955. start_line <= (self.comments[-1][2] + 1) and \
  2956. style == self.comments[-1][1]:
  2957. self.comment += comment
  2958. else:
  2959. self.comment = comment
  2960. self.comments.append([comment, style, start_line, start_column])
  2961. elif style == SCE_UDL_SSL_DEFAULT and \
  2962. self.lastStyle in self.PHP_COMMENT_STYLES and text[0] in "\r\n":
  2963. # This is necessary as line comments are supplied to us without
  2964. # the newlines, so check to see if this is a newline and if the
  2965. # last line was a comment, append it the newline to it.
  2966. if self.comment:
  2967. self.comment += "\n"
  2968. self.comments[-1][0] += "\n"
  2969. elif is_udl_csl_style(style):
  2970. self.csl_tokens.append({"style": style,
  2971. "text": text,
  2972. "start_column": start_column,
  2973. "start_line": start_line})
  2974. self.lastText = text
  2975. self.lastStyle = style
  2976. def scan_multilang_content(self, content):
  2977. """Scan the given PHP content, only processes SSL styles"""
  2978. PHPLexer().tokenize_by_style(content, self.token_next)
  2979. return self.csl_tokens
  2980. def convertToElementTreeFile(self, cixelement):
  2981. """Store PHP information into the cixelement as a file(s) sub element"""
  2982. self.cile.convertToElementTreeFile(cixelement)
  2983. def convertToElementTreeModule(self, cixblob):
  2984. """Store PHP information into already created cixblob"""
  2985. self.cile.convertToElementTreeModule(cixblob)
  2986. #---- internal utility functions
  2987. def _isident(char):
  2988. return "a" <= char <= "z" or "A" <= char <= "Z" or char == "_"
  2989. def _isdigit(char):
  2990. return "0" <= char <= "9"
  2991. #---- public module interface
  2992. #---- registration
  2993. def register(mgr):
  2994. """Register language support with the Manager."""
  2995. mgr.set_lang_info(lang,
  2996. silvercity_lexer=PHPLexer(),
  2997. buf_class=PHPBuffer,
  2998. langintel_class=PHPLangIntel,
  2999. import_handler_class=PHPImportHandler,
  3000. cile_driver_class=PHPCILEDriver,
  3001. is_cpln_lang=True,
  3002. import_everything=True)