1#!/usr/bin/env python
2
3# Programme to convert all single-line or multi-line "/* ... */"
4# comments to "//" form in C or C++ code.  Input source code file is
5# stdin, output file is stdout.
6import sys
7import re
8# Restore default signal handling for SIGPIPE as suggested by
9# http://www.velocityreviews.com/forums/t332662-ioerror-errno-32-broken-pipe.html
10# so that stdout can be sent (unix-only) to a pipelined process that terminates
11# early such as cmp.
12import signal
13signal.signal(signal.SIGPIPE, signal.SIG_DFL)
14
15ifsingleline = True
16previous_continue = False
17
18for line in sys.stdin.readlines():
19    start_comment = line.find("/*")
20    # FIXME?: the simple rules below to ignore all special strings
21    # in a line where the first instance is quoted obviously
22    # need generalization.  However, second instances
23    # of special strings are unlikely in practice so we will go with
24    # this simple rule until it is proved something more is required.
25    # Ignore all "/*" instances on a line where the first one is
26    # quoted.
27    if start_comment >=0 and re.search(r'^[^"]*("[^"]*"[^"]*)*"[^"]*/\*[^"]*"', line):
28        start_comment = -1
29
30    start_special = line.find("//*")
31    # FIXME?
32    # Ignore all "//*" instances on a line where the first one is
33    # quoted.
34    if start_special >= 0 and re.search(r'^[^"]*("[^"]*"[^"]*)*"[^"]*//\*[^"]*"', line):
35        start_special = -1
36
37    # if start_special corresponds to start_comment, then ignore start_comment
38    # to deal with issue of recursive changes to an original line of the
39    # form "/******..."
40    if start_special >= 0 and start_special == start_comment -1:
41        start_comment = -1
42
43    end_comment = line.find("*/")
44    # FIXME?
45    # Ignore all "*/" instances on a line where the first one is
46    # quoted.
47    if end_comment >= 0 and re.search(r'^[^"]*("[^"]*"[^"]*)*"[^"]*\*/[^"]*"', line):
48        end_comment = -1
49
50    start_special = line.find("//")
51    # FIXME?
52    # Ignore all "//" instances on a line where the first one is
53    # quoted.
54    if start_special >= 0 and re.search(r'^[^"]*("[^"]*"[^"]*)*"[^"]*//[^"]*"', line):
55        start_special = -1
56    # If unquoted // before /* and not in the middle of a comment block,
57    # then ignore both /* and */ beyond //.
58    if ifsingleline and start_special >= 0 and start_special <= start_comment -1:
59        start_comment = -1
60        end_comment = -1
61
62    # Note trailing "\n" has not (yet) been removed from line so
63    # that the next to last character is at position len(line) - 3.
64    if end_comment >=0 and end_comment !=  len(line) - 3:
65        if ifsingleline and start_comment >=0:
66            # Skip most further processing for a line with embedded
67            # comments outside of multiline blocks of comments.
68            start_comment = -1
69            end_comment = -1
70        else:
71            sys.stderr.write(line)
72            raise RuntimeError, "Cannot interpret trailing character(s) after */ for this line"
73
74    # Note trailing "\n" has not (yet) been removed from line so
75    # that the next to last character is at position len(line) - 3.
76    # print "start_comment = ", start_comment
77    # print "end_comment = ", end_comment
78    # print "start_special = ", start_special
79    if ifsingleline:
80        if start_comment >=0 and start_comment < end_comment and end_comment == len(line) - 3:
81            # Single-line comment case.
82            # Strip trailing "\n" (Unix line endings, only) since that might
83            # cause trouble with stripping trailing white space below.
84            # This trailing "\n" will be added back at the end of this block.
85            line = re.sub(r'^(.*)\n$', "\\1", line)
86            # Convert single-line comment.
87            # \1 corresponds to zero or more leading characters before "/*".
88            # \3 corresponds to zero or more characters between "/*" and
89            # "*/".
90            # N.B. preserves indentation.
91            line = re.sub(r'^(.*)(/\*)(.*)\*/$', "\\1//\\3", line)
92            # strip trailing white space (if any).
93            line = line.rstrip()
94            # Add back (Unix-only) line ending.
95            line = line + "\n"
96
97        elif end_comment >=0:
98            sys.stderr.write(line)
99            raise RuntimeError, "Trailing */ for a line which is not a comment"
100
101        elif start_comment >=0:
102            # Convert first line of multiline comment.
103            # N.B. preserves indentation.
104            line = line.replace("/*", "//", 1)
105            ifsingleline = False
106
107
108    else:
109        # Case where dealing with multi-line comment.
110        if start_comment >=0:
111            sys.stderr.write(line)
112            raise RuntimeError, "/* in the middle of a comment block"
113
114        # strip trailing "\n" (Unix line endings, only) since that might
115        # cause trouble with stripping trailing white space below.
116        # This trailing "\n" will be added back at the end of this block.
117        line = re.sub(r'^(.*)\n$', "\\1", line)
118        if end_comment < 0:
119            # Convert multiline comment line that is not start line
120            # or end line of that comment.
121            # Replace " *" after zero or more blanks (the standard form
122            # produced by uncrustify) if it is there by "//".
123            # N.B. preserves indentation.
124            if re.search(r'^( *) \*', line):
125                line = re.sub(r'^( *) \*', "\\1//", line)
126            else:
127                # If standard indented form not found....
128                line = "//" + line
129
130
131        else:
132            # Convert last line of multiline comment.
133            # Try converting vacuous form (initial blanks + " */")
134            # to initial blanks (if any) + "//".
135            # This consumes the blank in " */" that is customary as
136            # the last line of a multi-line block.
137            # N.B. preserves indentation.
138            # \1 contains the initial blanks
139            if re.search(r'^( *) \*/$', line):
140                line = re.sub(r'^( *) \*/$', "\\1//", line)
141
142            # Try converting non-vacuous standard form produced by
143            # uncrustify (initial blanks + " *" + any string + "*/")
144            # to initial blanks + "//" + any string.
145            # N.B. preserves indentation.
146            # \1 contains the initial blanks
147            # \2 contains the "any" string preceding "*/".
148            elif re.search(r'^( *) \*(.*)\*/$', line):
149                line = re.sub(r'^( *) \*(.*)\*/$', "\\1//\\2", line)
150
151            # If all previous conversion attempts failed....
152            # N.B. Does not preserve indentation.
153            else:
154                line = re.sub(r'^(.*)\*/$', "//\\1", line)
155
156            # strip trailing white space (if any).
157            line = line.rstrip()
158            ifsingleline = True
159
160        # Add back (Unix-only) line ending.
161        line = line + "\n"
162
163    # If previous comment continuation exists, check whether it is
164    # valid, i.e., whether it is followed by another line consisting
165    # zero or more blanks followed by a comment.
166    if previous_continue:
167        if re.search(r'^ *//', line):
168            previous_continue = False
169        else:
170            sys.stderr.write(line_old)
171            sys.stderr.write(line)
172            raise RuntimeError, "Comment continuation not allowed unless next line is a comment"
173    # End with some special processing for all lines which previously
174    # or now include "//".
175    start_special = line.find("//")
176    if start_special >= 0:
177        # Special transforms to get rid of left-over "\*" and "*\"
178        # forms which have historically been used to frame
179        # multi-block comments by some PLplot developers.
180        # Replace leading "// \*" ==> "//"
181        line = re.sub(r'^// \\\*(.*)$', "//\\1", line)
182        # Remove "*\" from end of comment lines
183        line = re.sub(r'^//(.*)\*\\$', "//\\1", line)
184        # Convert long "horizontal line" comment forms to standard form.
185        line = re.sub(r'^// *[-*=/+_\\# ]{50,200}$', "//--------------------------------------------------------------------------", line)
186        # Look for trailing continuation after comment lines and
187        # complain if you find any.
188        start_special = line.rfind("\\")
189        # Note line has trailing "\n" so that the last character
190        # is at position len(line) - 2.
191        if start_special >=0 and start_special == len(line) -2:
192            # Strings are immutable so this is a copy not a reference.
193            line_old = line
194            previous_continue = True
195
196    sys.stdout.write(line)
197
198if not ifsingleline:
199    sys.stderr.write(line)
200    raise RuntimeError, "Last line of file leaves unterminated comment block."
201
202if previous_continue:
203    sys.stderr.write(line_old)
204    raise RuntimeError, "Last line of file is a continued comment."
205
206sys.exit()
207