This shortens the often-long line of ....
[dwarf-doc.git] / dwarf5 / tools / uses.py
1 # Copyright 2012 DWARF Debugging Information Format Committee
2 #
3 # Looks for odd things in the .tex source
4 # and prints some information about oddities.
5 # It does not create new .tex, it just 
6 # prints (to standard-out) potential issues in the .tex files.
7 #
8 # It sort of parses the source lines, but the parsing
9 # is barely adequate to the task of finding
10 # oddities. Just barely.
11 #
12 # Run as (for example)
13 #   python uses.py ../latexdoc/*.tex
14
15
16 import sys
17 import fileio
18
19 global linkdefinitionsdict
20 global linkusesdict
21 global labeldefinitionsdict
22 global labelusesdict
23 global ignorethesedict
24 global indexsetdict
25 global dupdefcount
26 global unresolveddwdict
27 # Links meaning \livelink \livetarg \livetargi macros
28 linkdefinitionsdict = {}
29 linkusesdict  = {}
30 # labels meaning \refersec (a ref) and \label  (a def)
31 labeldefinitionsdict = {}
32 labelusesdict =  {}
33 # The dict of indexed things.
34 indexsetdict ={}
35 # DW sorts of names not sensibly resolved.
36 unresolveddwdict = {}
37 dupdefcount = 0
38
39
40
41 # a list of words to ignore: silly stuff.
42 ignorethesedict = {"of":0, "a":0, "the":0, "and":0, "but":0,"DWARF":0,
43 "Standards":0,"Committee":0,"Version":0 }
44
45 class tokmention:
46   def __init__(self):
47     self._token = '' 
48     self._file = ""
49     self._line = 0
50     # Class is "id", "ind","other","none"
51   def __init__(self,tok,filename,line):
52     self._token = tok
53     self._file = filename
54     self._line = line
55
56
57
58
59 def ischar(tok,c):
60    if tok._class != "ind":
61       return "n"
62    if len(tok._tex) != 1:
63        return "n"
64    if tok._tex[0] != c:
65        return "n"
66    return "y"
67
68 def dwspace(tok):
69   if ischar(tok," ") == "y":
70     return "y"
71   if ischar(tok,"\t") == "y":
72     return "y"
73   return "n"
74   
75   
76 def isbrace(tok,brace):
77   if tok._class != "ind":
78      return "n"
79   if len(tok._tex) != 1:
80      return "n"
81   if brace == tok._tex[0]:
82      return "y"
83   return "n"
84
85 def toknamestring(t):
86   """ Turn a token into its string as a string """
87   return ''.join(t._tex)
88
89
90 def pickup(linetoks,tnumin,pattern,myfile,linenum):
91   """ The token pattern characters are
92   i meaning identifier
93   [space] meaning whitespace
94   { meaning left brace
95   } meaning right brace
96   * meaning any token except } and end-line
97   
98   Precondition:  linetoks[tnumin] is identifier (meaning a command)
99   Returns: a token list, one per non-space in the pattern.
100      For the *, the token is itself a list of whatever it contains.
101   """
102   outtoks = []
103   numabsorbed = 1
104   inlen = len(linetoks) 
105   curnum = tnumin
106   curtok = linetoks[curnum]
107   patterncharnum = -1
108   for c in pattern:
109     patterncharnum = patterncharnum + 1
110     if curnum >= inlen:
111       print "ERROR line ended surprisingly, pattern ", pattern,"  line ",linenum," file ",myfile._name
112       return outtoks,numabsorbed
113     curtok = linetoks[curnum]
114     if c == " ":
115       while dwspace(curtok) == "y":
116         curnum = curnum + 1
117         if curnum >= inlen:
118           print "ERROR line ended surprisingly in space, pattern ", pattern, " line ",linenum," file ",myfile._name
119           return outtoks,numabsorbed
120         numabsorbed = numabsorbed + 1
121         curtok = linetoks[curnum]
122       continue
123     elif c == "i":
124       if curtok._class != "id":
125         print "ERROR line  expected identifier got ",curtok._tex, "pattern" , pattern, " line " ,linenum," file ",myfile._name
126         return outtoks,numabsorbed
127       numabsorbed = numabsorbed + 1
128       outtoks += [curtok]
129       curnum = curnum + 1
130       continue
131     elif c == "{":
132       if isbrace(curtok,"{")  == "y":
133         outtoks += [curtok]
134         curnum = curnum + 1
135         numabsorbed = numabsorbed + 1
136       else:
137         print "ERROR line  expected {  got ",curtok._tex," pattern ",pattern," line " ,linenum," file ",myfile._name
138         return outtoks,numabsorbed
139     elif c == "}":
140       if isbrace(curtok,"}")  == "y":
141         outtoks += [curtok]
142         curnum = curnum + 1
143         numabsorbed = numabsorbed + 1
144       else:
145         print "ERROR line  expected }  got ",curtok._tex,"pattern",pattern," line " ,linenum," file ",myfile._name
146         return outtoks,numabsorbed
147     elif c == "*":
148       outlist = []
149       curtok = linetoks[curnum]
150       while isbrace(curtok,"}") == "n":
151         if dwspace(curtok) == "n":
152            outlist += [curtok]
153         curnum = curnum + 1
154         if curnum >= inlen:
155           outtoks += [outlist]
156           if patterncharnum < (len(pattern) -1): 
157             print "ERROR insufficient tokens on line for pattern ", pattern," line " ,linenum," file ",myfile._name
158           return outtoks,numabsorbed
159         numabsorbed = numabsorbed + 1
160         curtok = linetoks[curnum]
161       # Found a right brace, so done here.
162       outtoks += [outlist]
163     else:
164         print "ERROR pattern had unexpected character ",pattern
165         sys.exit(1)
166   return outtoks,numabsorbed
167
168 def reftodict(d,k,v):
169   keystring = toknamestring(k._token)
170   if d.has_key(keystring) == 0:
171      d[keystring] =  [v]
172   else:
173      existing = d.get(keystring)
174      existing += [v]
175      d[keystring] =  existing
176
177 def deftodict(d,k,v):
178   global dupdefcount
179   keystring = toknamestring(k._token)
180   if d.has_key(keystring) == 0:
181      d[keystring] =  [v]
182   else:
183      # This is a duplication, we just record it here,
184      # we will report on it shortly.
185      dupdefcount = dupdefcount + 1
186      existing = d.get(keystring)
187      existing += [v]
188      d[keystring] =  existing
189
190 def livetargprocess(linetoks,tnumin,myfile,linenum):
191   """ \livetarg{chap:DWTAGtemplatevalueparameter}{DW\-\_TAG\-\_template\-\_value\-\_parameter} """
192   global linkdefinitionsdict
193   global linkusesdict
194   global indexsetdict
195   t = linetoks[tnumin]
196   ourtoks,inlen = pickup(linetoks,tnumin,"i { i } { i }",myfile,linenum)
197   if len(ourtoks) > 5:
198     targlink= tokmention(ourtoks[2],myfile,linenum)
199     targname= tokmention(ourtoks[5],myfile,linenum)
200     deftodict(linkdefinitionsdict,targlink,targname)
201     reftodict(indexsetdict,targname,targname)
202   return inlen
203 def livetargiprocess(linetoks,tnumin,myfile,linenum):
204   """ \livetargi{chap:DWTAGtemplatevalueparameter}{DW\-\_TAG\-\_template\-\_value\-\_parameter}{name of targ} """
205   global linkdefinitionsdict
206   global linkusesdict
207   global indexsetdict
208   t = linetoks[tnumin]
209   ourtoks,inlen = pickup(linetoks,tnumin,"i { i } { i } { * }",myfile,linenum)
210   if len(ourtoks) > 5:
211     targlink= tokmention(ourtoks[2],myfile,linenum)
212     targname= tokmention(ourtoks[5],myfile,linenum)
213     deftodict(linkdefinitionsdict,targlink,targname)
214     reftodict(indexsetdict,targname,targname)
215   return inlen
216 def livelinkprocess(linetoks,tnumin,myfile,linenum):
217   """ \livelink{chap:DWTAGtemplatevalueparameter}{DW\-\_TAG\-\_template\-\_value\-\_parameter} """
218   global linkdefinitionsdict
219   global linkusesdict
220   global indexsetdict
221   t = linetoks[tnumin]
222   ourtoks,inlen = pickup(linetoks,tnumin,"i { i } { i }",myfile,linenum)
223   if len(ourtoks) > 5:
224     targlink= tokmention(ourtoks[2],myfile,linenum)
225     targname= tokmention(ourtoks[5],myfile,linenum)
226     reftodict(linkusesdict,targlink,targname)
227     reftodict(indexsetdict,targname,targname)
228   return inlen
229 def labelprocess(linetoks,tnumin,myfile,linenum):
230   """ \label{alabel} """
231   global labeldefinitionsdict
232   t = linetoks[tnumin]
233   ourtoks,inlen = pickup(linetoks,tnumin,"i { i }",myfile,linenum)
234   if len(ourtoks) > 2:
235     label = tokmention(ourtoks[2],myfile,linenum)
236     deftodict(labeldefinitionsdict,label,label)
237   return inlen
238 def addtoindexprocess(linetoks,tnumin,myfile,linenum):
239   """ \addtoindex{alabel} """
240   t = linetoks[tnumin]
241   ourtoks,inlen = pickup(linetoks,tnumin,"i { i }",myfile,linenum)
242   if len(ourtoks) > 2:
243     index = tokmention(ourtoks[2],myfile,linenum)
244     reftodict(indexsetdict,index,index)
245   return inlen
246 def indexprocess(linetoks,tnumin,myfile,linenum):
247   """ \index{indexentryname} """
248   global labelusesdict
249   t = linetoks[tnumin]
250   ourtoks,inlen = pickup(linetoks,tnumin,"i { i }",myfile,linenum)
251   if len(ourtoks) > 2:
252     myentry = tokmention(ourtoks[2],myfile,linenum)
253     reftodict(indexsetdict,myentry,myentry)
254   return inlen
255 def refersecprocess(linetoks,tnumin,myfile,linenum):
256   """ \refersec{label} """
257   global labelusesdict
258   t = linetoks[tnumin]
259   ourtoks,inlen = pickup(linetoks,tnumin,"i { i }",myfile,linenum)
260   if len(ourtoks) > 2:
261     label = tokmention(ourtoks[2],myfile,linenum)
262     reftodict(labelusesdict,label,label)
263   return inlen
264
265 def transfunc(linetoks,myfile,linenum):
266   if len(linetoks) < 1:
267     return linetoks
268   initialtok = linetoks[0]
269   if ''.join(initialtok._tex) == "\\newcommand":
270     # We ignore newcommand lines, they are not stuff
271     # we want to look at, they are new macros, not macro uses.
272     # We don't want to transform or touch them, nor report on them.
273     return linetoks
274   tnumin = 0
275   lasttoknum = len(linetoks)
276   while tnumin < lasttoknum:
277     t = linetoks[tnumin]
278     tnumcount = 1
279     rawtok = ''.join(t._tex)
280     stdname= ''.join(t._std)
281     if rawtok == "\\livetarg":
282       tnumcount = livetargprocess(linetoks,tnumin,myfile,linenum)
283     elif rawtok == "\\livetargi":
284       tnumcount = livetargiprocess(linetoks,tnumin,myfile,linenum)
285     elif rawtok == "\\livelink":
286       tnumcount = livelinkprocess(linetoks,tnumin,myfile,linenum)
287     elif rawtok == "\\label":
288       tnumcount = labelprocess(linetoks,tnumin,myfile,linenum)
289     elif rawtok == "\\refersec":
290       tnumcount = refersecprocess(linetoks,tnumin,myfile,linenum)
291     elif rawtok == "\\addtoindex":
292       tnumcount = addtoindexprocess(linetoks,tnumin,myfile,linenum)
293     elif rawtok == "\\index":
294       tnumcount = indexprocess(linetoks,tnumin,myfile,linenum)
295     else:  
296       if t._class == "id":
297         namemention = tokmention(t,myfile,linenum)
298         namestring = ''.join(t._std)
299         if namestring.startswith("DW"):
300           global unresolveddwdict
301           reftodict(unresolveddwdict,namemention,namemention)
302         # Else we might want to build a dict of all other words 
303         # while leaving out all \latex commands we don't know?
304     tnumin = tnumin + tnumcount
305     # End of for loop.
306   return linetoks
307
308 def process_files(filelist):
309   dwf = fileio.readFilelist(filelist)
310   # We will really just report, not transform
311   # anything, but this works.
312   dwf.dwtransformline(transfunc)
313
314   # Here we report on our discoveries.
315
316 def read_file_args(targlist):
317   cur = 1
318   filelist = []
319   while  len(sys.argv) > cur:
320     v = sys.argv[cur]
321     filelist += [v]
322     cur = int(cur) + 1
323   process_files(filelist)
324
325 def sort_tokmlist(mylist):
326    aux = [ (''.join(x._token._tex),x) for x in mylist ]
327    aux.sort()
328    return[ (x[1]) for x in aux]
329   
330 def printdups(d,name):
331   lablist = d.keys()
332   if len(lablist) < 1:
333     return
334   lablist.sort()
335   for k in lablist:
336      tokmlist = d[k]
337      stokmlist = sort_tokmlist(tokmlist)
338      if len(stokmlist) > 1:
339        print name,k,
340        for i in range (len(stokmlist)):
341          t = stokmlist[i]
342          if i == 0:
343            print t._file._name, t._line,
344          else:
345            print ", ",t._file._name, t._line,
346        print ""
347
348 def printoddDWentries(d,title):
349   """ An odd entry is one where the label text
350       does not match the condensed DW name properly.
351       \livelink{chap:DWTAGfoo}{DW_TAG_fx}
352       for example. 
353   """
354   names = d.keys()
355   for n in names:
356     if n.startswith("chap:DW"):
357       tn = d[n][0]
358       keytok = tn._token
359       toklab = "chap:" + ''.join(keytok._label)
360       if n != toklab:
361         print title, toknamestring(keytok),"mismatch", n ,tn._file._name,tn._line
362
363 def print_stats():
364   global linkdefinitionsdict
365   global linkusesdict
366   global labeldefinitionsdict
367   global labelusesdict
368   global ignorethesedict
369   global indexsetdict
370   global dupdefcount
371   global unresolveddwdict
372  
373   if dupdefcount > 0:
374     print "Duplicate definitions count: ",dupdefcount
375
376   printdups(labeldefinitionsdict,"Duplicated Labels")
377   printdups(linkdefinitionsdict,"Duplicated Links")
378
379   lablist = unresolveddwdict.keys()
380   if len(lablist) >0:
381     lablist.sort()
382     for k in lablist:
383       tokm = unresolveddwdict[k][0]
384       print "Unresolved DW string:", toknamestring(tokm._token)," at ",tokm._file._name,tokm._line
385
386   targlist = linkdefinitionsdict.keys()
387   targlist.sort()
388   for t in targlist:
389      u = linkusesdict.get(t)
390      if u == None:
391        tm = linkdefinitionsdict.get(t)
392        print  "Unused:",t, tm[0]._file._name,tm[0]._line
393     
394   printoddDWentries(linkdefinitionsdict,"link definitions");
395   printoddDWentries(linkusesdict,"link uses");
396   printoddDWentries(labeldefinitionsdict,"label definitions");
397   printoddDWentries(labelusesdict,"label uses");
398   
399
400
401   #FIXME More reporting needed.
402
403 def read_all_args():
404   filelist = []
405   fileio.setkeepordeletecomments("d")
406   cur = 1
407   while  len(sys.argv) > cur:
408     v = sys.argv[cur]
409     filelist += [v]
410     cur = int(cur) + 1
411   if len(filelist) < 1:
412     print >> sys.stderr , "No files specified."
413     printlegals()
414     sys.exit(1)
415   process_files(filelist)
416   print_stats()
417
418 #  anylink [-t <class>] ... [file] ...
419
420 if __name__ == '__main__':
421   read_all_args()
422