6 import cPickle as pickle
14 from Cython import Plex
15 from Cython.Plex import Scanner
16 from Cython.Plex.Errors import UnrecognizedInput
17 from Errors import CompileError, error
18 from Lexicon import string_prefixes, make_lexicon
20 plex_version = getattr(Plex, '_version', None)
21 #print "Plex version:", plex_version ###
25 scanner_debug_flags = 0
26 scanner_dump_file = None
27 binary_lexicon_pickle = 1
28 notify_lexicon_unpickling = 0
29 notify_lexicon_pickling = 1
33 #-----------------------------------------------------------------
35 def hash_source_file(path):
36 # Try to calculate a hash code for the given source file.
37 # Returns an empty string if the file cannot be accessed.
38 #print "Hashing", path ###
45 print("Unable to hash scanner source file (%s)" % e)
49 # Normalise spaces/tabs. We don't know what sort of
50 # space-tab substitution the file may have been
51 # through, so we replace all spans of spaces and
52 # tabs by a single space.
54 text = re.sub("[ \t]+", " ", text)
55 hash = md5.new(text).hexdigest()
58 def open_pickled_lexicon(expected_hash):
59 # Try to open pickled lexicon file and verify that
60 # it matches the source file. Returns the opened
61 # file if successful, otherwise None. ???
64 if os.path.exists(lexicon_pickle):
66 f = open(lexicon_pickle, "rb")
67 actual_hash = pickle.load(f)
68 if actual_hash == expected_hash:
72 print("Lexicon hash mismatch:") ###
73 print(" expected " + expected_hash) ###
74 print(" got " + actual_hash) ###
76 print("Warning: Unable to read pickled lexicon " + lexicon_pickle)
82 def try_to_unpickle_lexicon():
83 global lexicon, lexicon_pickle, lexicon_hash
84 dir = os.path.dirname(__file__)
85 source_file = os.path.join(dir, "Lexicon.py")
86 lexicon_hash = hash_source_file(source_file)
87 lexicon_pickle = os.path.join(dir, "Lexicon.pickle")
88 f = open_pickled_lexicon(expected_hash = lexicon_hash)
90 if notify_lexicon_unpickling:
92 print("Unpickling lexicon...")
93 lexicon = pickle.load(f)
95 if notify_lexicon_unpickling:
97 print("Done (%.2f seconds)" % (t1 - t0))
99 def create_new_lexicon():
102 print("Creating lexicon...")
103 lexicon = make_lexicon()
105 print("Done (%.2f seconds)" % (t1 - t0))
107 def pickle_lexicon():
110 f = open(lexicon_pickle, "wb")
112 print("Warning: Unable to save pickled lexicon in " + lexicon_pickle)
114 if notify_lexicon_pickling:
116 print("Pickling lexicon...")
117 pickle.dump(lexicon_hash, f, binary_lexicon_pickle)
118 pickle.dump(lexicon, f, binary_lexicon_pickle)
120 if notify_lexicon_pickling:
122 print("Done (%.2f seconds)" % (t1 - t0))
126 if not lexicon and plex_version is None:
127 try_to_unpickle_lexicon()
130 if plex_version is None:
134 #------------------------------------------------------------------
137 "global", "include", "ctypedef", "cdef", "def", "class",
138 "print", "del", "pass", "break", "continue", "return",
139 "raise", "import", "exec", "try", "except", "finally",
140 "while", "if", "elif", "else", "for", "in", "assert",
141 "and", "or", "not", "is", "in", "lambda", "from",
142 "NULL", "cimport", "by", "with", "cpdef", "DEF", "IF", "ELIF", "ELSE"
147 def __init__(self, name):
149 self.__name__ = name # for Plex tracing
151 def __call__(self, stream, text):
152 return getattr(stream, self.name)(text)
154 #------------------------------------------------------------------
156 def build_resword_dict():
158 for word in reserved_words:
162 #------------------------------------------------------------------
164 class CompileTimeScope(object):
166 def __init__(self, outer = None):
170 def declare(self, name, value):
171 self.entries[name] = value
173 def lookup_here(self, name):
174 return self.entries[name]
176 def lookup(self, name):
178 return self.lookup_here(name)
182 return outer.lookup(name)
186 def initial_compile_time_env():
187 benv = CompileTimeScope()
188 names = ('UNAME_SYSNAME', 'UNAME_NODENAME', 'UNAME_RELEASE',
189 'UNAME_VERSION', 'UNAME_MACHINE')
190 for name, value in zip(names, platform.uname()):
191 benv.declare(name, value)
193 names = ('False', 'True',
194 'abs', 'bool', 'chr', 'cmp', 'complex', 'dict', 'divmod', 'enumerate',
195 'float', 'hash', 'hex', 'int', 'len', 'list', 'long', 'map', 'max', 'min',
196 'oct', 'ord', 'pow', 'range', 'reduce', 'repr', 'round', 'slice', 'str',
197 'sum', 'tuple', 'xrange', 'zip')
199 benv.declare(name, getattr(__builtin__, name))
200 denv = CompileTimeScope(benv)
203 #------------------------------------------------------------------
205 class PyrexScanner(Scanner):
206 # context Context Compilation context
207 # type_names set Identifiers to be treated as type names
208 # compile_time_env dict Environment for conditional compilation
209 # compile_time_eval boolean In a true conditional compilation context
210 # compile_time_expr boolean In a compile-time expression context
212 resword_dict = build_resword_dict()
214 def __init__(self, file, filename, parent_scanner = None,
215 type_names = None, context = None):
216 Scanner.__init__(self, get_lexicon(), file, filename)
218 self.context = parent_scanner.context
219 self.type_names = parent_scanner.type_names
220 self.compile_time_env = parent_scanner.compile_time_env
221 self.compile_time_eval = parent_scanner.compile_time_eval
222 self.compile_time_expr = parent_scanner.compile_time_expr
224 self.context = context
225 self.type_names = type_names
226 self.compile_time_env = initial_compile_time_env()
227 self.compile_time_eval = 1
228 self.compile_time_expr = 0
229 self.trace = trace_scanner
230 self.indentation_stack = [0]
231 self.indentation_char = None
232 self.bracket_nesting_level = 0
237 def current_level(self):
238 return self.indentation_stack[-1]
240 def open_bracket_action(self, text):
241 self.bracket_nesting_level = self.bracket_nesting_level + 1
244 def close_bracket_action(self, text):
245 self.bracket_nesting_level = self.bracket_nesting_level - 1
248 def newline_action(self, text):
249 if self.bracket_nesting_level == 0:
251 self.produce('NEWLINE', '')
260 def begin_string_action(self, text):
261 if text[:1] in string_prefixes:
263 self.begin(self.string_states[text])
264 self.produce('BEGIN_STRING')
266 def end_string_action(self, text):
268 self.produce('END_STRING')
270 def unclosed_string_action(self, text):
271 self.end_string_action(text)
272 self.error("Unclosed string literal")
274 def indentation_action(self, text):
276 # Indentation within brackets should be ignored.
277 #if self.bracket_nesting_level > 0:
279 # Check that tabs and spaces are being used consistently.
282 #print "Scanner.indentation_action: indent with", repr(c) ###
283 if self.indentation_char is None:
284 self.indentation_char = c
285 #print "Scanner.indentation_action: setting indent_char to", repr(c)
287 if self.indentation_char != c:
288 self.error("Mixed use of tabs and spaces")
289 if text.replace(c, "") != "":
290 self.error("Mixed use of tabs and spaces")
291 # Figure out how many indents/dedents to do
292 current_level = self.current_level()
293 new_level = len(text)
294 #print "Changing indent level from", current_level, "to", new_level ###
295 if new_level == current_level:
297 elif new_level > current_level:
298 #print "...pushing level", new_level ###
299 self.indentation_stack.append(new_level)
300 self.produce('INDENT', '')
302 while new_level < self.current_level():
303 #print "...popping level", self.indentation_stack[-1] ###
304 self.indentation_stack.pop()
305 self.produce('DEDENT', '')
306 #print "...current level now", self.current_level() ###
307 if new_level != self.current_level():
308 self.error("Inconsistent indentation")
310 def eof_action(self, text):
311 while len(self.indentation_stack) > 1:
312 self.produce('DEDENT', '')
313 self.indentation_stack.pop()
314 self.produce('EOF', '')
318 sy, systring = self.read()
319 except UnrecognizedInput:
320 self.error("Unrecognized character")
321 if sy == 'IDENT' and systring in self.resword_dict:
324 self.systring = systring
326 _, line, col = self.position()
327 if not self.systring or self.sy == self.systring:
330 t = "%s %s" % (self.sy, self.systring)
331 print("--- %3d %2d %s" % (line, col, t))
333 def put_back(self, sy, systring):
334 self.unread(self.sy, self.systring)
336 self.systring = systring
338 def unread(self, token, value):
339 # This method should be added to Plex
340 self.queue.insert(0, (token, value))
342 def add_type_name(self, name):
343 self.type_names[name] = 1
345 def looking_at_type_name(self):
346 return self.sy == 'IDENT' and self.systring in self.type_names
348 def error(self, message, pos = None):
350 pos = self.position()
351 if self.sy == 'INDENT':
352 error(pos, "Possible inconsistent indentation")
353 raise error(pos, message)
355 def expect(self, what, message = None):
359 self.expected(what, message)
361 def expect_keyword(self, what, message = None):
362 if self.sy == 'IDENT' and self.systring == what:
365 self.expected(what, message)
367 def expected(self, what, message):
371 self.error("Expected '%s'" % what)
373 def expect_indent(self):
374 self.expect('INDENT',
375 "Expected an increase in indentation level")
377 def expect_dedent(self):
378 self.expect('DEDENT',
379 "Expected a decrease in indentation level")
381 def expect_newline(self, message = "Expected a newline"):
382 # Expect either a newline or end of file
384 self.expect('NEWLINE', message)