Home | History | Annotate | Download | only in Compiler
      1 # Note: Work in progress
      2 
      3 import os
      4 import re
      5 import codecs
      6 from xml.sax.saxutils import escape as html_escape
      7 from StringIO import StringIO
      8 
      9 import Version
     10 from Code import CCodeWriter
     11 from Cython import Utils
     12 
     13 # need one-characters subsitutions (for now) so offsets aren't off
     14 special_chars = [
     15     (u'&', u'\xF2', u'&'),
     16     (u'<', u'\xF0', u'&lt;'),
     17     (u'>', u'\xF1', u'&gt;'),
     18 ]
     19 
     20 
     21 class AnnotationCCodeWriter(CCodeWriter):
     22 
     23     def __init__(self, create_from=None, buffer=None, copy_formatting=True):
     24         CCodeWriter.__init__(self, create_from, buffer, copy_formatting=True)
     25         if create_from is None:
     26             self.annotation_buffer = StringIO()
     27             self.annotations = []
     28             self.last_pos = None
     29             self.code = {}
     30         else:
     31             # When creating an insertion point, keep references to the same database
     32             self.annotation_buffer = create_from.annotation_buffer
     33             self.annotations = create_from.annotations
     34             self.code = create_from.code
     35             self.last_pos = create_from.last_pos
     36 
     37     def create_new(self, create_from, buffer, copy_formatting):
     38         return AnnotationCCodeWriter(create_from, buffer, copy_formatting)
     39 
     40     def write(self, s):
     41         CCodeWriter.write(self, s)
     42         self.annotation_buffer.write(s)
     43 
     44     def mark_pos(self, pos):
     45         if pos is not None:
     46             CCodeWriter.mark_pos(self, pos)
     47         if self.last_pos:
     48             pos_code = self.code.setdefault(self.last_pos[0].filename,{})
     49             code = pos_code.get(self.last_pos[1], "")
     50             pos_code[self.last_pos[1]] = code + self.annotation_buffer.getvalue()
     51         self.annotation_buffer = StringIO()
     52         self.last_pos = pos
     53 
     54     def annotate(self, pos, item):
     55         self.annotations.append((pos, item))
     56 
     57     def save_annotation(self, source_filename, target_filename):
     58         self.mark_pos(None)
     59         f = Utils.open_source_file(source_filename)
     60         lines = f.readlines()
     61         for k, line in enumerate(lines):
     62             for c, cc, html in special_chars:
     63                 line = line.replace(c, cc)
     64             lines[k] = line
     65         f.close()
     66         all = []
     67         if False:
     68             for pos, item in self.annotations:
     69                 if pos[0].filename == source_filename:
     70                     start = item.start()
     71                     size, end = item.end()
     72                     if size:
     73                         all.append((pos, start))
     74                         all.append(((source_filename, pos[1], pos[2]+size), end))
     75                     else:
     76                         all.append((pos, start+end))
     77 
     78         all.sort(reverse=True)
     79         for pos, item in all:
     80             _, line_no, col = pos
     81             line_no -= 1
     82             col += 1
     83             line = lines[line_no]
     84             lines[line_no] = line[:col] + item + line[col:]
     85 
     86         html_filename = os.path.splitext(target_filename)[0] + ".html"
     87         f = codecs.open(html_filename, "w", encoding="UTF-8")
     88         f.write(u'<!DOCTYPE html>\n')
     89         f.write(u'<!-- Generated by Cython %s -->\n' % Version.watermark)
     90         f.write(u'<html>\n')
     91         f.write(u"""
     92 <head>
     93 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
     94 <style type="text/css">
     95 
     96 body { font-family: courier; font-size: 12; }
     97 
     98 .code  { font-size: 9; color: #444444; display: none; margin-left: 20px; }
     99 .py_c_api  { color: red; }
    100 .py_macro_api  { color: #FF7000; }
    101 .pyx_c_api  { color: #FF3000; }
    102 .pyx_macro_api  { color: #FF7000; }
    103 .refnanny  { color: #FFA000; }
    104 
    105 .error_goto  { color: #FFA000; }
    106 
    107 .tag  {  }
    108 
    109 .coerce  { color: #008000; border: 1px dotted #008000 }
    110 
    111 .py_attr { color: #FF0000; font-weight: bold; }
    112 .c_attr  { color: #0000FF; }
    113 
    114 .py_call { color: #FF0000; font-weight: bold; }
    115 .c_call  { color: #0000FF; }
    116 
    117 .line { margin: 0em }
    118 
    119 </style>
    120 <script>
    121 function toggleDiv(id) {
    122     theDiv = document.getElementById(id);
    123     if (theDiv.style.display != 'block') theDiv.style.display = 'block';
    124     else theDiv.style.display = 'none';
    125 }
    126 </script>
    127 </head>
    128         """)
    129         f.write(u'<body>\n')
    130         f.write(u'<p>Generated by Cython %s\n' % Version.watermark)
    131         c_file = Utils.decode_filename(os.path.basename(target_filename))
    132         f.write(u'<p>Raw output: <a href="%s">%s</a>\n' % (c_file, c_file))
    133 
    134         zero_calls = dict((name, 0) for name in
    135                           'refnanny py_macro_api py_c_api pyx_macro_api pyx_c_api error_goto'.split())
    136 
    137         def annotate(match):
    138             group_name = match.lastgroup
    139             calls[group_name] += 1
    140             return ur"<span class='%s'>%s</span>" % (
    141                 group_name, match.group(group_name))
    142 
    143         pos_comment_marker = u'/* \N{HORIZONTAL ELLIPSIS} */\n'
    144         k = 0
    145         code_source_file = self.code.get(source_filename, {})
    146         for line in lines:
    147             k += 1
    148             try:
    149                 code = code_source_file[k]
    150             except KeyError:
    151                 code = ''
    152             else:
    153                 code = _replace_pos_comment(pos_comment_marker, code)
    154                 if code.startswith(pos_comment_marker):
    155                     code = code[len(pos_comment_marker):]
    156                 code = html_escape(code)
    157 
    158             calls = zero_calls.copy()
    159             code = _parse_code(annotate, code)
    160             score = (5 * calls['py_c_api'] + 2 * calls['pyx_c_api'] +
    161                      calls['py_macro_api'] + calls['pyx_macro_api'])
    162             color = u"FFFF%02x" % int(255/(1+score/10.0))
    163             f.write(u"<pre class='line' style='background-color: #%s' onclick='toggleDiv(\"line%s\")'>" % (color, k))
    164 
    165             f.write(u" %d: " % k)
    166             for c, cc, html in special_chars:
    167                 line = line.replace(cc, html)
    168             f.write(line.rstrip())
    169 
    170             f.write(u'</pre>\n')
    171             f.write(u"<pre id='line%s' class='code' style='background-color: #%s'>%s</pre>" % (k, color, code))
    172         f.write(u'</body></html>\n')
    173         f.close()
    174 
    175 
    176 _parse_code = re.compile(
    177     ur'(?P<refnanny>__Pyx_X?(?:GOT|GIVE)REF|__Pyx_RefNanny[A-Za-z]+)|'
    178     ur'(?:'
    179     ur'(?P<pyx_macro_api>__Pyx_[A-Z][A-Z_]+)|'
    180     ur'(?P<pyx_c_api>__Pyx_[A-Z][a-z_][A-Za-z_]+)|'
    181     ur'(?P<py_macro_api>Py[A-Z][a-z]+_[A-Z][A-Z_]+)|'
    182     ur'(?P<py_c_api>Py[A-Z][a-z]+_[A-Z][a-z][A-Za-z_]+)'
    183     ur')(?=\()|'       # look-ahead to exclude subsequent '(' from replacement
    184     ur'(?P<error_goto>(?:(?<=;) *if .* +)?\{__pyx_filename = .*goto __pyx_L\w+;\})'
    185 ).sub
    186 
    187 
    188 _replace_pos_comment = re.compile(
    189     # this matches what Cython generates as code line marker comment
    190     ur'^\s*/\*(?:(?:[^*]|\*[^/])*\n)+\s*\*/\s*\n',
    191     re.M
    192 ).sub
    193 
    194 
    195 class AnnotationItem(object):
    196 
    197     def __init__(self, style, text, tag="", size=0):
    198         self.style = style
    199         self.text = text
    200         self.tag = tag
    201         self.size = size
    202 
    203     def start(self):
    204         return u"<span class='tag %s' title='%s'>%s" % (self.style, self.text, self.tag)
    205 
    206     def end(self):
    207         return self.size, u"</span>"
    208