Ticket #11: dagss-branch-changes.diff
| File dagss-branch-changes.diff, 42.9 KB (added by dagss, 5 years ago) |
|---|
-
Cython/Compiler/Code.py
# HG changeset patch # User Dag Sverre Seljebotn <dagss@student.matnat.uio.no> # Date 1210947140 -7200 # Node ID 1caa3ed4962ba04c165bda6187a194fd05032b7a # Parent 9a731464ea49650627ac6a988518aee93e6991aa Replace filename strings with more generic source descriptors. This facilitates using the parser and compiler with runtime sources (such as strings), while still being able to provide context for error messages/C debugging comments. diff -r 9a731464ea49 -r 1caa3ed4962b Cython/Compiler/Code.py
a b 8 8 from Cython.Utils import open_new_file, open_source_file 9 9 from PyrexTypes import py_object_type, typecast 10 10 from TypeSlots import method_coexist 11 from Scanning import SourceDescriptor 11 12 12 13 class CCodeWriter: 13 14 # f file output file … … 89 90 def get_py_version_hex(self, pyversion): 90 91 return "0x%02X%02X%02X%02X" % (tuple(pyversion) + (0,0,0,0))[:4] 91 92 92 def file_contents(self, file):93 def file_contents(self, source_desc): 93 94 try: 94 return self.input_file_contents[ file]95 return self.input_file_contents[source_desc] 95 96 except KeyError: 96 97 F = [line.encode('ASCII', 'replace').replace( 97 98 '*/', '*[inserted by cython to avoid comment closer]/') 98 for line in open_source_file(file)]99 self.input_file_contents[ file] = F99 for line in source_desc.get_lines(decode=True)] 100 self.input_file_contents[source_desc] = F 100 101 return F 101 102 102 103 def mark_pos(self, pos): 103 104 if pos is None: 104 105 return 105 filename, line, col = pos 106 contents = self.file_contents(filename) 106 source_desc, line, col = pos 107 assert isinstance(source_desc, SourceDescriptor) 108 contents = self.file_contents(source_desc) 107 109 108 110 context = '' 109 111 for i in range(max(0,line-3), min(line+2, len(contents))): … … 112 114 s = s.rstrip() + ' # <<<<<<<<<<<<<< ' + '\n' 113 115 context += " * " + s 114 116 115 marker = '"%s":%d\n%s' % ( filename.encode('ASCII', 'replace'), line, context)117 marker = '"%s":%d\n%s' % (str(source_desc).encode('ASCII', 'replace'), line, context) 116 118 if self.last_marker != marker: 117 119 self.marker = marker 118 120 -
Cython/Compiler/Errors.py
diff -r 9a731464ea49 -r 1caa3ed4962b Cython/Compiler/Errors.py
a b 12 12 class PyrexWarning(Exception): 13 13 pass 14 14 15 15 16 def context(position): 16 F = open(position[0]).readlines() 17 s = ''.join(F[position[1]-6:position[1]]) 17 source = position[0] 18 assert not (isinstance(source, unicode) or isinstance(source, str)), ( 19 "Please replace filename strings with Scanning.FileSourceDescriptor instances %r" % source) 20 F = list(source.get_lines()) 21 s = ''.join(F[min(0, position[1]-6):position[1]]) 18 22 s += ' '*(position[2]-1) + '^' 19 23 s = '-'*60 + '\n...\n' + s + '\n' + '-'*60 + '\n' 20 24 return s 21 25 22 26 class CompileError(PyrexError): 23 27 24 28 def __init__(self, position = None, message = ""): -
Cython/Compiler/Main.py
diff -r 9a731464ea49 -r 1caa3ed4962b Cython/Compiler/Main.py
a b 9 9 10 10 from time import time 11 11 import Version 12 from Scanning import PyrexScanner 12 from Scanning import PyrexScanner, FileSourceDescriptor 13 13 import Errors 14 14 from Errors import PyrexError, CompileError, error 15 15 import Parsing … … 85 85 try: 86 86 if debug_find_module: 87 87 print("Context.find_module: Parsing %s" % pxd_pathname) 88 pxd_tree = self.parse(pxd_pathname, scope.type_names, pxd = 1, 88 source_desc = FileSourceDescriptor(pxd_pathname) 89 pxd_tree = self.parse(source_desc, scope.type_names, pxd = 1, 89 90 full_module_name = module_name) 90 91 pxd_tree.analyse_declarations(scope) 91 92 except CompileError: … … 116 117 # None if not found, but does not report an error. 117 118 dirs = self.include_directories 118 119 if pos: 119 here_dir = os.path.dirname(pos[0]) 120 file_desc = pos[0] 121 if not isinstance(file_desc, FileSourceDescriptor): 122 raise RuntimeError("Only file sources for code supported") 123 here_dir = os.path.dirname(file_desc.filename) 120 124 dirs = [here_dir] + dirs 121 125 for dir in dirs: 122 126 path = os.path.join(dir, filename) … … 137 141 self.modules[name] = scope 138 142 return scope 139 143 140 def parse(self, source_filename, type_names, pxd, full_module_name): 141 name = Utils.encode_filename(source_filename) 144 def parse(self, source_desc, type_names, pxd, full_module_name): 145 if not isinstance(source_desc, FileSourceDescriptor): 146 raise RuntimeError("Only file sources for code supported") 147 source_filename = Utils.encode_filename(source_desc.filename) 142 148 # Parse the given source file and return a parse tree. 143 149 try: 144 150 f = Utils.open_source_file(source_filename, "rU") 145 151 try: 146 s = PyrexScanner(f, name, source_encoding = f.encoding,152 s = PyrexScanner(f, source_desc, source_encoding = f.encoding, 147 153 type_names = type_names, context = self) 148 154 tree = Parsing.p_module(s, pxd, full_module_name) 149 155 finally: 150 156 f.close() 151 157 except UnicodeDecodeError, msg: 152 error(( name, 0, 0), "Decoding error, missing or incorrect coding=<encoding-name> at top of source (%s)" % msg)158 error((source_desc, 0, 0), "Decoding error, missing or incorrect coding=<encoding-name> at top of source (%s)" % msg) 153 159 if Errors.num_errors > 0: 154 160 raise CompileError 155 161 return tree … … 197 203 except EnvironmentError: 198 204 pass 199 205 module_name = full_module_name # self.extract_module_name(source, options) 206 source = FileSourceDescriptor(source) 200 207 initial_pos = (source, 1, 0) 201 208 scope = self.find_module(module_name, pos = initial_pos, need_pxd = 0) 202 209 errors_occurred = False … … 339 346 if any_failures: 340 347 sys.exit(1) 341 348 349 350 342 351 #------------------------------------------------------------------------ 343 352 # 344 353 # Set the default options depending on the platform -
Cython/Compiler/ModuleNode.py
diff -r 9a731464ea49 -r 1caa3ed4962b Cython/Compiler/ModuleNode.py
a b 427 427 code.putln("") 428 428 code.putln("static char *%s[] = {" % Naming.filenames_cname) 429 429 if code.filename_list: 430 for filenamein code.filename_list:431 filename = os.path.basename( filename)430 for source_desc in code.filename_list: 431 filename = os.path.basename(str(source_desc)) 432 432 escaped_filename = filename.replace("\\", "\\\\").replace('"', r'\"') 433 433 code.putln('"%s",' % 434 434 escaped_filename) -
Cython/Compiler/Parsing.py
diff -r 9a731464ea49 -r 1caa3ed4962b Cython/Compiler/Parsing.py
a b 5 5 import os, re 6 6 from string import join, replace 7 7 from types import ListType, TupleType 8 from Scanning import PyrexScanner 8 from Scanning import PyrexScanner, FileSourceDescriptor 9 9 import Nodes 10 10 import ExprNodes 11 11 from ModuleNode import ModuleNode … … 1182 1182 include_file_path = s.context.find_include_file(include_file_name, pos) 1183 1183 if include_file_path: 1184 1184 f = Utils.open_source_file(include_file_path, mode="rU") 1185 s2 = PyrexScanner(f, include_file_path, s, source_encoding=f.encoding) 1185 source_desc = FileSourceDescriptor(include_file_path) 1186 s2 = PyrexScanner(f, source_desc, s, source_encoding=f.encoding) 1186 1187 try: 1187 1188 tree = p_statement_list(s2, level) 1188 1189 finally: -
Cython/Compiler/Scanning.py
diff -r 9a731464ea49 -r 1caa3ed4962b Cython/Compiler/Scanning.py
a b 16 16 from Cython.Plex.Errors import UnrecognizedInput 17 17 from Errors import CompileError, error 18 18 from Lexicon import string_prefixes, make_lexicon 19 20 from Cython import Utils 19 21 20 22 plex_version = getattr(Plex, '_version', None) 21 23 #print "Plex version:", plex_version ### … … 203 205 204 206 #------------------------------------------------------------------ 205 207 208 class SourceDescriptor: 209 pass 210 211 class FileSourceDescriptor(SourceDescriptor): 212 """ 213 Represents a code source. A code source is a more generic abstraction 214 for a "filename" (as sometimes the code doesn't come from a file). 215 Instances of code sources are passed to Scanner.__init__ as the 216 optional name argument and will be passed back when asking for 217 the position()-tuple. 218 """ 219 def __init__(self, filename): 220 self.filename = filename 221 222 def get_lines(self, decode=False): 223 # decode is True when called from Code.py (which reserializes in a standard way to ASCII), 224 # while decode is False when called from Errors.py. 225 # 226 # Note that if changing Errors.py in this respect, raising errors over wrong encoding 227 # will no longer be able to produce the line where the encoding problem occurs ... 228 if decode: 229 return Utils.open_source_file(self.filename) 230 else: 231 return open(self.filename) 232 233 def __str__(self): 234 return self.filename 235 236 def __repr__(self): 237 return "<FileSourceDescriptor:%s>" % self 238 239 class StringSourceDescriptor(SourceDescriptor): 240 """ 241 Instances of this class can be used instead of a filenames if the 242 code originates from a string object. 243 """ 244 def __init__(self, name, code): 245 self.name = name 246 self.codelines = [x + "\n" for x in code.split("\n")] 247 248 def get_lines(self, decode=False): 249 return self.codelines 250 251 def __str__(self): 252 return self.name 253 254 def __repr__(self): 255 return "<StringSourceDescriptor:%s>" % self 256 257 #------------------------------------------------------------------ 258 206 259 class PyrexScanner(Scanner): 207 260 # context Context Compilation context 208 261 # type_names set Identifiers to be treated as type names -
Cython/Compiler/Transform.py
# HG changeset patch # User Dag Sverre Seljebotn <dagss@student.matnat.uio.no> # Date 1210951140 -7200 # Node ID e12195139626875f71dd708e3dab648b233dc330 # Parent 1caa3ed4962ba04c165bda6187a194fd05032b7a VisitorTransform + smaller Transform changes diff -r 1caa3ed4962b -r e12195139626 Cython/Compiler/Transform.py
a b 3 3 # 4 4 import Nodes 5 5 import ExprNodes 6 import inspect 6 7 7 8 class Transform(object): 8 # parent_stack [Node] A stack providing information about where in the tree 9 # we currently are. Nodes here should be considered 10 # read-only. 9 # parent_stack [Node] A stack providing information about where in the tree 10 # we currently are. Nodes here should be considered 11 # read-only. 12 # 13 # attr_stack [(string,int|None)] 14 # A stack providing information about the attribute names 15 # followed to get to the current location in the tree. 16 # The first tuple item is the attribute name, the second is 17 # the index if the attribute is a list, or None otherwise. 18 # 19 # 20 # Additionally, any keyword arguments to __call__ will be set as fields while in 21 # a transformation. 11 22 12 23 # Transforms for the parse tree should usually extend this class for convenience. 13 24 # The caller of a transform will only first call initialize and then process_node on … … 18 29 # return the input node untouched. Returning None will remove the node from the 19 30 # parent. 20 31 21 def __init__(self):22 self.parent_stack = []23 24 def initialize(self, phase, **options):25 pass26 27 32 def process_children(self, node): 28 33 """For all children of node, either process_list (if isinstance(node, list)) 29 34 or process_node (otherwise) is called.""" … … 36 41 newchild = self.process_list(child, childacc.name()) 37 42 if not isinstance(newchild, list): raise Exception("Cannot replace list with non-list!") 38 43 else: 39 newchild = self.process_node(child, childacc.name()) 44 self.attr_stack.append((childacc.name(), None)) 45 newchild = self.process_node(child) 40 46 if newchild is not None and not isinstance(newchild, Nodes.Node): 41 47 raise Exception("Cannot replace Node with non-Node!") 48 self.attr_stack.pop() 42 49 childacc.set(newchild) 43 50 self.parent_stack.pop() 44 51 45 def process_list(self, l, name): 46 """Calls process_node on all the items in l, using the name one gets when appending 47 [idx] to the name. Each item in l is transformed in-place by the item process_node 48 returns, then l is returned.""" 49 # Comment: If moving to a copying strategy, it might makes sense to return a 50 # new list instead. 52 def process_list(self, l, attrname): 53 """Calls process_node on all the items in l. Each item in l is transformed 54 in-place by the item process_node returns, then l is returned. If process_node 55 returns None, the item is removed from the list.""" 51 56 for idx in xrange(len(l)): 52 l[idx] = self.process_node(l[idx], "%s[%d]" % (name, idx)) 53 return l 57 self.attr_stack.append((attrname, idx)) 58 l[idx] = self.process_node(l[idx]) 59 self.attr_stack.pop() 60 return [x for x in l if x is not None] 54 61 55 def process_node(self, node , name):62 def process_node(self, node): 56 63 """Override this method to process nodes. name specifies which kind of relation the 57 64 parent has with child. This method should always return the node which the parent 58 65 should use for this relation, which can either be the same node, None to remove 59 66 the node, or a different node.""" 60 67 raise NotImplementedError("Not implemented") 68 69 def __call__(self, root, **params): 70 self.parent_stack = [] 71 self.attr_stack = [] 72 for key, value in params.iteritems(): 73 setattr(self, key, value) 74 root = self.process_node(root) 75 for key, value in params.iteritems(): 76 delattr(self, key) 77 del self.parent_stack 78 del self.attr_stack 79 return root 80 81 82 class VisitorTransform(Transform): 83 84 # Note: If needed, this can be replaced with a more efficient metaclass 85 # approach, resolving the jump table at module load time. 86 87 def __init__(self, readonly=False, **kw): 88 """readonly - If this is set to True, the results of process_node 89 will be discarded (so that one can return None without changing 90 the tree).""" 91 super(VisitorTransform, self).__init__(**kw) 92 self.visitmethods = {'process_' : {}, 'pre_' : {}, 'post_' : {}} 93 self.attrname = "" 94 self.readonly = readonly 95 96 def get_visitfunc(self, prefix, cls): 97 mname = prefix + cls.__name__ 98 m = self.visitmethods[prefix].get(mname) 99 if m is None: 100 # Must resolve, try entire hierarchy 101 for cls in inspect.getmro(cls): 102 m = getattr(self, prefix + cls.__name__, None) 103 if m is not None: 104 break 105 if m is None: raise RuntimeError("Not a Node descendant: " + cls.__name__) 106 self.visitmethods[prefix][mname] = m 107 return m 108 109 def process_node(self, node, name="_"): 110 # Pass on to calls registered in self.visitmethods 111 self.attrname = name 112 if node is None: 113 return None 114 result = self.get_visitfunc("process_", node.__class__)(node) 115 if self.readonly: 116 return node 117 else: 118 return result 119 120 def process_Node(self, node): 121 descend = self.get_visitfunc("pre_", node.__class__)(node) 122 if descend: 123 self.process_children(node) 124 self.get_visitfunc("post_", node.__class__)(node) 125 return node 126 127 def pre_Node(self, node): 128 return True 129 130 def post_Node(self, node): 131 pass 132 133 134 # Utils 135 def ensure_statlist(node): 136 if not isinstance(node, Nodes.StatListNode): 137 node = Nodes.StatListNode(pos=node.pos, stats=[node]) 138 return node 139 61 140 62 141 class PrintTree(Transform): 63 142 """Prints a representation of the tree to standard output. … … 72 151 def unindent(self): 73 152 self._indent = self._indent[:-2] 74 153 75 def initialize(self, phase, **options):154 def __call__(self, tree, phase=None, **params): 76 155 print("Parse tree dump at phase '%s'" % phase) 156 super(PrintTree, self).__call__(tree, phase=phase, **params) 77 157 78 158 # Don't do anything about process_list, the defaults gives 79 159 # nice-looking name[idx] nodes which will visually appear 80 160 # under the parent-node, not displaying the list itself in 81 161 # the hierarchy. 82 162 83 def process_node(self, node, name): 163 def process_node(self, node): 164 if len(self.attr_stack) == 0: 165 name = "(root)" 166 else: 167 attr, idx = self.attr_stack[-1] 168 if idx is not None: 169 name = "%s[%d]" % (attr, idx) 170 else: 171 name = attr 84 172 print("%s- %s: %s" % (self._indent, name, self.repr_of(node))) 85 173 self.indent() 86 174 self.process_children(node) … … 92 180 return "(none)" 93 181 else: 94 182 result = node.__class__.__name__ 95 if isinstance(node, ExprNodes.ExprNode): 183 if isinstance(node, ExprNodes.NameNode): 184 result += "(type=%s, name=\"%s\")" % (repr(node.type), node.name) 185 elif isinstance(node, Nodes.DefNode): 186 result += "(name=\"%s\")" % node.name 187 elif isinstance(node, ExprNodes.ExprNode): 96 188 t = node.type 97 189 result += "(type=%s)" % repr(t) 190 98 191 return result 99 192 100 193 … … 108 201 for name in PHASES: 109 202 self[name] = [] 110 203 def run(self, name, node, **options): 111 assert name in self 204 assert name in self, "Transform phase %s not defined" % name 112 205 for transform in self[name]: 113 transform.initialize(phase=name, **options) 114 transform.process_node(node, "(root)") 206 transform(node, phase=name, **options) 115 207 116 208 -
Cython/Compiler/ModuleNode.py
# HG changeset patch # User Dag Sverre Seljebotn <dagss@student.matnat.uio.no> # Date 1210951173 -7200 # Node ID 7141900aa6a46503724501409bdac66e582829a8 # Parent e12195139626875f71dd708e3dab648b233dc330 Fixed typo children_attrs -> child_attrs diff -r e12195139626 -r 7141900aa6a4 Cython/Compiler/ModuleNode.py
a b 33 33 # module_temp_cname string 34 34 # full_module_name string 35 35 36 child ren_attrs = ["body"]36 child_attrs = ["body"] 37 37 38 38 def analyse_declarations(self, env): 39 39 if Options.embed_pos_in_docstring: -
Cython/Compiler/Transform.py
# HG changeset patch # User Dag Sverre Seljebotn <dagss@student.matnat.uio.no> # Date 1210953293 -7200 # Node ID c93feb4713475cbb5218157ea61f1c751716a3e0 # Parent 7141900aa6a46503724501409bdac66e582829a8 Added ReadonlyVisitor. There was an option in VisitorTransform for this but it was way too obscure, better to have a seperate class. diff -r 7141900aa6a4 -r c93feb471347 Cython/Compiler/Transform.py
a b 84 84 # Note: If needed, this can be replaced with a more efficient metaclass 85 85 # approach, resolving the jump table at module load time. 86 86 87 def __init__(self, readonly=False,**kw):87 def __init__(self, **kw): 88 88 """readonly - If this is set to True, the results of process_node 89 89 will be discarded (so that one can return None without changing 90 90 the tree).""" 91 91 super(VisitorTransform, self).__init__(**kw) 92 92 self.visitmethods = {'process_' : {}, 'pre_' : {}, 'post_' : {}} 93 self.attrname = ""94 self.readonly = readonly95 93 96 94 def get_visitfunc(self, prefix, cls): 97 95 mname = prefix + cls.__name__ … … 106 104 self.visitmethods[prefix][mname] = m 107 105 return m 108 106 109 def process_node(self, node , name="_"):107 def process_node(self, node): 110 108 # Pass on to calls registered in self.visitmethods 111 self.attrname = name112 109 if node is None: 113 110 return None 114 111 result = self.get_visitfunc("process_", node.__class__)(node) 115 if self.readonly: 116 return node 117 else: 118 return result 112 return node 119 113 120 114 def process_Node(self, node): 121 115 descend = self.get_visitfunc("pre_", node.__class__)(node) … … 130 124 def post_Node(self, node): 131 125 pass 132 126 127 class ReadonlyVisitor(VisitorTransform): 128 """ 129 Like VisitorTransform, however process_X methods do not have to return 130 the result node -- the result of process_X is always discarded and the 131 structure of the original tree is not changed. 132 """ 133 def process_node(self, node): 134 super(ReadonlyVisitor, self).process_node(node) # discard result 135 return node 133 136 134 137 # Utils 135 138 def ensure_statlist(node): -
Cython/Compiler/Nodes.py
# HG changeset patch # User Dag Sverre Seljebotn <dagss@student.matnat.uio.no> # Date 1210954141 -7200 # Node ID 3ed80b6f894b5f7faba4872d74565b44cfcdd22a # Parent c93feb4713475cbb5218157ea61f1c751716a3e0 Added Node.clone_node utility. A method for cloning nodes. I expect this one to work on all descandants, but it can be overriden if a node has special needs. It seems natural to put such core functionality in the node classes rather than in a visitor. diff -r c93feb471347 -r 3ed80b6f894b Cython/Compiler/Nodes.py
a b 2 2 # Pyrex - Parse tree nodes 3 3 # 4 4 5 import string, sys, os, time 5 import string, sys, os, time, copy 6 6 7 7 import Code 8 8 from Errors import error, warning, InternalError … … 149 149 """Utility method for more easily implementing get_child_accessors. 150 150 If you override get_child_accessors then this method is not used.""" 151 151 return self.child_attrs 152 153 def clone_node(self): 154 """Clone the node. This is defined as a shallow copy, except for member lists 155 amongst the child attributes (from get_child_accessors) which are also 156 copied. Lists containing child nodes are thus seen as a way for the node 157 to hold multiple children directly; the list is not treated as a seperate 158 level in the tree.""" 159 c = copy.copy(self) 160 for acc in c.get_child_accessors(): 161 value = acc.get() 162 if isinstance(value, list): 163 acc.set([x for x in value]) 164 return c 152 165 153 166 154 167 # -
(a) /dev/null vs. (b) b/Cython/CodeWriter.py
# HG changeset patch # User Dag Sverre Seljebotn <dagss@student.matnat.uio.no> # Date 1210954341 -7200 # Node ID 7e8ca264b2812a5ce9bce28a9be56e2a6b333e5f # Parent 3ed80b6f894b5f7faba4872d74565b44cfcdd22a New features: CodeWriter, TreeFragment, and a transform unit test framework. See the documentation of each class for details. It is a rather big commit, however seperating it is non-trivial. The tests for all of these features all rely on using each other, so there's a circular dependency in the tests and I wanted to commit the tests and features at the same time. (However, the non-test-code does not have a circular dependency.) diff -r 3ed80b6f894b -r 7e8ca264b281 Cython/CodeWriter.py
a b 1 from Cython.Compiler.Transform import ReadonlyVisitor 2 from Cython.Compiler.Nodes import * 3 4 """ 5 Serializes a Cython code tree to Cython code. This is primarily useful for 6 debugging and testing purposes. 7 8 The output is in a strict format, no whitespace or comments from the input 9 is preserved (and it could not be as it is not present in the code tree). 10 """ 11 12 class LinesResult(object): 13 def __init__(self): 14 self.lines = [] 15 self.s = u"" 16 17 def put(self, s): 18 self.s += s 19 20 def newline(self): 21 self.lines.append(self.s) 22 self.s = u"" 23 24 def putline(self, s): 25 self.put(s) 26 self.newline() 27 28 class CodeWriter(ReadonlyVisitor): 29 30 indent_string = u" " 31 32 def __init__(self, result = None): 33 super(CodeWriter, self).__init__() 34 if result is None: 35 result = LinesResult() 36 self.result = result 37 self.numindents = 0 38 39 def indent(self): 40 self.numindents += 1 41 42 def dedent(self): 43 self.numindents -= 1 44 45 def startline(self, s = u""): 46 self.result.put(self.indent_string * self.numindents + s) 47 48 def put(self, s): 49 self.result.put(s) 50 51 def endline(self, s = u""): 52 self.result.putline(s) 53 54 def line(self, s): 55 self.startline(s) 56 self.endline() 57 58 def comma_seperated_list(self, items, output_rhs=False): 59 if len(items) > 0: 60 for item in items[:-1]: 61 self.process_node(item) 62 if output_rhs and item.rhs is not None: 63 self.put(u" = ") 64 self.process_node(item.rhs) 65 self.put(u", ") 66 self.process_node(items[-1]) 67 68 def process_Node(self, node): 69 raise AssertionError("Node not handled by serializer: %r" % node) 70 71 def process_ModuleNode(self, node): 72 self.process_children(node) 73 74 def process_StatListNode(self, node): 75 self.process_children(node) 76 77 def process_FuncDefNode(self, node): 78 self.startline(u"def %s(" % node.name) 79 self.comma_seperated_list(node.args) 80 self.endline(u"):") 81 self.indent() 82 self.process_node(node.body) 83 self.dedent() 84 85 def process_CArgDeclNode(self, node): 86 if node.base_type.name is not None: 87 self.process_node(node.base_type) 88 self.put(u" ") 89 self.process_node(node.declarator) 90 if node.default is not None: 91 self.put(u" = ") 92 self.process_node(node.default) 93 94 def process_CNameDeclaratorNode(self, node): 95 self.put(node.name) 96 97 def process_CSimpleBaseTypeNode(self, node): 98 # See Parsing.p_sign_and_longness 99 if node.is_basic_c_type: 100 self.put(("unsigned ", "", "signed ")[node.signed]) 101 if node.longness < 0: 102 self.put("short " * -node.longness) 103 elif node.longness > 0: 104 self.put("long " * node.longness) 105 106 self.put(node.name) 107 108 def process_SingleAssignmentNode(self, node): 109 self.startline() 110 self.process_node(node.lhs) 111 self.put(u" = ") 112 self.process_node(node.rhs) 113 self.endline() 114 115 def process_NameNode(self, node): 116 self.put(node.name) 117 118 def process_IntNode(self, node): 119 self.put(node.value) 120 121 def process_IfStatNode(self, node): 122 # The IfClauseNode is handled directly without a seperate match 123 # for clariy. 124 self.startline(u"if ") 125 self.process_node(node.if_clauses[0].condition) 126 self.endline(":") 127 self.indent() 128 self.process_node(node.if_clauses[0].body) 129 self.dedent() 130 for clause in node.if_clauses[1:]: 131 self.startline("elif ") 132 self.process_node(clause.condition) 133 self.endline(":") 134 self.indent() 135 self.process_node(clause.body) 136 self.dedent() 137 if node.else_clause is not None: 138 self.line("else:") 139 self.indent() 140 self.process_node(node.else_clause) 141 self.dedent() 142 143 def process_PassStatNode(self, node): 144 self.startline(u"pass") 145 self.endline() 146 147 def process_PrintStatNode(self, node): 148 self.startline(u"print ") 149 self.comma_seperated_list(node.args) 150 if node.ends_with_comma: 151 self.put(u",") 152 self.endline() 153 154 def process_BinopNode(self, node): 155 self.process_node(node.operand1) 156 self.put(u" %s " % node.operator) 157 self.process_node(node.operand2) 158 159 def process_CVarDefNode(self, node): 160 self.startline(u"cdef ") 161 self.process_node(node.base_type) 162 self.put(u" ") 163 self.comma_seperated_list(node.declarators, output_rhs=True) 164 self.endline() 165 166 def process_ForInStatNode(self, node): 167 self.startline(u"for ") 168 self.process_node(node.target) 169 self.put(u" in ") 170 self.process_node(node.iterator.sequence) 171 self.endline(u":") 172 self.indent() 173 self.process_node(node.body) 174 self.dedent() 175 if node.else_clause is not None: 176 self.line(u"else:") 177 self.indent() 178 self.process_node(node.else_clause) 179 self.dedent() 180 181 def process_SequenceNode(self, node): 182 self.comma_seperated_list(node.args) # Might need to discover whether we need () around tuples...hmm... 183 184 def process_SimpleCallNode(self, node): 185 self.put(node.function.name + u"(") 186 self.comma_seperated_list(node.args) 187 self.put(")") 188 189 def process_ExprStatNode(self, node): 190 self.startline() 191 self.process_node(node.expr) 192 self.endline() 193 194 def process_InPlaceAssignmentNode(self, node): 195 self.startline() 196 self.process_node(node.lhs) 197 self.put(" %s= " % node.operator) 198 self.process_node(node.rhs) 199 self.endline() 200 201 202 -
(a) /dev/null vs. (b) b/Cython/Compiler/Tests/TestTreeFragment.py
diff -r 3ed80b6f894b -r 7e8ca264b281 Cython/Compiler/Tests/TestTreeFragment.py
a b 1 from Cython.TestUtils import CythonTest 2 from Cython.Compiler.TreeFragment import * 3 4 class TestTreeFragments(CythonTest): 5 def test_basic(self): 6 F = self.fragment(u"x = 4") 7 T = F.copy() 8 self.assertCode(u"x = 4", T) 9 10 def test_copy_is_independent(self): 11 F = self.fragment(u"if True: x = 4") 12 T1 = F.root 13 T2 = F.copy() 14 self.assertEqual("x", T2.body.if_clauses[0].body.lhs.name) 15 T2.body.if_clauses[0].body.lhs.name = "other" 16 self.assertEqual("x", T1.body.if_clauses[0].body.lhs.name) 17 18 def test_substitution(self): 19 F = self.fragment(u"x = 4") 20 y = NameNode(pos=None, name=u"y") 21 T = F.substitute({"x" : y}) 22 self.assertCode(u"y = 4", T) 23 24 if __name__ == "__main__": 25 import unittest 26 unittest.main() -
(a) /dev/null vs. (b) b/Cython/Compiler/Tests/__init__.py
diff -r 3ed80b6f894b -r 7e8ca264b281 Cython/Compiler/Tests/__init__.py
a b 1 #empty -
Cython/Compiler/Transform.py
diff -r 3ed80b6f894b -r 7e8ca264b281 Cython/Compiler/Transform.py
a b 109 109 if node is None: 110 110 return None 111 111 result = self.get_visitfunc("process_", node.__class__)(node) 112 return node112 return result 113 113 114 114 def process_Node(self, node): 115 115 descend = self.get_visitfunc("pre_", node.__class__)(node) -
(a) /dev/null vs. (b) b/Cython/Compiler/TreeFragment.py
diff -r 3ed80b6f894b -r 7e8ca264b281 Cython/Compiler/TreeFragment.py
a b 1 # 2 # TreeFragments - parsing of strings to trees 3 # 4 5 import re 6 from cStringIO import StringIO 7 from Scanning import PyrexScanner, StringSourceDescriptor 8 from Symtab import BuiltinScope, ModuleScope 9 from Transform import Transform, VisitorTransform 10 from Nodes import Node 11 from ExprNodes import NameNode 12 import Parsing 13 import Main 14 15 """ 16 Support for parsing strings into code trees. 17 """ 18 19 class StringParseContext(Main.Context): 20 def __init__(self, include_directories, name): 21 Main.Context.__init__(self, include_directories) 22 self.module_name = name 23 24 def find_module(self, module_name, relative_to = None, pos = None, need_pxd = 1): 25 if module_name != self.module_name: 26 raise AssertionError("Not yet supporting any cimports/includes from string code snippets") 27 return ModuleScope(module_name, parent_module = None, context = self) 28 29 def parse_from_strings(name, code, pxds={}): 30 """ 31 Utility method to parse a (unicode) string of code. This is mostly 32 used for internal Cython compiler purposes (creating code snippets 33 that transforms should emit, as well as unit testing). 34 35 code - a unicode string containing Cython (module-level) code 36 name - a descriptive name for the code source (to use in error messages etc.) 37 """ 38 39 # Since source files carry an encoding, it makes sense in this context 40 # to use a unicode string so that code fragments don't have to bother 41 # with encoding. This means that test code passed in should not have an 42 # encoding header. 43 assert isinstance(code, unicode), "unicode code snippets only please" 44 encoding = "UTF-8" 45 46 module_name = name 47 initial_pos = (name, 1, 0) 48 code_source = StringSourceDescriptor(name, code) 49 50 context = StringParseContext([], name) 51 scope = context.find_module(module_name, pos = initial_pos, need_pxd = 0) 52 53 buf = StringIO(code.encode(encoding)) 54 55 scanner = PyrexScanner(buf, code_source, source_encoding = encoding, 56 type_names = scope.type_names, context = context) 57 tree = Parsing.p_module(scanner, 0, module_name) 58 return tree 59 60 class TreeCopier(Transform): 61 def process_node(self, node): 62 if node is None: 63 return node 64 else: 65 c = node.clone_node() 66 self.process_children(c) 67 return c 68 69 class SubstitutionTransform(VisitorTransform): 70 def process_Node(self, node): 71 if node is None: 72 return node 73 else: 74 c = node.clone_node() 75 self.process_children(c) 76 return c 77 78 def process_NameNode(self, node): 79 if node.name in self.substitute: 80 # Name matched, substitute node 81 return self.substitute[node.name] 82 else: 83 # Clone 84 return self.process_Node(node) 85 86 def copy_code_tree(node): 87 return TreeCopier()(node) 88 89 INDENT_RE = re.compile(ur"^ *") 90 def strip_common_indent(lines): 91 "Strips empty lines and common indentation from the list of strings given in lines" 92 lines = [x for x in lines if x.strip() != u""] 93 minindent = min(len(INDENT_RE.match(x).group(0)) for x in lines) 94 lines = [x[minindent:] for x in lines] 95 return lines 96 97 class TreeFragment(object): 98 def __init__(self, code, name, pxds={}): 99 if isinstance(code, unicode): 100 def fmt(x): return u"\n".join(strip_common_indent(x.split(u"\n"))) 101 102 fmt_code = fmt(code) 103 fmt_pxds = {} 104 for key, value in pxds.iteritems(): 105 fmt_pxds[key] = fmt(value) 106 107 self.root = parse_from_strings(name, fmt_code, fmt_pxds) 108 elif isinstance(code, Node): 109 if pxds != {}: raise NotImplementedError() 110 self.root = code 111 else: 112 raise ValueError("Unrecognized code format (accepts unicode and Node)") 113 114 def copy(self): 115 return copy_code_tree(self.root) 116 117 def substitute(self, nodes={}): 118 return SubstitutionTransform()(self.root, substitute = nodes) 119 120 121 122 -
(a) /dev/null vs. (b) b/Cython/TestUtils.py
diff -r 3ed80b6f894b -r 7e8ca264b281 Cython/TestUtils.py
a b 1 import Cython.Compiler.Errors as Errors 2 from Cython.CodeWriter import CodeWriter 3 import unittest 4 from Cython.Compiler.ModuleNode import ModuleNode 5 import Cython.Compiler.Main as Main 6 from Cython.Compiler.TreeFragment import TreeFragment, strip_common_indent 7 8 class CythonTest(unittest.TestCase): 9 def assertCode(self, expected, result_tree): 10 writer = CodeWriter() 11 writer(result_tree) 12 result_lines = writer.result.lines 13 14 expected_lines = strip_common_indent(expected.split("\n")) 15 16 for idx, (line, expected_line) in enumerate(zip(result_lines, expected_lines)): 17 self.assertEqual(expected_line, line, "Line %d:\nGot: %s\nExp: %s" % (idx, line, expected_line)) 18 self.assertEqual(len(result_lines), len(expected_lines), 19 "Unmatched lines. Got:\n%s\nExpected:\n%s" % ("\n".join(result_lines), expected)) 20 21 def fragment(self, code, pxds={}): 22 "Simply create a tree fragment using the name of the test-case in parse errors." 23 name = self.id() 24 if name.startswith("__main__."): name = name[len("__main__."):] 25 name = name.replace(".", "_") 26 return TreeFragment(code, name, pxds) 27 28 29 class TransformTest(CythonTest): 30 """ 31 Utility base class for transform unit tests. It is based around constructing 32 test trees (either explicitly or by parsing a Cython code string); running 33 the transform, serialize it using a customized Cython serializer (with 34 special markup for nodes that cannot be represented in Cython), 35 and do a string-comparison line-by-line of the result. 36 37 To create a test case: 38 - Call run_pipeline. The pipeline should at least contain the transform you 39 are testing; pyx should be either a string (passed to the parser to 40 create a post-parse tree) or a ModuleNode representing input to pipeline. 41 The result will be a transformed result (usually a ModuleNode). 42 43 - Check that the tree is correct. If wanted, assertCode can be used, which 44 takes a code string as expected, and a ModuleNode in result_tree 45 (it serializes the ModuleNode to a string and compares line-by-line). 46 47 All code strings are first stripped for whitespace lines and then common 48 indentation. 49 50 Plans: One could have a pxd dictionary parameter to run_pipeline. 51 """ 52 53 54 def run_pipeline(self, pipeline, pyx, pxds={}): 55 tree = self.fragment(pyx, pxds).root 56 assert isinstance(tree, ModuleNode) 57 # Run pipeline 58 for T in pipeline: 59 tree = T(tree) 60 return tree 61 -
(a) /dev/null vs. (b) b/Cython/Tests/TestCodeWriter.py
diff -r 3ed80b6f894b -r 7e8ca264b281 Cython/Tests/TestCodeWriter.py
a b 1 from Cython.TestUtils import CythonTest 2 3 class TestCodeWriter(CythonTest): 4 # CythonTest uses the CodeWriter heavily, so do some checking by 5 # roundtripping Cython code through the test framework. 6 7 # Note that this test is dependant upon the normal Cython parser 8 # to generate the input trees to the CodeWriter. This save *a lot* 9 # of time; better to spend that time writing other tests than perfecting 10 # this one... 11 12 # Whitespace is very significant in this process: 13 # - always newline on new block (!) 14 # - indent 4 spaces 15 # - 1 space around every operator 16 17 def t(self, codestr): 18 self.assertCode(codestr, self.fragment(codestr).root) 19 20 def test_print(self): 21 self.t(u""" 22 print x, y 23 print x + y ** 2 24 print x, y, z, 25 """) 26 27 def test_if(self): 28 self.t(u"if x:\n pass") 29 30 def test_ifelifelse(self): 31 self.t(u""" 32 if x: 33 pass 34 elif y: 35 pass 36 elif z + 34 ** 34 - 2: 37 pass 38 else: 39 pass 40 """) 41 42 def test_def(self): 43 self.t(u""" 44 def f(x, y, z): 45 pass 46 def f(x = 34, y = 54, z): 47 pass 48 """) 49 50 def test_longness_and_signedness(self): 51 self.t(u"def f(unsigned long long long long long int y):\n pass") 52 53 def test_signed_short(self): 54 self.t(u"def f(signed short int y):\n pass") 55 56 def test_typed_args(self): 57 self.t(u"def f(int x, unsigned long int y):\n pass") 58 59 def test_cdef_var(self): 60 self.t(u""" 61 cdef int hello 62 cdef int hello = 4, x = 3, y, z 63 """) 64 65 def test_for_loop(self): 66 self.t(u""" 67 for x, y, z in f(g(h(34) * 2) + 23): 68 print x, y, z 69 else: 70 print 43 71 """) 72 73 def test_inplace_assignment(self): 74 self.t(u"x += 43") 75 76 if __name__ == "__main__": 77 import unittest 78 unittest.main() 79 -
(a) /dev/null vs. (b) b/Cython/Tests/__init__.py
diff -r 3ed80b6f894b -r 7e8ca264b281 Cython/Tests/__init__.py
a b 1 #empty
