Macroscope can now handle multiple definitions of macros gracefully,

as long as all but one is local (e.g. scope is terminated by an #undef)
This commit is contained in:
Eric S. Raymond 2007-04-27 21:57:30 +00:00
parent cfabaaf134
commit 234ddfe2d9

View file

@ -96,15 +96,26 @@ def iswml(filename):
class reference:
"Describes a location by file and line."
def __init__(self, filename, line=None, docstring=None):
def __init__(self, filename, lineno=None, docstring=None):
self.filename = filename
self.line = line
self.lineno = lineno
self.references = {}
self.docstring = docstring
self.undef = None
def append(self, fn, n):
if fn not in self.references:
self.references[fn] = []
self.references[fn].append(n+1)
def visible_from(self, fn, n):
"Is this definition visible from the specified file and line?"
if self.undef != None:
return self.filename == fn
else:
# Sigh. This doesn't match the actual preprocessor semantics.
# It means any macro without an undef is visible anywhere.
# We can't do better than this without a lot of hairy graph-
# coloring logic to simulate include path interpretation.
return True
def dump_references(self):
for (file, linenumbers) in self.references.items():
print " %s: %s" % (file, `linenumbers`[1:-1])
@ -117,10 +128,10 @@ class reference:
if byfile:
return byfile
else:
return cmp(self.line, other.line)
return cmp(self.lineno, other.lineno)
def __str__(self):
if self.line:
return '"%s", line %d' % (self.filename, self.line)
if self.lineno:
return '"%s", line %d' % (self.filename, self.lineno)
else:
return self.filename
@ -207,16 +218,20 @@ class CrossRef:
here.hash.update(line)
here.hash = here.hash.digest()
if name in self.xref:
if self.xref[name].hash != here.hash:
print >>sys.stderr, "*** Warning: different " \
"definition of %s from %s, at %s" \
% (name, self.xref[name], here)
elif warnlevel > 0:
print >>sys.stderr, "*** Warning: duplicate " \
"definition of %s from %s, at %s" \
% (name, self.xref[name], here)
else:
self.xref[name] = here
for defn in self.xref[name]:
if not defn.visible_from(filename, n):
continue
elif defn.hash != here.hash:
print >>sys.stderr, \
"%s: overrides different %s definition at %s" \
% (here, name, defn)
elif warnlevel > 0:
print >>sys.stderr, \
"%s: duplicates %s definition at %s" \
% (here, name, defn)
if name not in self.xref:
self.xref[name] = []
self.xref[name].append(here)
state = "outside"
elif state == "macro_header" and line and line[0] != "#":
state = "macro_body"
@ -224,6 +239,11 @@ class CrossRef:
here.docstring += line[1:]
if state in ("macro_header", "macro_body"):
here.hash.update(line)
elif line.strip().startswith("#undef"):
tokens = line.split()
name = tokens[1]
# Potential bug here on unbalanced #undef
self.xref[name][-1].undef = n
dfp.close()
elif filename.endswith(".def"):
# It's a list of names to be considered defined
@ -252,11 +272,17 @@ class CrossRef:
# Find references to macros
for match in re.finditer(CrossRef.macro_reference, line):
name = match.group(1)
candidates = 0
if name in formals:
continue
elif name in self.xref:
self.xref[name].append(fn, n+1)
else:
for defn in self.xref[name]:
if defn.visible_from(fn, n):
candidates += 1
defn.append(fn, n+1)
if candidates > 1:
print "%s: more than one definition of %s is visible here." % (reference(fn, n), name)
if candidates == 0:
self.unresolved.append((name, reference(fn,n+1)))
# Find references to resource files
for match in re.finditer(CrossRef.file_reference, line):
@ -292,15 +318,20 @@ class CrossRef:
rfp.close()
def xrefdump(self, pred=None):
"Report resolved macro references."
for (name, defloc) in self.xref.items():
if pred and not pred(name, defloc):
continue
nrefs = len(defloc.references)
if nrefs == 0:
print "%s: macro %s is unused" % (defloc, name)
else:
print "%s: macro %s is used in %d files:" % (defloc, name, nrefs)
defloc.dump_references()
for name in self.xref:
for defn in self.xref[name]:
if pred and not pred(name, defn):
continue
defn.dump_references()
if defn.undef:
type = "local"
else:
type = "global"
nrefs = len(defn.references)
if nrefs == 0:
print "%s: %s macro %s is unused" % (defn, type, name)
else:
print "%s: %s macro %s is used in %d files:" % (defn, type, name, nrefs)
for (name, defloc) in self.fileref.items():
if pred and not pred(name, defloc):
continue
@ -321,22 +352,24 @@ class CrossRef:
print "%s -> %s" % (reference, name)
def deflist(self, pred=None):
"List all resource definitions."
for (name, defloc) in self.xref.items() + self.fileref.items():
if pred and not pred(name, defloc):
continue
nrefs = len(defloc.references)
if nrefs:
for name in self.xref:
for defn in self.xref[name]:
if not pred or pred(name, defn):
print name
for (name, defloc) in self.fileref.items():
if not pred or pred(name, defloc):
print name
def extracthelp(self, pref, fp):
"Deliver all macro help comments in HTML form."
# Bug: finds only the first definition of each macro in scope.
doclist = self.xref.keys()
doclist = filter(lambda x: self.xref[x].docstring.count("\n") > 1, doclist)
doclist.sort(lambda x, y: cmp(self.xref[x], self.xref[y]))
doclist = filter(lambda x: self.xref[x][0].docstring.count("\n") > 1, doclist)
doclist.sort(lambda x, y: cmp(self.xref[x][0], self.xref[y]))
outstr = ""
filename = None
counted = 0
for name in doclist:
entry = self.xref[name]
entry = self.xref[name][0]
if entry.filename != filename:
if counted:
outstr += "</dl>\n"