]> git.donarmstrong.com Git - lilypond.git/blob - buildscripts/fixcc.py
Fixes (Werner).
[lilypond.git] / buildscripts / fixcc.py
1 #!/usr/bin/python
2
3 # fixcc -- nitpick lily's c++ code
4
5 # TODO
6 #  * maintainable rules: regexp's using whitespace (?x) and match names
7 #    <identifier>)
8 #  * trailing * vs function definition
9 #  * check lexer, parser
10 #  * rewrite in elisp, add to cc-mode
11 #  * ?
12 #  * profit
13
14 import __main__
15 import getopt
16 import os
17 import re
18 import string
19 import sys
20 import time
21
22 COMMENT = 'COMMENT'
23 CXX = 'C++'
24 verbose_p = 0
25 indent_p = 0
26
27 rules = {
28         CXX:
29         [
30         # space before parenthesis open
31         ('([^\( \]])[ \t]*\(', '\\1 ('),
32         # space after comma
33         (',[ \t]*', ', '),
34         # delete inline tabs
35         ('(\w)\t+', '\\1 '),
36         # delete inline double spaces
37         ('   *', ' '),
38         # delete space after parenthesis open
39         ('\([ \t]*', '('),
40         # delete space before parenthesis close
41         ('[ \t]*\)', ')'),
42         # delete spaces after prefix
43         ('(--|\+\+)[ \t]*([\w\)])', '\\1\\2'),
44         # delete spaces before postfix
45         ('([\w\)\]])[ \t]*(--|\+\+)', '\\1\\2'),
46         # delete space after parenthesis close
47         #('\)[ \t]*([^\w])', ')\\1'),
48         # delete space around operator
49         # ('([\w\(\)\]])([ \t]*)(::|\.)([ \t]*)([\w\(\)])', '\\1\\3\\5'),
50         ('([\w\(\)\]])([ \t]*)(\.)([ \t]*)([\w\(\)])', '\\1\\3\\5'),
51         # delete space after operator
52         ('(::)([ \t]*)([\w\(\)])', '\\1\\3'),
53         # delete superflous space around operator
54         ('([\w\(\)\]])([ \t]+)(&&|\|\||<=|>=|!=|\|=|==|\+=|-=|\*=|/=|\?|<|>|\+|-|=|/|:|&|\||\*)([ \t]+)([\w\(\)])', '\\1 \\3 \\5'),
55         # space around operator1
56         ('([\w\)\]]) *(&&|\|\||<=|>=|!=|\|=|==|\+=|-=|\*=|/=|\?|<|>|=|/|:|&|\||\*) *([\w\(])', '\\1 \\2 \\3'),
57         # space around operator2
58         ('([\w\)\]]) *(&&|\|\||<=|>=|!=|\|=|==|\+=|-=|\*=|/=|\?|<|>|=|/|:|&|\||\*) ([^\w\s])', '\\1 \\2 \\3'),
59         # space around operator3
60         ('([^\w\s]) (&&|\|\||<=|>=|!=|\|=|==|\+=|-=|\*=|/=|\?|<|>|=|/|:|&|\||\*) *([\w\(])', '\\1 \\2 \\3'),
61         # space around +/-; exponent
62         ('([\w\)\]])(\+|-)([_A-Za-z\(])', '\\1 \\2 \\3'),
63         ('([_\dA-Za-df-z\)\]])(\+|-)([\w\(])', '\\1 \\2 \\3'),
64         # trailing operator
65         (' (::|&&|\|\||<=|>=|!=|\|=|==|\+=|-=|\*=|/=|\?|<|>|\+|-|=|/|:|&XXX|\||\*XXX)[ \t]*\n([ \t]*)',  '\n\\2\\1 '),
66         #breaks function definitions
67         #to#(' (::|&&|\|\||<=|>=|!=|\|=|==|\+=|-=|\*=|/=|<|>|\+|-|=|/|&|\||\*)[ \t]*\n([ \t]*)',         '\n\\2\\1 '),
68         # pointer
69         ('(bool|char|const|delete|int|stream|unsigned|void|(struct \w+)|([A-Z]\w*)|[,]|&&|\|\|)[ \t]*(\*|&)[ \t]*', '\\1 \\4'),
70         #to#('(bool|char|const|delete|int|stream|unsigned|void|([A-Z]\w*)|[,])[ \n\t]*(\*|&)[ \t]*', '\\1 \\3'),
71         # pointer with template
72         ('(( *((bool|char|delete|int|stream|unsigned|void|(class[ \t]+\w*)|([A-Z]\w*)|[,])[ \*&],*)+)>) *(\*|&) *', '\\1 \\7'),
73         #to#('(( *((bool|char|delete|int|stream|unsigned|void|(class[ \t]+\w*)|([A-Z]\w*)|[,])[ \*&],*)+)>)[ \t\n]*(\*|&) *', '\\1 \\7'),
74         # unary pointer, minus, not
75         ('(return|=) (\*|&|-|!) ([\w\(])', '\\1 \\2\\3'),
76         # space after `operator'
77         ('(\Woperator) *([^\w\s])', '\\1 \\2'),
78         # dangling newline
79         ('\n[ \t]*\n[ \t]*\n', '\n\n'),
80         # dangling parenthesis open
81         #('[ \t]*\n[ \t]*\([ \t]*\n', '('),
82         ('\([ \t]*\n', '('),
83         # dangling parenthesis close
84         ('\n[ \t]*\)', ')'),
85         # dangling comma
86         ('\n[ \t]*,', ','),
87         # dangling semicolon
88         ('\n[ \t]*;', ';'),
89         # brace open
90         ('(\w)[ \t]*([^\s]*){([ \t]*\n)', '\\1\\2\n{\n'),
91         # brace open backslash
92         ('(\w[^\n]*){[ \t]*\\\\\n', '\\1\\\n{\\\n'),
93         # brace close
94         ('}[ \t]*([^\n]*\w[^\n\\\]*)\n', '}\n\\1\n'),
95         # brace close backslash
96         ('}[ \t]*([^\n]*\w[^\n\\\]*)', '\n}\n\\1'),
97         # delete space after `operator'
98         #('(\Woperator) (\W)', '\\1\\2'),
99         # delete space after case, label
100         ('(\W(case|label) ([\w]+)) :', '\\1:'),
101         # delete space before comma
102         ('[ \t]*,', ','),
103         # delete space before semicolon
104         ('[ \t]*;', ';'),
105         # delete space before eol-backslash
106         ('[ \t]*\\\\\n', '\\\n'),
107         # delete trailing whitespace
108         ('[ \t]*\n', '\n'),
109
110         ## Deuglify code that also gets ugly by rules above.
111         # delete newline after typedef struct
112         ('(typedef struct\s+([\w]*\s){([^}]|{[^}]*})*})\s*\n\s*(\w[\w\d]*;)', '\\1 \\4'),
113         # delete spaces around template brackets
114         #('(dynamic_cast|template|([A-Z]\w*))[ \t]*<[ \t]*(( *(bool|char|int|unsigned|void|(class[ \t]+\w*)|([A-Z]\w*)),?)+)[ \t]?(| [\*&])[ \t]*>', '\\1<\\3\\8>'),
115         ('(dynamic_cast|template|([A-Z]\w*))[ \t]*<[ \t]*(( *(bool|char|int|unsigned|void|(class[ \t]+\w*)|([A-Z]\w*))[,\*&]*)+)[ \t]?(| [\*&])[ \t]*>', '\\1<\\3\\8>'),
116         ('((if|while)\s+\(([^\)]|\([^\)]*\))*\))\s*;', '\\1\n;'),
117         ('(for\s+\(([^;]*;[^;]*;([^\)]|\([^\)]*\))*)\))\s*;', '\\1\n;'),
118
119         ## Fix code that gets broken by rules above.
120         # delete space before #define x()
121         ('#[ \t]*define (\w*)[ \t]*\(', '#define \\1('),
122         # add space in #define x ()
123         ('#[ \t]*define (\w*)(\(([^\(\)]|\([^\(\)]*\))*\)\\n)',
124          '#define \\1 \\2'),
125         # delete space in #include <>
126         ('#[ \t]*include[ \t]*<[ \t]*([^ \t>]*)[ \t]*(/?)[ \t]*([^ \t>]*)[ \t]*>',
127         '#include <\\1\\2\\3>'),
128         # delete backslash before empty line (emacs' indent region is broken)
129         ('\\\\\n\n', '\n\n'),
130         ],
131
132         COMMENT:
133         [
134         # delete trailing whitespace
135         ('[ \t]*\n', '\n'),
136         # delete empty first lines
137         ('(/\*\n)\n*', '\\1'),
138         # delete empty last lines
139         ('\n*(\n\*/)', '\\1'),
140         ## delete newline after start?
141         #('/(\*)\n', '\\1'),
142         ## delete newline before end?
143         #('\n(\*/)', '\\1'),
144         ],
145         }
146
147 # Recognize special sequences in the input.
148 #
149 #   (?P<name>regex) -- Assign result of REGEX to NAME.
150 #   *? -- Match non-greedily.
151 #   (?m) -- Multiline regex: Make ^ and $ match at each line.
152 #   (?s) -- Make the dot match all characters including newline.
153 #   (?x) -- Ignore whitespace in patterns.
154 no_match = 'a\ba'
155 snippet_res = {
156         CXX: {
157                 'include':
158                   no_match,
159
160                 'multiline_comment':
161                   r'''(?sx)
162                     (?P<match>
163                     (?P<code>
164                     [ \t]*/\*.*?\*/))''',
165
166                 'singleline_comment':
167                   r'''(?mx)
168                     ^.*
169                     (?P<match>
170                       (?P<code>
171                       [ \t]*//([ \t][^\n]*|)\n))''',
172
173                 'string':
174                   r'''(?x)
175                     (?P<match>
176                     (?P<code>
177                     "([^"]|(([^\\]|(\\\\))\\"))*"))''',
178
179                 'char':
180                   r'''(?x)
181                     (?P<match>
182                     (?P<code>
183                     '([^']+|\')))''',
184
185                 'include':
186                   r'''(?x)
187                     (?P<match>
188                     (?P<code>
189                     "#[ \t]*include[ \t]*<[^>]*>''',
190         },
191         }
192
193 class Chunk:
194         def replacement_text (self):
195                 return ''
196
197         def filter_text (self):
198                 return self.replacement_text ()
199
200         def ly_is_outdated (self):
201                 return 0
202
203         def png_is_outdated (self):
204                 return 0
205
206 class Substring (Chunk):
207         def __init__ (self, source, start, end):
208                 self.source = source
209                 self.start = start
210                 self.end = end
211
212         def replacement_text (self):
213                 s = self.source[self.start:self.end]
214                 if verbose_p:
215                         sys.stderr.write ('CXX Rules')
216                 for i in rules[CXX]:
217                         if verbose_p:
218                                 sys.stderr.write ('.')
219                                 #sys.stderr.write ('\n*********\n')
220                                 #sys.stderr.write (i[0])
221                                 #sys.stderr.write ('\n=========\n')
222                                 #sys.stderr.write (s)
223                                 #sys.stderr.write ('\n*********\n')
224                         s = re.sub (i[0], i[1], s)
225                 if verbose_p:
226                         sys.stderr.write ('done\n')
227                 return s
228                 
229
230 class Snippet (Chunk):
231         def __init__ (self, type, match, format):
232                 self.type = type
233                 self.match = match
234                 self.hash = 0
235                 self.options = []
236                 self.format = format
237
238         def replacement_text (self):
239                 return self.match.group ('match')
240
241         def substring (self, s):
242                 return self.match.group (s)
243
244         def __repr__ (self):
245                 return `self.__class__` + ' type = ' + self.type
246
247 class Multiline_comment (Snippet):
248         def __init__ (self, source, match, format):
249                 self.type = type
250                 self.match = match
251                 self.hash = 0
252                 self.options = []
253                 self.format = format
254
255         def replacement_text (self):
256                 s = self.match.group ('match')
257                 if verbose_p:
258                         sys.stderr.write ('COMMENT Rules')
259                 for i in rules[COMMENT]:
260                         if verbose_p:
261                                 sys.stderr.write ('.')
262                         s = re.sub (i[0], i[1], s)
263                 return s
264
265 snippet_type_to_class = {
266         'multiline_comment': Multiline_comment,
267 #       'lilypond_block': Lilypond_snippet,
268 #       'lilypond': Lilypond_snippet,
269 #       'include': Include_snippet,
270 }
271
272 def find_toplevel_snippets (s, types):
273         if verbose_p:
274                 sys.stderr.write ('Dissecting')
275
276         res = {}
277         for i in types:
278                 res[i] = re.compile (snippet_res[format][i])
279
280         snippets = []
281         index = 0
282         ## found = dict (map (lambda x: (x, None),
283         ##                    types))
284         ## urg python2.1
285         found = {}
286         map (lambda x, f = found: f.setdefault (x, None),
287              types)
288
289         # We want to search for multiple regexes, without searching
290         # the string multiple times for one regex.
291         # Hence, we use earlier results to limit the string portion
292         # where we search.
293         # Since every part of the string is traversed at most once for
294         # every type of snippet, this is linear.
295
296         while 1:
297                 if verbose_p:
298                         sys.stderr.write ('.')
299                 first = None
300                 endex = 1 << 30
301                 for type in types:
302                         if not found[type] or found[type][0] < index:
303                                 found[type] = None
304                                 m = res[type].search (s[index:endex])
305                                 if not m:
306                                         continue
307
308                                 cl = Snippet
309                                 if snippet_type_to_class.has_key (type):
310                                         cl = snippet_type_to_class[type]
311                                 snip = cl (type, m, format)
312                                 start = index + m.start ('match')
313                                 found[type] = (start, snip)
314
315                         if found[type] \
316                            and (not first \
317                                 or found[type][0] < found[first][0]):
318                                 first = type
319
320                                 # FIXME.
321
322                                 # Limiting the search space is a cute
323                                 # idea, but this *requires* to search
324                                 # for possible containing blocks
325                                 # first, at least as long as we do not
326                                 # search for the start of blocks, but
327                                 # always/directly for the entire
328                                 # @block ... @end block.
329
330                                 endex = found[first][0]
331
332                 if not first:
333                         snippets.append (Substring (s, index, len (s)))
334                         break
335
336                 (start, snip) = found[first]
337                 snippets.append (Substring (s, index, start))
338                 snippets.append (snip)
339                 found[first] = None
340                 index = start + len (snip.match.group ('match'))
341
342         return snippets
343
344 def nitpick_file (outdir, file):
345         s = open (file).read ()
346
347         # FIXME: Containing blocks must be first, see
348         #        find_toplevel_snippets.
349         snippet_types = (
350                 'multiline_comment',
351                 'singleline_comment',
352                 'string',
353                 'char',
354                 )
355
356         chunks = find_toplevel_snippets (s, snippet_types)
357         #code = filter (lambda x: is_derived_class (x.__class__, Substring),
358         #              chunks)
359
360         t = string.join (map (lambda x: x.filter_text (), chunks), '')
361         fixt = file
362         if s != t:
363                 if not outdir:
364                         os.system ('mv %s %s~' % (file, file))
365                 else: 
366                         fixt = os.path.join (outdir,
367                                              os.path.basename (file))
368                 h = open (fixt, "w")
369                 h.write (t)
370                 h.close ()
371         if s != t or indent_p:
372                 indent_file (fixt)
373
374 def indent_file (file):
375         emacs = '''emacs\
376         --no-window-system\
377         --batch\
378         --no-site-file\
379         --no-init-file\
380         %(file)s\
381         --eval '(let ((error nil)
382                       (version-control nil))
383                  (load-library "cc-mode")
384                  (c++-mode)
385                  (indent-region (point-min) (point-max))
386                  (if (buffer-modified-p (current-buffer))
387                   (save-buffer)))' ''' % vars ()
388         emacsclient = '''emacsclient\
389         --socket-name=%(socketdir)s/%(socketname)s\
390         --no-wait\
391         --eval '(let ((error nil)
392                       (version-control nil))
393                  (load-library "cc-mode")
394                  (find-file "%(file)s")
395                  (c++-mode)
396                  (indent-region (point-min) (point-max))
397                  (if (buffer-modified-p (current-buffer))
398                   (save-buffer)))' ''' \
399                   % { 'file': file,
400                       'socketdir' : socketdir,
401                       'socketname' : socketname, }
402         if verbose_p:
403                 sys.stderr.write (emacs)
404                 sys.stderr.write ('\n')
405         os.system (emacs)
406
407
408 def usage ():
409         sys.stdout.write (r'''
410 Usage:
411 fixcc [OPTION]... FILE...
412
413 Options:
414    --help
415    --indent   reindent, even if no changes
416    --verbose
417    --test
418
419 Typical use with LilyPond:
420
421    fixcc $(find flower kpath-guile lily -name '*cc' -o -name '*hh' | grep -v /out)
422
423 This script is licensed under the GNU GPL
424 ''')
425
426 def do_options ():
427         global indent_p, outdir, verbose_p
428         (options, files) = getopt.getopt (sys.argv[1:], '',
429                                           ['help', 'indent', 'outdir=',
430                                            'test', 'verbose'])
431         for (o, a) in options:
432                 if o == '--help':
433                         usage ()
434                         sys.exit (0)
435                 elif o == '--indent':
436                         indent_p = 1
437                 elif o == '--outdir':
438                         outdir = a
439                 elif o == '--verbose':
440                         verbose_p = 1
441                 elif o == '--test':
442                         test ()
443                         sys.exit (0)
444                 else:
445                         assert unimplemented
446         if not files:
447                 usage ()
448                 sys.exit (2)
449         return files
450
451
452 outdir = 0
453 format = CXX
454 socketdir = '/tmp/fixcc'
455 socketname = 'fixcc%d' % os.getpid ()
456
457 def setup_client ():
458         #--no-window-system\
459         #--batch\
460         os.unlink (os.path.join (socketdir, socketname))
461         os.mkdir (socketdir, 0700)
462         emacs='''emacs\
463                 --no-site-file\
464                 --no-init-file\
465                 --eval '(let ((error nil)
466                               (version-control nil))
467                          (load-library "server")
468                          (setq server-socket-dir "%(socketdir)s")
469                          (setq server-name "%(socketname)s")
470                          (server-start)
471                          (while t) (sleep 1000))' ''' \
472                          % { 'socketdir' : socketdir,
473                              'socketname' : socketname, }
474                              
475         if not os.fork ():
476                 os.system (emacs)
477                 sys.exit (0)
478         while not os.path.exists (os.path.join (socketdir, socketname)):
479                 time.sleep (1)
480
481 def main ():
482         #emacsclient should be faster, but this does not work yet
483         #setup_client ()
484         files = do_options ()
485         if outdir and not os.path.isdir (outdir):
486                 os.makedirs (outdir)
487         for i in files:
488                 sys.stderr.write ('%s...\n' % i)
489                 nitpick_file (outdir, i)
490
491
492 TEST = '''
493 ostream &
494 operator << (ostream & os, String d);
495
496 typedef struct _t_ligature
497 {
498   char *succ, *lig;
499   struct _t_ligature *next;
500   struct _t_ligature * next;
501 }  AFM_Ligature;
502
503 char *
504 Bar:: foe ()
505 {
506   char* a= ++ 3  ;
507   a [x] = foe (*i, &bar) *
508   2;
509   int operator double ();
510   int x =foe(1 ,3);
511   Interval_t<T> &operator*= (T r);
512   int compare (Pqueue_ent < K, T > const& e1, Pqueue_ent < K,T> *e2);
513   delete *p;
514   if (abs (f)*2 > abs (d) *FUDGE)
515     ;
516   while (0);
517   for (; i < x (); foo > bar);
518   for (; i < x > y;
519   foo > bar)
520 ;
521
522   squiggle. extent;
523
524   1 && * unsmob_moment (lf);
525   
526   line_spanner_ = make_spanner ("DynamicLineSpanner", rq ? rq->self_scm
527 (): SCM_EOL);
528
529   case foo: k;
530
531   typedef struct
532   {
533     ...
534   } cookie_io_functions_t;
535
536
537   if (0) {a=b;} else {
538    c=d;
539   }
540
541   cookie_io_functions_t Memory_out_stream::functions_ = {
542     Memory_out_stream::reader,
543     ...
544   };
545
546 }
547 '''
548
549 def test ():
550         test_file = 'fixcc.cc'
551         open (test_file, 'w').write (TEST)
552         nitpick_file (outdir, test_file)
553         sys.stdout.write (open (test_file).read ())
554
555 if __name__ == '__main__':
556         main ()
557