It's not as trivial as it may seem if you take into account things like: puts("string with /*")
bearing in mind that "
s can occur in ch = '"'
.
Or line continuations:
printf("..."); /\
* yes, this is a comment */
/\
/ and this as well
Or trigraphs.
To cover those, we can adapt this answer to the opposite question to make it print rather than remove the comments:
perl -0777 -pe '
s{
(?<comment>
# /* ... */ C comments
/ (?<lc> # line continuation
(?<bs> # backslash in its regular or trigraph form
\\ | \?\?/
)
(?: \n | \r\n?) # handling LF, CR and CRLF line delimiters
)* \* .*? \* (?&lc)* /
| / (?&lc)* / (?:(?&lc) | [^\r\n])* # // C++/C99 comments
) |
"(?:(?&bs)(?&lc)*.|.)*?" # "strings" literals
| '\''(?&lc)*(?:(?&bs)(?&lc)*(?:\?\?.|.))?(?:\?\?.|.)*?'\'' # (w)char literals
| \?\?'\'' # trigraph form of ^
| .[^'\''"/?]* # anything else
}{$+{comment} eq "" ? "" : "$+{comment}\n"}exsg'
Which on the contrived examples from the other question which cover most of the corner cases:
#include <stdio.h>
int main()
{
printf("%d %s %s %c%c%c%c%c %s %s %d\n",
1-/* comment */-1,
/\
* comment */
"/* not a comment */",
/* multiline
comment */
// comment
/\
/ comment
// multiline\
comment
"// not a comment",
'"' /* comment */ , '"',
'\'','"'/* comment */,
'\
\
"', /* comment */
"\\
" /* not a comment */ ",
"??/" /* not a comment */ ",
'??''+'"' /* "comment" */);
return 0;
}
Gives:
/* comment */
/\
* comment */
/* multiline
comment */
// comment
/\
/ comment
// multiline\
comment
/* comment */
/* comment */
/* comment */
/* "comment" */
To get the line numbers, as we're running in slurp mode where the subject is the whole input as opposed to processing the input one line at a time, it's a bit more tricky. We could do it though using the (?{code})
regexp operator to increment a counter each time a line delimiter (CR, LF or CRLF in C) is found:
perl -0777 -pe '
s{
(?<comment>(?{$l=$n+1})
/
(?<lc> # line continuation
(?<bs> # backslash in its regular or trigraph form
\\ | \?\?/
) (?<nl>(?:\n|\r\n?) (?{$n++})) # handling LF, CR and CRLF line delimiters
)*
(?:
\* (?: (?&nl) | .)*? \* (?&lc)* / # /* ... */ C comments
| / (?:(?&lc) | [^\r\n])* # // C++/C99 comments
)
) |
"(?:(?&bs)(?&lc)*.|.)*?" # "strings" literals
| '\''(?&lc)*(?:(?&bs)(?&lc)*(?:\?\?.|.))?(?:\?\?.|.)*?'\'' # (w)char literals
| \?\?'\'' # trigraph form of ^
| (?&nl)
| .[^'\''"/?\r\n]* # anything else
}{$+{comment} eq "" ? "" : sprintf("%5d %s\n", $l, $+{comment})}exsg'
Which on that same sample gives:
5 /* comment */
6 /\
* comment */
9 /* multiline
comment */
11 // comment
12 /\
/ comment
14 // multiline\
comment
17 /* comment */
18 /* comment */
21 /* comment */
26 /* "comment" */
printf("/* Automatically generated file, do not edit!\n")
in them! To solve this problem you pretty much need to go character by character through the source file, making choices on each one. This tends to rule of grep. Let me see what I can come up with.