]> git.donarmstrong.com Git - lilypond.git/blob - scripts/auxiliar/fixcc.py
Adapt fixcc.py to use Astyle
[lilypond.git] / scripts / auxiliar / fixcc.py
1 #!/usr/bin/env python
2
3 # fixcc -- indent and space lily's c++ code
4
5 # This file is part of LilyPond, the GNU music typesetter.
6 #
7 # LilyPond is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # LilyPond is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
19
20 #  Performs string substitution on files, then applies astyle
21 #  (http://astyle.sourceforge.net)
22 # TODO
23 #  Remove prefiltering as the equivalent formatting becomes available in
24 #  astyle, or as the prefiltering is deemed un-necessary.
25 #  Soon, this script might be replaced by a simple invocation of astyle
26
27 import __main__
28 import getopt
29 import os
30 import re
31 import string
32 import sys
33 import time
34
35 COMMENT = 'COMMENT'
36 STRING = 'STRING'
37 GLOBAL_CXX = 'GC++'
38 CXX = 'C++'
39 verbose_p = 0
40 indent_p = 1
41
42 rules = {
43     GLOBAL_CXX:
44     [
45     # delete trailing whitespace
46     ('[ \t]*\n', '\n'),
47     ],
48     CXX:
49     [
50     # space before parenthesis open
51     ('([\w\)\]])\(', '\\1 ('),
52     # delete inline double spaces
53     ('(\S)  +', '\\1 '),
54     # delete space before parenthesis close
55     (' *\)', ')'),
56     # delete spaces after prefix
57     ('(--|\+\+) *([\w\(])', '\\1\\2'),
58     # delete spaces before postfix
59     ('([\w\)\]]) *(--|\+\+)', '\\1\\2'),
60
61     # delete space around operator
62     ('([\w\(\)\]]) *(\.|->) *([\w\(\)])', '\\1\\2\\3'),
63     # delete space after operator
64     ('(::) *([\w\(\)])', '\\1\\2'),
65
66     # delete superflous space around operator
67     ('([\w\(\)\]]) +(&&|\|\||<=|>=|!=|\|=|==|\+=|-=|\*=|/=|\?|<|>|\+|-|=|/|:|&|\||\*) +([\w\(\)])', '\\1 \\2 \\3'),
68
69     # trailing operator, but don't un-trail close angle-braces > nor pointer *, and not before a preprocessor line
70     (' (::|&&|\|\||<=|>=|!=|\|=|==|\+=|-=|\*=|/=|\?|<|\+|-|=|/|:|&XXX|\||\*XXX) *\n( *)([^\s#])', '\n\\2\\1 \\3'),
71     # space after `operator'
72     ('(\Woperator) *([^\w\s])', '\\1 \\2'),
73     # trailing parenthesis open
74     ('\( *\n *', '('),
75     # dangling parenthesis close: Disabled to leave ADD_TRANSLATOR format in place
76     #('\n *\)', ')'),
77     # dangling comma
78     ('\n( *),', ',\n\\1'),
79     # delete space after case, label
80     ('(\W(case|label) [\w]+) :', '\\1:'),
81     # delete space before comma
82     (' +,', ','),
83     # delete space before semicolon
84     ('([^;]) +;', '\\1;'),
85     # dangling newline
86     ('\n\n+', '\n\n'),
87
88     # delete backslash before empty line (emacs' indent region is broken)
89     ('\\\\\n\n', '\n\n'),
90     ],
91
92     COMMENT:
93     [
94     # delete empty first lines
95     ('(/\*\n)\n*', '\\1'),
96     # delete empty last lines
97     ('\n*(\n\*/)', '\\1'),
98     ## delete newline after start?
99     #('/(\*)\n', '\\1'),
100     ## delete newline before end?
101     #('\n(\*/)', '\\1'),
102     ],
103     }
104
105 # Recognize special sequences in the input.
106 #
107 #   (?P<name>regex) -- Assign result of REGEX to NAME.
108 #   *? -- Match non-greedily.
109 #   (?m) -- Multiline regex: Make ^ and $ match at each line.
110 #   (?s) -- Make the dot match all characters including newline.
111 #   (?x) -- Ignore whitespace in patterns.
112 no_match = 'a\ba'
113 snippet_res = {
114     CXX: {
115     'define':
116     r'''(?x)
117     (?P<match>
118     (?P<code>
119     \#[ \t]*define[ \t]+([^\n]*\\\n)*[^\n]*))''',
120
121     'multiline_comment':
122     r'''(?sx)
123     (?P<match>
124     (?P<code>
125     [ \t]*/\*.*?\*/))''',
126     
127     'singleline_comment':
128     r'''(?mx)
129     ^.*?    # leave leading spaces for the comment snippet
130     (?P<match>
131     (?P<code>
132     [ \t]*//[^\n]*\n))''',
133
134     'string':
135     r'''(?x)
136     (?P<match>
137     (?P<code>
138     "([^"\n]|\\")*"))''',
139     
140     'char':
141     r'''(?x)
142     (?P<match>
143     (?P<code>
144     '([^']+|\')))''',
145      
146     'include':
147     r'''(?x)
148     (?P<match>
149     (?P<code>
150     \#[ \t]*include[ \t]*<[^>]*>))''',
151     },
152     }
153
154 class Chunk:
155     def replacement_text (self):
156         return ''
157
158     def filter_text (self):
159         return self.replacement_text ()
160
161 class Substring (Chunk):
162     def __init__ (self, source, start, end):
163         self.source = source
164         self.start = start
165         self.end = end
166
167     def replacement_text (self):
168         s = self.source[self.start:self.end]
169         if verbose_p:
170             sys.stderr.write ('CXX Rules')
171         for i in rules[CXX]:
172             if verbose_p:
173                 sys.stderr.write ('.')
174                 #sys.stderr.write ('\n\n***********\n')
175                 #sys.stderr.write (i[0])
176                 #sys.stderr.write ('\n***********\n')
177                 #sys.stderr.write ('\n=========>>\n')
178                 #sys.stderr.write (s)
179                 #sys.stderr.write ('\n<<=========\n')
180             s = re.sub (i[0], i[1], s)
181         if verbose_p:
182             sys.stderr.write ('done\n')
183         return s
184         
185
186 class Snippet (Chunk):
187     def __init__ (self, type, match, format):
188         self.type = type
189         self.match = match
190         self.hash = 0
191         self.options = []
192         self.format = format
193
194     def replacement_text (self):
195         return self.match.group ('match')
196
197     def substring (self, s):
198         return self.match.group (s)
199
200     def __repr__ (self):
201         return `self.__class__` + ' type = ' + self.type
202
203 class Multiline_comment (Snippet):
204     def __init__ (self, source, match, format):
205         self.type = type
206         self.match = match
207         self.hash = 0
208         self.options = []
209         self.format = format
210
211     def replacement_text (self):
212         s = self.match.group ('match')
213         if verbose_p:
214             sys.stderr.write ('COMMENT Rules')
215         for i in rules[COMMENT]:
216             if verbose_p:
217                 sys.stderr.write ('.')
218             s = re.sub (i[0], i[1], s)
219         return s
220
221 snippet_type_to_class = {
222     'multiline_comment': Multiline_comment,
223 #        'string': Multiline_comment,
224 #        'include': Include_snippet,
225 }
226
227 def find_toplevel_snippets (s, types):
228     if verbose_p:
229         sys.stderr.write ('Dissecting')
230
231     res = {}
232     for i in types:
233         res[i] = re.compile (snippet_res[format][i])
234
235     snippets = []
236     index = 0
237     ## found = dict (map (lambda x: (x, None),
238     ##                      types))
239     ## urg python2.1
240     found = {}
241     map (lambda x, f = found: f.setdefault (x, None),
242       types)
243
244     # We want to search for multiple regexes, without searching
245     # the string multiple times for one regex.
246     # Hence, we use earlier results to limit the string portion
247     # where we search.
248     # Since every part of the string is traversed at most once for
249     # every type of snippet, this is linear.
250
251     while 1:
252         if verbose_p:
253             sys.stderr.write ('.')
254         first = None
255         endex = 1 << 30
256         for type in types:
257             if not found[type] or found[type][0] < index:
258                 found[type] = None
259                 m = res[type].search (s[index:endex])
260                 if not m:
261                     continue
262
263                 cl = Snippet
264                 if snippet_type_to_class.has_key (type):
265                     cl = snippet_type_to_class[type]
266                 snip = cl (type, m, format)
267                 start = index + m.start ('match')
268                 found[type] = (start, snip)
269
270             if found[type] \
271              and (not first \
272                 or found[type][0] < found[first][0]):
273                 first = type
274
275                 # FIXME.
276
277                 # Limiting the search space is a cute
278                 # idea, but this *requires* to search
279                 # for possible containing blocks
280                 # first, at least as long as we do not
281                 # search for the start of blocks, but
282                 # always/directly for the entire
283                 # @block ... @end block.
284
285                 endex = found[first][0]
286
287         if not first:
288             snippets.append (Substring (s, index, len (s)))
289             break
290
291         (start, snip) = found[first]
292         snippets.append (Substring (s, index, start))
293         snippets.append (snip)
294         found[first] = None
295         index = start + len (snip.match.group ('match'))
296
297     return snippets
298
299 def nitpick_file (outdir, file):
300     s = open (file).read ()
301
302     t = s.expandtabs(8)
303     for i in rules[GLOBAL_CXX]:
304         t = re.sub (i[0], i[1], t)
305
306     # FIXME: Containing blocks must be first, see
307     #        find_toplevel_snippets.
308     #        We leave simple strings be part of the code
309     snippet_types = (
310         'define',
311         'multiline_comment',
312         'singleline_comment',
313         'string',
314 #                'char',
315         'include',
316         )
317
318     chunks = find_toplevel_snippets (t, snippet_types)
319     #code = filter (lambda x: is_derived_class (x.__class__, Substring),
320     #               chunks)
321
322     t = string.join (map (lambda x: x.filter_text (), chunks), '')
323     fixt = file
324     if s != t:
325         if not outdir:
326             os.system ('mv %s %s~' % (file, file))
327         else: 
328             fixt = os.path.join (outdir,
329                       os.path.basename (file))
330         h = open (fixt, "w")
331         h.write (t)
332         h.close ()
333     if s != t or indent_p:
334         indent_file (fixt)
335
336 def indent_file (file):
337     astyle = '''astyle\
338   --options=none --quiet -n \
339   --style=gnu --indent=spaces=2 \
340   --max-instatement-indent=60 \
341   --indent-cases \
342   --align-pointer=name --pad-oper \
343   --keep-one-line-blocks \
344   %(file)s
345   ''' % vars ()
346     if verbose_p:
347         sys.stderr.write (astyle)
348         sys.stderr.write ('\n')
349     os.system (astyle)
350
351
352 def usage ():
353     sys.stdout.write (r'''
354 Usage:
355 fixcc [OPTION]... FILE...
356
357 Options:
358  --help
359  --lazy   skip astyle, if no changes
360  --verbose
361  --test
362
363 Typical use with LilyPond:
364
365  fixcc $(find flower lily -name '*cc' -o -name '*hh' | grep -v /out)
366
367 ''')
368
369 def do_options ():
370     global indent_p, outdir, verbose_p
371     (options, files) = getopt.getopt (sys.argv[1:], '',
372                      ['help', 'lazy', 'outdir=',
373                      'test', 'verbose'])
374     for (o, a) in options:
375         if o == '--help':
376             usage ()
377             sys.exit (0)
378         elif o == '--lazy':
379             indent_p = 0
380         elif o == '--outdir':
381             outdir = a
382         elif o == '--verbose':
383             verbose_p = 1
384         elif o == '--test':
385             test ()
386             sys.exit (0)
387         else:
388             assert unimplemented
389     if not files:
390         usage ()
391         sys.exit (2)
392     return files
393
394
395 outdir = 0
396 format = CXX
397 socketdir = '/tmp/fixcc'
398 socketname = 'fixcc%d' % os.getpid ()
399
400 def main ():
401     files = do_options ()
402     if outdir and not os.path.isdir (outdir):
403         os.makedirs (outdir)
404     for i in files:
405         sys.stderr.write ('%s...\n' % i)
406         nitpick_file (outdir, i)
407
408
409 ## TODO: make this compilable and check with g++
410 TEST = '''
411 #include <libio.h>
412 #include <map>
413 class
414 ostream ;
415
416 class Foo {
417 public: static char* foo ();
418 std::map<char*,int>* bar (char, char) { return 0; }
419 };
420 typedef struct
421 {
422  Foo **bar;
423 } String;
424
425 ostream &
426 operator << (ostream & os, String d);
427
428 typedef struct _t_ligature
429 {
430  char *succ, *lig;
431  struct _t_ligature * next;
432 }  AFM_Ligature;
433  
434 typedef std::map < AFM_Ligature const *, int > Bar;
435
436  /**
437  Copyright (C) 1997--2011 Han-Wen Nienhuys <hanwen@cs.uu.nl>
438  */
439  
440 /*      ||
441 *      vv
442 * !OK  OK
443 */
444 /*     ||
445    vv
446  !OK  OK
447 */
448 char *
449 Foo:: foo ()
450 {
451 int
452 i
453 ;
454  char* a= &++ i ;
455  a [*++ a] = (char*) foe (*i, &bar) *
456  2;
457  int operator double ();
458  std::map<char*,int> y =*bar(-*a ,*b);
459  Interval_t<T> & operator*= (T r);
460  Foo<T>*c;
461  int compare (Pqueue_ent < K, T > const& e1, Pqueue_ent < K,T> *e2);
462  delete *p;
463  if (abs (f)*2 > abs (d) *FUDGE)
464   ;
465  while (0);
466  for (; i<x foo(); foo>bar);
467  for (; *p && > y;
468    foo > bar)
469 ;
470  do {
471  ;;;
472  }
473  while (foe);
474
475  squiggle. extent;
476  1 && * unsmob_moment (lf);
477  line_spanner_ = make_spanner ("DynamicLineSpanner", rq ? rq->*self_scm
478 (): SCM_EOL);
479  case foo: k;
480
481  if (0) {a=b;} else {
482  c=d;
483  }
484
485  cookie_io_functions_t Memory_out_stream::functions_ = {
486   Memory_out_stream::reader,
487   ...
488  };
489
490  int compare (Array < Pitch> *, Array < Pitch> *);
491  original_ = (Grob *) & s;
492  Drul_array< Link_array<Grob> > o;
493 }
494
495  header_.char_info_pos = (6 + header_length) * 4;
496  return ly_bool2scm (*ma < * mb);
497
498  1 *::sign(2);
499
500  (shift) *-d;
501
502  a = 0 ? *x : *y;
503
504 a = "foo() 2,2,4";
505 {
506  if (!span_)
507   {
508    span_ = make_spanner ("StaffSymbol", SCM_EOL);
509   }
510 }
511 {
512  if (!span_)
513   {
514    span_ = make_spanner (StaffSymbol, SCM_EOL);
515   }
516 }
517 '''
518
519 def test ():
520     test_file = 'fixcc.cc'
521     open (test_file, 'w').write (TEST)
522     nitpick_file (outdir, test_file)
523     sys.stdout.write (open (test_file).read ())
524
525 if __name__ == '__main__':
526     main ()
527