3 # fixcc -- nitpick lily's c++ code
6 # * check lexer, parser
7 # * rewrite in elisp, add to cc-mode
25 # space before parenthesis open
26 ('([^\( \]])[ \t]*\(', '\\1 ('),
31 # delete inline double spaces
33 # delete space after parenthesis open
35 # delete space before parenthesis close
37 # delete spaces after prefix
38 ('(--|\+\+)[ \t]*([\w\)])', '\\1\\2'),
39 # delete spaces before postfix
40 ('([\w\)\]])[ \t]*(--|\+\+)', '\\1\\2'),
41 # delete space after parenthesis close
42 #('\)[ \t]*([^\w])', ')\\1'),
43 # delete superflous space around operator
44 ('([\w\)\]])([ \t]+)(&&|\|\||<=|>=|!=|\|=|==|\+=|-=|\*=|/=|<|>|\+|-|=|/|&|\|\*)([ \t]+)([\w\(])', '\\1 \\3 \\5'),
45 # space around operator
46 ('([\w\)\]])(&&|\|\||<=|>=|!=|\|=|==|\+=|-=|\*=|/=|<|>|=|/|&|\|\*)([\w\(])', '\\1 \\2 \\3'),
47 # space around +/-; exponent
48 ('([\w\)\]])(\+|-)([_A-Za-z\(])', '\\1 \\2 \\3'),
49 ('([_\dA-Za-df-z\)\]])(\+|-)([\w\(])', '\\1 \\2 \\3'),
51 (' (&&|\|\||<=|>=|!=|\|=|==|\+=|-=|\*=|/=|<|>|\+|-|=|/|\*XXX)[ \t]*\n([ \t]*)',
54 ('(bool|char|const|int|unsigned|void|([A-Z]\w*))[ \t]*(\*|&)[ \t]*',
56 # unary pointer, minus, not
57 ('(return|=) (\*|&|-|!) ([\w\(])', '\\1 \\2\\3'),
58 # space after `operator'
59 ('(\Woperator) (\W)', '\\1\\2'),
61 ('\n[ \t]*\n[ \t]*\n', '\n\n'),
62 # dangling parenthesis open
63 #('[ \t]*\n[ \t]*\([ \t]*\n', '('),
65 # dangling parenthesis close
72 ('(\w[^\n]*){[ \t]*\n', '\\1\n{\n'),
73 # brace open backslash
74 ('(\w[^\n]*){[ \t]*\\\\\n', '\\1\\\n{\\\n'),
76 ('}[ \t]*([^\n]*\w[^\n\\\]*\n)', '}\n\\1\n'),
77 # brace close backslash
78 ('}[ \t]*([^\n]*\w[^\n]*?\\\\\n)', '}\\\n\\1\n'),
79 # delete space before comma
81 # delete space before semicolon
83 # delete space before eol-backslash
84 ('[ \t]*\\\\\n', '\\\n'),
85 # delete trailing whitespace
88 ## Massage code that gets broken by rules above.
89 # delete spaces around template brackets
90 ('(dynamic_cast|template|([A-Z]\w*))[ \t]*<[ \t]*((bool|char|int|unsigned|void|(class[ \t]+\w*)|([A-Z]\w*)))[ \t]*?(| \*)[ \t]*>',
92 # delete space before #define x()
93 ('#[ \t]*define (\w*)[ \t]*\(', '#define \\1('),
94 # add space in #define x ()
95 ('#[ \t]*define (\w*)(\(([^\(\)]|\([^\(\)]*\))*\)\\n)',
97 # delete space in #include <>
98 ('#[ \t]*include[ \t]*<[ \t]*([^ \t>]*)[ \t]*(/?)[ \t]*([^ \t>]*)[ \t]*>',
99 '#include <\\1\\2\\3>'),
100 # delete backslash before empty line (emacs' indent region is broken)
101 ('\\\\\n\n', '\n\n'),
106 # delete trailing whitespace
108 # delete empty first lines
109 ('(/\*\n)\n*', '\\1'),
110 # delete empty last lines
111 ('\n*(\n\*/)', '\\1'),
112 ## delete newline after start?
114 ## delete newline before end?
120 # Recognize special sequences in the input.
122 # (?P<name>regex) -- Assign result of REGEX to NAME.
123 # *? -- Match non-greedily.
124 # (?m) -- Multiline regex: Make ^ and $ match at each line.
125 # (?s) -- Make the dot match all characters including newline.
126 # (?x) -- Ignore whitespace in patterns.
137 [ \t]*/\*.*?\*/))''',
139 'singleline_comment':
144 [ \t]*//([ \t][^\n]*|)\n))''',
150 "([^"]|(([^\\]|(\\\\))\\"))*"))''',
162 "#[ \t]*include[ \t]*<[^>]*>''',
167 def replacement_text (self):
170 def filter_text (self):
171 return self.replacement_text ()
173 def ly_is_outdated (self):
176 def png_is_outdated (self):
179 class Substring (Chunk):
180 def __init__ (self, source, start, end):
185 def replacement_text (self):
186 s = self.source[self.start:self.end]
188 s = re.sub (i[0], i[1], s)
192 class Snippet (Chunk):
193 def __init__ (self, type, match, format):
200 def replacement_text (self):
201 return self.match.group ('match')
203 def substring (self, s):
204 return self.match.group (s)
207 return `self.__class__` + ' type = ' + self.type
209 class Multiline_comment (Snippet):
210 def __init__ (self, source, match, format):
217 def replacement_text (self):
218 s = self.match.group ('match')
219 for i in rules[COMMENT]:
220 s = re.sub (i[0], i[1], s)
223 snippet_type_to_class = {
224 'multiline_comment': Multiline_comment,
225 # 'lilypond_block': Lilypond_snippet,
226 # 'lilypond': Lilypond_snippet,
227 # 'include': Include_snippet,
230 def find_toplevel_snippets (s, types):
233 res[i] = re.compile (snippet_res[format][i])
237 ## found = dict (map (lambda x: (x, None),
241 map (lambda x, f = found: f.setdefault (x, None),
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
248 # Since every part of the string is traversed at most once for
249 # every type of snippet, this is linear.
255 if not found[type] or found[type][0] < index:
257 m = res[type].search (s[index:endex])
262 if snippet_type_to_class.has_key (type):
263 cl = snippet_type_to_class[type]
264 snip = cl (type, m, format)
265 start = index + m.start ('match')
266 found[type] = (start, snip)
270 or found[type][0] < found[first][0]):
275 # Limiting the search space is a cute
276 # idea, but this *requires* to search
277 # for possible containing blocks
278 # first, at least as long as we do not
279 # search for the start of blocks, but
280 # always/directly for the entire
281 # @block ... @end block.
283 endex = found[first][0]
286 snippets.append (Substring (s, index, len (s)))
289 (start, snip) = found[first]
290 snippets.append (Substring (s, index, start))
291 snippets.append (snip)
293 index = start + len (snip.match.group ('match'))
297 def nitpick_file (outdir, file):
298 s = open (file).read ()
300 # FIXME: Containing blocks must be first, see
301 # find_toplevel_snippets.
304 'singleline_comment',
309 chunks = find_toplevel_snippets (s, snippet_types)
310 #code = filter (lambda x: is_derived_class (x.__class__, Substring),
313 t = string.join (map (lambda x: x.filter_text (), chunks), '')
317 os.system ('mv %s %s~' % (file, file))
319 fixt = os.path.join (outdir,
320 os.path.basename (file))
326 def indent_file (file):
333 --eval '(let ((error nil)
334 (version-control nil))
335 (load-library "cc-mode")
337 (indent-region (point-min) (point-max))
338 (if (buffer-modified-p (current-buffer))
339 (save-buffer)))' ''' % vars ()
340 emacsclient = '''emacsclient\
341 --socket-name=%(socketdir)s/%(socketname)s\
343 --eval '(let ((error nil)
344 (version-control nil))
345 (load-library "cc-mode")
346 (find-file "%(file)s")
348 (indent-region (point-min) (point-max))
349 (if (buffer-modified-p (current-buffer))
350 (save-buffer)))' ''' \
352 'socketdir' : socketdir,
353 'socketname' : socketname, }
358 sys.stdout.write (r'''
360 fixcc [--outdir=DIR] FILE...
362 Typical use with LilyPond:
364 fixcc $(find flower kpath-guile lily -name '*cc' -o -name '*hh' | grep -v /out)
366 This script is licensed under the GNU GPL
371 (options, files) = getopt.getopt (sys.argv[1:], '',
373 for (o, a) in options:
377 elif o == '--outdir':
389 socketdir = '/tmp/fixcc'
390 socketname = 'fixcc%d' % os.getpid ()
395 os.unlink (os.path.join (socketdir, socketname))
396 os.mkdir (socketdir, 0700)
400 --eval '(let ((error nil)
401 (version-control nil))
402 (load-library "server")
403 (setq server-socket-dir "%(socketdir)s")
404 (setq server-name "%(socketname)s")
406 (while t) (sleep 1000))' ''' \
407 % { 'socketdir' : socketdir,
408 'socketname' : socketname, }
413 while not os.path.exists (os.path.join (socketdir, socketname)):
417 #emacsclient should be faster, but this does not work yet
419 files = do_options ()
420 if outdir and not os.path.isdir (outdir):
423 sys.stderr.write ('%s...\n' % i)
424 nitpick_file (outdir, i)
426 if __name__ == '__main__':