]> git.donarmstrong.com Git - lilypond.git/blob - scripts/auxiliar/fixcc.py
Merge branch 'lilypond/translation' of ssh://git.sv.gnu.org/srv/git/lilypond into...
[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     ('(?<!\s) (::|&&|\|\||<=|>=|!=|\|=|==|\+=|-=|\*=|/=|\?|<|\+|-|=|/|:|&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     "      # leave the leading " character visible to CXX rules
137     (?P<match>
138     (?P<code>
139     ([^"\n]|\\")*"))''',
140     
141     'char':
142     r'''(?x)
143     (?P<match>
144     (?P<code>
145     '([^']+|\')))''',
146      
147     'include':
148     r'''(?x)
149     (?P<match>
150     (?P<code>
151     \#[ \t]*include[ \t]*<[^>]*>))''',
152     },
153     }
154
155 class Chunk:
156     def replacement_text (self):
157         return ''
158
159     def filter_text (self):
160         return self.replacement_text ()
161
162 class Substring (Chunk):
163     def __init__ (self, source, start, end):
164         self.source = source
165         self.start = start
166         self.end = end
167
168     def replacement_text (self):
169         s = self.source[self.start:self.end]
170         if verbose_p:
171             sys.stderr.write ('CXX Rules')
172         for i in rules[CXX]:
173             if verbose_p:
174                 sys.stderr.write ('.')
175                 #sys.stderr.write ('\n\n***********\n')
176                 #sys.stderr.write (i[0])
177                 #sys.stderr.write ('\n***********\n')
178                 #sys.stderr.write ('\n=========>>\n')
179                 #sys.stderr.write (s)
180                 #sys.stderr.write ('\n<<=========\n')
181             s = re.sub (i[0], i[1], s)
182         if verbose_p:
183             sys.stderr.write ('done\n')
184         return s
185         
186
187 class Snippet (Chunk):
188     def __init__ (self, type, match, format):
189         self.type = type
190         self.match = match
191         self.hash = 0
192         self.options = []
193         self.format = format
194
195     def replacement_text (self):
196         return self.match.group ('match')
197
198     def substring (self, s):
199         return self.match.group (s)
200
201     def __repr__ (self):
202         return `self.__class__` + ' type = ' + self.type
203
204 class Multiline_comment (Snippet):
205     def __init__ (self, source, match, format):
206         self.type = type
207         self.match = match
208         self.hash = 0
209         self.options = []
210         self.format = format
211
212     def replacement_text (self):
213         s = self.match.group ('match')
214         if verbose_p:
215             sys.stderr.write ('COMMENT Rules')
216         for i in rules[COMMENT]:
217             if verbose_p:
218                 sys.stderr.write ('.')
219             s = re.sub (i[0], i[1], s)
220         return s
221
222 snippet_type_to_class = {
223     'multiline_comment': Multiline_comment,
224 #        'string': Multiline_comment,
225 #        'include': Include_snippet,
226 }
227
228 def find_toplevel_snippets (s, types):
229     if verbose_p:
230         sys.stderr.write ('Dissecting')
231
232     res = {}
233     for i in types:
234         res[i] = re.compile (snippet_res[format][i])
235
236     snippets = []
237     index = 0
238     ## found = dict (map (lambda x: (x, None),
239     ##                      types))
240     ## urg python2.1
241     found = {}
242     map (lambda x, f = found: f.setdefault (x, None),
243       types)
244
245     # We want to search for multiple regexes, without searching
246     # the string multiple times for one regex.
247     # Hence, we use earlier results to limit the string portion
248     # where we search.
249     # Since every part of the string is traversed at most once for
250     # every type of snippet, this is linear.
251
252     while 1:
253         if verbose_p:
254             sys.stderr.write ('.')
255         first = None
256         endex = 1 << 30
257         for type in types:
258             if not found[type] or found[type][0] < index:
259                 found[type] = None
260                 m = res[type].search (s[index:endex])
261                 if not m:
262                     continue
263
264                 cl = Snippet
265                 if snippet_type_to_class.has_key (type):
266                     cl = snippet_type_to_class[type]
267                 snip = cl (type, m, format)
268                 start = index + m.start ('match')
269                 found[type] = (start, snip)
270
271             if found[type] \
272              and (not first \
273                 or found[type][0] < found[first][0]):
274                 first = type
275
276                 # FIXME.
277
278                 # Limiting the search space is a cute
279                 # idea, but this *requires* to search
280                 # for possible containing blocks
281                 # first, at least as long as we do not
282                 # search for the start of blocks, but
283                 # always/directly for the entire
284                 # @block ... @end block.
285
286                 endex = found[first][0]
287
288         if not first:
289             snippets.append (Substring (s, index, len (s)))
290             break
291
292         (start, snip) = found[first]
293         snippets.append (Substring (s, index, start))
294         snippets.append (snip)
295         found[first] = None
296         index = start + len (snip.match.group ('match'))
297
298     return snippets
299
300 def nitpick_file (outdir, file):
301     s = open (file).read ()
302
303     t = s.expandtabs(8)
304     for i in rules[GLOBAL_CXX]:
305         t = re.sub (i[0], i[1], t)
306
307     # FIXME: Containing blocks must be first, see
308     #        find_toplevel_snippets.
309     #        We leave simple strings be part of the code
310     snippet_types = (
311         'define',
312         'multiline_comment',
313         'singleline_comment',
314         'string',
315 #                'char',
316         'include',
317         )
318
319     chunks = find_toplevel_snippets (t, snippet_types)
320     #code = filter (lambda x: is_derived_class (x.__class__, Substring),
321     #               chunks)
322
323     t = string.join (map (lambda x: x.filter_text (), chunks), '')
324     fixt = file
325     if s != t:
326         if not outdir:
327             os.system ('mv %s %s~' % (file, file))
328         else: 
329             fixt = os.path.join (outdir,
330                       os.path.basename (file))
331         h = open (fixt, "w")
332         h.write (t)
333         h.close ()
334     if s != t or indent_p:
335         indent_file (fixt)
336
337 def indent_file (file):
338     astyle = '''astyle\
339   --options=none --quiet -n \
340   --style=gnu --indent=spaces=2 \
341   --max-instatement-indent=60 \
342   --indent-cases \
343   --align-pointer=name --pad-oper \
344   --keep-one-line-blocks \
345   %(file)s
346   ''' % vars ()
347     if verbose_p:
348         sys.stderr.write (astyle)
349         sys.stderr.write ('\n')
350     os.system (astyle)
351
352
353 def usage ():
354     sys.stdout.write (r'''
355 Usage:
356 fixcc [OPTION]... FILE...
357
358 Options:
359  --help
360  --lazy   skip astyle, if no changes
361  --verbose
362  --test
363
364 Typical use with LilyPond:
365
366  fixcc $(find flower lily -name '*cc' -o -name '*hh' | grep -v /out)
367
368 ''')
369
370 def do_options ():
371     global indent_p, outdir, verbose_p
372     (options, files) = getopt.getopt (sys.argv[1:], '',
373                      ['help', 'lazy', 'outdir=',
374                      'test', 'verbose'])
375     for (o, a) in options:
376         if o == '--help':
377             usage ()
378             sys.exit (0)
379         elif o == '--lazy':
380             indent_p = 0
381         elif o == '--outdir':
382             outdir = a
383         elif o == '--verbose':
384             verbose_p = 1
385         elif o == '--test':
386             test ()
387             sys.exit (0)
388         else:
389             assert unimplemented
390     if not files:
391         usage ()
392         sys.exit (2)
393     return files
394
395
396 outdir = 0
397 format = CXX
398 socketdir = '/tmp/fixcc'
399 socketname = 'fixcc%d' % os.getpid ()
400
401 def main ():
402     files = do_options ()
403     if outdir and not os.path.isdir (outdir):
404         os.makedirs (outdir)
405     for i in files:
406         sys.stderr.write ('%s...\n' % i)
407         nitpick_file (outdir, i)
408
409
410 ## TODO: make this compilable and check with g++
411 TEST = '''
412 #include <libio.h>
413 #include <map>
414 class
415 ostream ;
416
417 class Foo {
418 public: static char* foo ();
419 std::map<char*,int>* bar (char, char) { return 0; }
420 };
421 typedef struct
422 {
423  Foo **bar;
424 } String;
425
426 ostream &
427 operator << (ostream & os, String d);
428
429 typedef struct _t_ligature
430 {
431  char *succ, *lig;
432  struct _t_ligature * next;
433 }  AFM_Ligature;
434  
435 typedef std::map < AFM_Ligature const *, int > Bar;
436
437  /**
438  Copyright (C) 1997--2011 Han-Wen Nienhuys <hanwen@cs.uu.nl>
439  */
440  
441 /*      ||
442 *      vv
443 * !OK  OK
444 */
445 /*     ||
446    vv
447  !OK  OK
448 */
449 char *
450 Foo:: foo ()
451 {
452 int
453 i
454 ;
455  char* a= &++ i ;
456  a [*++ a] = (char*) foe (*i, &bar) *
457  2;
458  int operator double ();
459  std::map<char*,int> y =*bar(-*a ,*b);
460  Interval_t<T> & operator*= (T r);
461  Foo<T>*c;
462  int compare (Pqueue_ent < K, T > const& e1, Pqueue_ent < K,T> *e2);
463  delete *p;
464  if (abs (f)*2 > abs (d) *FUDGE)
465   ;
466  while (0);
467  for (; i<x foo(); foo>bar);
468  for (; *p && > y;
469    foo > bar)
470 ;
471  do {
472  ;;;
473  }
474  while (foe);
475
476  squiggle. extent;
477  1 && * unsmob_moment (lf);
478  line_spanner_ = make_spanner ("DynamicLineSpanner", rq ? rq->*self_scm
479 (): SCM_EOL);
480  case foo: k;
481
482  if (0) {a=b;} else {
483  c=d;
484  }
485
486  cookie_io_functions_t Memory_out_stream::functions_ = {
487   Memory_out_stream::reader,
488   ...
489  };
490
491  int compare (Array < Pitch> *, Array < Pitch> *);
492  original_ = (Grob *) & s;
493  Drul_array< Link_array<Grob> > o;
494 }
495
496  header_.char_info_pos = (6 + header_length) * 4;
497  return ly_bool2scm (*ma < * mb);
498
499  1 *::sign(2);
500
501  (shift) *-d;
502
503  a = 0 ? *x : *y;
504
505 a = "foo() 2,2,4";
506 {
507  if (!span_)
508   {
509    span_ = make_spanner ("StaffSymbol", SCM_EOL);
510   }
511 }
512 {
513  if (!span_)
514   {
515    span_ = make_spanner (StaffSymbol, SCM_EOL);
516   }
517 }
518 '''
519
520 def test ():
521     test_file = 'fixcc.cc'
522     open (test_file, 'w').write (TEST)
523     nitpick_file (outdir, test_file)
524     sys.stdout.write (open (test_file).read ())
525
526 if __name__ == '__main__':
527     main ()
528