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 # delete space after `operator'
59 #('(\Woperator) (\W)', '\\1\\2'),
60 # space after `operator'
61 ('(\Woperator) *(\W)', '\\1 \\2'),
63 ('\n[ \t]*\n[ \t]*\n', '\n\n'),
64 # dangling parenthesis open
65 #('[ \t]*\n[ \t]*\([ \t]*\n', '('),
67 # dangling parenthesis close
74 ('(\w[^\n]*){[ \t]*\n', '\\1\n{\n'),
75 # brace open backslash
76 ('(\w[^\n]*){[ \t]*\\\\\n', '\\1\\\n{\\\n'),
78 ('}[ \t]*([^\n]*\w[^\n\\\]*\n)', '}\n\\1\n'),
79 # brace close backslash
80 ('}[ \t]*([^\n]*\w[^\n]*?\\\\\n)', '}\\\n\\1\n'),
81 # delete space before comma
83 # delete space before semicolon
85 # delete space before eol-backslash
86 ('[ \t]*\\\\\n', '\\\n'),
87 # delete trailing whitespace
90 ## Massage code that gets broken by rules above.
91 # delete spaces around template brackets
92 # Note that this does not work for PQueue_event. Fix C++ name?
93 ('(dynamic_cast|template|([A-Z]\w*))[ \t]*<[ \t]*((bool|char|int|unsigned|void|(class[ \t]+\w*)|([A-Z]\w*)))[ \t]*?(| \*)[ \t]*>',
95 # delete space before #define x()
96 ('#[ \t]*define (\w*)[ \t]*\(', '#define \\1('),
97 # add space in #define x ()
98 ('#[ \t]*define (\w*)(\(([^\(\)]|\([^\(\)]*\))*\)\\n)',
100 # delete space in #include <>
101 ('#[ \t]*include[ \t]*<[ \t]*([^ \t>]*)[ \t]*(/?)[ \t]*([^ \t>]*)[ \t]*>',
102 '#include <\\1\\2\\3>'),
103 # delete backslash before empty line (emacs' indent region is broken)
104 ('\\\\\n\n', '\n\n'),
109 # delete trailing whitespace
111 # delete empty first lines
112 ('(/\*\n)\n*', '\\1'),
113 # delete empty last lines
114 ('\n*(\n\*/)', '\\1'),
115 ## delete newline after start?
117 ## delete newline before end?
123 # Recognize special sequences in the input.
125 # (?P<name>regex) -- Assign result of REGEX to NAME.
126 # *? -- Match non-greedily.
127 # (?m) -- Multiline regex: Make ^ and $ match at each line.
128 # (?s) -- Make the dot match all characters including newline.
129 # (?x) -- Ignore whitespace in patterns.
140 [ \t]*/\*.*?\*/))''',
142 'singleline_comment':
147 [ \t]*//([ \t][^\n]*|)\n))''',
153 "([^"]|(([^\\]|(\\\\))\\"))*"))''',
165 "#[ \t]*include[ \t]*<[^>]*>''',
170 def replacement_text (self):
173 def filter_text (self):
174 return self.replacement_text ()
176 def ly_is_outdated (self):
179 def png_is_outdated (self):
182 class Substring (Chunk):
183 def __init__ (self, source, start, end):
188 def replacement_text (self):
189 s = self.source[self.start:self.end]
191 s = re.sub (i[0], i[1], s)
195 class Snippet (Chunk):
196 def __init__ (self, type, match, format):
203 def replacement_text (self):
204 return self.match.group ('match')
206 def substring (self, s):
207 return self.match.group (s)
210 return `self.__class__` + ' type = ' + self.type
212 class Multiline_comment (Snippet):
213 def __init__ (self, source, match, format):
220 def replacement_text (self):
221 s = self.match.group ('match')
222 for i in rules[COMMENT]:
223 s = re.sub (i[0], i[1], s)
226 snippet_type_to_class = {
227 'multiline_comment': Multiline_comment,
228 # 'lilypond_block': Lilypond_snippet,
229 # 'lilypond': Lilypond_snippet,
230 # 'include': Include_snippet,
233 def find_toplevel_snippets (s, types):
236 res[i] = re.compile (snippet_res[format][i])
240 ## found = dict (map (lambda x: (x, None),
244 map (lambda x, f = found: f.setdefault (x, None),
247 # We want to search for multiple regexes, without searching
248 # the string multiple times for one regex.
249 # Hence, we use earlier results to limit the string portion
251 # Since every part of the string is traversed at most once for
252 # every type of snippet, this is linear.
258 if not found[type] or found[type][0] < index:
260 m = res[type].search (s[index:endex])
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)
273 or found[type][0] < found[first][0]):
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.
286 endex = found[first][0]
289 snippets.append (Substring (s, index, len (s)))
292 (start, snip) = found[first]
293 snippets.append (Substring (s, index, start))
294 snippets.append (snip)
296 index = start + len (snip.match.group ('match'))
300 def nitpick_file (outdir, file):
301 s = open (file).read ()
303 # FIXME: Containing blocks must be first, see
304 # find_toplevel_snippets.
307 'singleline_comment',
312 chunks = find_toplevel_snippets (s, snippet_types)
313 #code = filter (lambda x: is_derived_class (x.__class__, Substring),
316 t = string.join (map (lambda x: x.filter_text (), chunks), '')
320 os.system ('mv %s %s~' % (file, file))
322 fixt = os.path.join (outdir,
323 os.path.basename (file))
329 def indent_file (file):
336 --eval '(let ((error nil)
337 (version-control nil))
338 (load-library "cc-mode")
340 (indent-region (point-min) (point-max))
341 (if (buffer-modified-p (current-buffer))
342 (save-buffer)))' ''' % vars ()
343 emacsclient = '''emacsclient\
344 --socket-name=%(socketdir)s/%(socketname)s\
346 --eval '(let ((error nil)
347 (version-control nil))
348 (load-library "cc-mode")
349 (find-file "%(file)s")
351 (indent-region (point-min) (point-max))
352 (if (buffer-modified-p (current-buffer))
353 (save-buffer)))' ''' \
355 'socketdir' : socketdir,
356 'socketname' : socketname, }
361 sys.stdout.write (r'''
363 fixcc [--outdir=DIR] FILE...
365 Typical use with LilyPond:
367 fixcc $(find flower kpath-guile lily -name '*cc' -o -name '*hh' | grep -v /out)
369 This script is licensed under the GNU GPL
374 (options, files) = getopt.getopt (sys.argv[1:], '',
376 for (o, a) in options:
380 elif o == '--outdir':
392 socketdir = '/tmp/fixcc'
393 socketname = 'fixcc%d' % os.getpid ()
398 os.unlink (os.path.join (socketdir, socketname))
399 os.mkdir (socketdir, 0700)
403 --eval '(let ((error nil)
404 (version-control nil))
405 (load-library "server")
406 (setq server-socket-dir "%(socketdir)s")
407 (setq server-name "%(socketname)s")
409 (while t) (sleep 1000))' ''' \
410 % { 'socketdir' : socketdir,
411 'socketname' : socketname, }
416 while not os.path.exists (os.path.join (socketdir, socketname)):
420 #emacsclient should be faster, but this does not work yet
422 files = do_options ()
423 if outdir and not os.path.isdir (outdir):
426 sys.stderr.write ('%s...\n' % i)
427 nitpick_file (outdir, i)
429 if __name__ == '__main__':