blob: 3b0b628d45677a8bba4d65ac9bf6fcf6c6caa038 [file] [log] [blame]
Guido van Rossum822218b2001-09-01 21:55:58 +00001#! /usr/bin/env python
2
3"""fixdiv - tool to fix division operators.
4
5To use this tool, first run `python -Dwarn yourscript.py 2>warnings'.
6This runs the script `yourscript.py' while writing warning messages
7about all uses of the classic division operator to the file
8`warnings'. (The warnings are written to stderr, so you must use `2>'
9for the I/O redirect. I don't yet know how to do this on Windows.)
10
11Then run `python fixdiv.py warnings'. This first reads the warnings,
12looking for classic division warnings, and sorts them by file name and
13line number. Then, for each file that received at least one warning,
14it parses the file and tries to match the warnings up to the division
15operators found in the source code. If it is successful, it writes a
16recommendation to stdout in the form of a context diff. If it is not
17successful, it writes recommendations to stdout instead.
18"""
19
20import sys
21import getopt
22import re
23import tokenize
24from pprint import pprint
25
26def main():
27 try:
28 opts, args = getopt.getopt(sys.argv[1:], "h")
29 except getopt.error, msg:
30 usage(2, msg)
31 for o, a in opts:
32 if o == "-h":
33 help()
34 if not args:
35 usage(2, "at least one file argument is required")
36 if args[1:]:
37 sys.stderr.write("%s: extra file arguments ignored\n", sys.argv[0])
38 readwarnings(args[0])
39
40def usage(exit, msg=None):
41 if msg:
42 sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
43 sys.stderr.write("Usage: %s warnings\n" % sys.argv[0])
44 sys.stderr.write("Try `%s -h' for more information.\n" % sys.argv[0])
45 sys.exit(exit)
46
47def help():
48 print __doc__
49 sys.exit(0)
50
51def readwarnings(warningsfile):
52 pat = re.compile(
53 "^(.+?):(\d+): DeprecationWarning: classic ([a-z]+) division$")
54 try:
55 f = open(warningsfile)
56 except IOError, msg:
57 sys.stderr.write("can't open: %s\n" % msg)
58 return
59 warnings = {}
60 while 1:
61 line = f.readline()
62 if not line:
63 break
64 m = pat.match(line)
65 if not m:
66 if line.find("division") >= 0:
67 sys.stderr.write("Warning: ignored input " + line)
68 continue
69 file, lineno, what = m.groups()
70 list = warnings.get(file)
71 if list is None:
72 warnings[file] = list = []
73 list.append((int(lineno), intern(what)))
74 f.close()
75 files = warnings.keys()
76 files.sort()
77 for file in files:
78 process(file, warnings[file])
79
80def process(file, list):
81 print "-"*70
82 if not list:
83 sys.stderr.write("no division warnings for %s\n" % file)
84 return
85 try:
86 fp = open(file)
87 except IOError, msg:
88 sys.stderr.write("can't open: %s\n" % msg)
89 return
90 print "Processing:", file
91 f = FileContext(fp)
92 list.sort()
93 index = 0 # list[:index] has been processed, list[index:] is still to do
94 orphans = [] # subset of list for which no / operator was found
95 unknown = [] # lines with / operators for which no warnings were seen
96 g = tokenize.generate_tokens(f.readline)
97 while 1:
98 startlineno, endlineno, slashes = lineinfo = scanline(g)
99 if startlineno is None:
100 break
101 assert startlineno <= endlineno is not None
102 while index < len(list) and list[index][0] < startlineno:
103 orphans.append(list[index])
104 index += 1
105 warnings = []
106 while index < len(list) and list[index][0] <= endlineno:
107 warnings.append(list[index])
108 index += 1
109 if not slashes and not warnings:
110 pass
111 elif slashes and not warnings:
112 report(slashes, "Unexecuted code")
113 elif warnings and not slashes:
114 reportphantomwarnings(warnings, f)
115 else:
116 if len(slashes) > 1:
117 report(slashes, "More than one / operator")
118 else:
119 (row, col), line = slashes[0]
120 line = chop(line)
121 if line[col:col+1] != "/":
122 print "*** Can't find the / operator in line %d:" % row
123 print "*", line
124 continue
125 intlong = []
126 floatcomplex = []
127 bad = []
128 for lineno, what in warnings:
129 if what in ("int", "long"):
130 intlong.append(what)
131 elif what in ("float", "complex"):
132 floatcomplex.append(what)
133 else:
134 bad.append(what)
135 if bad:
136 print "*** Bad warning for line %d:" % row, bad
137 print "*", line
138 elif intlong and not floatcomplex:
139 print "%dc%d" % (row, row)
140 print "<", line
141 print "---"
142 print ">", line[:col] + "/" + line[col:]
143 elif floatcomplex and not intlong:
144 print "True division / operator at line %d:" % row
145 print "=", line
146 fp.close()
147
148def reportphantomwarnings(warnings, f):
149 blocks = []
150 lastrow = None
151 lastblock = None
152 for row, what in warnings:
153 if row != lastrow:
154 lastblock = [row]
155 blocks.append(lastblock)
156 lastblock.append(what)
157 for block in blocks:
158 row = block[0]
159 whats = "/".join(block[1:])
160 print "*** Phantom %s warnings for line %d:" % (whats, row)
161 f.report(row, mark="*")
162
163def report(slashes, message):
164 lastrow = None
165 for (row, col), line in slashes:
166 if row != lastrow:
167 print "*** %s on line %d:" % (message, row)
168 print "*", chop(line)
169 lastrow = row
170
171class FileContext:
172 def __init__(self, fp, window=5, lineno=1):
173 self.fp = fp
174 self.window = 5
175 self.lineno = 1
176 self.eoflookahead = 0
177 self.lookahead = []
178 self.buffer = []
179 def fill(self):
180 while len(self.lookahead) < self.window and not self.eoflookahead:
181 line = self.fp.readline()
182 if not line:
183 self.eoflookahead = 1
184 break
185 self.lookahead.append(line)
186 def readline(self):
187 self.fill()
188 if not self.lookahead:
189 return ""
190 line = self.lookahead.pop(0)
191 self.buffer.append(line)
192 self.lineno += 1
193 return line
194 def truncate(self):
195 del self.buffer[-window:]
196 def __getitem__(self, index):
197 self.fill()
198 bufstart = self.lineno - len(self.buffer)
199 lookend = self.lineno + len(self.lookahead)
200 if bufstart <= index < self.lineno:
201 return self.buffer[index - bufstart]
202 if self.lineno <= index < lookend:
203 return self.lookahead[index - self.lineno]
204 raise KeyError
205 def report(self, first, last=None, mark="*"):
206 if last is None:
207 last = first
208 for i in range(first, last+1):
209 try:
210 line = self[first]
211 except KeyError:
212 line = "<missing line>"
213 print mark, chop(line)
214
215def scanline(g):
216 slashes = []
217 startlineno = None
218 endlineno = None
219 for type, token, start, end, line in g:
220 endlineno = end[0]
221 if startlineno is None:
222 startlineno = endlineno
223 if token in ("/", "/="):
224 slashes.append((start, line))
225 ## if type in (tokenize.NEWLINE, tokenize.NL, tokenize.COMMENT):
226 if type == tokenize.NEWLINE:
227 break
228 return startlineno, endlineno, slashes
229
230def chop(line):
231 if line.endswith("\n"):
232 return line[:-1]
233 else:
234 return line
235
236if __name__ == "__main__":
237 main()