1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
|
#!/usr/bin/env python
#
# Hacket is a character from Beckett's Watt. It is only briefly intorduced once at the beginning of Watt,
# (IIRC) shortly before Watt hears choir singing about big fat bun for everyone.
#
#
# This script was used (once) to convert deprecated (bastardized) version of wm3 in yade to the official version
# Arguments to provide are explained below.
# Many things are hardcoded, don't ever try to use it without reading the source throughly.
# You have been warned.
#
#
# since the tags files must contain prototypes: I ran
# ctags-exuberant --extra=+q --c++-kinds=+pf --language-force=c++ *
# in wm3's Foundation/Math directory as well as in yade-lib-wm3-math
#
# To perform the actual conversion, I did
# ./hackett.py tags-ok tags-bastard ~/yade/trunk/yade-libs/yade-lib-wm3-math/src/yade-lib-wm3-math
# where tags-ok and tags-bastard are symlinked from their respective directories
#
import sys,os
from re import *
import string
import logging
from pprint import *
import copy
renamedTags={}
# renames that are not obvious to be guess by the first-letter-case-changing algorithm
# (they must include class name)
renamedTags[('Math::cosinus')]=('Math::Cos')
renamedTags[('Math::tangent')]=('Math::Tan')
renamedTags[('Math::sinus')]=('Math::Sin')
renamedTags[('Math::sqRoot')]=('Math::Sqrt')
renamedTags[('Math::power')]=('Math::Pow')
renamedTags[('Math::roundDown')]=('Math::Floor')
renamedTags[('Math::roundUp')]=('Math::Ceil')
renamedTags[('Math::invSqRoot')]=('Math::InvSqrt')
renamedTags[('Math::eExp')]=('Math::Exp')
renamedTags[('Math::logarithm')]=('Math::Log')
logging.basicConfig(level=logging.DEBUG)
def tagsParse(tagsFile):
logging.info("Parsing tags file `%s'."%tagsFile)
tags={}
tagsFile=open(tagsFile)
for l in tagsFile:
l=l[:-1]
# ctags header
if l[0]=='!': continue
if match('^\S*operator\s.*$',l): continue # discard operators
if match('^.*\s[dntc]$',l): continue # discard #defines, namespaces, typedefs and classes
#since the text field can contain \t which is otherwise field separator, pattern is clumsy but should work
m=match('^(\S+|\S+operator \S+)\s+(\S+)\s+(/\^.*\$/;")\s+(\S+)\s+(\S+)?.*$',l)
if not m:
logging.warn("Line `%s' not matched"%l)
continue
name,file,text,type,extra=m.groups()[0:5]
#print name,file,text,type,extra
### this is probably not needed anymore
# these are typedefs, like this:
# Wm3::Matrix2d Wm3Matrix2.h /^typedef Matrix2<double> Matrix2d;$/;" t namespace:Wm3
# we don't need these, since they weren't bastardized
if extra[0:10]=='namespace:': continue
# some two bizzare cases (namespace opening and something different...)
if extra[0:5]=='file:': continue
#if extra[0:6]!='class:':
# logging.warning('Skipping weird tag: %s, %s, %s, %s, %s',name,file,text,type,extra)
# continue
## we want only prototypes and function definitions
# perhaps they wouldn't pass through the regexp filter above anyway
if not type in 'pf': continue
# this would discard classes (have no '::'), but that should have been caught by the above as well...
if name.find('::')<0: continue
# end
# prepend class name to the symbol name, unless it is already contained
if extra[0:6]=='class':
clss=extra[6:]
if name.find(clss)!=0:
name=clss+'::'+name
if name[0:5]=='Wm3::': name=name[5:] # chop Wm3:: off the symbol name
# ctags escape some metacharacters, we don't nee that
text=text.replace('\\','')
isAlready=False
if tags.has_key(name):
for t in tags[name]:
if t[0]==type and t[1]==text and t[2]==file:
# exact match found, skip
isAlready=True
if isAlready: continue
if not tags.has_key(name): tags[name]=[]
tags[name].append([type,text,file,False])
return tags
good=tagsParse(sys.argv[1])
bad=tagsParse(sys.argv[2])
badDir=sys.argv[3]
#pprint(bad); pprint(good)
#for c,n in good.keys(): print c
#for c,n in bad.keys(): print c
#tags that are in bad but not in good
addedTags={}
# tags that are only renamed in bad and otherwise exist in good
# keys are (badClss,badName), values are keys in good: (goodClss,goodName)
# list of (class,name) tags, that have been kept intact
keptTags=[]
for badTag in bad.keys():
# probably not needed
if match('::operator.*',badTag): continue
bb=bad[badTag]
# if the method is lowercase and uppercased appears in the good set, it was renamed
cls,meth=badTag.split('::')
goodTag=cls+'::'+meth[0].upper()+meth[1:]
if renamedTags.has_key(badTag): continue
elif good.has_key(badTag): keptTags.append(badTag)
elif meth[0].lower()==meth[0] and good.has_key(goodTag):
renamedTags[badTag]=goodTag
# otherwise, it is an added method that has no corresponding good class
else:
addedTags[badTag]=bb[0]
######################################################################
### uncomment this to get symbols map for hackett-warn-replace.py
if 0:
r={}
for t in renamedTags.keys(): r[t.split('::')[-1]]=renamedTags[t].split('::')[-1]
for t in sorted(r.keys()): print "\t'%s':'%s',"%(t,r[t])
sys.exit(0)
# now accumulate changes for particular files, so that we can iterate over files conveniently
fileReplace=[]
for t in renamedTags:
for bb in bad[t]:
f=bb[2]
if not f in fileReplace: fileReplace.append(f)
#pprint(fileReplace)
#pprint(renamedTags)
##############################################
######## here begins the actual work
##############################################
import shutil
from os.path import join
for fName in fileReplace:
origFile=join(badDir,fName)
bcupFile=origFile+'.~bastardized.0~'
logging.info("Processing `%s'."%origFile)
if 1: # if 1, files will be copied and written over; the next branch writes to "backup" files
shutil.move(origFile,bcupFile)
fout=open(origFile,'w')
fin=open(bcupFile)
else:
fin=open(origFile)
fout=open(bcupFile,'w')
# buffer for line that has not been syntactically terminated
leftOver=''
written=False
for l in fin.xreadlines():
if len(leftOver)>0: l=leftOver+l
written=False
for tt in renamedTags:
for t in bad[tt]:
#if t[0]!='p': continue # for now, only do prototypes...
#print t
pat=t[1][2:-4] # line hardcopy - discard leading /^ and trailing $;/"
f=t[2] # filename
if f!=fName: continue
if not l.find(pat)<0:
if t[3]: # there are two different tags in the same file, but their text signature is the same - presumaly argument list that differs continues on the next line etc. Since it will cause erroneous output, shout to the user
logging.info("The method `%s' had the same tag `%s' twice (overloaded?). Please fix by hand!!!"%(tt,pat))
continue
# classes may be discarded from names
badMethName=tt.split('::')[-1]
goodMethName=renamedTags[tt].split('::')[-1]
# badMethName is the old method inline declarationand definition that is marked as deprecated and merely wraps the new method
# goodMethName is the new method
# badMeth and goodMeth are corresponding source lines that we will create
# this pattern matches both declaration and definition header. It must return return type in \1, method argument list in \2.
methodPattern=r'^(.*?)\b'+badMethName+r'\b\s?(\s?\(.*\)\s*(const)?)\s*(;|{).*$';
badMeth,count=subn(methodPattern,r'\1'+badMethName+r'\2',l)
goodMeth,count2=subn(methodPattern,r'\1'+goodMethName+r'\2 \4',l)
# match the same times for both methods (always, since pattern is the same)
# match at most once; otherwise output might be syntactically incorrect
assert(count==count2); assert(count<=1)
# we will append function body (wrapper) to badMeth, get rid of newline
badMeth=badMeth.replace('\n',''); #goodMeth=goodMeth.replace('\n','')
badMeth="\t__attribute__((deprecated)) inline "+badMeth
if count==0: leftOver=l[:-1] # the declaration is not terminated at this lines, process with the next line
elif count==1:
leftOver=''
t[3]=True # this particular tag has been processed by us - this allows us to list unprocessed tags at the end, if there are any, since taht would probably indicate some error
if t[0]=='p': # we are dealing with prototype, hence we define wrapper for goodMeth
args=search(r'\((.*)\)',goodMeth); # extract arguments
assert(args)
a=args.group(1);
args=args.group(1).split(',') # process args one by one
try: args.remove('') # in case of void methods, remove empty args
except ValueError: pass
# begin building the wrapper
badMeth+='{return '+goodMethName+'('
# pass it all arguments
for i in range(0,len(args)):
var=args[i].split()[-1] # for every arg, only the last word (variable name), not type
var=sub('\[[0-9]+\]$','',var) # remove array brackets after variable
badMeth+=var
if i<len(args)-1: badMeth+=',' # append , except after the last arg
badMeth+=');}\n'
fout.write(badMeth) # wrapper is on the line preceeding the actual method
fout.write(goodMeth) # write goodMeth
written=True
if not written and leftOver=='': fout.write(l) # write lines that were not interesting
# list tags that have not been processed. There shouldn't be any.
for tt in renamedTags:
for t in bad[tt]:
if t[3]==False: logging.warning("Tag `%s' was not processed (%s)!"%(tt,t))
|