mirror of https://github.com/python/cpython
850 lines
30 KiB
Python
850 lines
30 KiB
Python
"""\
|
|
|
|
Tools for scanning header files in search of function prototypes.
|
|
|
|
Often, the function prototypes in header files contain enough information
|
|
to automatically generate (or reverse-engineer) interface specifications
|
|
from them. The conventions used are very vendor specific, but once you've
|
|
figured out what they are they are often a great help, and it sure beats
|
|
manually entering the interface specifications. (These are needed to generate
|
|
the glue used to access the functions from Python.)
|
|
|
|
In order to make this class useful, almost every component can be overridden.
|
|
The defaults are (currently) tuned to scanning Apple Macintosh header files,
|
|
although most Mac specific details are contained in header-specific subclasses.
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
import os
|
|
import fnmatch
|
|
from types import *
|
|
try:
|
|
import MacOS
|
|
except ImportError:
|
|
MacOS = None
|
|
|
|
try:
|
|
from bgenlocations import CREATOR, INCLUDEDIR
|
|
except ImportError:
|
|
CREATOR = None
|
|
INCLUDEDIR = os.curdir
|
|
|
|
Error = "scantools.Error"
|
|
|
|
BEGINHTMLREPORT="""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
|
<html>
|
|
<head>
|
|
<style type="text/css">
|
|
.unmatched { }
|
|
.commentstripping { color: grey; text-decoration: line-through }
|
|
.comment { text-decoration: line-through }
|
|
.notcomment { color: black }
|
|
.incomplete { color: maroon }
|
|
.constant { color: green }
|
|
.pyconstant { background-color: yellow }
|
|
.blconstant { background-color: yellow; color: red }
|
|
.declaration { color: blue }
|
|
.pydeclaration { background-color: yellow }
|
|
.type { font-style: italic }
|
|
.name { font-weight: bold }
|
|
.value { font-style: italic }
|
|
.arglist { text-decoration: underline }
|
|
.blacklisted { background-color: yellow; color: red }
|
|
</style>
|
|
<title>Bgen scan report</title>
|
|
</head>
|
|
<body>
|
|
<h1>Bgen scan report</h1>
|
|
<h2>Legend</h2>
|
|
<p>This scan report is intended to help you debug the regular expressions
|
|
used by the bgen scanner. It consists of the original ".h" header file(s)
|
|
marked up to show you what the regular expressions in the bgen parser matched
|
|
for each line. NOTE: comments in the original source files may or may not be
|
|
shown.</p>
|
|
<p>The typographic conventions of this file are as follows:</p>
|
|
<dl>
|
|
<dt>comment stripping</dt>
|
|
<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>
|
|
<p>If anything here does not appear to happen correctly look at
|
|
<tt>comment1_pat</tt> and <tt>comment2_pat</tt>.</p>
|
|
</dd>
|
|
<dt>constant definitions</dt>
|
|
<dd><pre><span class="constant">#define <span class="name">name</span> <span class="value">value</span></pre>
|
|
<p>Highlights name and value of the constant. Governed by <tt>sym_pat</tt>.</p>
|
|
</dd>
|
|
<dt>function declaration</dt>
|
|
<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>
|
|
<p>Highlights type, name and argument list. <tt>type_pat</tt>,
|
|
<tt>name_pat</tt> and <tt>args_pat</tt> are combined into <tt>whole_pat</tt>, which
|
|
is what is used here.</p></dd>
|
|
</dd>
|
|
<dt>incomplete match for function declaration</dt>
|
|
<dd><pre><span class="incomplete"><span class="type">char *</span>foo;</span></pre>
|
|
<p>The beginning of this looked promising, but it did not match a function declaration.
|
|
In other words, it matched <tt>head_pat</tt> but not <tt>whole_pat</tt>. If the next
|
|
declaration has also been gobbled up you need to look at <tt>end_pat</tt>.</p>
|
|
</dd>
|
|
<dt>unrecognized input</dt>
|
|
<dd><pre><span class="unmatched">#include "type.h"</span></pre>
|
|
<p>If there are function declarations the scanner has missed (i.e. things
|
|
are in this class but you want them to be declarations) you need to adapt
|
|
<tt>head_pat</tt>.
|
|
</dd>
|
|
</dl>
|
|
<h2>Output</h2>
|
|
<pre>
|
|
<span class="unmatched">
|
|
"""
|
|
ENDHTMLREPORT="""</span>
|
|
</pre>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
class Scanner:
|
|
|
|
# Set to 1 in subclass to debug your scanner patterns.
|
|
debug = 0
|
|
|
|
def __init__(self, input = None, output = None, defsoutput = None):
|
|
self.initsilent()
|
|
self.initblacklists()
|
|
self.initrepairinstructions()
|
|
self.initpaths()
|
|
self.initfiles()
|
|
self.initpatterns()
|
|
self.compilepatterns()
|
|
self.initosspecifics()
|
|
self.initusedtypes()
|
|
if output:
|
|
self.setoutput(output, defsoutput)
|
|
if input:
|
|
self.setinput(input)
|
|
|
|
def initusedtypes(self):
|
|
self.usedtypes = {}
|
|
|
|
def typeused(self, type, mode):
|
|
if not self.usedtypes.has_key(type):
|
|
self.usedtypes[type] = {}
|
|
self.usedtypes[type][mode] = None
|
|
|
|
def reportusedtypes(self):
|
|
types = self.usedtypes.keys()
|
|
types.sort()
|
|
for type in types:
|
|
modes = self.usedtypes[type].keys()
|
|
modes.sort()
|
|
self.report("%s %s", type, " ".join(modes))
|
|
|
|
def gentypetest(self, file):
|
|
fp = open(file, "w")
|
|
fp.write("types=[\n")
|
|
types = self.usedtypes.keys()
|
|
types.sort()
|
|
for type in types:
|
|
fp.write("\t'%s',\n"%type)
|
|
fp.write("]\n")
|
|
fp.write("""missing=0
|
|
for t in types:
|
|
try:
|
|
tt = eval(t)
|
|
except NameError:
|
|
print "** Missing type:", t
|
|
missing = 1
|
|
if missing: raise "Missing Types"
|
|
""")
|
|
fp.close()
|
|
|
|
def initsilent(self):
|
|
self.silent = 1
|
|
|
|
def error(self, format, *args):
|
|
if self.silent >= 0:
|
|
print format%args
|
|
|
|
def report(self, format, *args):
|
|
if not self.silent:
|
|
print format%args
|
|
|
|
def writeinitialdefs(self):
|
|
pass
|
|
|
|
def initblacklists(self):
|
|
self.blacklistnames = self.makeblacklistnames()
|
|
self.blacklisttypes = ["unknown", "-"] + self.makeblacklisttypes()
|
|
self.greydictnames = self.greylist2dict(self.makegreylist())
|
|
|
|
def greylist2dict(self, list):
|
|
rv = {}
|
|
for define, namelist in list:
|
|
for name in namelist:
|
|
rv[name] = define
|
|
return rv
|
|
|
|
def makeblacklistnames(self):
|
|
return []
|
|
|
|
def makeblacklisttypes(self):
|
|
return []
|
|
|
|
def makegreylist(self):
|
|
return []
|
|
|
|
def initrepairinstructions(self):
|
|
self.repairinstructions = self.makerepairinstructions()
|
|
self.inherentpointertypes = self.makeinherentpointertypes()
|
|
|
|
def makerepairinstructions(self):
|
|
"""Parse the repair file into repair instructions.
|
|
|
|
The file format is simple:
|
|
1) use \ to split a long logical line in multiple physical lines
|
|
2) everything after the first # on a line is ignored (as comment)
|
|
3) empty lines are ignored
|
|
4) remaining lines must have exactly 3 colon-separated fields:
|
|
functionpattern : argumentspattern : argumentsreplacement
|
|
5) all patterns use shell style pattern matching
|
|
6) an empty functionpattern means the same as *
|
|
7) the other two fields are each comma-separated lists of triples
|
|
8) a triple is a space-separated list of 1-3 words
|
|
9) a triple with less than 3 words is padded at the end with "*" words
|
|
10) when used as a pattern, a triple matches the type, name, and mode
|
|
of an argument, respectively
|
|
11) when used as a replacement, the words of a triple specify
|
|
replacements for the corresponding words of the argument,
|
|
with "*" as a word by itself meaning leave the original word
|
|
(no other uses of "*" is allowed)
|
|
12) the replacement need not have the same number of triples
|
|
as the pattern
|
|
"""
|
|
f = self.openrepairfile()
|
|
if not f: return []
|
|
print "Reading repair file", repr(f.name), "..."
|
|
list = []
|
|
lineno = 0
|
|
while 1:
|
|
line = f.readline()
|
|
if not line: break
|
|
lineno = lineno + 1
|
|
startlineno = lineno
|
|
while line[-2:] == '\\\n':
|
|
line = line[:-2] + ' ' + f.readline()
|
|
lineno = lineno + 1
|
|
i = line.find('#')
|
|
if i >= 0: line = line[:i]
|
|
words = [s.strip() for s in line.split(':')]
|
|
if words == ['']: continue
|
|
if len(words) <> 3:
|
|
print "Line", startlineno,
|
|
print ": bad line (not 3 colon-separated fields)"
|
|
print repr(line)
|
|
continue
|
|
[fpat, pat, rep] = words
|
|
if not fpat: fpat = "*"
|
|
if not pat:
|
|
print "Line", startlineno,
|
|
print "Empty pattern"
|
|
print repr(line)
|
|
continue
|
|
patparts = [s.strip() for s in pat.split(',')]
|
|
repparts = [s.strip() for s in rep.split(',')]
|
|
patterns = []
|
|
for p in patparts:
|
|
if not p:
|
|
print "Line", startlineno,
|
|
print "Empty pattern part"
|
|
print repr(line)
|
|
continue
|
|
pattern = p.split()
|
|
if len(pattern) > 3:
|
|
print "Line", startlineno,
|
|
print "Pattern part has > 3 words"
|
|
print repr(line)
|
|
pattern = pattern[:3]
|
|
else:
|
|
while len(pattern) < 3:
|
|
pattern.append("*")
|
|
patterns.append(pattern)
|
|
replacements = []
|
|
for p in repparts:
|
|
if not p:
|
|
print "Line", startlineno,
|
|
print "Empty replacement part"
|
|
print repr(line)
|
|
continue
|
|
replacement = p.split()
|
|
if len(replacement) > 3:
|
|
print "Line", startlineno,
|
|
print "Pattern part has > 3 words"
|
|
print repr(line)
|
|
replacement = replacement[:3]
|
|
else:
|
|
while len(replacement) < 3:
|
|
replacement.append("*")
|
|
replacements.append(replacement)
|
|
list.append((fpat, patterns, replacements))
|
|
return list
|
|
|
|
def makeinherentpointertypes(self):
|
|
return []
|
|
|
|
def openrepairfile(self, filename = "REPAIR"):
|
|
try:
|
|
return open(filename, "rU")
|
|
except IOError, msg:
|
|
print repr(filename), ":", msg
|
|
print "Cannot open repair file -- assume no repair needed"
|
|
return None
|
|
|
|
def initfiles(self):
|
|
self.specmine = 0
|
|
self.defsmine = 0
|
|
self.scanmine = 0
|
|
self.htmlmine = 0
|
|
self.specfile = sys.stdout
|
|
self.defsfile = None
|
|
self.scanfile = sys.stdin
|
|
self.htmlfile = None
|
|
self.lineno = 0
|
|
self.line = ""
|
|
|
|
def initpaths(self):
|
|
self.includepath = [os.curdir, INCLUDEDIR]
|
|
|
|
def initpatterns(self):
|
|
self.head_pat = r"^EXTERN_API[^_]"
|
|
self.tail_pat = r"[;={}]"
|
|
self.type_pat = r"EXTERN_API" + \
|
|
r"[ \t\n]*\([ \t\n]*" + \
|
|
r"(?P<type>[a-zA-Z0-9_* \t]*[a-zA-Z0-9_*])" + \
|
|
r"[ \t\n]*\)[ \t\n]*"
|
|
self.name_pat = r"(?P<name>[a-zA-Z0-9_]+)[ \t\n]*"
|
|
self.args_pat = r"\((?P<args>([^\(;=\)]+|\([^\(;=\)]*\))*)\)"
|
|
self.whole_pat = self.type_pat + self.name_pat + self.args_pat
|
|
self.sym_pat = r"^[ \t]*(?P<name>[a-zA-Z0-9_]+)[ \t]*=" + \
|
|
r"[ \t]*(?P<defn>[-0-9_a-zA-Z'\"\(][^\t\n,;}]*),?"
|
|
self.asplit_pat = r"^(?P<type>.*[^a-zA-Z0-9_])(?P<name>[a-zA-Z0-9_]+)(?P<array>\[\])?$"
|
|
self.comment1_pat = r"(?P<rest>.*)//.*"
|
|
# note that the next pattern only removes comments that are wholly within one line
|
|
self.comment2_pat = r"(?P<rest1>.*)/\*.*\*/(?P<rest2>.*)"
|
|
|
|
def compilepatterns(self):
|
|
for name in dir(self):
|
|
if name[-4:] == "_pat":
|
|
pat = getattr(self, name)
|
|
prog = re.compile(pat)
|
|
setattr(self, name[:-4], prog)
|
|
|
|
def initosspecifics(self):
|
|
if MacOS and CREATOR:
|
|
self.filetype = 'TEXT'
|
|
self.filecreator = CREATOR
|
|
else:
|
|
self.filetype = self.filecreator = None
|
|
|
|
def setfiletype(self, filename):
|
|
if MacOS and (self.filecreator or self.filetype):
|
|
creator, type = MacOS.GetCreatorAndType(filename)
|
|
if self.filecreator: creator = self.filecreator
|
|
if self.filetype: type = self.filetype
|
|
MacOS.SetCreatorAndType(filename, creator, type)
|
|
|
|
def close(self):
|
|
self.closefiles()
|
|
|
|
def closefiles(self):
|
|
self.closespec()
|
|
self.closedefs()
|
|
self.closescan()
|
|
self.closehtml()
|
|
|
|
def closespec(self):
|
|
tmp = self.specmine and self.specfile
|
|
self.specfile = None
|
|
if tmp: tmp.close()
|
|
|
|
def closedefs(self):
|
|
tmp = self.defsmine and self.defsfile
|
|
self.defsfile = None
|
|
if tmp: tmp.close()
|
|
|
|
def closescan(self):
|
|
tmp = self.scanmine and self.scanfile
|
|
self.scanfile = None
|
|
if tmp: tmp.close()
|
|
|
|
def closehtml(self):
|
|
if self.htmlfile: self.htmlfile.write(ENDHTMLREPORT)
|
|
tmp = self.htmlmine and self.htmlfile
|
|
self.htmlfile = None
|
|
if tmp: tmp.close()
|
|
|
|
def setoutput(self, spec, defs = None):
|
|
self.closespec()
|
|
self.closedefs()
|
|
if spec:
|
|
if type(spec) == StringType:
|
|
file = self.openoutput(spec)
|
|
mine = 1
|
|
else:
|
|
file = spec
|
|
mine = 0
|
|
self.specfile = file
|
|
self.specmine = mine
|
|
if defs:
|
|
if type(defs) == StringType:
|
|
file = self.openoutput(defs)
|
|
mine = 1
|
|
else:
|
|
file = defs
|
|
mine = 0
|
|
self.defsfile = file
|
|
self.defsmine = mine
|
|
|
|
def sethtmloutput(self, htmlfile):
|
|
self.closehtml()
|
|
if htmlfile:
|
|
if type(htmlfile) == StringType:
|
|
file = self.openoutput(htmlfile)
|
|
mine = 1
|
|
else:
|
|
file = htmlfile
|
|
mine = 0
|
|
self.htmlfile = file
|
|
self.htmlmine = mine
|
|
self.htmlfile.write(BEGINHTMLREPORT)
|
|
|
|
def openoutput(self, filename):
|
|
try:
|
|
file = open(filename, 'w')
|
|
except IOError, arg:
|
|
raise IOError, (filename, arg)
|
|
self.setfiletype(filename)
|
|
return file
|
|
|
|
def setinput(self, scan = sys.stdin):
|
|
if not type(scan) in (TupleType, ListType):
|
|
scan = [scan]
|
|
self.allscaninputs = scan
|
|
self._nextinput()
|
|
|
|
def _nextinput(self):
|
|
if not self.allscaninputs:
|
|
return 0
|
|
scan = self.allscaninputs[0]
|
|
self.allscaninputs = self.allscaninputs[1:]
|
|
self.closescan()
|
|
if scan:
|
|
if type(scan) == StringType:
|
|
file = self.openinput(scan)
|
|
mine = 1
|
|
else:
|
|
file = scan
|
|
mine = 0
|
|
self.scanfile = file
|
|
self.scanmine = mine
|
|
self.lineno = 0
|
|
return 1
|
|
|
|
def openinput(self, filename):
|
|
if not os.path.isabs(filename):
|
|
for dir in self.includepath:
|
|
fullname = os.path.join(dir, filename)
|
|
#self.report("trying full name %r", fullname)
|
|
try:
|
|
return open(fullname, 'rU')
|
|
except IOError:
|
|
pass
|
|
# If not on the path, or absolute, try default open()
|
|
try:
|
|
return open(filename, 'rU')
|
|
except IOError, arg:
|
|
raise IOError, (arg, filename)
|
|
|
|
def getline(self):
|
|
if not self.scanfile:
|
|
raise Error, "input file not set"
|
|
self.line = self.scanfile.readline()
|
|
if not self.line:
|
|
if self._nextinput():
|
|
return self.getline()
|
|
raise EOFError
|
|
self.lineno = self.lineno + 1
|
|
return self.line
|
|
|
|
def scan(self):
|
|
if not self.scanfile:
|
|
self.error("No input file has been specified")
|
|
return
|
|
inputname = self.scanfile.name
|
|
self.report("scanfile = %r", inputname)
|
|
if not self.specfile:
|
|
self.report("(No interface specifications will be written)")
|
|
else:
|
|
self.report("specfile = %r", self.specfile.name)
|
|
self.specfile.write("# Generated from %r\n\n" % (inputname,))
|
|
if not self.defsfile:
|
|
self.report("(No symbol definitions will be written)")
|
|
else:
|
|
self.report("defsfile = %r", (self.defsfile.name,))
|
|
self.defsfile.write("# Generated from %r\n\n" % (os.path.split(inputname)[1],))
|
|
self.writeinitialdefs()
|
|
self.alreadydone = []
|
|
try:
|
|
while 1:
|
|
try: line = self.getline()
|
|
except EOFError: break
|
|
if self.debug:
|
|
self.report("LINE: %r" % (line,))
|
|
match = self.comment1.match(line)
|
|
if match:
|
|
self.htmlreport(line, klass='commentstripping', ranges=[(
|
|
match.start('rest'), match.end('rest'), 'notcomment')])
|
|
line = match.group('rest')
|
|
if self.debug:
|
|
self.report("\tafter comment1: %r" % (line,))
|
|
match = self.comment2.match(line)
|
|
while match:
|
|
if match:
|
|
self.htmlreport(line, klass='commentstripping', ranges=[
|
|
(match.start('rest1'), match.end('rest1'), 'notcomment'),
|
|
(match.start('rest2'), match.end('rest2'), 'notcomment')])
|
|
line = match.group('rest1')+match.group('rest2')
|
|
if self.debug:
|
|
self.report("\tafter comment2: %r" % (line,))
|
|
match = self.comment2.match(line)
|
|
if self.defsfile:
|
|
match = self.sym.match(line)
|
|
if match:
|
|
if self.debug:
|
|
self.report("\tmatches sym.")
|
|
self.dosymdef(match, line)
|
|
continue
|
|
match = self.head.match(line)
|
|
if match:
|
|
if self.debug:
|
|
self.report("\tmatches head.")
|
|
self.dofuncspec()
|
|
continue
|
|
self.htmlreport(line, klass='unmatched')
|
|
except EOFError:
|
|
self.error("Uncaught EOF error")
|
|
self.reportusedtypes()
|
|
|
|
def dosymdef(self, match, line):
|
|
name, defn = match.group('name', 'defn')
|
|
self.htmlreport(line, klass='constant', ranges=[
|
|
(match.start('name'), match.end('name'), 'name'),
|
|
(match.start('defn'), match.end('defn'), 'value')])
|
|
defn = escape8bit(defn)
|
|
if self.debug:
|
|
self.report("\tsym: name=%r, defn=%r" % (name, defn))
|
|
if not name in self.blacklistnames:
|
|
oline = "%s = %s\n" % (name, defn)
|
|
self.defsfile.write(oline)
|
|
self.htmlreport(oline, klass="pyconstant")
|
|
else:
|
|
self.defsfile.write("# %s = %s\n" % (name, defn))
|
|
self.htmlreport("** no output: name is blacklisted", klass="blconstant")
|
|
# XXXX No way to handle greylisted names
|
|
|
|
def dofuncspec(self):
|
|
raw = self.line
|
|
while not self.tail.search(raw):
|
|
line = self.getline()
|
|
if self.debug:
|
|
self.report("* CONTINUATION LINE: %r" % (line,))
|
|
match = self.comment1.match(line)
|
|
if match:
|
|
line = match.group('rest')
|
|
if self.debug:
|
|
self.report("\tafter comment1: %r" % (line,))
|
|
match = self.comment2.match(line)
|
|
while match:
|
|
line = match.group('rest1')+match.group('rest2')
|
|
if self.debug:
|
|
self.report("\tafter comment1: %r" % (line,))
|
|
match = self.comment2.match(line)
|
|
raw = raw + line
|
|
if self.debug:
|
|
self.report("* WHOLE LINE: %r" % (raw,))
|
|
self.processrawspec(raw)
|
|
return raw
|
|
|
|
def processrawspec(self, raw):
|
|
match = self.whole.search(raw)
|
|
if not match:
|
|
self.report("Bad raw spec: %r", raw)
|
|
if self.debug:
|
|
match = self.type.search(raw)
|
|
if not match:
|
|
self.report("(Type already doesn't match)")
|
|
self.htmlreport(raw, klass='incomplete', ranges=[(
|
|
match.start('type'), match.end('type'), 'type')])
|
|
else:
|
|
self.report("(but type matched)")
|
|
self.htmlreport(raw, klass='incomplete')
|
|
return
|
|
type, name, args = match.group('type', 'name', 'args')
|
|
ranges=[
|
|
(match.start('type'), match.end('type'), 'type'),
|
|
(match.start('name'), match.end('name'), 'name'),
|
|
(match.start('args'), match.end('args'), 'arglist')]
|
|
self.htmlreport(raw, klass='declaration', ranges=ranges)
|
|
modifiers = self.getmodifiers(match)
|
|
type = self.pythonizename(type)
|
|
name = self.pythonizename(name)
|
|
if self.checkduplicate(name):
|
|
self.htmlreport("*** no output generated: duplicate name", klass="blacklisted")
|
|
return
|
|
self.report("==> %s %s <==", type, name)
|
|
if self.blacklisted(type, name):
|
|
self.htmlreport("*** no output generated: function name or return type blacklisted", klass="blacklisted")
|
|
self.report("*** %s %s blacklisted", type, name)
|
|
return
|
|
returnlist = [(type, name, 'ReturnMode')]
|
|
returnlist = self.repairarglist(name, returnlist)
|
|
[(type, name, returnmode)] = returnlist
|
|
arglist = self.extractarglist(args)
|
|
arglist = self.repairarglist(name, arglist)
|
|
if self.unmanageable(type, name, arglist):
|
|
self.htmlreport("*** no output generated: some argument blacklisted", klass="blacklisted")
|
|
##for arg in arglist:
|
|
## self.report(" %r", arg)
|
|
self.report("*** %s %s unmanageable", type, name)
|
|
return
|
|
if modifiers:
|
|
self.generate(type, name, arglist, modifiers)
|
|
else:
|
|
self.generate(type, name, arglist)
|
|
|
|
def getmodifiers(self, match):
|
|
return []
|
|
|
|
def checkduplicate(self, name):
|
|
if name in self.alreadydone:
|
|
self.report("Name has already been defined: %r", name)
|
|
return True
|
|
self.alreadydone.append(name)
|
|
return False
|
|
|
|
def pythonizename(self, name):
|
|
name = re.sub("\*", " ptr", name)
|
|
name = name.strip()
|
|
name = re.sub("[ \t]+", "_", name)
|
|
return name
|
|
|
|
def extractarglist(self, args):
|
|
args = args.strip()
|
|
if not args or args == "void":
|
|
return []
|
|
parts = [s.strip() for s in args.split(",")]
|
|
arglist = []
|
|
for part in parts:
|
|
arg = self.extractarg(part)
|
|
arglist.append(arg)
|
|
return arglist
|
|
|
|
def extractarg(self, part):
|
|
mode = "InMode"
|
|
part = part.strip()
|
|
match = self.asplit.match(part)
|
|
if not match:
|
|
self.error("Indecipherable argument: %r", part)
|
|
return ("unknown", part, mode)
|
|
type, name, array = match.group('type', 'name', 'array')
|
|
if array:
|
|
# array matches an optional [] after the argument name
|
|
type = type + " ptr "
|
|
type = self.pythonizename(type)
|
|
return self.modifyarg(type, name, mode)
|
|
|
|
def modifyarg(self, type, name, mode):
|
|
if type[:6] == "const_":
|
|
type = type[6:]
|
|
elif type[-4:] == "_ptr":
|
|
type = type[:-4]
|
|
mode = "OutMode"
|
|
elif type in self.inherentpointertypes:
|
|
mode = "OutMode"
|
|
if type[-4:] == "_far":
|
|
type = type[:-4]
|
|
return type, name, mode
|
|
|
|
def repairarglist(self, functionname, arglist):
|
|
arglist = arglist[:]
|
|
i = 0
|
|
while i < len(arglist):
|
|
for item in self.repairinstructions:
|
|
if len(item) == 2:
|
|
pattern, replacement = item
|
|
functionpat = "*"
|
|
else:
|
|
functionpat, pattern, replacement = item
|
|
if not fnmatch.fnmatchcase(functionname, functionpat):
|
|
continue
|
|
n = len(pattern)
|
|
if i+n > len(arglist): continue
|
|
current = arglist[i:i+n]
|
|
for j in range(n):
|
|
if not self.matcharg(pattern[j], current[j]):
|
|
break
|
|
else: # All items of the pattern match
|
|
new = self.substituteargs(
|
|
pattern, replacement, current)
|
|
if new is not None:
|
|
arglist[i:i+n] = new
|
|
i = i+len(new) # No recursive substitutions
|
|
break
|
|
else: # No patterns match
|
|
i = i+1
|
|
return arglist
|
|
|
|
def matcharg(self, patarg, arg):
|
|
return len(filter(None, map(fnmatch.fnmatchcase, arg, patarg))) == 3
|
|
|
|
def substituteargs(self, pattern, replacement, old):
|
|
new = []
|
|
for k in range(len(replacement)):
|
|
item = replacement[k]
|
|
newitem = [item[0], item[1], item[2]]
|
|
for i in range(3):
|
|
if item[i] == '*':
|
|
newitem[i] = old[k][i]
|
|
elif item[i][:1] == '$':
|
|
index = int(item[i][1:]) - 1
|
|
newitem[i] = old[index][i]
|
|
new.append(tuple(newitem))
|
|
##self.report("old: %r", old)
|
|
##self.report("new: %r", new)
|
|
return new
|
|
|
|
def generate(self, tp, name, arglist, modifiers=[]):
|
|
|
|
self.typeused(tp, 'return')
|
|
if modifiers:
|
|
classname, listname = self.destination(tp, name, arglist, modifiers)
|
|
else:
|
|
classname, listname = self.destination(tp, name, arglist)
|
|
if not classname or not listname:
|
|
self.htmlreport("*** no output generated: self.destination() returned None", klass="blacklisted")
|
|
return
|
|
if not self.specfile:
|
|
self.htmlreport("*** no output generated: no output file specified", klass="blacklisted")
|
|
return
|
|
self.specfile.write("f = %s(%s, %r,\n" % (classname, tp, name))
|
|
for atype, aname, amode in arglist:
|
|
self.typeused(atype, amode)
|
|
self.specfile.write(" (%s, %r, %s),\n" %
|
|
(atype, aname, amode))
|
|
if self.greydictnames.has_key(name):
|
|
self.specfile.write(" condition=%r,\n"%(self.greydictnames[name],))
|
|
self.generatemodifiers(classname, name, modifiers)
|
|
self.specfile.write(")\n")
|
|
self.specfile.write("%s.append(f)\n\n" % listname)
|
|
if self.htmlfile:
|
|
oline = "Adding to %s:\n%s(returntype=%s, name=%r" % (listname, classname, tp, name)
|
|
for atype, aname, amode in arglist:
|
|
oline += ",\n (%s, %r, %s)" % (atype, aname, amode)
|
|
oline += ")\n"
|
|
self.htmlreport(oline, klass="pydeclaration")
|
|
|
|
def destination(self, type, name, arglist):
|
|
return "FunctionGenerator", "functions"
|
|
|
|
def generatemodifiers(self, classname, name, modifiers):
|
|
pass
|
|
|
|
def blacklisted(self, type, name):
|
|
if type in self.blacklisttypes:
|
|
##self.report("return type %s is blacklisted", type)
|
|
return 1
|
|
if name in self.blacklistnames:
|
|
##self.report("function name %s is blacklisted", name)
|
|
return 1
|
|
return 0
|
|
|
|
def unmanageable(self, type, name, arglist):
|
|
for atype, aname, amode in arglist:
|
|
if atype in self.blacklisttypes:
|
|
self.report("argument type %s is blacklisted", atype)
|
|
return 1
|
|
return 0
|
|
|
|
def htmlreport(self, line, klass=None, ranges=None):
|
|
if not self.htmlfile: return
|
|
if ranges is None:
|
|
ranges = []
|
|
if klass:
|
|
ranges.insert(0, (0, len(line), klass))
|
|
oline = ''
|
|
i = 0
|
|
for c in line:
|
|
for b, e, name in ranges:
|
|
if b == i:
|
|
oline += '<span class="%s">' % name
|
|
if e == i:
|
|
oline += '</span>'
|
|
i += 1
|
|
|
|
if c == '<': oline += '<'
|
|
elif c == '>': oline += '>'
|
|
else: oline += c
|
|
for b, e, name in ranges:
|
|
if b >= i:
|
|
oline += '<span class="%s">' % name
|
|
if e >= i:
|
|
oline += '</span>'
|
|
if not line or line[-1] != '\n':
|
|
oline += '\n'
|
|
self.htmlfile.write(oline)
|
|
|
|
class Scanner_PreUH3(Scanner):
|
|
"""Scanner for Universal Headers before release 3"""
|
|
def initpatterns(self):
|
|
Scanner.initpatterns(self)
|
|
self.head_pat = "^extern pascal[ \t]+" # XXX Mac specific!
|
|
self.type_pat = "pascal[ \t\n]+(?P<type>[a-zA-Z0-9_ \t]*[a-zA-Z0-9_])[ \t\n]+"
|
|
self.whole_pat = self.type_pat + self.name_pat + self.args_pat
|
|
self.sym_pat = "^[ \t]*(?P<name>[a-zA-Z0-9_]+)[ \t]*=" + \
|
|
"[ \t]*(?P<defn>[-0-9'\"][^\t\n,;}]*),?"
|
|
|
|
class Scanner_OSX(Scanner):
|
|
"""Scanner for modern (post UH3.3) Universal Headers """
|
|
def initpatterns(self):
|
|
Scanner.initpatterns(self)
|
|
self.head_pat = "^EXTERN_API(_C)?"
|
|
self.type_pat = "EXTERN_API(_C)?" + \
|
|
"[ \t\n]*\([ \t\n]*" + \
|
|
"(?P<type>[a-zA-Z0-9_* \t]*[a-zA-Z0-9_*])" + \
|
|
"[ \t\n]*\)[ \t\n]*"
|
|
self.whole_pat = self.type_pat + self.name_pat + self.args_pat
|
|
self.sym_pat = "^[ \t]*(?P<name>[a-zA-Z0-9_]+)[ \t]*=" + \
|
|
"[ \t]*(?P<defn>[-0-9_a-zA-Z'\"\(][^\t\n,;}]*),?"
|
|
|
|
_8bit = re.compile(r"[\200-\377]")
|
|
|
|
def escape8bit(s):
|
|
if _8bit.search(s) is not None:
|
|
out = []
|
|
for c in s:
|
|
o = ord(c)
|
|
if o >= 128:
|
|
out.append("\\" + hex(o)[1:])
|
|
else:
|
|
out.append(c)
|
|
s = "".join(out)
|
|
return s
|
|
|
|
def test():
|
|
input = "D:Development:THINK C:Mac #includes:Apple #includes:AppleEvents.h"
|
|
output = "@aespecs.py"
|
|
defsoutput = "@aedefs.py"
|
|
s = Scanner(input, output, defsoutput)
|
|
s.scan()
|
|
|
|
if __name__ == '__main__':
|
|
test()
|