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