~yade-dev/yade/0.80

« back to all changes in this revision

Viewing changes to scripts/hackett.py

  • Committer: Anton Gladky
  • Date: 2012-05-02 21:50:42 UTC
  • Revision ID: gladky.anton@gmail.com-20120502215042-v1fa9r65usqe7kfk
0.80.0

Show diffs side-by-side

added added

removed removed

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