Ticket #11: dagss-branch-changes.diff

File dagss-branch-changes.diff, 42.9 KB (added by dagss, 6 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  
    88from Cython.Utils import open_new_file, open_source_file 
    99from PyrexTypes import py_object_type, typecast 
    1010from TypeSlots import method_coexist 
     11from Scanning import SourceDescriptor 
    1112 
    1213class CCodeWriter: 
    1314    # f                file            output file 
     
    8990    def get_py_version_hex(self, pyversion): 
    9091        return "0x%02X%02X%02X%02X" % (tuple(pyversion) + (0,0,0,0))[:4] 
    9192 
    92     def file_contents(self, file): 
     93    def file_contents(self, source_desc): 
    9394        try: 
    94             return self.input_file_contents[file] 
     95            return self.input_file_contents[source_desc] 
    9596        except KeyError: 
    9697            F = [line.encode('ASCII', 'replace').replace( 
    9798                    '*/', '*[inserted by cython to avoid comment closer]/') 
    98                  for line in open_source_file(file)] 
    99             self.input_file_contents[file] = F 
     99                 for line in source_desc.get_lines(decode=True)] 
     100            self.input_file_contents[source_desc] = F 
    100101            return F 
    101102 
    102103    def mark_pos(self, pos): 
    103104        if pos is None: 
    104105            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) 
    107109 
    108110        context = '' 
    109111        for i in range(max(0,line-3), min(line+2, len(contents))): 
     
    112114                s = s.rstrip() + '             # <<<<<<<<<<<<<< ' + '\n' 
    113115            context += " * " + s 
    114116 
    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) 
    116118        if self.last_marker != marker: 
    117119            self.marker = marker 
    118120 
  • Cython/Compiler/Errors.py

    diff -r 9a731464ea49 -r 1caa3ed4962b Cython/Compiler/Errors.py
    a b  
    1212class PyrexWarning(Exception): 
    1313    pass 
    1414 
     15 
    1516def 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]]) 
    1822    s += ' '*(position[2]-1) + '^' 
    1923    s = '-'*60 + '\n...\n' + s + '\n' + '-'*60 + '\n' 
    2024    return s 
    21  
     25     
    2226class CompileError(PyrexError): 
    2327     
    2428    def __init__(self, position = None, message = ""): 
  • Cython/Compiler/Main.py

    diff -r 9a731464ea49 -r 1caa3ed4962b Cython/Compiler/Main.py
    a b  
    99 
    1010from time import time 
    1111import Version 
    12 from Scanning import PyrexScanner 
     12from Scanning import PyrexScanner, FileSourceDescriptor 
    1313import Errors 
    1414from Errors import PyrexError, CompileError, error 
    1515import Parsing 
     
    8585                try: 
    8686                    if debug_find_module: 
    8787                        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, 
    8990                                          full_module_name = module_name) 
    9091                    pxd_tree.analyse_declarations(scope) 
    9192                except CompileError: 
     
    116117        # None if not found, but does not report an error. 
    117118        dirs = self.include_directories 
    118119        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) 
    120124            dirs = [here_dir] + dirs 
    121125        for dir in dirs: 
    122126            path = os.path.join(dir, filename) 
     
    137141            self.modules[name] = scope 
    138142        return scope 
    139143 
    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) 
    142148        # Parse the given source file and return a parse tree. 
    143149        try: 
    144150            f = Utils.open_source_file(source_filename, "rU") 
    145151            try: 
    146                 s = PyrexScanner(f, name, source_encoding = f.encoding, 
     152                s = PyrexScanner(f, source_desc, source_encoding = f.encoding, 
    147153                                 type_names = type_names, context = self) 
    148154                tree = Parsing.p_module(s, pxd, full_module_name) 
    149155            finally: 
    150156                f.close() 
    151157        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) 
    153159        if Errors.num_errors > 0: 
    154160            raise CompileError 
    155161        return tree 
     
    197203            except EnvironmentError: 
    198204                pass 
    199205        module_name = full_module_name # self.extract_module_name(source, options) 
     206        source = FileSourceDescriptor(source) 
    200207        initial_pos = (source, 1, 0) 
    201208        scope = self.find_module(module_name, pos = initial_pos, need_pxd = 0) 
    202209        errors_occurred = False 
     
    339346    if any_failures: 
    340347        sys.exit(1) 
    341348 
     349 
     350 
    342351#------------------------------------------------------------------------ 
    343352# 
    344353#  Set the default options depending on the platform 
  • Cython/Compiler/ModuleNode.py

    diff -r 9a731464ea49 -r 1caa3ed4962b Cython/Compiler/ModuleNode.py
    a b  
    427427        code.putln("") 
    428428        code.putln("static char *%s[] = {" % Naming.filenames_cname) 
    429429        if code.filename_list: 
    430             for filename in 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)) 
    432432                escaped_filename = filename.replace("\\", "\\\\").replace('"', r'\"') 
    433433                code.putln('"%s",' %  
    434434                    escaped_filename) 
  • Cython/Compiler/Parsing.py

    diff -r 9a731464ea49 -r 1caa3ed4962b Cython/Compiler/Parsing.py
    a b  
    55import os, re 
    66from string import join, replace 
    77from types import ListType, TupleType 
    8 from Scanning import PyrexScanner 
     8from Scanning import PyrexScanner, FileSourceDescriptor 
    99import Nodes 
    1010import ExprNodes 
    1111from ModuleNode import ModuleNode 
     
    11821182        include_file_path = s.context.find_include_file(include_file_name, pos) 
    11831183        if include_file_path: 
    11841184            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) 
    11861187            try: 
    11871188                tree = p_statement_list(s2, level) 
    11881189            finally: 
  • Cython/Compiler/Scanning.py

    diff -r 9a731464ea49 -r 1caa3ed4962b Cython/Compiler/Scanning.py
    a b  
    1616from Cython.Plex.Errors import UnrecognizedInput 
    1717from Errors import CompileError, error 
    1818from Lexicon import string_prefixes, make_lexicon 
     19 
     20from Cython import Utils 
    1921 
    2022plex_version = getattr(Plex, '_version', None) 
    2123#print "Plex version:", plex_version ### 
     
    203205 
    204206#------------------------------------------------------------------ 
    205207 
     208class SourceDescriptor: 
     209    pass 
     210 
     211class 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 
     239class 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 
    206259class PyrexScanner(Scanner): 
    207260    #  context            Context  Compilation context 
    208261    #  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  
    33# 
    44import Nodes 
    55import ExprNodes 
     6import inspect 
    67 
    78class 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. 
    1122 
    1223    # Transforms for the parse tree should usually extend this class for convenience. 
    1324    # The caller of a transform will only first call initialize and then process_node on 
     
    1829    # return the input node untouched. Returning None will remove the node from the 
    1930    # parent. 
    2031     
    21     def __init__(self): 
    22         self.parent_stack = [] 
    23      
    24     def initialize(self, phase, **options): 
    25         pass 
    26  
    2732    def process_children(self, node): 
    2833        """For all children of node, either process_list (if isinstance(node, list)) 
    2934        or process_node (otherwise) is called.""" 
     
    3641                newchild = self.process_list(child, childacc.name()) 
    3742                if not isinstance(newchild, list): raise Exception("Cannot replace list with non-list!") 
    3843            else: 
    39                 newchild = self.process_node(child, childacc.name()) 
     44                self.attr_stack.append((childacc.name(), None)) 
     45                newchild = self.process_node(child) 
    4046                if newchild is not None and not isinstance(newchild, Nodes.Node): 
    4147                    raise Exception("Cannot replace Node with non-Node!") 
     48                self.attr_stack.pop() 
    4249            childacc.set(newchild) 
    4350        self.parent_stack.pop() 
    4451 
    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.""" 
    5156        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] 
    5461 
    55     def process_node(self, node, name): 
     62    def process_node(self, node): 
    5663        """Override this method to process nodes. name specifies which kind of relation the 
    5764        parent has with child. This method should always return the node which the parent 
    5865        should use for this relation, which can either be the same node, None to remove 
    5966        the node, or a different node.""" 
    6067        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 
     82class 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 
     135def ensure_statlist(node): 
     136    if not isinstance(node, Nodes.StatListNode): 
     137        node = Nodes.StatListNode(pos=node.pos, stats=[node]) 
     138    return node 
     139 
    61140 
    62141class PrintTree(Transform): 
    63142    """Prints a representation of the tree to standard output. 
     
    72151    def unindent(self): 
    73152        self._indent = self._indent[:-2] 
    74153 
    75     def initialize(self, phase, **options): 
     154    def __call__(self, tree, phase=None, **params): 
    76155        print("Parse tree dump at phase '%s'" % phase) 
     156        super(PrintTree, self).__call__(tree, phase=phase, **params) 
    77157 
    78158    # Don't do anything about process_list, the defaults gives 
    79159    # nice-looking name[idx] nodes which will visually appear 
    80160    # under the parent-node, not displaying the list itself in 
    81161    # the hierarchy. 
    82162     
    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 
    84172        print("%s- %s: %s" % (self._indent, name, self.repr_of(node))) 
    85173        self.indent() 
    86174        self.process_children(node) 
     
    92180            return "(none)" 
    93181        else: 
    94182            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): 
    96188                t = node.type 
    97189                result += "(type=%s)" % repr(t) 
     190                 
    98191            return result 
    99192 
    100193 
     
    108201        for name in PHASES: 
    109202            self[name] = [] 
    110203    def run(self, name, node, **options): 
    111         assert name in self 
     204        assert name in self, "Transform phase %s not defined" % name 
    112205        for transform in self[name]: 
    113             transform.initialize(phase=name, **options) 
    114             transform.process_node(node, "(root)") 
     206            transform(node, phase=name, **options) 
    115207 
    116208 
  • 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  
    3333    #  module_temp_cname    string 
    3434    #  full_module_name     string 
    3535 
    36     children_attrs = ["body"] 
     36    child_attrs = ["body"] 
    3737     
    3838    def analyse_declarations(self, env): 
    3939        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  
    8484    # Note: If needed, this can be replaced with a more efficient metaclass 
    8585    # approach, resolving the jump table at module load time. 
    8686     
    87     def __init__(self, readonly=False, **kw): 
     87    def __init__(self, **kw): 
    8888        """readonly - If this is set to True, the results of process_node 
    8989        will be discarded (so that one can return None without changing 
    9090        the tree).""" 
    9191        super(VisitorTransform, self).__init__(**kw) 
    9292        self.visitmethods = {'process_' : {}, 'pre_' : {}, 'post_' : {}} 
    93         self.attrname = "" 
    94         self.readonly = readonly 
    9593      
    9694    def get_visitfunc(self, prefix, cls): 
    9795        mname = prefix + cls.__name__ 
     
    106104            self.visitmethods[prefix][mname] = m 
    107105        return m 
    108106 
    109     def process_node(self, node, name="_"): 
     107    def process_node(self, node): 
    110108        # Pass on to calls registered in self.visitmethods 
    111         self.attrname = name 
    112109        if node is None: 
    113110            return None 
    114111        result = self.get_visitfunc("process_", node.__class__)(node) 
    115         if self.readonly: 
    116             return node 
    117         else: 
    118             return result 
     112        return node 
    119113     
    120114    def process_Node(self, node): 
    121115        descend = self.get_visitfunc("pre_", node.__class__)(node) 
     
    130124    def post_Node(self, node): 
    131125        pass 
    132126 
     127class 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 
    133136 
    134137# Utils 
    135138def 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  
    22#   Pyrex - Parse tree nodes 
    33# 
    44 
    5 import string, sys, os, time 
     5import string, sys, os, time, copy 
    66 
    77import Code 
    88from Errors import error, warning, InternalError 
     
    149149        """Utility method for more easily implementing get_child_accessors. 
    150150        If you override get_child_accessors then this method is not used.""" 
    151151        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 
    152165     
    153166     
    154167    # 
  • (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  
     1from Cython.Compiler.Transform import ReadonlyVisitor 
     2from Cython.Compiler.Nodes import * 
     3 
     4""" 
     5Serializes a Cython code tree to Cython code. This is primarily useful for 
     6debugging and testing purposes. 
     7 
     8The output is in a strict format, no whitespace or comments from the input 
     9is preserved (and it could not be as it is not present in the code tree). 
     10""" 
     11 
     12class 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 
     28class 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  
     1from Cython.TestUtils import CythonTest 
     2from Cython.Compiler.TreeFragment import * 
     3 
     4class 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 
     24if __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  
    109109        if node is None: 
    110110            return None 
    111111        result = self.get_visitfunc("process_", node.__class__)(node) 
    112         return node 
     112        return result 
    113113     
    114114    def process_Node(self, node): 
    115115        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 
     5import re 
     6from cStringIO import StringIO 
     7from Scanning import PyrexScanner, StringSourceDescriptor 
     8from Symtab import BuiltinScope, ModuleScope 
     9from Transform import Transform, VisitorTransform 
     10from Nodes import Node 
     11from ExprNodes import NameNode 
     12import Parsing 
     13import Main 
     14 
     15""" 
     16Support for parsing strings into code trees. 
     17""" 
     18 
     19class 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         
     29def 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 
     60class 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 
     69class 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 
     86def copy_code_tree(node): 
     87    return TreeCopier()(node) 
     88 
     89INDENT_RE = re.compile(ur"^ *") 
     90def 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     
     97class 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  
     1import Cython.Compiler.Errors as Errors 
     2from Cython.CodeWriter import CodeWriter 
     3import unittest 
     4from Cython.Compiler.ModuleNode import ModuleNode 
     5import Cython.Compiler.Main as Main 
     6from Cython.Compiler.TreeFragment import TreeFragment, strip_common_indent 
     7 
     8class 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 
     29class 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  
     1from Cython.TestUtils import CythonTest 
     2 
     3class 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     
     76if __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