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