Scanning.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. # cython: infer_types=True, language_level=3, py2_import=True, auto_pickle=False
  2. #
  3. # Cython Scanner
  4. #
  5. from __future__ import absolute_import
  6. import cython
  7. cython.declare(make_lexicon=object, lexicon=object,
  8. print_function=object, error=object, warning=object,
  9. os=object, platform=object)
  10. import os
  11. import platform
  12. from .. import Utils
  13. from ..Plex.Scanners import Scanner
  14. from ..Plex.Errors import UnrecognizedInput
  15. from .Errors import error, warning
  16. from .Lexicon import any_string_prefix, make_lexicon, IDENT
  17. from .Future import print_function
  18. debug_scanner = 0
  19. trace_scanner = 0
  20. scanner_debug_flags = 0
  21. scanner_dump_file = None
  22. lexicon = None
  23. def get_lexicon():
  24. global lexicon
  25. if not lexicon:
  26. lexicon = make_lexicon()
  27. return lexicon
  28. #------------------------------------------------------------------
  29. py_reserved_words = [
  30. "global", "nonlocal", "def", "class", "print", "del", "pass", "break",
  31. "continue", "return", "raise", "import", "exec", "try",
  32. "except", "finally", "while", "if", "elif", "else", "for",
  33. "in", "assert", "and", "or", "not", "is", "lambda",
  34. "from", "yield", "with",
  35. ]
  36. pyx_reserved_words = py_reserved_words + [
  37. "include", "ctypedef", "cdef", "cpdef",
  38. "cimport", "DEF", "IF", "ELIF", "ELSE"
  39. ]
  40. class Method(object):
  41. def __init__(self, name, **kwargs):
  42. self.name = name
  43. self.kwargs = kwargs or None
  44. self.__name__ = name # for Plex tracing
  45. def __call__(self, stream, text):
  46. method = getattr(stream, self.name)
  47. # self.kwargs is almost always unused => avoid call overhead
  48. return method(text, **self.kwargs) if self.kwargs is not None else method(text)
  49. def __copy__(self):
  50. return self # immutable, no need to copy
  51. def __deepcopy__(self, memo):
  52. return self # immutable, no need to copy
  53. #------------------------------------------------------------------
  54. class CompileTimeScope(object):
  55. def __init__(self, outer=None):
  56. self.entries = {}
  57. self.outer = outer
  58. def declare(self, name, value):
  59. self.entries[name] = value
  60. def update(self, other):
  61. self.entries.update(other)
  62. def lookup_here(self, name):
  63. return self.entries[name]
  64. def __contains__(self, name):
  65. return name in self.entries
  66. def lookup(self, name):
  67. try:
  68. return self.lookup_here(name)
  69. except KeyError:
  70. outer = self.outer
  71. if outer:
  72. return outer.lookup(name)
  73. else:
  74. raise
  75. def initial_compile_time_env():
  76. benv = CompileTimeScope()
  77. names = ('UNAME_SYSNAME', 'UNAME_NODENAME', 'UNAME_RELEASE', 'UNAME_VERSION', 'UNAME_MACHINE')
  78. for name, value in zip(names, platform.uname()):
  79. benv.declare(name, value)
  80. try:
  81. import __builtin__ as builtins
  82. except ImportError:
  83. import builtins
  84. names = (
  85. 'False', 'True',
  86. 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes',
  87. 'chr', 'cmp', 'complex', 'dict', 'divmod', 'enumerate', 'filter',
  88. 'float', 'format', 'frozenset', 'hash', 'hex', 'int', 'len',
  89. 'list', 'map', 'max', 'min', 'oct', 'ord', 'pow', 'range',
  90. 'repr', 'reversed', 'round', 'set', 'slice', 'sorted', 'str',
  91. 'sum', 'tuple', 'zip',
  92. ### defined below in a platform independent way
  93. # 'long', 'unicode', 'reduce', 'xrange'
  94. )
  95. for name in names:
  96. try:
  97. benv.declare(name, getattr(builtins, name))
  98. except AttributeError:
  99. # ignore, likely Py3
  100. pass
  101. # Py2/3 adaptations
  102. from functools import reduce
  103. benv.declare('reduce', reduce)
  104. benv.declare('unicode', getattr(builtins, 'unicode', getattr(builtins, 'str')))
  105. benv.declare('long', getattr(builtins, 'long', getattr(builtins, 'int')))
  106. benv.declare('xrange', getattr(builtins, 'xrange', getattr(builtins, 'range')))
  107. denv = CompileTimeScope(benv)
  108. return denv
  109. #------------------------------------------------------------------
  110. class SourceDescriptor(object):
  111. """
  112. A SourceDescriptor should be considered immutable.
  113. """
  114. filename = None
  115. _file_type = 'pyx'
  116. _escaped_description = None
  117. _cmp_name = ''
  118. def __str__(self):
  119. assert False # To catch all places where a descriptor is used directly as a filename
  120. def set_file_type_from_name(self, filename):
  121. name, ext = os.path.splitext(filename)
  122. self._file_type = ext in ('.pyx', '.pxd', '.py') and ext[1:] or 'pyx'
  123. def is_cython_file(self):
  124. return self._file_type in ('pyx', 'pxd')
  125. def is_python_file(self):
  126. return self._file_type == 'py'
  127. def get_escaped_description(self):
  128. if self._escaped_description is None:
  129. esc_desc = \
  130. self.get_description().encode('ASCII', 'replace').decode("ASCII")
  131. # Use forward slashes on Windows since these paths
  132. # will be used in the #line directives in the C/C++ files.
  133. self._escaped_description = esc_desc.replace('\\', '/')
  134. return self._escaped_description
  135. def __gt__(self, other):
  136. # this is only used to provide some sort of order
  137. try:
  138. return self._cmp_name > other._cmp_name
  139. except AttributeError:
  140. return False
  141. def __lt__(self, other):
  142. # this is only used to provide some sort of order
  143. try:
  144. return self._cmp_name < other._cmp_name
  145. except AttributeError:
  146. return False
  147. def __le__(self, other):
  148. # this is only used to provide some sort of order
  149. try:
  150. return self._cmp_name <= other._cmp_name
  151. except AttributeError:
  152. return False
  153. def __copy__(self):
  154. return self # immutable, no need to copy
  155. def __deepcopy__(self, memo):
  156. return self # immutable, no need to copy
  157. class FileSourceDescriptor(SourceDescriptor):
  158. """
  159. Represents a code source. A code source is a more generic abstraction
  160. for a "filename" (as sometimes the code doesn't come from a file).
  161. Instances of code sources are passed to Scanner.__init__ as the
  162. optional name argument and will be passed back when asking for
  163. the position()-tuple.
  164. """
  165. def __init__(self, filename, path_description=None):
  166. filename = Utils.decode_filename(filename)
  167. self.path_description = path_description or filename
  168. self.filename = filename
  169. # Prefer relative paths to current directory (which is most likely the project root) over absolute paths.
  170. workdir = os.path.abspath('.') + os.sep
  171. self.file_path = filename[len(workdir):] if filename.startswith(workdir) else filename
  172. self.set_file_type_from_name(filename)
  173. self._cmp_name = filename
  174. self._lines = {}
  175. def get_lines(self, encoding=None, error_handling=None):
  176. # we cache the lines only the second time this is called, in
  177. # order to save memory when they are only used once
  178. key = (encoding, error_handling)
  179. try:
  180. lines = self._lines[key]
  181. if lines is not None:
  182. return lines
  183. except KeyError:
  184. pass
  185. with Utils.open_source_file(self.filename, encoding=encoding, error_handling=error_handling) as f:
  186. lines = list(f)
  187. if key in self._lines:
  188. self._lines[key] = lines
  189. else:
  190. # do not cache the first access, but remember that we
  191. # already read it once
  192. self._lines[key] = None
  193. return lines
  194. def get_description(self):
  195. # Dump path_description, it's already arcadia root relative (required for proper file matching in coverage)
  196. return self.path_description
  197. try:
  198. return os.path.relpath(self.path_description)
  199. except ValueError:
  200. # path not under current directory => use complete file path
  201. return self.path_description
  202. def get_error_description(self):
  203. path = self.filename
  204. cwd = Utils.decode_filename(os.getcwd() + os.path.sep)
  205. if path.startswith(cwd):
  206. return path[len(cwd):]
  207. return path
  208. def get_filenametable_entry(self):
  209. return self.file_path
  210. def __eq__(self, other):
  211. return isinstance(other, FileSourceDescriptor) and self.filename == other.filename
  212. def __hash__(self):
  213. return hash(self.filename)
  214. def __repr__(self):
  215. return "<FileSourceDescriptor:%s>" % self.filename
  216. class StringSourceDescriptor(SourceDescriptor):
  217. """
  218. Instances of this class can be used instead of a filenames if the
  219. code originates from a string object.
  220. """
  221. def __init__(self, name, code):
  222. self.name = name
  223. #self.set_file_type_from_name(name)
  224. self.codelines = [x + "\n" for x in code.split("\n")]
  225. self._cmp_name = name
  226. def get_lines(self, encoding=None, error_handling=None):
  227. if not encoding:
  228. return self.codelines
  229. else:
  230. return [line.encode(encoding, error_handling).decode(encoding)
  231. for line in self.codelines]
  232. def get_description(self):
  233. return self.name
  234. get_error_description = get_description
  235. def get_filenametable_entry(self):
  236. return "stringsource"
  237. def __hash__(self):
  238. return id(self)
  239. # Do not hash on the name, an identical string source should be the
  240. # same object (name is often defaulted in other places)
  241. # return hash(self.name)
  242. def __eq__(self, other):
  243. return isinstance(other, StringSourceDescriptor) and self.name == other.name
  244. def __repr__(self):
  245. return "<StringSourceDescriptor:%s>" % self.name
  246. #------------------------------------------------------------------
  247. class PyrexScanner(Scanner):
  248. # context Context Compilation context
  249. # included_files [string] Files included with 'include' statement
  250. # compile_time_env dict Environment for conditional compilation
  251. # compile_time_eval boolean In a true conditional compilation context
  252. # compile_time_expr boolean In a compile-time expression context
  253. def __init__(self, file, filename, parent_scanner=None,
  254. scope=None, context=None, source_encoding=None, parse_comments=True, initial_pos=None):
  255. Scanner.__init__(self, get_lexicon(), file, filename, initial_pos)
  256. if filename.is_python_file():
  257. self.in_python_file = True
  258. self.keywords = set(py_reserved_words)
  259. else:
  260. self.in_python_file = False
  261. self.keywords = set(pyx_reserved_words)
  262. self.async_enabled = 0
  263. if parent_scanner:
  264. self.context = parent_scanner.context
  265. self.included_files = parent_scanner.included_files
  266. self.compile_time_env = parent_scanner.compile_time_env
  267. self.compile_time_eval = parent_scanner.compile_time_eval
  268. self.compile_time_expr = parent_scanner.compile_time_expr
  269. if parent_scanner.async_enabled:
  270. self.enter_async()
  271. else:
  272. self.context = context
  273. self.included_files = scope.included_files
  274. self.compile_time_env = initial_compile_time_env()
  275. self.compile_time_eval = 1
  276. self.compile_time_expr = 0
  277. if getattr(context.options, 'compile_time_env', None):
  278. self.compile_time_env.update(context.options.compile_time_env)
  279. self.parse_comments = parse_comments
  280. self.source_encoding = source_encoding
  281. self.trace = trace_scanner
  282. self.indentation_stack = [0]
  283. self.indentation_char = None
  284. self.bracket_nesting_level = 0
  285. self.begin('INDENT')
  286. self.sy = ''
  287. self.next()
  288. def commentline(self, text):
  289. if self.parse_comments:
  290. self.produce('commentline', text)
  291. def strip_underscores(self, text, symbol):
  292. self.produce(symbol, text.replace('_', ''))
  293. def current_level(self):
  294. return self.indentation_stack[-1]
  295. def open_bracket_action(self, text):
  296. self.bracket_nesting_level += 1
  297. return text
  298. def close_bracket_action(self, text):
  299. self.bracket_nesting_level -= 1
  300. return text
  301. def newline_action(self, text):
  302. if self.bracket_nesting_level == 0:
  303. self.begin('INDENT')
  304. self.produce('NEWLINE', '')
  305. string_states = {
  306. "'": 'SQ_STRING',
  307. '"': 'DQ_STRING',
  308. "'''": 'TSQ_STRING',
  309. '"""': 'TDQ_STRING'
  310. }
  311. def begin_string_action(self, text):
  312. while text[:1] in any_string_prefix:
  313. text = text[1:]
  314. self.begin(self.string_states[text])
  315. self.produce('BEGIN_STRING')
  316. def end_string_action(self, text):
  317. self.begin('')
  318. self.produce('END_STRING')
  319. def unclosed_string_action(self, text):
  320. self.end_string_action(text)
  321. self.error("Unclosed string literal")
  322. def indentation_action(self, text):
  323. self.begin('')
  324. # Indentation within brackets should be ignored.
  325. #if self.bracket_nesting_level > 0:
  326. # return
  327. # Check that tabs and spaces are being used consistently.
  328. if text:
  329. c = text[0]
  330. #print "Scanner.indentation_action: indent with", repr(c) ###
  331. if self.indentation_char is None:
  332. self.indentation_char = c
  333. #print "Scanner.indentation_action: setting indent_char to", repr(c)
  334. else:
  335. if self.indentation_char != c:
  336. self.error("Mixed use of tabs and spaces")
  337. if text.replace(c, "") != "":
  338. self.error("Mixed use of tabs and spaces")
  339. # Figure out how many indents/dedents to do
  340. current_level = self.current_level()
  341. new_level = len(text)
  342. #print "Changing indent level from", current_level, "to", new_level ###
  343. if new_level == current_level:
  344. return
  345. elif new_level > current_level:
  346. #print "...pushing level", new_level ###
  347. self.indentation_stack.append(new_level)
  348. self.produce('INDENT', '')
  349. else:
  350. while new_level < self.current_level():
  351. #print "...popping level", self.indentation_stack[-1] ###
  352. self.indentation_stack.pop()
  353. self.produce('DEDENT', '')
  354. #print "...current level now", self.current_level() ###
  355. if new_level != self.current_level():
  356. self.error("Inconsistent indentation")
  357. def eof_action(self, text):
  358. while len(self.indentation_stack) > 1:
  359. self.produce('DEDENT', '')
  360. self.indentation_stack.pop()
  361. self.produce('EOF', '')
  362. def next(self):
  363. try:
  364. sy, systring = self.read()
  365. except UnrecognizedInput:
  366. self.error("Unrecognized character")
  367. return # just a marker, error() always raises
  368. if sy == IDENT:
  369. if systring in self.keywords:
  370. if systring == u'print' and print_function in self.context.future_directives:
  371. self.keywords.discard('print')
  372. elif systring == u'exec' and self.context.language_level >= 3:
  373. self.keywords.discard('exec')
  374. else:
  375. sy = systring
  376. systring = self.context.intern_ustring(systring)
  377. self.sy = sy
  378. self.systring = systring
  379. if False: # debug_scanner:
  380. _, line, col = self.position()
  381. if not self.systring or self.sy == self.systring:
  382. t = self.sy
  383. else:
  384. t = "%s %s" % (self.sy, self.systring)
  385. print("--- %3d %2d %s" % (line, col, t))
  386. def peek(self):
  387. saved = self.sy, self.systring
  388. self.next()
  389. next = self.sy, self.systring
  390. self.unread(*next)
  391. self.sy, self.systring = saved
  392. return next
  393. def put_back(self, sy, systring):
  394. self.unread(self.sy, self.systring)
  395. self.sy = sy
  396. self.systring = systring
  397. def unread(self, token, value):
  398. # This method should be added to Plex
  399. self.queue.insert(0, (token, value))
  400. def error(self, message, pos=None, fatal=True):
  401. if pos is None:
  402. pos = self.position()
  403. if self.sy == 'INDENT':
  404. error(pos, "Possible inconsistent indentation")
  405. err = error(pos, message)
  406. if fatal: raise err
  407. def expect(self, what, message=None):
  408. if self.sy == what:
  409. self.next()
  410. else:
  411. self.expected(what, message)
  412. def expect_keyword(self, what, message=None):
  413. if self.sy == IDENT and self.systring == what:
  414. self.next()
  415. else:
  416. self.expected(what, message)
  417. def expected(self, what, message=None):
  418. if message:
  419. self.error(message)
  420. else:
  421. if self.sy == IDENT:
  422. found = self.systring
  423. else:
  424. found = self.sy
  425. self.error("Expected '%s', found '%s'" % (what, found))
  426. def expect_indent(self):
  427. self.expect('INDENT', "Expected an increase in indentation level")
  428. def expect_dedent(self):
  429. self.expect('DEDENT', "Expected a decrease in indentation level")
  430. def expect_newline(self, message="Expected a newline", ignore_semicolon=False):
  431. # Expect either a newline or end of file
  432. useless_trailing_semicolon = None
  433. if ignore_semicolon and self.sy == ';':
  434. useless_trailing_semicolon = self.position()
  435. self.next()
  436. if self.sy != 'EOF':
  437. self.expect('NEWLINE', message)
  438. if useless_trailing_semicolon is not None:
  439. warning(useless_trailing_semicolon, "useless trailing semicolon")
  440. def enter_async(self):
  441. self.async_enabled += 1
  442. if self.async_enabled == 1:
  443. self.keywords.add('async')
  444. self.keywords.add('await')
  445. def exit_async(self):
  446. assert self.async_enabled > 0
  447. self.async_enabled -= 1
  448. if not self.async_enabled:
  449. self.keywords.discard('await')
  450. self.keywords.discard('async')
  451. if self.sy in ('async', 'await'):
  452. self.sy, self.systring = IDENT, self.context.intern_ustring(self.sy)