]>
Commit | Line | Data |
---|---|---|
4710c53d | 1 | """\\r |
2 | \r | |
3 | Tools for scanning header files in search of function prototypes.\r | |
4 | \r | |
5 | Often, the function prototypes in header files contain enough information\r | |
6 | to automatically generate (or reverse-engineer) interface specifications\r | |
7 | from them. The conventions used are very vendor specific, but once you've\r | |
8 | figured out what they are they are often a great help, and it sure beats\r | |
9 | manually entering the interface specifications. (These are needed to generate\r | |
10 | the glue used to access the functions from Python.)\r | |
11 | \r | |
12 | In order to make this class useful, almost every component can be overridden.\r | |
13 | The defaults are (currently) tuned to scanning Apple Macintosh header files,\r | |
14 | although most Mac specific details are contained in header-specific subclasses.\r | |
15 | """\r | |
16 | \r | |
17 | import re\r | |
18 | import sys\r | |
19 | import os\r | |
20 | import fnmatch\r | |
21 | from types import *\r | |
22 | try:\r | |
23 | import MacOS\r | |
24 | except ImportError:\r | |
25 | MacOS = None\r | |
26 | \r | |
27 | try:\r | |
28 | from bgenlocations import CREATOR, INCLUDEDIR\r | |
29 | except ImportError:\r | |
30 | CREATOR = None\r | |
31 | INCLUDEDIR = os.curdir\r | |
32 | \r | |
33 | Error = "scantools.Error"\r | |
34 | \r | |
35 | BEGINHTMLREPORT="""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r | |
36 | <html>\r | |
37 | <head>\r | |
38 | <style type="text/css">\r | |
39 | .unmatched { }\r | |
40 | .commentstripping { color: grey; text-decoration: line-through }\r | |
41 | .comment { text-decoration: line-through }\r | |
42 | .notcomment { color: black }\r | |
43 | .incomplete { color: maroon }\r | |
44 | .constant { color: green }\r | |
45 | .pyconstant { background-color: yellow }\r | |
46 | .blconstant { background-color: yellow; color: red }\r | |
47 | .declaration { color: blue }\r | |
48 | .pydeclaration { background-color: yellow }\r | |
49 | .type { font-style: italic }\r | |
50 | .name { font-weight: bold }\r | |
51 | .value { font-style: italic }\r | |
52 | .arglist { text-decoration: underline }\r | |
53 | .blacklisted { background-color: yellow; color: red }\r | |
54 | </style>\r | |
55 | <title>Bgen scan report</title>\r | |
56 | </head>\r | |
57 | <body>\r | |
58 | <h1>Bgen scan report</h1>\r | |
59 | <h2>Legend</h2>\r | |
60 | <p>This scan report is intended to help you debug the regular expressions\r | |
61 | used by the bgen scanner. It consists of the original ".h" header file(s)\r | |
62 | marked up to show you what the regular expressions in the bgen parser matched\r | |
63 | for each line. NOTE: comments in the original source files may or may not be\r | |
64 | shown.</p>\r | |
65 | <p>The typographic conventions of this file are as follows:</p>\r | |
66 | <dl>\r | |
67 | <dt>comment stripping</dt>\r | |
68 | <dd><pre><span class="commentstripping"><span class="notcomment">comment stripping is </span><span class="comment">/* marked up */</span><span class="notcomment"> and the line is repeated if needed</span></span></pre>\r | |
69 | <p>If anything here does not appear to happen correctly look at\r | |
70 | <tt>comment1_pat</tt> and <tt>comment2_pat</tt>.</p>\r | |
71 | </dd>\r | |
72 | <dt>constant definitions</dt>\r | |
73 | <dd><pre><span class="constant">#define <span class="name">name</span> <span class="value">value</span></pre>\r | |
74 | <p>Highlights name and value of the constant. Governed by <tt>sym_pat</tt>.</p>\r | |
75 | </dd>\r | |
76 | <dt>function declaration</dt>\r | |
77 | <dd><pre><span class="declaration"><span class="type">char *</span><span class="name">rindex</span><span class="arglist">(<span class="type">const char *</span><span class="name">s</span>, <span class="type">int </span><span class="name">c</span>)</span>;</span></pre>\r | |
78 | <p>Highlights type, name and argument list. <tt>type_pat</tt>,\r | |
79 | <tt>name_pat</tt> and <tt>args_pat</tt> are combined into <tt>whole_pat</tt>, which\r | |
80 | is what is used here.</p></dd>\r | |
81 | </dd>\r | |
82 | <dt>incomplete match for function declaration</dt>\r | |
83 | <dd><pre><span class="incomplete"><span class="type">char *</span>foo;</span></pre>\r | |
84 | <p>The beginning of this looked promising, but it did not match a function declaration.\r | |
85 | In other words, it matched <tt>head_pat</tt> but not <tt>whole_pat</tt>. If the next\r | |
86 | declaration has also been gobbled up you need to look at <tt>end_pat</tt>.</p>\r | |
87 | </dd>\r | |
88 | <dt>unrecognized input</dt>\r | |
89 | <dd><pre><span class="unmatched">#include "type.h"</span></pre>\r | |
90 | <p>If there are function declarations the scanner has missed (i.e. things\r | |
91 | are in this class but you want them to be declarations) you need to adapt\r | |
92 | <tt>head_pat</tt>.\r | |
93 | </dd>\r | |
94 | </dl>\r | |
95 | <h2>Output</h2>\r | |
96 | <pre>\r | |
97 | <span class="unmatched">\r | |
98 | """\r | |
99 | ENDHTMLREPORT="""</span>\r | |
100 | </pre>\r | |
101 | </body>\r | |
102 | </html>\r | |
103 | """\r | |
104 | \r | |
105 | class Scanner:\r | |
106 | \r | |
107 | # Set to 1 in subclass to debug your scanner patterns.\r | |
108 | debug = 0\r | |
109 | \r | |
110 | def __init__(self, input = None, output = None, defsoutput = None):\r | |
111 | self.initsilent()\r | |
112 | self.initblacklists()\r | |
113 | self.initrepairinstructions()\r | |
114 | self.initpaths()\r | |
115 | self.initfiles()\r | |
116 | self.initpatterns()\r | |
117 | self.compilepatterns()\r | |
118 | self.initosspecifics()\r | |
119 | self.initusedtypes()\r | |
120 | if output:\r | |
121 | self.setoutput(output, defsoutput)\r | |
122 | if input:\r | |
123 | self.setinput(input)\r | |
124 | \r | |
125 | def initusedtypes(self):\r | |
126 | self.usedtypes = {}\r | |
127 | \r | |
128 | def typeused(self, type, mode):\r | |
129 | if not self.usedtypes.has_key(type):\r | |
130 | self.usedtypes[type] = {}\r | |
131 | self.usedtypes[type][mode] = None\r | |
132 | \r | |
133 | def reportusedtypes(self):\r | |
134 | types = self.usedtypes.keys()\r | |
135 | types.sort()\r | |
136 | for type in types:\r | |
137 | modes = self.usedtypes[type].keys()\r | |
138 | modes.sort()\r | |
139 | self.report("%s %s", type, " ".join(modes))\r | |
140 | \r | |
141 | def gentypetest(self, file):\r | |
142 | fp = open(file, "w")\r | |
143 | fp.write("types=[\n")\r | |
144 | types = self.usedtypes.keys()\r | |
145 | types.sort()\r | |
146 | for type in types:\r | |
147 | fp.write("\t'%s',\n"%type)\r | |
148 | fp.write("]\n")\r | |
149 | fp.write("""missing=0\r | |
150 | for t in types:\r | |
151 | try:\r | |
152 | tt = eval(t)\r | |
153 | except NameError:\r | |
154 | print "** Missing type:", t\r | |
155 | missing = 1\r | |
156 | if missing: raise "Missing Types"\r | |
157 | """)\r | |
158 | fp.close()\r | |
159 | \r | |
160 | def initsilent(self):\r | |
161 | self.silent = 1\r | |
162 | \r | |
163 | def error(self, format, *args):\r | |
164 | if self.silent >= 0:\r | |
165 | print format%args\r | |
166 | \r | |
167 | def report(self, format, *args):\r | |
168 | if not self.silent:\r | |
169 | print format%args\r | |
170 | \r | |
171 | def writeinitialdefs(self):\r | |
172 | pass\r | |
173 | \r | |
174 | def initblacklists(self):\r | |
175 | self.blacklistnames = self.makeblacklistnames()\r | |
176 | self.blacklisttypes = ["unknown", "-"] + self.makeblacklisttypes()\r | |
177 | self.greydictnames = self.greylist2dict(self.makegreylist())\r | |
178 | \r | |
179 | def greylist2dict(self, list):\r | |
180 | rv = {}\r | |
181 | for define, namelist in list:\r | |
182 | for name in namelist:\r | |
183 | rv[name] = define\r | |
184 | return rv\r | |
185 | \r | |
186 | def makeblacklistnames(self):\r | |
187 | return []\r | |
188 | \r | |
189 | def makeblacklisttypes(self):\r | |
190 | return []\r | |
191 | \r | |
192 | def makegreylist(self):\r | |
193 | return []\r | |
194 | \r | |
195 | def initrepairinstructions(self):\r | |
196 | self.repairinstructions = self.makerepairinstructions()\r | |
197 | self.inherentpointertypes = self.makeinherentpointertypes()\r | |
198 | \r | |
199 | def makerepairinstructions(self):\r | |
200 | """Parse the repair file into repair instructions.\r | |
201 | \r | |
202 | The file format is simple:\r | |
203 | 1) use \ to split a long logical line in multiple physical lines\r | |
204 | 2) everything after the first # on a line is ignored (as comment)\r | |
205 | 3) empty lines are ignored\r | |
206 | 4) remaining lines must have exactly 3 colon-separated fields:\r | |
207 | functionpattern : argumentspattern : argumentsreplacement\r | |
208 | 5) all patterns use shell style pattern matching\r | |
209 | 6) an empty functionpattern means the same as *\r | |
210 | 7) the other two fields are each comma-separated lists of triples\r | |
211 | 8) a triple is a space-separated list of 1-3 words\r | |
212 | 9) a triple with less than 3 words is padded at the end with "*" words\r | |
213 | 10) when used as a pattern, a triple matches the type, name, and mode\r | |
214 | of an argument, respectively\r | |
215 | 11) when used as a replacement, the words of a triple specify\r | |
216 | replacements for the corresponding words of the argument,\r | |
217 | with "*" as a word by itself meaning leave the original word\r | |
218 | (no other uses of "*" is allowed)\r | |
219 | 12) the replacement need not have the same number of triples\r | |
220 | as the pattern\r | |
221 | """\r | |
222 | f = self.openrepairfile()\r | |
223 | if not f: return []\r | |
224 | print "Reading repair file", repr(f.name), "..."\r | |
225 | list = []\r | |
226 | lineno = 0\r | |
227 | while 1:\r | |
228 | line = f.readline()\r | |
229 | if not line: break\r | |
230 | lineno = lineno + 1\r | |
231 | startlineno = lineno\r | |
232 | while line[-2:] == '\\\n':\r | |
233 | line = line[:-2] + ' ' + f.readline()\r | |
234 | lineno = lineno + 1\r | |
235 | i = line.find('#')\r | |
236 | if i >= 0: line = line[:i]\r | |
237 | words = [s.strip() for s in line.split(':')]\r | |
238 | if words == ['']: continue\r | |
239 | if len(words) <> 3:\r | |
240 | print "Line", startlineno,\r | |
241 | print ": bad line (not 3 colon-separated fields)"\r | |
242 | print repr(line)\r | |
243 | continue\r | |
244 | [fpat, pat, rep] = words\r | |
245 | if not fpat: fpat = "*"\r | |
246 | if not pat:\r | |
247 | print "Line", startlineno,\r | |
248 | print "Empty pattern"\r | |
249 | print repr(line)\r | |
250 | continue\r | |
251 | patparts = [s.strip() for s in pat.split(',')]\r | |
252 | repparts = [s.strip() for s in rep.split(',')]\r | |
253 | patterns = []\r | |
254 | for p in patparts:\r | |
255 | if not p:\r | |
256 | print "Line", startlineno,\r | |
257 | print "Empty pattern part"\r | |
258 | print repr(line)\r | |
259 | continue\r | |
260 | pattern = p.split()\r | |
261 | if len(pattern) > 3:\r | |
262 | print "Line", startlineno,\r | |
263 | print "Pattern part has > 3 words"\r | |
264 | print repr(line)\r | |
265 | pattern = pattern[:3]\r | |
266 | else:\r | |
267 | while len(pattern) < 3:\r | |
268 | pattern.append("*")\r | |
269 | patterns.append(pattern)\r | |
270 | replacements = []\r | |
271 | for p in repparts:\r | |
272 | if not p:\r | |
273 | print "Line", startlineno,\r | |
274 | print "Empty replacement part"\r | |
275 | print repr(line)\r | |
276 | continue\r | |
277 | replacement = p.split()\r | |
278 | if len(replacement) > 3:\r | |
279 | print "Line", startlineno,\r | |
280 | print "Pattern part has > 3 words"\r | |
281 | print repr(line)\r | |
282 | replacement = replacement[:3]\r | |
283 | else:\r | |
284 | while len(replacement) < 3:\r | |
285 | replacement.append("*")\r | |
286 | replacements.append(replacement)\r | |
287 | list.append((fpat, patterns, replacements))\r | |
288 | return list\r | |
289 | \r | |
290 | def makeinherentpointertypes(self):\r | |
291 | return []\r | |
292 | \r | |
293 | def openrepairfile(self, filename = "REPAIR"):\r | |
294 | try:\r | |
295 | return open(filename, "rU")\r | |
296 | except IOError, msg:\r | |
297 | print repr(filename), ":", msg\r | |
298 | print "Cannot open repair file -- assume no repair needed"\r | |
299 | return None\r | |
300 | \r | |
301 | def initfiles(self):\r | |
302 | self.specmine = 0\r | |
303 | self.defsmine = 0\r | |
304 | self.scanmine = 0\r | |
305 | self.htmlmine = 0\r | |
306 | self.specfile = sys.stdout\r | |
307 | self.defsfile = None\r | |
308 | self.scanfile = sys.stdin\r | |
309 | self.htmlfile = None\r | |
310 | self.lineno = 0\r | |
311 | self.line = ""\r | |
312 | \r | |
313 | def initpaths(self):\r | |
314 | self.includepath = [os.curdir, INCLUDEDIR]\r | |
315 | \r | |
316 | def initpatterns(self):\r | |
317 | self.head_pat = r"^EXTERN_API[^_]"\r | |
318 | self.tail_pat = r"[;={}]"\r | |
319 | self.type_pat = r"EXTERN_API" + \\r | |
320 | r"[ \t\n]*\([ \t\n]*" + \\r | |
321 | r"(?P<type>[a-zA-Z0-9_* \t]*[a-zA-Z0-9_*])" + \\r | |
322 | r"[ \t\n]*\)[ \t\n]*"\r | |
323 | self.name_pat = r"(?P<name>[a-zA-Z0-9_]+)[ \t\n]*"\r | |
324 | self.args_pat = r"\((?P<args>([^\(;=\)]+|\([^\(;=\)]*\))*)\)"\r | |
325 | self.whole_pat = self.type_pat + self.name_pat + self.args_pat\r | |
326 | self.sym_pat = r"^[ \t]*(?P<name>[a-zA-Z0-9_]+)[ \t]*=" + \\r | |
327 | r"[ \t]*(?P<defn>[-0-9_a-zA-Z'\"\(][^\t\n,;}]*),?"\r | |
328 | self.asplit_pat = r"^(?P<type>.*[^a-zA-Z0-9_])(?P<name>[a-zA-Z0-9_]+)(?P<array>\[\])?$"\r | |
329 | self.comment1_pat = r"(?P<rest>.*)//.*"\r | |
330 | # note that the next pattern only removes comments that are wholly within one line\r | |
331 | self.comment2_pat = r"(?P<rest1>.*)/\*.*\*/(?P<rest2>.*)"\r | |
332 | \r | |
333 | def compilepatterns(self):\r | |
334 | for name in dir(self):\r | |
335 | if name[-4:] == "_pat":\r | |
336 | pat = getattr(self, name)\r | |
337 | prog = re.compile(pat)\r | |
338 | setattr(self, name[:-4], prog)\r | |
339 | \r | |
340 | def initosspecifics(self):\r | |
341 | if MacOS and CREATOR:\r | |
342 | self.filetype = 'TEXT'\r | |
343 | self.filecreator = CREATOR\r | |
344 | else:\r | |
345 | self.filetype = self.filecreator = None\r | |
346 | \r | |
347 | def setfiletype(self, filename):\r | |
348 | if MacOS and (self.filecreator or self.filetype):\r | |
349 | creator, type = MacOS.GetCreatorAndType(filename)\r | |
350 | if self.filecreator: creator = self.filecreator\r | |
351 | if self.filetype: type = self.filetype\r | |
352 | MacOS.SetCreatorAndType(filename, creator, type)\r | |
353 | \r | |
354 | def close(self):\r | |
355 | self.closefiles()\r | |
356 | \r | |
357 | def closefiles(self):\r | |
358 | self.closespec()\r | |
359 | self.closedefs()\r | |
360 | self.closescan()\r | |
361 | self.closehtml()\r | |
362 | \r | |
363 | def closespec(self):\r | |
364 | tmp = self.specmine and self.specfile\r | |
365 | self.specfile = None\r | |
366 | if tmp: tmp.close()\r | |
367 | \r | |
368 | def closedefs(self):\r | |
369 | tmp = self.defsmine and self.defsfile\r | |
370 | self.defsfile = None\r | |
371 | if tmp: tmp.close()\r | |
372 | \r | |
373 | def closescan(self):\r | |
374 | tmp = self.scanmine and self.scanfile\r | |
375 | self.scanfile = None\r | |
376 | if tmp: tmp.close()\r | |
377 | \r | |
378 | def closehtml(self):\r | |
379 | if self.htmlfile: self.htmlfile.write(ENDHTMLREPORT)\r | |
380 | tmp = self.htmlmine and self.htmlfile\r | |
381 | self.htmlfile = None\r | |
382 | if tmp: tmp.close()\r | |
383 | \r | |
384 | def setoutput(self, spec, defs = None):\r | |
385 | self.closespec()\r | |
386 | self.closedefs()\r | |
387 | if spec:\r | |
388 | if type(spec) == StringType:\r | |
389 | file = self.openoutput(spec)\r | |
390 | mine = 1\r | |
391 | else:\r | |
392 | file = spec\r | |
393 | mine = 0\r | |
394 | self.specfile = file\r | |
395 | self.specmine = mine\r | |
396 | if defs:\r | |
397 | if type(defs) == StringType:\r | |
398 | file = self.openoutput(defs)\r | |
399 | mine = 1\r | |
400 | else:\r | |
401 | file = defs\r | |
402 | mine = 0\r | |
403 | self.defsfile = file\r | |
404 | self.defsmine = mine\r | |
405 | \r | |
406 | def sethtmloutput(self, htmlfile):\r | |
407 | self.closehtml()\r | |
408 | if htmlfile:\r | |
409 | if type(htmlfile) == StringType:\r | |
410 | file = self.openoutput(htmlfile)\r | |
411 | mine = 1\r | |
412 | else:\r | |
413 | file = htmlfile\r | |
414 | mine = 0\r | |
415 | self.htmlfile = file\r | |
416 | self.htmlmine = mine\r | |
417 | self.htmlfile.write(BEGINHTMLREPORT)\r | |
418 | \r | |
419 | def openoutput(self, filename):\r | |
420 | try:\r | |
421 | file = open(filename, 'w')\r | |
422 | except IOError, arg:\r | |
423 | raise IOError, (filename, arg)\r | |
424 | self.setfiletype(filename)\r | |
425 | return file\r | |
426 | \r | |
427 | def setinput(self, scan = sys.stdin):\r | |
428 | if not type(scan) in (TupleType, ListType):\r | |
429 | scan = [scan]\r | |
430 | self.allscaninputs = scan\r | |
431 | self._nextinput()\r | |
432 | \r | |
433 | def _nextinput(self):\r | |
434 | if not self.allscaninputs:\r | |
435 | return 0\r | |
436 | scan = self.allscaninputs[0]\r | |
437 | self.allscaninputs = self.allscaninputs[1:]\r | |
438 | self.closescan()\r | |
439 | if scan:\r | |
440 | if type(scan) == StringType:\r | |
441 | file = self.openinput(scan)\r | |
442 | mine = 1\r | |
443 | else:\r | |
444 | file = scan\r | |
445 | mine = 0\r | |
446 | self.scanfile = file\r | |
447 | self.scanmine = mine\r | |
448 | self.lineno = 0\r | |
449 | return 1\r | |
450 | \r | |
451 | def openinput(self, filename):\r | |
452 | if not os.path.isabs(filename):\r | |
453 | for dir in self.includepath:\r | |
454 | fullname = os.path.join(dir, filename)\r | |
455 | #self.report("trying full name %r", fullname)\r | |
456 | try:\r | |
457 | return open(fullname, 'rU')\r | |
458 | except IOError:\r | |
459 | pass\r | |
460 | # If not on the path, or absolute, try default open()\r | |
461 | try:\r | |
462 | return open(filename, 'rU')\r | |
463 | except IOError, arg:\r | |
464 | raise IOError, (arg, filename)\r | |
465 | \r | |
466 | def getline(self):\r | |
467 | if not self.scanfile:\r | |
468 | raise Error, "input file not set"\r | |
469 | self.line = self.scanfile.readline()\r | |
470 | if not self.line:\r | |
471 | if self._nextinput():\r | |
472 | return self.getline()\r | |
473 | raise EOFError\r | |
474 | self.lineno = self.lineno + 1\r | |
475 | return self.line\r | |
476 | \r | |
477 | def scan(self):\r | |
478 | if not self.scanfile:\r | |
479 | self.error("No input file has been specified")\r | |
480 | return\r | |
481 | inputname = self.scanfile.name\r | |
482 | self.report("scanfile = %r", inputname)\r | |
483 | if not self.specfile:\r | |
484 | self.report("(No interface specifications will be written)")\r | |
485 | else:\r | |
486 | self.report("specfile = %r", self.specfile.name)\r | |
487 | self.specfile.write("# Generated from %r\n\n" % (inputname,))\r | |
488 | if not self.defsfile:\r | |
489 | self.report("(No symbol definitions will be written)")\r | |
490 | else:\r | |
491 | self.report("defsfile = %r", (self.defsfile.name,))\r | |
492 | self.defsfile.write("# Generated from %r\n\n" % (os.path.split(inputname)[1],))\r | |
493 | self.writeinitialdefs()\r | |
494 | self.alreadydone = []\r | |
495 | try:\r | |
496 | while 1:\r | |
497 | try: line = self.getline()\r | |
498 | except EOFError: break\r | |
499 | if self.debug:\r | |
500 | self.report("LINE: %r" % (line,))\r | |
501 | match = self.comment1.match(line)\r | |
502 | if match:\r | |
503 | self.htmlreport(line, klass='commentstripping', ranges=[(\r | |
504 | match.start('rest'), match.end('rest'), 'notcomment')])\r | |
505 | line = match.group('rest')\r | |
506 | if self.debug:\r | |
507 | self.report("\tafter comment1: %r" % (line,))\r | |
508 | match = self.comment2.match(line)\r | |
509 | while match:\r | |
510 | if match:\r | |
511 | self.htmlreport(line, klass='commentstripping', ranges=[\r | |
512 | (match.start('rest1'), match.end('rest1'), 'notcomment'),\r | |
513 | (match.start('rest2'), match.end('rest2'), 'notcomment')])\r | |
514 | line = match.group('rest1')+match.group('rest2')\r | |
515 | if self.debug:\r | |
516 | self.report("\tafter comment2: %r" % (line,))\r | |
517 | match = self.comment2.match(line)\r | |
518 | if self.defsfile:\r | |
519 | match = self.sym.match(line)\r | |
520 | if match:\r | |
521 | if self.debug:\r | |
522 | self.report("\tmatches sym.")\r | |
523 | self.dosymdef(match, line)\r | |
524 | continue\r | |
525 | match = self.head.match(line)\r | |
526 | if match:\r | |
527 | if self.debug:\r | |
528 | self.report("\tmatches head.")\r | |
529 | self.dofuncspec()\r | |
530 | continue\r | |
531 | self.htmlreport(line, klass='unmatched')\r | |
532 | except EOFError:\r | |
533 | self.error("Uncaught EOF error")\r | |
534 | self.reportusedtypes()\r | |
535 | \r | |
536 | def dosymdef(self, match, line):\r | |
537 | name, defn = match.group('name', 'defn')\r | |
538 | self.htmlreport(line, klass='constant', ranges=[\r | |
539 | (match.start('name'), match.end('name'), 'name'),\r | |
540 | (match.start('defn'), match.end('defn'), 'value')])\r | |
541 | defn = escape8bit(defn)\r | |
542 | if self.debug:\r | |
543 | self.report("\tsym: name=%r, defn=%r" % (name, defn))\r | |
544 | if not name in self.blacklistnames:\r | |
545 | oline = "%s = %s\n" % (name, defn)\r | |
546 | self.defsfile.write(oline)\r | |
547 | self.htmlreport(oline, klass="pyconstant")\r | |
548 | else:\r | |
549 | self.defsfile.write("# %s = %s\n" % (name, defn))\r | |
550 | self.htmlreport("** no output: name is blacklisted", klass="blconstant")\r | |
551 | # XXXX No way to handle greylisted names\r | |
552 | \r | |
553 | def dofuncspec(self):\r | |
554 | raw = self.line\r | |
555 | while not self.tail.search(raw):\r | |
556 | line = self.getline()\r | |
557 | if self.debug:\r | |
558 | self.report("* CONTINUATION LINE: %r" % (line,))\r | |
559 | match = self.comment1.match(line)\r | |
560 | if match:\r | |
561 | line = match.group('rest')\r | |
562 | if self.debug:\r | |
563 | self.report("\tafter comment1: %r" % (line,))\r | |
564 | match = self.comment2.match(line)\r | |
565 | while match:\r | |
566 | line = match.group('rest1')+match.group('rest2')\r | |
567 | if self.debug:\r | |
568 | self.report("\tafter comment1: %r" % (line,))\r | |
569 | match = self.comment2.match(line)\r | |
570 | raw = raw + line\r | |
571 | if self.debug:\r | |
572 | self.report("* WHOLE LINE: %r" % (raw,))\r | |
573 | self.processrawspec(raw)\r | |
574 | return raw\r | |
575 | \r | |
576 | def processrawspec(self, raw):\r | |
577 | match = self.whole.search(raw)\r | |
578 | if not match:\r | |
579 | self.report("Bad raw spec: %r", raw)\r | |
580 | if self.debug:\r | |
581 | match = self.type.search(raw)\r | |
582 | if not match:\r | |
583 | self.report("(Type already doesn't match)")\r | |
584 | self.htmlreport(raw, klass='incomplete', ranges=[(\r | |
585 | match.start('type'), match.end('type'), 'type')])\r | |
586 | else:\r | |
587 | self.report("(but type matched)")\r | |
588 | self.htmlreport(raw, klass='incomplete')\r | |
589 | return\r | |
590 | type, name, args = match.group('type', 'name', 'args')\r | |
591 | ranges=[\r | |
592 | (match.start('type'), match.end('type'), 'type'),\r | |
593 | (match.start('name'), match.end('name'), 'name'),\r | |
594 | (match.start('args'), match.end('args'), 'arglist')]\r | |
595 | self.htmlreport(raw, klass='declaration', ranges=ranges)\r | |
596 | modifiers = self.getmodifiers(match)\r | |
597 | type = self.pythonizename(type)\r | |
598 | name = self.pythonizename(name)\r | |
599 | if self.checkduplicate(name):\r | |
600 | self.htmlreport("*** no output generated: duplicate name", klass="blacklisted")\r | |
601 | return\r | |
602 | self.report("==> %s %s <==", type, name)\r | |
603 | if self.blacklisted(type, name):\r | |
604 | self.htmlreport("*** no output generated: function name or return type blacklisted", klass="blacklisted")\r | |
605 | self.report("*** %s %s blacklisted", type, name)\r | |
606 | return\r | |
607 | returnlist = [(type, name, 'ReturnMode')]\r | |
608 | returnlist = self.repairarglist(name, returnlist)\r | |
609 | [(type, name, returnmode)] = returnlist\r | |
610 | arglist = self.extractarglist(args)\r | |
611 | arglist = self.repairarglist(name, arglist)\r | |
612 | if self.unmanageable(type, name, arglist):\r | |
613 | self.htmlreport("*** no output generated: some argument blacklisted", klass="blacklisted")\r | |
614 | ##for arg in arglist:\r | |
615 | ## self.report(" %r", arg)\r | |
616 | self.report("*** %s %s unmanageable", type, name)\r | |
617 | return\r | |
618 | if modifiers:\r | |
619 | self.generate(type, name, arglist, modifiers)\r | |
620 | else:\r | |
621 | self.generate(type, name, arglist)\r | |
622 | \r | |
623 | def getmodifiers(self, match):\r | |
624 | return []\r | |
625 | \r | |
626 | def checkduplicate(self, name):\r | |
627 | if name in self.alreadydone:\r | |
628 | self.report("Name has already been defined: %r", name)\r | |
629 | return True\r | |
630 | self.alreadydone.append(name)\r | |
631 | return False\r | |
632 | \r | |
633 | def pythonizename(self, name):\r | |
634 | name = re.sub("\*", " ptr", name)\r | |
635 | name = name.strip()\r | |
636 | name = re.sub("[ \t]+", "_", name)\r | |
637 | return name\r | |
638 | \r | |
639 | def extractarglist(self, args):\r | |
640 | args = args.strip()\r | |
641 | if not args or args == "void":\r | |
642 | return []\r | |
643 | parts = [s.strip() for s in args.split(",")]\r | |
644 | arglist = []\r | |
645 | for part in parts:\r | |
646 | arg = self.extractarg(part)\r | |
647 | arglist.append(arg)\r | |
648 | return arglist\r | |
649 | \r | |
650 | def extractarg(self, part):\r | |
651 | mode = "InMode"\r | |
652 | part = part.strip()\r | |
653 | match = self.asplit.match(part)\r | |
654 | if not match:\r | |
655 | self.error("Indecipherable argument: %r", part)\r | |
656 | return ("unknown", part, mode)\r | |
657 | type, name, array = match.group('type', 'name', 'array')\r | |
658 | if array:\r | |
659 | # array matches an optional [] after the argument name\r | |
660 | type = type + " ptr "\r | |
661 | type = self.pythonizename(type)\r | |
662 | return self.modifyarg(type, name, mode)\r | |
663 | \r | |
664 | def modifyarg(self, type, name, mode):\r | |
665 | if type[:6] == "const_":\r | |
666 | type = type[6:]\r | |
667 | elif type[-4:] == "_ptr":\r | |
668 | type = type[:-4]\r | |
669 | mode = "OutMode"\r | |
670 | elif type in self.inherentpointertypes:\r | |
671 | mode = "OutMode"\r | |
672 | if type[-4:] == "_far":\r | |
673 | type = type[:-4]\r | |
674 | return type, name, mode\r | |
675 | \r | |
676 | def repairarglist(self, functionname, arglist):\r | |
677 | arglist = arglist[:]\r | |
678 | i = 0\r | |
679 | while i < len(arglist):\r | |
680 | for item in self.repairinstructions:\r | |
681 | if len(item) == 2:\r | |
682 | pattern, replacement = item\r | |
683 | functionpat = "*"\r | |
684 | else:\r | |
685 | functionpat, pattern, replacement = item\r | |
686 | if not fnmatch.fnmatchcase(functionname, functionpat):\r | |
687 | continue\r | |
688 | n = len(pattern)\r | |
689 | if i+n > len(arglist): continue\r | |
690 | current = arglist[i:i+n]\r | |
691 | for j in range(n):\r | |
692 | if not self.matcharg(pattern[j], current[j]):\r | |
693 | break\r | |
694 | else: # All items of the pattern match\r | |
695 | new = self.substituteargs(\r | |
696 | pattern, replacement, current)\r | |
697 | if new is not None:\r | |
698 | arglist[i:i+n] = new\r | |
699 | i = i+len(new) # No recursive substitutions\r | |
700 | break\r | |
701 | else: # No patterns match\r | |
702 | i = i+1\r | |
703 | return arglist\r | |
704 | \r | |
705 | def matcharg(self, patarg, arg):\r | |
706 | return len(filter(None, map(fnmatch.fnmatchcase, arg, patarg))) == 3\r | |
707 | \r | |
708 | def substituteargs(self, pattern, replacement, old):\r | |
709 | new = []\r | |
710 | for k in range(len(replacement)):\r | |
711 | item = replacement[k]\r | |
712 | newitem = [item[0], item[1], item[2]]\r | |
713 | for i in range(3):\r | |
714 | if item[i] == '*':\r | |
715 | newitem[i] = old[k][i]\r | |
716 | elif item[i][:1] == '$':\r | |
717 | index = int(item[i][1:]) - 1\r | |
718 | newitem[i] = old[index][i]\r | |
719 | new.append(tuple(newitem))\r | |
720 | ##self.report("old: %r", old)\r | |
721 | ##self.report("new: %r", new)\r | |
722 | return new\r | |
723 | \r | |
724 | def generate(self, tp, name, arglist, modifiers=[]):\r | |
725 | \r | |
726 | self.typeused(tp, 'return')\r | |
727 | if modifiers:\r | |
728 | classname, listname = self.destination(tp, name, arglist, modifiers)\r | |
729 | else:\r | |
730 | classname, listname = self.destination(tp, name, arglist)\r | |
731 | if not classname or not listname:\r | |
732 | self.htmlreport("*** no output generated: self.destination() returned None", klass="blacklisted")\r | |
733 | return\r | |
734 | if not self.specfile:\r | |
735 | self.htmlreport("*** no output generated: no output file specified", klass="blacklisted")\r | |
736 | return\r | |
737 | self.specfile.write("f = %s(%s, %r,\n" % (classname, tp, name))\r | |
738 | for atype, aname, amode in arglist:\r | |
739 | self.typeused(atype, amode)\r | |
740 | self.specfile.write(" (%s, %r, %s),\n" %\r | |
741 | (atype, aname, amode))\r | |
742 | if self.greydictnames.has_key(name):\r | |
743 | self.specfile.write(" condition=%r,\n"%(self.greydictnames[name],))\r | |
744 | self.generatemodifiers(classname, name, modifiers)\r | |
745 | self.specfile.write(")\n")\r | |
746 | self.specfile.write("%s.append(f)\n\n" % listname)\r | |
747 | if self.htmlfile:\r | |
748 | oline = "Adding to %s:\n%s(returntype=%s, name=%r" % (listname, classname, tp, name)\r | |
749 | for atype, aname, amode in arglist:\r | |
750 | oline += ",\n (%s, %r, %s)" % (atype, aname, amode)\r | |
751 | oline += ")\n"\r | |
752 | self.htmlreport(oline, klass="pydeclaration")\r | |
753 | \r | |
754 | def destination(self, type, name, arglist):\r | |
755 | return "FunctionGenerator", "functions"\r | |
756 | \r | |
757 | def generatemodifiers(self, classname, name, modifiers):\r | |
758 | pass\r | |
759 | \r | |
760 | def blacklisted(self, type, name):\r | |
761 | if type in self.blacklisttypes:\r | |
762 | ##self.report("return type %s is blacklisted", type)\r | |
763 | return 1\r | |
764 | if name in self.blacklistnames:\r | |
765 | ##self.report("function name %s is blacklisted", name)\r | |
766 | return 1\r | |
767 | return 0\r | |
768 | \r | |
769 | def unmanageable(self, type, name, arglist):\r | |
770 | for atype, aname, amode in arglist:\r | |
771 | if atype in self.blacklisttypes:\r | |
772 | self.report("argument type %s is blacklisted", atype)\r | |
773 | return 1\r | |
774 | return 0\r | |
775 | \r | |
776 | def htmlreport(self, line, klass=None, ranges=None):\r | |
777 | if not self.htmlfile: return\r | |
778 | if ranges is None:\r | |
779 | ranges = []\r | |
780 | if klass:\r | |
781 | ranges.insert(0, (0, len(line), klass))\r | |
782 | oline = ''\r | |
783 | i = 0\r | |
784 | for c in line:\r | |
785 | for b, e, name in ranges:\r | |
786 | if b == i:\r | |
787 | oline += '<span class="%s">' % name\r | |
788 | if e == i:\r | |
789 | oline += '</span>'\r | |
790 | i += 1\r | |
791 | \r | |
792 | if c == '<': oline += '<'\r | |
793 | elif c == '>': oline += '>'\r | |
794 | else: oline += c\r | |
795 | for b, e, name in ranges:\r | |
796 | if b >= i:\r | |
797 | oline += '<span class="%s">' % name\r | |
798 | if e >= i:\r | |
799 | oline += '</span>'\r | |
800 | if not line or line[-1] != '\n':\r | |
801 | oline += '\n'\r | |
802 | self.htmlfile.write(oline)\r | |
803 | \r | |
804 | class Scanner_PreUH3(Scanner):\r | |
805 | """Scanner for Universal Headers before release 3"""\r | |
806 | def initpatterns(self):\r | |
807 | Scanner.initpatterns(self)\r | |
808 | self.head_pat = "^extern pascal[ \t]+" # XXX Mac specific!\r | |
809 | self.type_pat = "pascal[ \t\n]+(?P<type>[a-zA-Z0-9_ \t]*[a-zA-Z0-9_])[ \t\n]+"\r | |
810 | self.whole_pat = self.type_pat + self.name_pat + self.args_pat\r | |
811 | self.sym_pat = "^[ \t]*(?P<name>[a-zA-Z0-9_]+)[ \t]*=" + \\r | |
812 | "[ \t]*(?P<defn>[-0-9'\"][^\t\n,;}]*),?"\r | |
813 | \r | |
814 | class Scanner_OSX(Scanner):\r | |
815 | """Scanner for modern (post UH3.3) Universal Headers """\r | |
816 | def initpatterns(self):\r | |
817 | Scanner.initpatterns(self)\r | |
818 | self.head_pat = "^EXTERN_API(_C)?"\r | |
819 | self.type_pat = "EXTERN_API(_C)?" + \\r | |
820 | "[ \t\n]*\([ \t\n]*" + \\r | |
821 | "(?P<type>[a-zA-Z0-9_* \t]*[a-zA-Z0-9_*])" + \\r | |
822 | "[ \t\n]*\)[ \t\n]*"\r | |
823 | self.whole_pat = self.type_pat + self.name_pat + self.args_pat\r | |
824 | self.sym_pat = "^[ \t]*(?P<name>[a-zA-Z0-9_]+)[ \t]*=" + \\r | |
825 | "[ \t]*(?P<defn>[-0-9_a-zA-Z'\"\(][^\t\n,;}]*),?"\r | |
826 | \r | |
827 | _8bit = re.compile(r"[\200-\377]")\r | |
828 | \r | |
829 | def escape8bit(s):\r | |
830 | if _8bit.search(s) is not None:\r | |
831 | out = []\r | |
832 | for c in s:\r | |
833 | o = ord(c)\r | |
834 | if o >= 128:\r | |
835 | out.append("\\" + hex(o)[1:])\r | |
836 | else:\r | |
837 | out.append(c)\r | |
838 | s = "".join(out)\r | |
839 | return s\r | |
840 | \r | |
841 | def test():\r | |
842 | input = "D:Development:THINK C:Mac #includes:Apple #includes:AppleEvents.h"\r | |
843 | output = "@aespecs.py"\r | |
844 | defsoutput = "@aedefs.py"\r | |
845 | s = Scanner(input, output, defsoutput)\r | |
846 | s.scan()\r | |
847 | \r | |
848 | if __name__ == '__main__':\r | |
849 | test()\r |