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